Table of Contents
Framework Structure & Configuration
Introduction
In this section, we will begin to add our folders and files that our framework will need. We will also add a .gitignore file so that if you decide to put your framework in a Git repository, you are able to without unnecessary files being included in the repository.
Adding a .gitignore file
- Open up a terminal (or command prompt) and change directory (cd) in to your project root…
cd your/project/root
(change ‘your/project/root’ to the path of your local project’s root)
- First, let’s initialise an empty git repository…
git init
- Next, create (touch) a .gitignore file…
touch .gitignore
- Next, go into the nano editor for the .gitignore file we just created…
nano .gitignore
- Add the following lines in your .gitignore file to ignore the packages in our ‘/node_modules’ folder as well as any folders specific to our code editor (e.g. ‘.vscode/’) and the folder where we keep screenshots of errors (this doesn’t need to be in the git repository either)…
node_modules/ .vscode/ errorShots/
You’re now ready to setup your own GitHub/GitLab repository, if you desire to do so (I won’t be covering that in this blog series).
Directory and File Structure
- Open up a terminal (or command prompt) and change directory (cd) in to your project root…
cd your/project/root
(change ‘your/project/root’ to the path of your local project’s root)
- First, let’s open our project root in VS Code…
code .
Features
- Next, in the Project Explorer in Visual Studio code, add a ‘features’ folder to the project root by hovering the mouse over the project folder’s name and clicking the ‘New Folder’ icon
- Now, right click the ‘features’ folder and add a feature file called ‘google-about.feature’ (we will be testing the site https://about.google/ later in this series)
Steps
- Next, right-click the ‘features’ folder and add another sub-folder called ‘steps’, which is where all of our step definition files will go
- After, right-click the ‘steps’ folder and add a step definition file called ‘google-about.steps.ts’ (notice the .ts extension for TypeScript)
Pages
- In the Project Explorer again, add a ‘pages’ folder to the project root
- Right-click the ‘pages’ folder and add a ‘base-page.ts’ file (again notice the .ts file extension for TypeScript)
Configuration
We will now add necessary configuration files for Protractor and TypeScript, as well as tweak the package.json file 🙂
protractor.conf.ts
We will now create a ‘protractor.conf.ts’ file that will hold all of our Protractor configuration settings
- With Visual Studio Code open, add a new file to the project root called ‘protractor.conf.ts’
- Open up the ‘protractor.conf.ts’ file and add the following configuration code (I have added comments to try and explain what is going on)…
import { Config } from "protractor"; //import the Configuration object from protractor, so we can use it export const config: Config = { //create an object of Config called 'config' and export it so it is accessible by other files directConnect: true, //Your test script communicates directly Chrome Driver or Firefox Driver, bypassing any Selenium Server. capabilities: { browserName: 'chrome' //set test browser to 'chrome', can also be set to 'firefox' as well as some others }, specs: ['./features/*.feature'], // set the location of our tests to any and all feature files in the 'features' folder framework: 'custom', frameworkPath: require.resolve('protractor-cucumber-framework'), //this module needs to be called as a custom framework cucumberOpts: { require: ['./features/steps/*.steps.ts'] //require our step definition files } };
tsconfig.json
We will now create a ‘tsconfig.json’ file that allows you to specify the root level files and the compiler options that are required to compile a TypeScript project. The presence of this file in a directory specifies that the said directory is the TypeScript project root.
- With Visual Studio Code open, add a new file to the project root called ‘tsconfig.json’
- Open up the file and paste in the following, the uncommented lines are the only ones we need for now, however I have included all the other options commented out so you can see what they are…
{ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": false, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "sourceMap": false, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./js-files", /* Redirect output structure to the directory. */ "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ "removeComments": false, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ /* Enable all strict type-checking options. */ "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "exclude": [ "node_modules" ] }
package.json
Finally, we will tweak our ‘package.json’ file to add some scripts that will run our tests when we input ‘npm test’ in to the command line as well as run any ‘pretest’ scripts we may need around ts-node and also defining the location of the local protractor install as well as ‘webdriver-manager update’ 🙂
- Open up the package.json file and edit the scripts block so it looks like below…
"scripts": { "test": "protractor js-files/protractor.conf.js", "pretest": "tsc", "protractor": "./node_modules/protractor/built/cli.js", "webdriver-update": "./node_modules/.bin/webdriver-manager update" },
The whole thing should look like below…
{ "name": "protractor-framework", "version": "1.0.0", "description": "", "main": "js-files/protractor.conf.js", "scripts": { "test": "protractor js-files/protractor.conf.js", "pretest": "tsc", "protractor": "./node_modules/protractor/built/cli.js", "webdriver-update": "./node_modules/.bin/webdriver-manager update" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@types/cucumber": "^6.0.0", "@types/node": "^13.1.8", "cucumber": "^6.0.5", "protractor": "^5.4.2", "protractor-cucumber-framework": "^6.2.0", "ts-node": "^8.6.2", "typescript": "^3.6.4" } }
You might be wondering what this ‘js-files/’ path is. When we run our tests in Protractor, we essentially need to transpile our ‘.ts’ files in to ‘.js’ files. We can use TypeScript’s built in ‘tsc’ command to do this.
When we now run npm test, it will first run the pretest command of ‘tsc’ and convert our .ts files in to .js files and place them in the ./js-files/ directory. Before that though, we can (and should) run the following command to get our webdriver binaries…
npm run webdriver-update
Because of this, the protractor configuration that actually gets read will be the ‘./js-files/protractor.conf.js’ file, so we will have to update our features path in the protractor.conf.ts file so that it is correct when the converted .js file is made and also change the file type of the steps files to ‘.js’
- Edit the paths in the ‘protractor.conf.ts’ file so it looks like below…
import { Config } from "protractor"; //import the Configuration object from protractor, so we can use it export let config: Config = { //create an object of Config called 'config' and export it so it is accessible by other files directConnect: true, //Your test script communicates directly Chrome Driver or Firefox Driver, bypassing any Selenium Server. capabilities: { browserName: 'chrome' //set test browser to 'chrome', can also be set to 'firefox' as well as some others }, specs: ['../features/*.feature'], // set the location of our tests to any and all feature files in the 'features' folder framework: 'custom', frameworkPath: require.resolve('protractor-cucumber-framework'), //this module needs to be called as a custom framework cucumberOpts: { require: ['./features/steps/*.steps.js'] //require our step definition files } };
If we now run npm test, it should launch the Chrome browser!
In the next section, we will begin coding our test 🙂