├── .circleci └── config.yml ├── .dockerignore ├── .env ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Dockerfile ├── LICENSE ├── README.md ├── _config.yml ├── config ├── chrome.config.ts ├── firefox.config.ts ├── hooks.config.ts ├── index.ts ├── logging.config.ts ├── reporting.config.ts ├── server.config.ts └── tests.config.ts ├── cucumber.report.conf.js ├── docker-compose.ci.yml ├── docker-compose.vnc.yml ├── docker-compose.yml ├── package-lock.json ├── package.json ├── scripts ├── run.sh └── wait-for-it.sh ├── src ├── features │ ├── google.search.feature │ └── sample.snippets.feature ├── steps │ ├── given.ts │ ├── hooks.ts │ ├── then.ts │ └── when.ts └── support │ ├── action │ ├── clearInputField.ts │ ├── clickElement.ts │ ├── closeAllButFirstTab.ts │ ├── closeLastOpenedWindow.ts │ ├── deleteCookies.ts │ ├── dragElement.ts │ ├── focusLastOpenedWindow.ts │ ├── handleModal.ts │ ├── moveTo.ts │ ├── openWebsite.ts │ ├── pause.ts │ ├── pressButton.ts │ ├── scroll.ts │ ├── selectOption.ts │ ├── selectOptionByIndex.ts │ ├── setCookie.ts │ ├── setInputField.ts │ ├── setPromptText.ts │ ├── setWindowSize.ts │ ├── switchToIframe.ts │ ├── waitFor.ts │ └── waitForDisplayed.ts │ ├── check │ ├── checkClass.ts │ ├── checkContainsAnyText.ts │ ├── checkContainsText.ts │ ├── checkCookieContent.ts │ ├── checkCookieExists.ts │ ├── checkDimension.ts │ ├── checkElementExists.ts │ ├── checkEqualsText.ts │ ├── checkFocus.ts │ ├── checkFontProperty.ts │ ├── checkInURLPath.ts │ ├── checkIsEmpty.ts │ ├── checkIsOpenedInNewWindow.ts │ ├── checkModal.ts │ ├── checkModalText.ts │ ├── checkNewWindow.ts │ ├── checkOffset.ts │ ├── checkProperty.ts │ ├── checkSelected.ts │ ├── checkTitle.ts │ ├── checkTitleContains.ts │ ├── checkUrl.ts │ ├── checkUrlPath.ts │ ├── checkWithinViewport.ts │ ├── compareText.ts │ ├── isDisplayed.ts │ ├── isEnabled.ts │ └── isExisting.ts │ └── lib │ ├── checkIfElementExists.ts │ └── context.ts ├── tsconfig.json └── wdio.conf.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | docker: circleci/docker@1.0.0 5 | 6 | jobs: 7 | e2e-tests: 8 | executor: docker/docker 9 | working_directory: ~/project 10 | 11 | steps: 12 | - checkout: 13 | path: ~/project 14 | - setup_remote_docker 15 | - run: 16 | name: Pull docker images 17 | command: docker-compose -f docker-compose.ci.yml pull 18 | no_output_timeout: 1m 19 | - run: 20 | name: Build docker images 21 | command: docker-compose -f docker-compose.ci.yml build 22 | no_output_timeout: 2m 23 | - run: 24 | name: Run tests 25 | command: docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from node 26 | no_output_timeout: 2m 27 | - run: 28 | name: Copy report 29 | command: docker cp e2e-tests-container:/usr/app/report/cucumber/html/. ~/report 30 | - store_artifacts: 31 | path: ~/report 32 | 33 | workflows: 34 | e2e-tests: 35 | jobs: 36 | - e2e-tests -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | report 4 | .dockerignore 5 | .gitignore 6 | Dockerfile -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SELENIUM_VERSION=3.141.59-zirconium 2 | SCREEN_WIDTH=1280 3 | SCREEN_HEIGHT=720 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | plugins: ["@typescript-eslint"], 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended", 8 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | sourceType: 'module', 13 | project: './tsconfig.json', 14 | tsconfigRootDir: __dirname, 15 | }, 16 | rules: { 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Pull docker images 13 | run: docker-compose -f docker-compose.ci.yml pull 14 | 15 | - name: Build docker images 16 | run: docker-compose -f docker-compose.ci.yml build 17 | 18 | - name: Run tests 19 | run: docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from node 20 | 21 | - uses: actions/upload-artifact@v1 22 | with: 23 | name: report 24 | path: report 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | report -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | services: 4 | - docker 5 | 6 | before_install: 7 | - docker-compose -f docker-compose.ci.yml pull 8 | 9 | install: 10 | - docker-compose -f docker-compose.ci.yml build 11 | 12 | script: 13 | - docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from node 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug current test", 9 | "type": "node", 10 | "request": "launch", 11 | "args": ["wdio.conf.js", "--spec", "${file}"], 12 | "cwd": "${workspaceFolder}", 13 | "autoAttachChildProcesses": true, 14 | "program": "${workspaceRoot}/node_modules/@wdio/cli/bin/wdio.js", 15 | "console": "integratedTerminal", 16 | "env": { 17 | "DEBUG_TESTS": "true", 18 | "VNC_SUPPORT": "false" 19 | }, 20 | }, 21 | { 22 | "name": "Debug all tests", 23 | "type": "node", 24 | "request": "launch", 25 | "args": ["wdio.conf.js", "--spec"], 26 | "cwd": "${workspaceFolder}", 27 | "autoAttachChildProcesses": true, 28 | "program": "${workspaceRoot}/node_modules/@wdio/cli/bin/wdio.js", 29 | "console": "integratedTerminal", 30 | "env": { 31 | "DEBUG_TESTS": "true", 32 | "VNC_SUPPORT": "false" 33 | }, 34 | }, 35 | { 36 | "name": "VNC Debug current test", 37 | "type": "node", 38 | "request": "launch", 39 | "args": ["wdio.conf.js", "--spec", "${file}"], 40 | "cwd": "${workspaceFolder}", 41 | "autoAttachChildProcesses": true, 42 | "program": "${workspaceRoot}/node_modules/@wdio/cli/bin/wdio.js", 43 | "console": "integratedTerminal", 44 | "env": { 45 | "DEBUG_TESTS": "true", 46 | "VNC_SUPPORT": "true" 47 | }, 48 | }, 49 | { 50 | "name": "VNC Debug all tests", 51 | "type": "node", 52 | "request": "launch", 53 | "args": ["wdio.conf.js", "--spec"], 54 | "cwd": "${workspaceFolder}", 55 | "autoAttachChildProcesses": true, 56 | "program": "${workspaceRoot}/node_modules/@wdio/cli/bin/wdio.js", 57 | "console": "integratedTerminal", 58 | "env": { 59 | "DEBUG_TESTS": "true", 60 | "VNC_SUPPORT": "true" 61 | }, 62 | }, 63 | ] 64 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/node_modules": true, 6 | "**/dist": true, 7 | }, 8 | "[typescript]": { 9 | "editor.formatOnSave": true, 10 | "editor.formatOnPaste": true 11 | }, 12 | "[markdown]": { 13 | "editor.formatOnSave": true, 14 | "editor.renderWhitespace": "all", 15 | "editor.acceptSuggestionOnEnter": "off" 16 | }, 17 | "editor.autoIndent": "full", 18 | "typescript.preferences.importModuleSpecifier": "relative", 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "selenium: start", 6 | "type": "shell", 7 | "command": "npm run selenium", 8 | "group": "build", 9 | "problemMatcher": [ 10 | "$tsc" 11 | ], 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "shared" 15 | } 16 | }, 17 | { 18 | "label": "selenium: start vnc", 19 | "type": "shell", 20 | "command": "npm run selenium:vnc", 21 | "group": "build", 22 | "problemMatcher": [ 23 | "$tsc" 24 | ], 25 | "presentation": { 26 | "reveal": "always", 27 | "panel": "shared" 28 | } 29 | }, 30 | { 31 | "label": "selenium: stop", 32 | "type": "shell", 33 | "command": "npm run selenium:stop", 34 | "group": "build", 35 | "problemMatcher": [ 36 | "$tsc" 37 | ], 38 | "presentation": { 39 | "reveal": "always", 40 | "panel": "shared" 41 | } 42 | }, 43 | { 44 | "label": "test", 45 | "type": "shell", 46 | "command": "npm run test", 47 | "group": "test", 48 | "problemMatcher": [ 49 | "$tsc" 50 | ], 51 | "presentation": { 52 | "reveal": "always", 53 | "panel": "new" 54 | }, 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | LABEL maintainer="Labs42" 3 | 4 | WORKDIR /usr/app 5 | 6 | ADD ./scripts/wait-for-it.sh ./ 7 | RUN chmod 755 wait-for-it.sh 8 | ADD ./scripts/run.sh ./ 9 | RUN chmod 755 run.sh 10 | 11 | ADD ./package.json package.json 12 | ADD ./package-lock.json package-lock.json 13 | 14 | RUN npm ci 15 | ADD ./.env ./ 16 | ADD ./tsconfig.json ./ 17 | ADD ./.eslintrc.js ./ 18 | ADD ./wdio.conf.js ./ 19 | ADD ./cucumber.report.conf.js ./ 20 | ADD ./config config 21 | ADD ./src src 22 | RUN npm run lint 23 | 24 | CMD [ "./run.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Labs42 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Automation Framework 2 | 3 | [![CircleCI](https://circleci.com/gh/labs42io/web-automation/tree/master.svg?style=svg)](https://circleci.com/gh/labs42io/web-automation/tree/master) 4 | [![CI](https://github.com/labs42io/web-automation/workflows/CI/badge.svg)](https://github.com/labs42io/web-automation/actions) 5 | [![Build Status](https://travis-ci.org/labs42io/web-automation.svg?branch=master)](https://travis-ci.org/labs42io/web-automation) 6 | 7 | Boilerplate project to write BDD tests with [Cucumber](https://cucumber.io/) and execute with [docker selenium](https://github.com/SeleniumHQ/docker-selenium). 8 | Tests are written in an ordinary language that bridges the gap between business and technical people. 9 | The docker selenium simplifies the setup and avoids any local installation of browser specific dependencies. 10 | 11 | ## Features 12 | 13 | - Simple setup, no need for local preinstalled Selenium Grid and browser drivers 14 | - Test with *Chrome* and *Firefox* with zero configuration 15 | - Integrated with [WebdriverIO](https://webdriver.io/) 16 | - BDD tests with [Cucumber](https://cucumber.io/docs/cucumber/) and over 150 predefined steps 17 | - Implement custom steps with [TypeScript](https://www.typescriptlang.org/) 18 | - Support for debugging tests 19 | - Possibility to visually see the execution in browser with *VNC* 20 | - Detailed report generation ([example](https://wswebcreation.github.io/multiple-cucumber-html-reporter/browsers/index.html)) 21 | - Integration with CI tools 22 | 23 | ## Requirements 24 | 25 | - To run *Firefox* and *Chrome* browsers with docker selenium you need: 26 | - `docker` 27 | - `docker-compose` 28 | 29 | - Tests are executed with Node.js, requires: 30 | - `Node.js` version 10 or higher 31 | - `npm` version 6 or higher 32 | 33 | ## Quick start 34 | 35 | 1. Install dependencies required to run the tests: 36 | 37 | ```sh 38 | npm install 39 | ``` 40 | 41 | 2. Start docker selenium containers with `docker-compose`: 42 | 43 | ```sh 44 | # starts the selenium hub and browser nodes in docker containers 45 | npm run selenium 46 | ``` 47 | 48 | 3. Run the tests and view the report: 49 | 50 | ```sh 51 | # run tests and open the report 52 | npm run test 53 | ``` 54 | 55 | To stop all the docker containers from step 2: 56 | 57 | ```sh 58 | npm run selenium:stop 59 | ``` 60 | 61 | Note that selenium containers can be started once and then used across multiple sessions of running and debugging tests. 62 | 63 | ## Test examples 64 | 65 | |File|| 66 | |--|--| 67 | |`./src/features/google.search.feature`|An example of testing the Google search| 68 | |`./src/features/sample.snippets.feature`|Samples of using the existing test snippets. Credits [Christian Bromann](https://github.com/christian-bromann)| 69 | 70 | ## Adding tests 71 | 72 | Tests are written using [Gherkin syntax](https://cucumber.io/docs/gherkin/) in a fashion that can be used as feature documentation: 73 | 74 | ```gherkin 75 | # This is a single line comment 76 | Feature: Performing a Google Search 77 | 78 | As a user on the Google search page 79 | I want to search for Selenium-Webdriver 80 | Because I want to learn more about it 81 | 82 | Background: 83 | Given I open the url "https://google.com" 84 | 85 | Scenario: Searching for Selenium Webdriver 86 | When I set "Selenium Webdriver" to the inputfield "[name=q]" 87 | And I press "Enter" 88 | Then I expect that element "#search" becomes displayed 89 | ``` 90 | 91 | All tests should be located in `./src/features/*` directory with extension `.feature` (configured in `./config/tests.config.ts`). 92 | For a list of predefined and supported steps see files: 93 | - `./src/steps/given.ts` 94 | - `./src/steps/when.ts` 95 | - `./src/steps/then.ts`. 96 | 97 | The steps are inspired from [cucumber-boilerplate](https://github.com/webdriverio/cucumber-boilerplate#list-of-predefined-steps) repository. 98 | 99 | ### Implementing custom steps 100 | There are over 150 predefined steps, but in case you need an extra step you can add it in one of the `./src/steps` file. 101 | The snippets are defined using regular expressions. It allows to implement powerful and complex sentences. 102 | Everything that's within `"([^"]*)?"` gets captured and appended to the callback. 103 | To access browser functionality, reference the global variable `browser` which is a *WebdriverIO* browser instance. 104 | See the [documentation](https://webdriver.io/docs/api.html) for a list of supported methods. 105 | Assertions are written using [chai](https://www.chaijs.com/). 106 | 107 | ### Browser specific tests 108 | To run a test against a specific browser use predefined [tags](https://cucumber.io/docs/cucumber/api/#tags): 109 | 110 | ```gherkin 111 | Feature: Performing a Google Search 112 | 113 | ... 114 | 115 | # This scenario will run only in Chrome browser 116 | @OnlyChrome 117 | Scenario: Searching in chrome browser 118 | ... 119 | 120 | # This scenario will run only in Firefox browser 121 | @OnlyFirefox 122 | Scenario: Searching in Firefox browser 123 | ... 124 | ``` 125 | 126 | ### Pending tests 127 | 128 | To skip a test, use the `@Pending` tag: 129 | ```gherkin 130 | Feature: Performing a Google Search 131 | 132 | ... 133 | 134 | # This scenario will be skipped 135 | @Pending 136 | Scenario: Searching for WebdriverIO 137 | ... 138 | ``` 139 | 140 | ### Verbose tests 141 | 142 | Currently, a screenshot is attached only for a failing test. In case you want screenshots for a test regardless of its completion status, 143 | use the `@Verbose` tag: 144 | 145 | ```gherkin 146 | Feature: Performing a Google Search 147 | 148 | ... 149 | 150 | # A screenshot and additional test details will be attached to the report 151 | @Verbose 152 | Scenario: Searching for WebdriverIO 153 | ... 154 | ``` 155 | 156 | ### Hooks 157 | 158 | Hooks are blocks of code that can run at various points in the Cucumber execution cycle. It is possible to write conditional hooks. 159 | See examples of scenario hooks in `./src/steps/hooks.ts`. For a more advanced usage, configure hooks in 160 | `./config/hooks.config.ts`. 161 | 162 | You can customize existing hooks or implement your own. 163 | See the WebdriverIO [documentation]((https://webdriver.io/docs/options.html#hooks)) about hooks. 164 | 165 | ## Configurations 166 | 167 | ### Environment variables 168 | 169 | The configurable options are set in the `.env` file. 170 | 171 | |Variable|Usage| 172 | |--|--| 173 | |`SELENIUM_VERSION`|Configure the version of selenium hub and nodes. Change this version if you want to run tests against a specific browser version. See the [list](https://github.com/SeleniumHQ/docker-selenium/releases) of available selenium releases and browser versions.| 174 | |`SCREEN_WIDTH` `SCREEN_HEIGHT`|Configure browser window size.| 175 | 176 | ### WebdriverIO options 177 | 178 | WebdriverIO specific [options](https://webdriver.io/docs/options.html) are all in `./config` directory. 179 | For example, to configure a default url change the `baseUrl` option in `./config/index.ts`: 180 | 181 | ```typescript 182 | export const config = { 183 | runner: 'local', 184 | baseUrl: 'https://webdriver.io', 185 | ... 186 | ``` 187 | 188 | ## Debugging tests 189 | 190 | There is a `./.vscode/launch.json` file that has a debugger configuration for *Visual Studio Code*, but you can enable debugging in any other editor 191 | that supports integration with Node.js debugger. 192 | 193 | To debug a single feature file: 194 | 195 | - *Prerequisites*: selenium containers are running (`npm run selenium`) 196 | 197 | - The `.feature` file to test is active in VS Code 198 | 199 | - From VS Code *Run and Debug* menu select the *Debug current test* option 200 | 201 | The test will start and run only the current file. Once started you can navigate to any `.ts` file and place a breakpoint. 202 | 203 | To debug all files follow the same steps but use the *Debug all tests* option. 204 | 205 | ## VNC support 206 | 207 | In some cases, you might need to visually see the execution in the browser. That is possible thanks to docker selenium debug images that 208 | have `XVFB` and `VNC` server installed. Note that debug images are slower and are intended only for development mode. 209 | 210 | ### Prerequisites 211 | 212 | Download on your machine the [VNC viewer](https://www.realvnc.com/en/connect/download/viewer/). 213 | 214 | ### Selenium Debug containers 215 | 216 | If you already have docker selenium containers running, stop them: 217 | 218 | ```sh 219 | npm run selenium:stop 220 | ``` 221 | 222 | Start selenium *debug* containers that enable the VNC support: 223 | 224 | ```sh 225 | # starts the selenium containers with VNC support 226 | npm run selenium:vnc 227 | ``` 228 | 229 | ### VNC connection options 230 | 231 | |Browser|Connection options| 232 | |--|--| 233 | |**Chrome**|`127.0.0.1:5900`| 234 | |**Firefox**|`127.0.0.1:5901`| 235 | 236 | 237 | Now you can connect and enter the remote session. 238 | 239 | ### Running tests 240 | 241 | Tests by default run in *headless* mode so that a browser window is not visually created. 242 | To run the tests with enabled browser window: 243 | 244 | ```sh 245 | # runs the tests without headless option 246 | npm run test:vnc 247 | ``` 248 | 249 | Note that even if you started selenium with VNC support, you need to run the `test:vnc` command to see the browsers visually. 250 | 251 | Debugging with VNC support is also possible. If you're using *Visual Studio Code* there are `VNC Debug current test` and 252 | `VNC Debug all tests` debugging configurations that work similar to configurations described in [Debugging tests](#debugging-tests) section. 253 | 254 | To stop the debug containers use the same command: 255 | 256 | ```sh 257 | npm run selenium:stop 258 | ``` 259 | 260 | ## CI integration 261 | 262 | Integration with a CI tool is easy if it supports `docker` and `docker-compose` tools. 263 | There is a *Dockerfile* to build an image that bundles Node.js, npm and tests. 264 | The `docker-compose.ci.yml` configures all the dependencies required to run the tests in containers: 265 | 266 | ```sh 267 | docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from node 268 | ``` 269 | 270 | There are `npm` scripts to avoid running long commands: 271 | 272 | ```sh 273 | # only builds the Dockerfile image 274 | npm run ci:build 275 | 276 | # runs the tests in containers 277 | npm run ci 278 | ``` 279 | 280 | |CI|Status|Config|Artifacts| 281 | |--|--|--|--| 282 | |**CircleCI**| [![CircleCI](https://circleci.com/gh/labs42io/web-automation/tree/master.svg?style=svg)](https://circleci.com/gh/labs42io/web-automation/tree/master) |`./.circleci/default.yml`|Report uploaded as artifacts that can be viewed directly in the browser.| 283 | |**Github Actions**| [![CI](https://github.com/labs42io/web-automation/workflows/CI/badge.svg)](https://github.com/labs42io/web-automation/actions) |`./.github/workflows/main.yml`|Report files available as a downloadable zip in artifacts. | 284 | |**TravisCI**| [![Build Status](https://travis-ci.org/labs42io/web-automation.svg?branch=master)](https://travis-ci.org/labs42io/web-automation) |`./.travis.yml`|You need to configure Amazon S3 account to enable artifacts. | 285 | 286 | ## License 287 | 288 | [MIT](LICENSE) 289 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /config/chrome.config.ts: -------------------------------------------------------------------------------- 1 | export const capabilitiesChromeConfig = { 2 | 'maxInstances': 1, 3 | 'browserName': 'chrome', 4 | 'goog:chromeOptions': { 5 | args: [ 6 | '--no-sandbox', 7 | ].concat( 8 | process.env.VNC_SUPPORT === 'true' ? [ 9 | // When debugging with VNC support headless mode is not enabled 10 | // to allow viewing actions in the browser. 11 | ] : [ 12 | '--headless', 13 | '--disable-gpu', 14 | ], 15 | ), 16 | }, 17 | 'cjson:metadata': { 18 | device: process.env.SELENIUM_VERSION, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /config/firefox.config.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/mozilla/geckodriver/blob/master/README.md#firefox-capabilities 2 | 3 | export const capabilitiesFirefoxConfig = { 4 | 'maxInstances': 1, 5 | 'browserName': 'firefox', 6 | 'moz:firefoxOptions': { 7 | args: [].concat( 8 | process.env.VNC_SUPPORT === 'true' ? [ 9 | // When debugging with VNC support headless mode is not enabled 10 | // to allow viewing actions in the browser. 11 | ] : [ 12 | 13 | '-headless', 14 | ]), 15 | }, 16 | 'acceptInsecureCerts': true, 17 | 'cjson:metadata': { 18 | device: process.env.SELENIUM_VERSION, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /config/hooks.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-unused-vars: "off" */ 2 | import log from '@wdio/logger'; 3 | import { addObject, addScreenshot } from '../src/support/lib/context'; 4 | 5 | const logger = log('@automation'); 6 | logger.info('Starting tests...'); 7 | 8 | export const hooksConfig = { 9 | // ===== 10 | // Hooks 11 | // ===== 12 | // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance 13 | // it and to build services around it. You can either apply a single function or an array of 14 | // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got 15 | // resolved to continue. 16 | /** 17 | * Gets executed once before all workers get launched. 18 | * @param {Object} config wdio configuration object 19 | * @param {Array.} capabilities list of capabilities details 20 | */ 21 | // onPrepare: function (config, capabilities) { 22 | // }, 23 | /** 24 | * Gets executed just before initialising the webdriver session and test framework. It allows you 25 | * to manipulate configurations depending on the capability or spec. 26 | * @param {Object} config wdio configuration object 27 | * @param {Array.} capabilities list of capabilities details 28 | * @param {Array.} specs List of spec file paths that are to be run 29 | */ 30 | // beforeSession: function (config, capabilities, specs) { 31 | // }, 32 | /** 33 | * Gets executed before test execution begins. At this point you can access to all global 34 | * variables like `browser`. It is the perfect place to define custom commands. 35 | * @param {Array.} capabilities list of capabilities details 36 | * @param {Array.} specs List of spec file paths that are to be run 37 | */ 38 | before(capabilities, specs): void { 39 | browser.setWindowSize( 40 | parseInt(process.env.SCREEN_WIDTH, 10), 41 | parseInt(process.env.SCREEN_HEIGHT, 10), 42 | ); 43 | }, 44 | /** 45 | * Runs before a WebdriverIO command gets executed. 46 | * @param {String} commandName hook command name 47 | * @param {Array} args arguments that command would receive 48 | */ 49 | // beforeCommand: function (commandName, args) { 50 | // }, 51 | 52 | /** 53 | * Hook that gets executed before the suite starts 54 | * @param {Object} suite suite details 55 | */ 56 | // beforeSuite: function (suite) { 57 | // }, 58 | /** 59 | * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts. 60 | * @param {Object} test test details 61 | */ 62 | // beforeTest: function (test) { 63 | // browser.setWindowSize(1920, 1080) 64 | // }, 65 | /** 66 | * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling 67 | * beforeEach in Mocha) 68 | */ 69 | // beforeHook: function () { 70 | // }, 71 | /** 72 | * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling 73 | * afterEach in Mocha) 74 | */ 75 | // afterHook: function () { 76 | // }, 77 | /** 78 | * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts. 79 | * @param {Object} test test details 80 | */ 81 | // tslint:disable-next-line: object-literal-shorthand 82 | // afterTest: function (test) { 83 | // }, 84 | /** 85 | * Hook that gets executed after the suite has ended 86 | * @param {Object} suite suite details 87 | */ 88 | // afterSuite: function (suite) { 89 | // }, 90 | 91 | /** 92 | * Runs after a WebdriverIO command gets executed 93 | * @param {String} commandName hook command name 94 | * @param {Array} args arguments that command would receive 95 | * @param {Number} result 0 - command success, 1 - command error 96 | * @param {Object} error error object if any 97 | */ 98 | // afterCommand: function (commandName, args, result, error) { 99 | // }, 100 | /** 101 | * Gets executed after all tests are done. You still have access to all global variables from 102 | * the test. 103 | * @param {Number} result 0 - test pass, 1 - test fail 104 | * @param {Array.} capabilities list of capabilities details 105 | * @param {Array.} specs List of spec file paths that ran 106 | */ 107 | // after: function (result, capabilities, specs) { 108 | // }, 109 | /** 110 | * Gets executed right after terminating the webdriver session. 111 | * @param {Object} config wdio configuration object 112 | * @param {Array.} capabilities list of capabilities details 113 | * @param {Array.} specs List of spec file paths that ran 114 | */ 115 | // afterSession: function (config, capabilities, specs) { 116 | // }, 117 | /** 118 | * Gets executed after all workers got shut down and the process is about to exit. 119 | * @param {Object} exitCode 0 - success, 1 - fail 120 | * @param {Object} config wdio configuration object 121 | * @param {Array.} capabilities list of capabilities details 122 | * @param {} results object containing test results 123 | */ 124 | // onComplete: function(exitCode, config, capabilities, results) { 125 | // } 126 | 127 | /** 128 | * Cucumber-specific hooks 129 | */ 130 | // beforeFeature(uri, feature, scenarios) { 131 | // }, 132 | // beforeScenario(uri, feature, scenario, sourceLocation) { 133 | // }, 134 | // beforeStep(uri, feature, stepData, context) { 135 | // }, 136 | afterStep(uri, feature, { error, result, duration, passed }, stepData, context): void { 137 | if (error) { 138 | addObject(error); 139 | addScreenshot(); 140 | } 141 | }, 142 | // afterScenario(uri, feature, scenario, result, sourceLocation) { 143 | // }, 144 | // afterFeature(uri, feature, scenarios) { 145 | // } 146 | }; 147 | -------------------------------------------------------------------------------- /config/index.ts: -------------------------------------------------------------------------------- 1 | import { capabilitiesChromeConfig } from './chrome.config'; 2 | import { capabilitiesFirefoxConfig } from './firefox.config'; 3 | import { hooksConfig } from './hooks.config'; 4 | import { loggingConfig } from './logging.config'; 5 | import { reportingConfig } from './reporting.config'; 6 | import { serverConfig } from './server.config'; 7 | import { testsConfig } from './tests.config'; 8 | 9 | export const config = { 10 | runner: 'local', 11 | baseUrl: 'http://localhost', 12 | 13 | framework: 'cucumber', 14 | 15 | maxInstances: process.env.DEBUG_TESTS === 'true' ? 1 : 2, 16 | capabilities: [ 17 | capabilitiesChromeConfig, 18 | capabilitiesFirefoxConfig, 19 | ], 20 | 21 | services: [], 22 | 23 | ...serverConfig, 24 | ...testsConfig, 25 | ...loggingConfig, 26 | ...reportingConfig, 27 | ...hooksConfig, 28 | }; 29 | -------------------------------------------------------------------------------- /config/logging.config.ts: -------------------------------------------------------------------------------- 1 | export const loggingConfig = { 2 | // Level of logging verbosity: trace | debug | info | warn | error 3 | logLevel: 'error', 4 | 5 | // Warns when a deprecated command is used 6 | deprecationWarnings: true, 7 | }; 8 | -------------------------------------------------------------------------------- /config/reporting.config.ts: -------------------------------------------------------------------------------- 1 | export const reportingConfig = { 2 | reporters: ['spec', [ 3 | 'cucumberjs-json', { 4 | jsonFolder: './report/cucumber/', 5 | }], 6 | ], 7 | 8 | cucumberOpts: { 9 | // show full backtrace for errors 10 | backtrace: false, 11 | // module used for processing required features 12 | // requireModule: ['@babel/register'], 13 | // invoke formatters without executing steps 16 | // dryRun: false, 17 | // abort the run on first failure 18 | failFast: false, 19 | // Enable this config to treat undefined definitions as 20 | // warnings 21 | ignoreUndefinedDefinitions: false, 22 | // ("extension:module") require files with the given 23 | // EXTENSION after requiring MODULE (repeatable) 24 | name: [], 25 | // hide step definition snippets for pending steps 26 | snippets: true, 27 | // hide source uris 28 | source: true, 29 | // (name) specify the profile to use 30 | profile: [], 31 | // (file/dir) require files before executing features 32 | require: [ 33 | './src/steps/**/*.ts', 34 | ], 35 | // specify a custom snippet syntax 36 | snippetSyntax: undefined, 37 | // fail if there are any undefined or pending steps 38 | strict: true, 39 | // (expression) only execute the features or scenarios with 40 | // tags matching the expression, see 41 | // https://docs.cucumber.io/tag-expressions/ 42 | tagExpression: 'not @Pending', 43 | // add cucumber tags to feature or scenario name 44 | tagsInTitle: false, 45 | // timeout for step definitions 46 | timeout: 10000, 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /config/server.config.ts: -------------------------------------------------------------------------------- 1 | export const serverConfig = { 2 | hostname: process.env.HUB_HOST || 'localhost', 3 | port: parseInt(process.env.HUB_PORT, 10) || 4444, 4 | }; 5 | -------------------------------------------------------------------------------- /config/tests.config.ts: -------------------------------------------------------------------------------- 1 | export const testsConfig = { 2 | specs: [ 3 | './src/features/**/*.feature', 4 | ], 5 | exclude: [ 6 | ], 7 | 8 | // If you only want to run your tests until a specific amount of tests have failed use 9 | // bail (default is 0 - don't bail, run all tests). 10 | bail: 0, 11 | 12 | // Default timeout for all waitFor* commands. 13 | waitforTimeout: 20000, 14 | 15 | // Default timeout in milliseconds for request 16 | // if Selenium Grid doesn't send response 17 | connectionRetryTimeout: 90000, 18 | 19 | // Default request retries count 20 | connectionRetryCount: 3, 21 | }; 22 | -------------------------------------------------------------------------------- /cucumber.report.conf.js: -------------------------------------------------------------------------------- 1 | const report = require('multiple-cucumber-html-reporter'); 2 | 3 | const customData = { 4 | title: 'Run info', 5 | data: [ 6 | { 7 | label: 'Project', 8 | value: 'Web automation' 9 | }, 10 | { 11 | label: 'Generated on:', 12 | value: new Date().toUTCString() 13 | }, 14 | { 15 | label: 'Reporter:', 16 | value: 'multiple-cucumber-html-reporter' 18 | }, 19 | ] 20 | }; 21 | 22 | report.generate({ 23 | jsonDir: './report/cucumber/', 24 | reportPath: './report/cucumber/html', 25 | displayDuration: true, 26 | removeFolders: true, 27 | 28 | pageTitle: 'Web Automation Framework', 29 | reportName: 'Web Automation Framework', 30 | openReportInBrowser: true, 31 | pageFooter: 32 | '

Powered by © Labs42

', 33 | 34 | customData: addCIMetadata(customData), 35 | }); 36 | 37 | function addCIMetadata(customData) { 38 | customData.data = customData.data 39 | .concat(...fromCircleCI()) 40 | .concat(...fromGithubActions()) 41 | .concat(...fromTravis()); 42 | 43 | return customData; 44 | } 45 | 46 | function* fromCircleCI() { 47 | if (process.env.CIRCLECI) { 48 | yield { label: 'CI', value: 'CircleCI' }; 49 | } 50 | 51 | if (process.env.CIRCLE_BRANCH) { 52 | yield { label: 'Branch', value: process.env.CIRCLE_BRANCH }; 53 | } 54 | 55 | if (process.env.CIRCLE_SHA1) { 56 | yield { label: 'Commit', value: process.env.CIRCLE_SHA1 } 57 | } 58 | 59 | if (process.env.CIRCLE_BUILD_NUM) { 60 | yield { 61 | label: 'Build', 62 | value: `${process.env.CIRCLE_BUILD_NUM}` 63 | }; 64 | } 65 | } 66 | 67 | function* fromGithubActions() { 68 | if (process.env.GITHUB_ACTIONS) { 69 | yield { label: 'CI', value: 'Github Actions' }; 70 | } 71 | 72 | if (process.env.GITHUB_REF) { 73 | yield { label: 'Branch', value: process.env.GITHUB_REF.replace('refs/heads/', '') }; 74 | } 75 | 76 | if (process.env.GITHUB_SHA) { 77 | yield { label: 'Commit', value: process.env.GITHUB_SHA } 78 | } 79 | 80 | if (process.env.GITHUB_RUN_NUMBER) { 81 | yield { label: 'Run', value: process.env.GITHUB_RUN_NUMBER }; 82 | } 83 | } 84 | 85 | function* fromTravis() { 86 | if (process.env.TRAVIS) { 87 | yield { label: 'CI', value: 'TravisCI' }; 88 | } 89 | 90 | if (process.env.TRAVIS_BRANCH) { 91 | yield { label: 'Branch', value: process.env.TRAVIS_BRANCH } 92 | } 93 | 94 | if (process.env.TRAVIS_COMMIT) { 95 | yield { label: 'Commit', value: process.env.TRAVIS_COMMIT }; 96 | } 97 | 98 | if (process.env.TRAVIS_BUILD_NUMBER) { 99 | yield { 100 | label: 'Build', 101 | value: `${process.env.TRAVIS_BUILD_NUMBER}` 102 | }; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /docker-compose.ci.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | selenium-hub: 4 | image: selenium/hub:${SELENIUM_VERSION} 5 | 6 | chrome: 7 | image: selenium/node-chrome:${SELENIUM_VERSION} 8 | volumes: 9 | - /dev/shm:/dev/shm 10 | depends_on: 11 | - selenium-hub 12 | environment: 13 | - HUB_HOST=selenium-hub 14 | - HUB_PORT=4444 15 | - START_XVFB=false 16 | depends_on: 17 | - selenium-hub 18 | 19 | firefox: 20 | image: selenium/node-firefox:${SELENIUM_VERSION} 21 | volumes: 22 | - /dev/shm:/dev/shm 23 | depends_on: 24 | - selenium-hub 25 | environment: 26 | - HUB_HOST=selenium-hub 27 | - HUB_PORT=4444 28 | - START_XVFB=false 29 | depends_on: 30 | - selenium-hub 31 | 32 | node: 33 | container_name: e2e-tests-container 34 | build: 35 | context: . 36 | dockerfile: ./Dockerfile 37 | volumes: 38 | - /dev/shm:/dev/shm 39 | - ./report:/usr/app/report 40 | environment: 41 | - HUB_HOST=selenium-hub 42 | - HUB_PORT=4444 43 | - CIRCLECI=${CIRCLECI} 44 | - CIRCLE_BRANCH=${CIRCLE_BRANCH} 45 | - CIRCLE_SHA1=${CIRCLE_SHA1} 46 | - CIRCLE_BUILD_NUM=${CIRCLE_BUILD_NUM} 47 | - CIRCLE_BUILD_URL=${CIRCLE_BUILD_URL} 48 | - GITHUB_ACTIONS=${GITHUB_ACTIONS} 49 | - GITHUB_REF=${GITHUB_REF} 50 | - GITHUB_SHA=${GITHUB_SHA} 51 | - GITHUB_RUN_NUMBER=${GITHUB_RUN_NUMBER} 52 | - TRAVIS=${TRAVIS} 53 | - TRAVIS_BRANCH=${TRAVIS_BRANCH} 54 | - TRAVIS_COMMIT=${TRAVIS_COMMIT} 55 | - TRAVIS_BUILD_NUMBER=${TRAVIS_BUILD_NUMBER} 56 | - TRAVIS_BUILD_WEB_URL=${TRAVIS_BUILD_WEB_URL} 57 | depends_on: 58 | - chrome 59 | - firefox 60 | -------------------------------------------------------------------------------- /docker-compose.vnc.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | selenium-hub: 4 | image: selenium/hub:${SELENIUM_VERSION} 5 | ports: 6 | - "4444:4444" 7 | 8 | chrome: 9 | image: selenium/node-chrome-debug:${SELENIUM_VERSION} 10 | volumes: 11 | - /dev/shm:/dev/shm 12 | depends_on: 13 | - selenium-hub 14 | environment: 15 | - HUB_HOST=selenium-hub 16 | - HUB_PORT=4444 17 | - VNC_NO_PASSWORD=1 18 | - SCREEN_WIDTH=${SCREEN_WIDTH} 19 | - SCREEN_HEIGHT=${SCREEN_HEIGHT} 20 | ports: 21 | - "5555:5555" 22 | - "5900:5900" 23 | 24 | firefox: 25 | image: selenium/node-firefox-debug:${SELENIUM_VERSION} 26 | volumes: 27 | - /dev/shm:/dev/shm 28 | depends_on: 29 | - selenium-hub 30 | environment: 31 | - HUB_HOST=selenium-hub 32 | - HUB_PORT=4444 33 | - VNC_NO_PASSWORD=1 34 | - SCREEN_WIDTH=${SCREEN_WIDTH} 35 | - SCREEN_HEIGHT=${SCREEN_HEIGHT} 36 | ports: 37 | - "5556:5555" 38 | - "5901:5900" 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | services: 3 | selenium-hub: 4 | image: selenium/hub:${SELENIUM_VERSION} 5 | ports: 6 | - "4444:4444" 7 | healthcheck: 8 | test: ["CMD", "/opt/bin/check-grid.sh", "--host", "0.0.0.0", "--port", "4444"] 9 | interval: 15s 10 | timeout: 15s 11 | retries: 3 12 | start_period: 30s 13 | 14 | chrome: 15 | image: selenium/node-chrome:${SELENIUM_VERSION} 16 | volumes: 17 | - /dev/shm:/dev/shm 18 | depends_on: 19 | - selenium-hub 20 | environment: 21 | - HUB_HOST=selenium-hub 22 | - HUB_PORT=4444 23 | - START_XVFB=false 24 | ports: 25 | - "5555:5555" 26 | healthcheck: 27 | test: ["CMD", "/opt/bin/check-grid.sh", "--host", "0.0.0.0", "--port", "5555"] 28 | interval: 15s 29 | timeout: 15s 30 | retries: 3 31 | start_period: 30s 32 | depends_on: 33 | - selenium-hub 34 | 35 | firefox: 36 | image: selenium/node-firefox:${SELENIUM_VERSION} 37 | volumes: 38 | - /dev/shm:/dev/shm 39 | depends_on: 40 | - selenium-hub 41 | environment: 42 | - HUB_HOST=selenium-hub 43 | - HUB_PORT=4444 44 | - START_XVFB=false 45 | ports: 46 | - "5556:5555" 47 | healthcheck: 48 | test: ["CMD", "/opt/bin/check-grid.sh", "--host", "0.0.0.0", "--port", "5555"] 49 | interval: 15s 50 | timeout: 15s 51 | retries: 3 52 | start_period: 30s 53 | depends_on: 54 | - selenium-hub 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-automation", 3 | "description": "Web Automation Framework", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "scripts": { 8 | "lint": "eslint -c .eslintrc.js --ext .ts ./src ./config", 9 | "selenium": "docker-compose up -d", 10 | "selenium:vnc": "docker-compose -f docker-compose.vnc.yml up -d", 11 | "selenium:stop": "docker-compose down -v", 12 | "test": "./node_modules/.bin/cross-env VNC_SUPPORT=false DEBUG_TEST=false npm run test:run:local", 13 | "test:vnc": "./node_modules/.bin/cross-env VNC_SUPPORT=true DEBUG_TEST=false npm run test:run:local", 14 | "test:run:local": "npm run report:clean && npm run test:run && npm run report || npm run report", 15 | "test:run": "node ./node_modules/@wdio/cli/bin/wdio.js --spec", 16 | "report": "node ./cucumber.report.conf.js", 17 | "report:clean": "rimraf ./report", 18 | "ci:build": "docker-compose -f docker-compose.ci.yml build", 19 | "ci": "docker-compose -f docker-compose.ci.yml up --force-recreate --renew-anon-volumes --abort-on-container-exit --exit-code-from node" 20 | }, 21 | "devDependencies": { 22 | "@types/chai": "^4.2.10", 23 | "@types/cucumber": "^6.0.1", 24 | "@types/mocha": "^7.0.2", 25 | "@types/node": "^13.9.0", 26 | "@typescript-eslint/eslint-plugin": "^2.23.0", 27 | "@typescript-eslint/parser": "^2.23.0", 28 | "@wdio/cli": "^5.19.0", 29 | "@wdio/cucumber-framework": "^5.20.0", 30 | "@wdio/local-runner": "^5.19.0", 31 | "@wdio/spec-reporter": "^5.18.7", 32 | "@wdio/sync": "^5.18.7", 33 | "chai": "^4.2.0", 34 | "cross-env": "^7.0.2", 35 | "dotenv": "^8.2.0", 36 | "eslint": "^6.8.0", 37 | "multiple-cucumber-html-reporter": "^1.16.0", 38 | "rimraf": "^3.0.2", 39 | "ts-node": "^8.6.2", 40 | "typescript": "^3.8.3", 41 | "wdio-cucumberjs-json-reporter": "^1.1.3", 42 | "webdriverio": "^5.19.0" 43 | }, 44 | "engines": { 45 | "node": ">=10.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ./wait-for-it.sh -t 15 chrome:5555 && ./wait-for-it.sh -t 15 firefox:5555 5 | 6 | if [ $? -ne 0 ] 7 | then 8 | echo "Failure: Timed out to connect to selenium hub." >&2 9 | exit 1 10 | fi 11 | 12 | # run tests and get the exit code 13 | ./node_modules/.bin/wdio --spec || EXIT_CODE=$? 14 | 15 | # generate report 16 | npm run report 17 | 18 | exit $EXIT_CODE -------------------------------------------------------------------------------- /scripts/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | # https://docs.docker.com/compose/startup-order/ 5 | # https://github.com/vishnubob/wait-for-it/ 6 | 7 | cmdname=$(basename $0) 8 | 9 | echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 10 | 11 | usage() 12 | { 13 | cat << USAGE >&2 14 | Usage: 15 | $cmdname host:port [-s] [-t timeout] [-- command args] 16 | -h HOST | --host=HOST Host or IP under test 17 | -p PORT | --port=PORT TCP port under test 18 | Alternatively, you specify the host and port as host:port 19 | -s | --strict Only execute subcommand if the test succeeds 20 | -q | --quiet Don't output any status messages 21 | -t TIMEOUT | --timeout=TIMEOUT 22 | Timeout in seconds, zero for no timeout 23 | -- COMMAND ARGS Execute command with args after the test finishes 24 | USAGE 25 | exit 1 26 | } 27 | 28 | wait_for() 29 | { 30 | if [[ $TIMEOUT -gt 0 ]]; then 31 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 32 | else 33 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 34 | fi 35 | start_ts=$(date +%s) 36 | while : 37 | do 38 | if [[ $ISBUSY -eq 1 ]]; then 39 | nc -z $HOST $PORT 40 | result=$? 41 | else 42 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 43 | result=$? 44 | fi 45 | if [[ $result -eq 0 ]]; then 46 | end_ts=$(date +%s) 47 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 48 | break 49 | fi 50 | sleep 1 51 | done 52 | return $result 53 | } 54 | 55 | wait_for_wrapper() 56 | { 57 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 58 | if [[ $QUIET -eq 1 ]]; then 59 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 60 | else 61 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 62 | fi 63 | PID=$! 64 | trap "kill -INT -$PID" INT 65 | wait $PID 66 | RESULT=$? 67 | if [[ $RESULT -ne 0 ]]; then 68 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 69 | fi 70 | return $RESULT 71 | } 72 | 73 | # process arguments 74 | while [[ $# -gt 0 ]] 75 | do 76 | case "$1" in 77 | *:* ) 78 | hostport=(${1//:/ }) 79 | HOST=${hostport[0]} 80 | PORT=${hostport[1]} 81 | shift 1 82 | ;; 83 | --child) 84 | CHILD=1 85 | shift 1 86 | ;; 87 | -q | --quiet) 88 | QUIET=1 89 | shift 1 90 | ;; 91 | -s | --strict) 92 | STRICT=1 93 | shift 1 94 | ;; 95 | -h) 96 | HOST="$2" 97 | if [[ $HOST == "" ]]; then break; fi 98 | shift 2 99 | ;; 100 | --host=*) 101 | HOST="${1#*=}" 102 | shift 1 103 | ;; 104 | -p) 105 | PORT="$2" 106 | if [[ $PORT == "" ]]; then break; fi 107 | shift 2 108 | ;; 109 | --port=*) 110 | PORT="${1#*=}" 111 | shift 1 112 | ;; 113 | -t) 114 | TIMEOUT="$2" 115 | if [[ $TIMEOUT == "" ]]; then break; fi 116 | shift 2 117 | ;; 118 | --timeout=*) 119 | TIMEOUT="${1#*=}" 120 | shift 1 121 | ;; 122 | --) 123 | shift 124 | CLI=("$@") 125 | break 126 | ;; 127 | --help) 128 | usage 129 | ;; 130 | *) 131 | echoerr "Unknown argument: $1" 132 | usage 133 | ;; 134 | esac 135 | done 136 | 137 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 138 | echoerr "Error: you need to provide a host and port to test." 139 | usage 140 | fi 141 | 142 | TIMEOUT=${TIMEOUT:-15} 143 | STRICT=${STRICT:-0} 144 | CHILD=${CHILD:-0} 145 | QUIET=${QUIET:-0} 146 | 147 | # check to see if timeout is from busybox? 148 | # check to see if timeout is from busybox? 149 | TIMEOUT_PATH=$(realpath $(which timeout)) 150 | if [[ $TIMEOUT_PATH =~ "busybox" ]]; then 151 | ISBUSY=1 152 | BUSYTIMEFLAG="-t" 153 | else 154 | ISBUSY=0 155 | BUSYTIMEFLAG="" 156 | fi 157 | 158 | if [[ $CHILD -gt 0 ]]; then 159 | wait_for 160 | RESULT=$? 161 | exit $RESULT 162 | else 163 | if [[ $TIMEOUT -gt 0 ]]; then 164 | wait_for_wrapper 165 | RESULT=$? 166 | else 167 | wait_for 168 | RESULT=$? 169 | fi 170 | fi 171 | 172 | if [[ $CLI != "" ]]; then 173 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 174 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 175 | exit $RESULT 176 | fi 177 | exec "${CLI[@]}" 178 | else 179 | exit $RESULT 180 | fi -------------------------------------------------------------------------------- /src/features/google.search.feature: -------------------------------------------------------------------------------- 1 | Feature: Performing a Google Search 2 | 3 | As a user on the Google search page 4 | I want to search for Selenium-Webdriver 5 | Because I want to learn more about it 6 | 7 | Background: 8 | Given I open the url "https://google.com" 9 | 10 | Scenario: Searching for unknown term 11 | When I set "cO8QFWBdl0jJD5AHFOox" to the inputfield "[name=q]" 12 | And I press "Enter" 13 | Then I expect that element "#search" becomes not displayed 14 | 15 | # The @Verbose tag adds a screenshot and additional test metadata. 16 | @Verbose 17 | Scenario Outline: Searching for term "" 18 | When I set "" to the inputfield "[name=q]" 19 | And I press "Enter" 20 | Then I expect that element "#search" becomes displayed 21 | 22 | Examples: 23 | |searchItem| 24 | |Selenium Webdriver| 25 | |Docker| 26 | |Labs42| -------------------------------------------------------------------------------- /src/features/sample.snippets.feature: -------------------------------------------------------------------------------- 1 | Feature: Sample Snippets test 2 | As a developer 3 | I should be able to use given text snippets 4 | 5 | Scenario: open URL 6 | Given the page url is not "http://webdriverjs.christian-bromann.com/" 7 | And I open the url "http://webdriverjs.christian-bromann.com/" 8 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" 9 | And I expect that the url is not "http://google.com" 10 | 11 | Scenario: open sub page of weburl 12 | Given the page url is not "http://webdriverjs.christian-bromann.com/two.html" 13 | And I open the url "http://webdriverjs.christian-bromann.com/" 14 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" 15 | And I expect that the url is not "http://google.com" 16 | 17 | Scenario: click on link 18 | Given the title is not "two" 19 | And I open the url "http://webdriverjs.christian-bromann.com/" 20 | When I click on the link "two" 21 | Then I expect that the title is "two" 22 | 23 | Scenario: click on button 24 | Given I open the url "http://webdriverjs.christian-bromann.com/" 25 | And the element ".btn1_clicked" is not displayed 26 | When I click on the button ".btn1" 27 | Then I expect that element ".btn1_clicked" is displayed 28 | 29 | Scenario: double click on a button 30 | Given I open the url "http://webdriverjs.christian-bromann.com/" 31 | And the element ".btn1_dblclicked" is not displayed 32 | When I doubleclick on the element ".btn1" 33 | Then I expect that element ".btn1_dblclicked" is displayed 34 | 35 | Scenario: click on element 36 | Given I open the url "http://webdriverjs.christian-bromann.com/" 37 | And the element ".btn1_clicked" is not displayed 38 | When I click on the element ".btn1" 39 | Then I expect that element ".btn1_clicked" is displayed 40 | 41 | Scenario: add value to an input element 42 | Given I open the url "http://webdriverjs.christian-bromann.com/" 43 | And the element "//html/body/section/form/input[1]" not contains the text "abc" 44 | When I add "bc" to the inputfield "//html/body/section/form/input[1]" 45 | Then I expect that element "//html/body/section/form/input[1]" contains the text "abc" 46 | 47 | Scenario: set value to an input element 48 | Given I open the url "http://webdriverjs.christian-bromann.com/" 49 | And the element "//html/body/section/form/input[1]" not contains the text "bc" 50 | When I set "bc" to the inputfield "//html/body/section/form/input[1]" 51 | Then I expect that element "//html/body/section/form/input[1]" contains the text "bc" 52 | 53 | Scenario: clear value of input element 54 | Given I open the url "http://webdriverjs.christian-bromann.com/" 55 | When I set "test" to the inputfield "//html/body/section/form/input[1]" 56 | And I clear the inputfield "//html/body/section/form/input[1]" 57 | Then I expect that element "//html/body/section/form/input[1]" not contains any text 58 | 59 | Scenario: wait for element 60 | Given I open the url "http://webdriverjs.christian-bromann.com/" 61 | And there is no element ".lateElem" on the page 62 | Then I wait on element ".lateElem" for 5000ms to be displayed 63 | 64 | Scenario: wait for element using default wait time 65 | Given I open the url "http://webdriverjs.christian-bromann.com/" 66 | And there is no element ".lateElem" on the page 67 | Then I wait on element ".lateElem" to be displayed 68 | 69 | Scenario: pause 70 | Given I open the url "http://webdriverjs.christian-bromann.com/" 71 | And there is no element ".lateElem" on the page 72 | When I pause for 3000ms 73 | Then I expect that element ".lateElem" is displayed 74 | 75 | Scenario: query title 76 | Given I open the url "http://webdriverjs.christian-bromann.com/" 77 | And the title is "WebdriverJS Testpage" 78 | And the title is not "Other title" 79 | Then I expect that the title is "WebdriverJS Testpage" 80 | And I expect that the title is not "Other title" 81 | 82 | Scenario: check visibility 83 | Given I open the url "http://webdriverjs.christian-bromann.com/" 84 | And the element ".btn1" is displayed 85 | And the element ".btn1_clicked" is not displayed 86 | Then I expect that element ".btn1" is displayed 87 | And I expect that element ".btn1_clicked" is not displayed 88 | 89 | Scenario: compare texts 90 | Given I open the url "http://webdriverjs.christian-bromann.com/" 91 | And the element "#secondPageLink" contains the same text as element "#secondPageLink" 92 | And the element "#secondPageLink" contains not the same text as element "#githubRepo" 93 | Then I expect that element "#secondPageLink" contains the same text as element "#secondPageLink" 94 | And I expect that element "#secondPageLink" not contains the same text as element "#githubRepo" 95 | 96 | Scenario: check text content 97 | Given I open the url "http://webdriverjs.christian-bromann.com/" 98 | And the element "#secondPageLink" contains the text "two" 99 | And the element "#secondPageLink" not contains the text "andere linktext" 100 | Then I expect that element "#secondPageLink" contains the text "two" 101 | And I expect that element "#secondPageLink" not contains the text "anderer linktext" 102 | 103 | Scenario: check input content 104 | Given I open the url "http://webdriverjs.christian-bromann.com/" 105 | And the element "//html/body/section/form/input[1]" contains the text "a" 106 | And the element "//html/body/section/form/input[1]" not contains the text "aa" 107 | Then I expect that element "//html/body/section/form/input[1]" contains the text "a" 108 | And I expect that element "//html/body/section/form/input[1]" not contains the text "aa" 109 | 110 | Scenario: check attribute 111 | Given I open the url "http://webdriverjs.christian-bromann.com/" 112 | And the attribute "data-foundby" from element "#newWindow" is "partial link text" 113 | And the attribute "data-foundby" from element "#newWindow" is not "something else" 114 | Then I expect that the attribute "data-foundby" from element "#newWindow" is "partial link text" 115 | And I expect that the attribute "data-foundby" from element "#newWindow" is not "something else" 116 | 117 | # Firefox returns `rgb` instead of `rgba` 118 | @OnlyChrome 119 | Scenario: check css attribute 120 | Given I open the url "http://webdriverjs.christian-bromann.com/" 121 | And the css attribute "background-color" from element ".red" is "rgba(255,0,0,1)" 122 | And the css attribute "background-color" from element ".red" is not "rgba(0,255,0,1)" 123 | Then I expect that the css attribute "background-color" from element ".red" is "rgba(255,0,0,1)" 124 | And I expect that the css attribute "background-color" from element ".red" is not "rgba(0,255,0,1)" 125 | 126 | Scenario: check width and height 127 | Given I open the url "http://webdriverjs.christian-bromann.com/" 128 | And the element ".red" is 102px broad 129 | And the element ".red" is 102px tall 130 | And the element ".red" is not 103px broad 131 | And the element ".red" is not 103px tall 132 | Then I expect that element ".red" is 102px broad 133 | And I expect that element ".red" is 102px tall 134 | And I expect that element ".red" is not 103px broad 135 | And I expect that element ".red" is not 103px tall 136 | 137 | Scenario: check offset 138 | Given I open the url "http://webdriverjs.christian-bromann.com/" 139 | And the element ".red" is positioned at 15px on the x axis 140 | And the element ".red" is positioned at 242px on the y axis 141 | And the element ".red" is not positioned at 16px on the x axis 142 | And the element ".red" is not positioned at 243px on the y axis 143 | Then I expect that element ".red" is positioned at 15px on the x axis 144 | And I expect that element ".red" is positioned at 242px on the y axis 145 | And I expect that element ".red" is not positioned at 16px on the x axis 146 | And I expect that element ".red" is not positioned at 243px on the y axis 147 | 148 | Scenario: check selected 149 | Given I open the url "http://webdriverjs.christian-bromann.com/" 150 | And the checkbox ".checkbox_notselected" is not checked 151 | When I click on the element ".checkbox_notselected" 152 | Then I expect that checkbox ".checkbox_notselected" is checked 153 | 154 | Scenario: set / read cookie 155 | Given I open the url "http://webdriverjs.christian-bromann.com/" 156 | And the cookie "test" does not exist 157 | When I set a cookie "test" with the content "test123" 158 | Then I expect that cookie "test" exists 159 | And I expect that cookie "test" contains "test123" 160 | And I expect that cookie "test" not contains "test1234" 161 | 162 | Scenario: delete cookie 163 | Given I open the url "http://webdriverjs.christian-bromann.com/" 164 | And the cookie "test" does exist 165 | When I delete the cookie "test" 166 | Then I expect that cookie "test" not exists -------------------------------------------------------------------------------- /src/steps/given.ts: -------------------------------------------------------------------------------- 1 | import { Given } from 'cucumber'; 2 | import { closeAllButFirstTab } from '../support/action/closeAllButFirstTab'; 3 | import { openWebsite } from '../support/action/openWebsite'; 4 | import { setWindowSize } from '../support/action/setWindowSize'; 5 | import { checkContainsAnyText } from '../support/check/checkContainsAnyText'; 6 | import { checkContainsText } from '../support/check/checkContainsText'; 7 | import { checkCookieContent } from '../support/check/checkCookieContent'; 8 | import { checkCookieExists } from '../support/check/checkCookieExists'; 9 | import { checkDimension } from '../support/check/checkDimension'; 10 | import { checkElementExists } from '../support/check/checkElementExists'; 11 | import { checkEqualsText } from '../support/check/checkEqualsText'; 12 | import { checkIsEmpty } from '../support/check/checkIsEmpty'; 13 | import { checkModal } from '../support/check/checkModal'; 14 | import { checkOffset } from '../support/check/checkOffset'; 15 | import { checkProperty } from '../support/check/checkProperty'; 16 | import { checkSelected } from '../support/check/checkSelected'; 17 | import { checkTitle } from '../support/check/checkTitle'; 18 | import { checkUrl } from '../support/check/checkUrl'; 19 | import { compareText } from '../support/check/compareText'; 20 | import { isDisplayed } from '../support/check/isDisplayed'; 21 | import { isEnabled } from '../support/check/isEnabled'; 22 | 23 | Given( 24 | /^I open the (url|site) "([^"]*)?"$/, 25 | openWebsite, 26 | ); 27 | 28 | Given( 29 | /^the element "([^"]*)?" is( not)* displayed$/, 30 | isDisplayed, 31 | ); 32 | 33 | Given( 34 | /^the element "([^"]*)?" is( not)* enabled$/, 35 | isEnabled, 36 | ); 37 | 38 | Given( 39 | /^the element "([^"]*)?" is( not)* selected$/, 40 | checkSelected, 41 | ); 42 | 43 | Given( 44 | /^the checkbox "([^"]*)?" is( not)* checked$/, 45 | checkSelected, 46 | ); 47 | 48 | Given( 49 | /^there is (an|no) element "([^"]*)?" on the page$/, 50 | checkElementExists, 51 | ); 52 | 53 | Given( 54 | /^the title is( not)* "([^"]*)?"$/, 55 | checkTitle, 56 | ); 57 | 58 | Given( 59 | /^the element "([^"]*)?" contains( not)* the same text as element "([^"]*)?"$/, 60 | compareText, 61 | ); 62 | 63 | Given( 64 | /^the (button|element) "([^"]*)?"( not)* matches the text "([^"]*)?"$/, 65 | checkEqualsText, 66 | ); 67 | 68 | Given( 69 | /^the (button|element|container) "([^"]*)?"( not)* contains the text "([^"]*)?"$/, 70 | checkContainsText, 71 | ); 72 | 73 | Given( 74 | /^the (button|element) "([^"]*)?"( not)* contains any text$/, 75 | checkContainsAnyText, 76 | ); 77 | 78 | Given( 79 | /^the (button|element) "([^"]*)?" is( not)* empty$/, 80 | checkIsEmpty, 81 | ); 82 | 83 | Given( 84 | /^the page url is( not)* "([^"]*)?"$/, 85 | checkUrl, 86 | ); 87 | 88 | Given( 89 | /^the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"$/, 90 | checkProperty, 91 | ); 92 | 93 | Given( 94 | /^the cookie "([^"]*)?" contains( not)* the value "([^"]*)?"$/, 95 | checkCookieContent, 96 | ); 97 | 98 | Given( 99 | /^the cookie "([^"]*)?" does( not)* exist$/, 100 | checkCookieExists, 101 | ); 102 | 103 | Given( 104 | /^the element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)$/, 105 | checkDimension, 106 | ); 107 | 108 | Given( 109 | /^the element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis$/, 110 | checkOffset, 111 | ); 112 | 113 | Given( 114 | /^I have a screen that is ([\d]+) by ([\d]+) pixels$/, 115 | setWindowSize, 116 | ); 117 | 118 | Given( 119 | /^I have closed all but the first (window|tab)$/, 120 | closeAllButFirstTab, 121 | ); 122 | 123 | Given( 124 | /^a (alertbox|confirmbox|prompt) is( not)* opened$/, 125 | checkModal, 126 | ); 127 | -------------------------------------------------------------------------------- /src/steps/hooks.ts: -------------------------------------------------------------------------------- 1 | import { After, Before, HookScenarioResult } from 'cucumber'; 2 | import { addObject, addScreenshot } from '../support/lib/context'; 3 | 4 | Before({ tags: '@OnlyChrome' }, () => { 5 | if (browser.capabilities.browserName !== 'chrome') { 6 | return 'skipped'; 7 | } 8 | 9 | return undefined; 10 | }); 11 | 12 | Before({ tags: '@OnlyFirefox' }, () => { 13 | if (browser.capabilities.browserName !== 'firefox') { 14 | return 'skipped'; 15 | } 16 | 17 | return undefined; 18 | }); 19 | 20 | After({ tags: '@Verbose' }, (scenario: HookScenarioResult) => { 21 | addObject({ 22 | browser: { 23 | url: browser.getUrl(), 24 | title: browser.getTitle(), 25 | }, 26 | duration: scenario.result.duration, 27 | status: scenario.result.status, 28 | error: scenario.result.exception, 29 | }); 30 | 31 | addScreenshot(); 32 | }); 33 | -------------------------------------------------------------------------------- /src/steps/then.ts: -------------------------------------------------------------------------------- 1 | import { Then } from 'cucumber'; 2 | import { waitFor } from '../support/action/waitFor'; 3 | import { waitForDisplayed } from '../support/action/waitForDisplayed'; 4 | import { checkClass } from '../support/check/checkClass'; 5 | import { checkContainsAnyText } from '../support/check/checkContainsAnyText'; 6 | import { checkContainsText } from '../support/check/checkContainsText'; 7 | import { checkCookieContent } from '../support/check/checkCookieContent'; 8 | import { checkCookieExists } from '../support/check/checkCookieExists'; 9 | import { checkDimension } from '../support/check/checkDimension'; 10 | import { checkEqualsText } from '../support/check/checkEqualsText'; 11 | import { checkFocus } from '../support/check/checkFocus'; 12 | import { checkFontProperty } from '../support/check/checkFontProperty'; 13 | import { checkInURLPath } from '../support/check/checkInURLPath'; 14 | import { checkIsEmpty } from '../support/check/checkIsEmpty'; 15 | import { checkIsOpenedInNewWindow } from '../support/check/checkIsOpenedInNewWindow'; 16 | import { checkModal } from '../support/check/checkModal'; 17 | import { checkModalText } from '../support/check/checkModalText'; 18 | import { checkNewWindow } from '../support/check/checkNewWindow'; 19 | import { checkOffset } from '../support/check/checkOffset'; 20 | import { checkProperty } from '../support/check/checkProperty'; 21 | import { checkSelected } from '../support/check/checkSelected'; 22 | import { checkTitle } from '../support/check/checkTitle'; 23 | import { checkTitleContains } from '../support/check/checkTitleContains'; 24 | import { checkUrl } from '../support/check/checkUrl'; 25 | import { checkUrlPath } from '../support/check/checkUrlPath'; 26 | import { checkWithinViewport } from '../support/check/checkWithinViewport'; 27 | import { compareText } from '../support/check/compareText'; 28 | import { isDisplayed } from '../support/check/isDisplayed'; 29 | import { isEnabled } from '../support/check/isEnabled'; 30 | import { isExisting } from '../support/check/isExisting'; 31 | import { checkIfElementExists } from '../support/lib/checkIfElementExists'; 32 | 33 | Then( 34 | /^I expect that the title is( not)* "([^"]*)?"$/, 35 | checkTitle, 36 | ); 37 | 38 | Then( 39 | /^I expect that the title( not)* contains "([^"]*)?"$/, 40 | checkTitleContains, 41 | ); 42 | 43 | Then( 44 | /^I expect that element "([^"]*)?" does( not)* appear exactly "([^"]*)?" times$/, 45 | checkIfElementExists, 46 | ); 47 | 48 | Then( 49 | /^I expect that element "([^"]*)?" is( not)* displayed$/, 50 | isDisplayed, 51 | ); 52 | 53 | Then( 54 | /^I expect that element "([^"]*)?" becomes( not)* displayed$/, 55 | waitForDisplayed, 56 | ); 57 | 58 | Then( 59 | /^I expect that element "([^"]*)?" is( not)* within the viewport$/, 60 | checkWithinViewport, 61 | ); 62 | 63 | Then( 64 | /^I expect that element "([^"]*)?" does( not)* exist$/, 65 | isExisting, 66 | ); 67 | 68 | Then( 69 | /^I expect that element "([^"]*)?"( not)* contains the same text as element "([^"]*)?"$/, 70 | compareText, 71 | ); 72 | 73 | Then( 74 | /^I expect that (button|element) "([^"]*)?"( not)* matches the text "([^"]*)?"$/, 75 | checkEqualsText, 76 | ); 77 | 78 | Then( 79 | /^I expect that (button|element|container) "([^"]*)?"( not)* contains the text "([^"]*)?"$/, 80 | checkContainsText, 81 | ); 82 | 83 | Then( 84 | /^I expect that (button|element) "([^"]*)?"( not)* contains any text$/, 85 | checkContainsAnyText, 86 | ); 87 | 88 | Then( 89 | /^I expect that (button|element) "([^"]*)?" is( not)* empty$/, 90 | checkIsEmpty, 91 | ); 92 | 93 | Then( 94 | /^I expect that the url is( not)* "([^"]*)?"$/, 95 | checkUrl, 96 | ); 97 | 98 | Then( 99 | /^I expect that the path is( not)* "([^"]*)?"$/, 100 | checkUrlPath, 101 | ); 102 | 103 | Then( 104 | /^I expect the url to( not)* contain "([^"]*)?"$/, 105 | checkInURLPath, 106 | ); 107 | 108 | Then( 109 | /^I expect that the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"$/, 110 | checkProperty, 111 | ); 112 | 113 | Then( 114 | /^I expect that the font( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"$/, 115 | checkFontProperty, 116 | ); 117 | 118 | Then( 119 | /^I expect that checkbox "([^"]*)?" is( not)* checked$/, 120 | checkSelected, 121 | ); 122 | 123 | Then( 124 | /^I expect that element "([^"]*)?" is( not)* selected$/, 125 | checkSelected, 126 | ); 127 | 128 | Then( 129 | /^I expect that element "([^"]*)?" is( not)* enabled$/, 130 | isEnabled, 131 | ); 132 | 133 | Then( 134 | /^I expect that cookie "([^"]*)?"( not)* contains "([^"]*)?"$/, 135 | checkCookieContent, 136 | ); 137 | 138 | Then( 139 | /^I expect that cookie "([^"]*)?"( not)* exists$/, 140 | checkCookieExists, 141 | ); 142 | 143 | Then( 144 | /^I expect that element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)$/, 145 | checkDimension, 146 | ); 147 | 148 | Then( 149 | /^I expect that element "([^"]*)?" is( not)* positioned at ([\d+.?\d*]+)px on the (x|y) axis$/, 150 | checkOffset, 151 | ); 152 | 153 | Then( 154 | /^I expect that element "([^"]*)?" (has|does not have) the class "([^"]*)?"$/, 155 | checkClass, 156 | ); 157 | 158 | Then( 159 | /^I expect a new (window|tab) has( not)* been opened$/, 160 | checkNewWindow, 161 | ); 162 | 163 | Then( 164 | /^I expect the url "([^"]*)?" is opened in a new (tab|window)$/, 165 | checkIsOpenedInNewWindow, 166 | ); 167 | 168 | Then( 169 | /^I expect that element "([^"]*)?" is( not)* focused$/, 170 | checkFocus, 171 | ); 172 | 173 | Then( 174 | /^I wait on element "([^"]*)?"(?: for (\d+)ms)*(?: to( not)* (be checked|be enabled|be selected|be displayed|contain a text|contain a value|exist))*$/, 175 | { 176 | wrapperOptions: { 177 | retry: 3, 178 | }, 179 | }, 180 | waitFor, 181 | ); 182 | 183 | Then( 184 | /^I expect that a (alertbox|confirmbox|prompt) is( not)* opened$/, 185 | checkModal, 186 | ); 187 | 188 | Then( 189 | /^I expect that a (alertbox|confirmbox|prompt)( not)* contains the text "([^"]*)?"$/, 190 | checkModalText, 191 | ); 192 | -------------------------------------------------------------------------------- /src/steps/when.ts: -------------------------------------------------------------------------------- 1 | import { When } from 'cucumber'; 2 | import { clearInputField } from '../support/action/clearInputField'; 3 | import { clickElement } from '../support/action/clickElement'; 4 | import { closeLastOpenedWindow } from '../support/action/closeLastOpenedWindow'; 5 | import { deleteCookies } from '../support/action/deleteCookies'; 6 | import { dragElement } from '../support/action/dragElement'; 7 | import { focusLastOpenedWindow } from '../support/action/focusLastOpenedWindow'; 8 | import { handleModal } from '../support/action/handleModal'; 9 | import { moveTo } from '../support/action/moveTo'; 10 | import { pause } from '../support/action/pause'; 11 | import { pressButton } from '../support/action/pressButton'; 12 | import { scroll } from '../support/action/scroll'; 13 | import { selectOption } from '../support/action/selectOption'; 14 | import { selectOptionByIndex } from '../support/action/selectOptionByIndex'; 15 | import { setCookie } from '../support/action/setCookie'; 16 | import { setInputField } from '../support/action/setInputField'; 17 | import { setPromptText } from '../support/action/setPromptText'; 18 | import { switchToFrame } from '../support/action/switchToIframe'; 19 | 20 | When( 21 | /^I (click|doubleclick) on the (link|button|element) "([^"]*)?"$/, 22 | clickElement, 23 | ); 24 | 25 | When( 26 | /^I (add|set) "([^"]*)?" to the inputfield "([^"]*)?"$/, 27 | setInputField, 28 | ); 29 | 30 | When( 31 | /^I clear the inputfield "([^"]*)?"$/, 32 | clearInputField, 33 | ); 34 | 35 | When( 36 | /^I drag element "([^"]*)?" to element "([^"]*)?"$/, 37 | dragElement, 38 | ); 39 | 40 | When( 41 | /^I pause for (\d+)ms$/, 42 | pause, 43 | ); 44 | 45 | When( 46 | /^I set a cookie "([^"]*)?" with the content "([^"]*)?"$/, 47 | setCookie, 48 | ); 49 | 50 | When( 51 | /^I delete the cookie "([^"]*)?"$/, 52 | deleteCookies, 53 | ); 54 | 55 | When( 56 | /^I press "([^"]*)?"$/, 57 | pressButton, 58 | ); 59 | 60 | When( 61 | /^I (accept|dismiss) the (alertbox|confirmbox|prompt)$/, 62 | handleModal, 63 | ); 64 | 65 | When( 66 | /^I enter "([^"]*)?" into the prompt$/, 67 | setPromptText, 68 | ); 69 | 70 | When( 71 | /^I scroll to element "([^"]*)?"$/, 72 | scroll, 73 | ); 74 | 75 | When( 76 | /^I close the last opened (window|tab)$/, 77 | closeLastOpenedWindow, 78 | ); 79 | 80 | When( 81 | /^I focus the last opened (window|tab)$/, 82 | focusLastOpenedWindow, 83 | ); 84 | 85 | When( 86 | /^I select the (\d+)(st|nd|rd|th) option for element "([^"]*)?"$/, 87 | selectOptionByIndex, 88 | ); 89 | 90 | When( 91 | /^I select the option with the (name|value|text) "([^"]*)?" for element "([^"]*)?"$/, 92 | selectOption, 93 | ); 94 | 95 | When( 96 | /^I move to element "([^"]*)?"(?: with an offset of (\d+),(\d+))*$/, 97 | moveTo, 98 | ); 99 | 100 | 101 | When( 102 | /^I switch to iframe "([^"]*)?"$/, 103 | switchToFrame, 104 | ); -------------------------------------------------------------------------------- /src/support/action/clearInputField.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Clear a given input field (placeholder for WDIO's clearValue) 3 | * @param {String} selector Element selector 4 | */ 5 | export function clearInputField(selector: string): void { 6 | $(selector).clearValue(); 7 | } 8 | -------------------------------------------------------------------------------- /src/support/action/clickElement.ts: -------------------------------------------------------------------------------- 1 | import { checkIfElementExists } from '../lib/checkIfElementExists'; 2 | 3 | /** 4 | * Perform an click action on the given element 5 | * @param {String} action The action to perform (click or doubleClick) 6 | * @param {String} type Type of the element (link or selector) 7 | * @param {String} selector Element selector 8 | */ 9 | export function clickElement(action: string, type: string, selector: string): void { 10 | const selector2 = (type === 'link') ? `=${selector}` : selector; 11 | const method = (action === 'click') ? 'click' : 'doubleClick'; 12 | 13 | checkIfElementExists(selector2); 14 | 15 | $(selector2)[method](); 16 | } 17 | -------------------------------------------------------------------------------- /src/support/action/closeAllButFirstTab.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Close all but the first tab 3 | * @param {String} obsolete Type of object to close (window or tab) 4 | */ 5 | /* eslint @typescript-eslint/no-unused-vars: "off" */ 6 | export function closeAllButFirstTab(obsolete: string): void { 7 | const windowHandles = browser.getWindowHandles(); 8 | 9 | // Close all tabs but the first one 10 | windowHandles.reverse(); 11 | windowHandles.forEach((handle, index) => { 12 | browser.switchToWindow(handle); 13 | if (index < windowHandles.length - 1) { 14 | browser.closeWindow(); 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/support/action/closeLastOpenedWindow.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Close the last opened window 3 | * @param {String} obsolete Type of object to close (window or tab) 4 | */ 5 | /* eslint @typescript-eslint/no-unused-vars: "off" */ 6 | export function closeLastOpenedWindow(obsolete: string): void { 7 | const lastWindowHandle = browser.getWindowHandles().slice(-1)[0]; 8 | 9 | browser.closeWindow(); 10 | browser.switchToWindow(lastWindowHandle); 11 | } 12 | -------------------------------------------------------------------------------- /src/support/action/deleteCookies.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Delete a cookie 3 | * @param {String} name The name of the cookie to delete 4 | */ 5 | export function deleteCookies(name: string): void { 6 | browser.deleteCookies([name]); 7 | } 8 | -------------------------------------------------------------------------------- /src/support/action/dragElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Drag a element to a given destination 3 | * @param {String} selector The selector for the source element 4 | * @param {String} destination The selector for the destination element 5 | */ 6 | export function dragElement(selector: string, destination: string): void { 7 | $(selector).dragAndDrop($(destination)); 8 | } 9 | -------------------------------------------------------------------------------- /src/support/action/focusLastOpenedWindow.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Focus the last opened window 3 | * @param {String} obsolete Type of object to close (window or tab) 4 | */ 5 | /* eslint @typescript-eslint/no-unused-vars: "off" */ 6 | export function focusLastOpenedWindow(obsolete: string): void { 7 | const lastWindowHandle = browser.getWindowHandles().slice(-1)[0]; 8 | 9 | browser.switchToWindow(lastWindowHandle); 10 | } 11 | -------------------------------------------------------------------------------- /src/support/action/handleModal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle a modal 3 | * @param {String} action Action to perform on the modal (accept, dismiss) 4 | * @param {String} modalType Type of modal (alertbox, confirmbox, prompt) 5 | */ 6 | export function handleModal(action: 'accept' | 'dismiss', modalType: 'alertbox' | 'confirmbox' | 'prompt'): void { 7 | let command = `${action.slice(0, 1).toLowerCase()}${action.slice(1)}Alert`; 8 | 9 | // Alert boxes can't be dismissed, this causes Chrome to crash during tests 10 | if (modalType === 'alertbox') { 11 | command = 'acceptAlert'; 12 | } 13 | 14 | browser[command](); 15 | } 16 | -------------------------------------------------------------------------------- /src/support/action/moveTo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Move to the given selector with an optional offset on a X and Y position 3 | * @param {String} selector Element selector 4 | * @param {String} x X coordinate to move to 5 | * @param {String} y Y coordinate to move to 6 | */ 7 | export function moveTo(selector: string, x: string, y: string): void { 8 | const intX = parseInt(x, 10) || undefined; 9 | const intY = parseInt(y, 10) || undefined; 10 | 11 | $(selector).moveTo(intX, intY); 12 | } 13 | -------------------------------------------------------------------------------- /src/support/action/openWebsite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Open the given URL 3 | * @param {String} type Type of navigation (url or site) 4 | * @param {String} page The URL to navigate to 5 | */ 6 | export function openWebsite(type: 'url' | 'site', page: string): void { 7 | const url = (type === 'url') ? page : browser.options.baseUrl + page; 8 | browser.url(url); 9 | } 10 | -------------------------------------------------------------------------------- /src/support/action/pause.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pause execution for a given number of milliseconds 3 | * @param {String} ms Number of milliseconds to pause 4 | */ 5 | export function pause(ms: string): void { 6 | const intMs = parseInt(ms, 10); 7 | browser.pause(intMs); 8 | } 9 | -------------------------------------------------------------------------------- /src/support/action/pressButton.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Perform a key press 3 | * @param {String} key The key to press 4 | */ 5 | export function pressButton(key: string): void { 6 | browser.keys(key); 7 | } 8 | -------------------------------------------------------------------------------- /src/support/action/scroll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Scroll the page to the given element 3 | * @param {String} selector Element selector 4 | */ 5 | export function scroll(selector: string): void { 6 | $(selector).scrollIntoView(); 7 | } 8 | -------------------------------------------------------------------------------- /src/support/action/selectOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Select an option of a select element 3 | * @param {String} selectionType Type of method to select by (name, value or 4 | * text) 5 | * @param {String} selectionValue Value to select by 6 | * @param {String} selector Element selector 7 | */ 8 | export function selectOption( 9 | selectionType: 'name' | 'value' | 'text', 10 | selectionValue: string, selector: string, 11 | ): void { 12 | let command = ''; 13 | const commandArguments = [selectionValue]; 14 | 15 | switch (selectionType) { 16 | case 'name': { 17 | command = 'selectByAttribute'; 18 | 19 | // The selectByAttribute command expects the attribute name as it 20 | // second argument so let's add it 21 | commandArguments.unshift('name'); 22 | 23 | break; 24 | } 25 | 26 | case 'value': { 27 | // The selectByAttribute command expects the attribute name as it 28 | // second argument so let's add it 29 | commandArguments.unshift('value'); 30 | command = 'selectByAttribute'; 31 | break; 32 | } 33 | 34 | case 'text': { 35 | command = 'selectByVisibleText'; 36 | break; 37 | } 38 | 39 | default: { 40 | throw new Error(`Unknown selection type "${selectionType}"`); 41 | } 42 | } 43 | 44 | $(selector)[command](...commandArguments); 45 | } 46 | -------------------------------------------------------------------------------- /src/support/action/selectOptionByIndex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Select a option from a select element by it's index 3 | * @param {String} index The index of the option 4 | * @param {String} obsolete The ordinal indicator of the index (unused) 5 | * @param {String} selector Element selector 6 | * 7 | * @todo merge with selectOption 8 | */ 9 | export function selectOptionByIndex(index: string, obsolete: string, selector: string): void { 10 | const optionIndex = parseInt(index, 10); 11 | $(selector).selectByIndex(optionIndex); 12 | } 13 | -------------------------------------------------------------------------------- /src/support/action/setCookie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Set a given cookie to a given value. When the cookies does not exist it will 3 | * be created 4 | * @param {String} cookieName The name of the cookie 5 | * @param {String} cookieContent The value of the cookie 6 | */ 7 | export function setCookie(cookieName: string, cookieContent: string): void { 8 | browser.setCookies({ 9 | name: cookieName, 10 | value: cookieContent, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/support/action/setInputField.ts: -------------------------------------------------------------------------------- 1 | import { checkIfElementExists } from '../lib/checkIfElementExists'; 2 | 3 | /** 4 | * Set the value of the given input field to a new value or add a value to the 5 | * current selector value 6 | * @param {String} method The method to use (add or set) 7 | * @param {String} value The value to set the selector to 8 | * @param {String} selector Element selector 9 | */ 10 | export function setInputField(method: 'add' | 'set', value: string, selector: string): void { 11 | const command = (method === 'add') ? 'addValue' : 'setValue'; 12 | 13 | checkIfElementExists(selector, false, 1); 14 | $(selector)[command](value || ''); 15 | } 16 | -------------------------------------------------------------------------------- /src/support/action/setPromptText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Set the text of the current prompt 5 | * @param {String} modalText The text to set to the prompt 6 | */ 7 | export function setPromptText(modalText: string): void { 8 | try { 9 | browser.sendAlertText(modalText); 10 | } catch (e) { 11 | expect.fail('A prompt was not open when it should have been open'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/support/action/setWindowSize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Resize the browser window 3 | * @param {String} screenWidth The width of the window to resize to 4 | * @param {String} screenHeight The height of the window to resize to 5 | */ 6 | export function setWindowSize(screenWidth: string, screenHeight: string): void { 7 | browser.setWindowSize( 8 | parseInt(screenWidth, 10), 9 | parseInt(screenHeight, 10), 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/support/action/switchToIframe.ts: -------------------------------------------------------------------------------- 1 | export function switchToFrame(selector: string) { 2 | browser.switchToFrame($(selector)); 3 | } -------------------------------------------------------------------------------- /src/support/action/waitFor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wait for the given element to be enabled, displayed, or to exist 3 | * @param {String} selector Element selector 4 | * @param {String} ms Wait duration (optional, default 3000) 5 | * @param {String} falseState Check for opposite state 6 | * @param {String} state State to check for (default 7 | * existence) 8 | */ 9 | export function waitFor(selector: string, ms: string, falseState: string, state: string): void { 10 | // Maximum number of milliseconds to wait, default 3000 11 | const intMs = parseInt(ms, 10) || 3000; 12 | 13 | // Command to perform on the browser object 14 | let command = 'waitForExist'; 15 | 16 | let boolFalseState = !!falseState; 17 | let parsedState = ''; 18 | 19 | if (falseState || state) { 20 | parsedState = state.includes(' ') 21 | ? state.split(/\s/)[state.split(/\s/).length - 1] 22 | : state; 23 | 24 | if (parsedState) { 25 | command = `waitFor${parsedState[0].toUpperCase()}` 26 | + `${parsedState.slice(1)}`; 27 | } 28 | } 29 | 30 | if (typeof falseState === 'undefined') { 31 | boolFalseState = false; 32 | } 33 | 34 | $(selector)[command](intMs, boolFalseState); 35 | } 36 | -------------------------------------------------------------------------------- /src/support/action/waitForDisplayed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wait for the given element to become visible 3 | * @param {String} selector Element selector 4 | * @param {String} falseCase Whether or not to expect a visible or hidden 5 | * state 6 | */ 7 | export function waitForDisplayed(selector: string, falseCase: string): void { 8 | const ms = 10000; 9 | $(selector).waitForDisplayed(ms, !!falseCase); 10 | } 11 | -------------------------------------------------------------------------------- /src/support/check/checkClass.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element has the given class 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check for the class to exist 7 | * or not ('has', 'does not have') 8 | * @param {String} expectedClassName The class name to check 9 | */ 10 | export function checkClass(selector: string, falseCase: string, expectedClassName: string): void { 11 | const classesList = $(selector).getAttribute('class').split(' '); 12 | 13 | if (falseCase === 'does not have') { 14 | expect(classesList).to.not 15 | .include(expectedClassName, 16 | `Element ${selector} should not have the class ` 17 | + `${expectedClassName}`); 18 | } else { 19 | expect(classesList).to 20 | .include( 21 | expectedClassName, 22 | `Element ${selector} should have the class ${expectedClassName}`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/checkContainsAnyText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given elements contains text 5 | * @param {String} elementType Element type (element or button) 6 | * @param {String} selector Element selector 7 | * @param {String} falseCase Whether to check if the content contains text or not 8 | */ 9 | export function checkContainsAnyText(elementType: string, selector: string, falseCase?: string | boolean): void { 10 | let command = 'getValue'; 11 | 12 | if (elementType === 'button' || $(selector).getAttribute('value') === null) { 13 | command = 'getText'; 14 | } 15 | 16 | let boolFalseCase; 17 | const text = $(selector)[command](); 18 | 19 | if (typeof falseCase === 'undefined') { 20 | boolFalseCase = false; 21 | } else { 22 | boolFalseCase = !!falseCase; 23 | } 24 | 25 | if (boolFalseCase) { 26 | expect(text).to.equal(''); 27 | } else { 28 | expect(text).to.not.equal(''); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/support/check/checkContainsText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given elements contains text 5 | * @param {String} elementType Element type (element or button) 6 | * @param {String} selector Element selector 7 | * @param {String} falseCase Whether to check if the content contains the given text or not 8 | * @param {String} expectedText The text to check against 9 | */ 10 | export function checkContainsText( 11 | elementType: string, 12 | selector: string, 13 | falseCase: string, 14 | expectedText: string, 15 | ): void { 16 | let command = 'getValue'; 17 | 18 | if ( 19 | ['button', 'container'].includes(elementType) 20 | || $(selector).getAttribute('value') === null 21 | ) { 22 | command = 'getText'; 23 | } 24 | 25 | let boolFalseCase; 26 | let stringExpectedText = expectedText; 27 | 28 | const elem = $(selector); 29 | elem.waitForDisplayed(); 30 | const text = elem[command](); 31 | 32 | if (typeof expectedText === 'undefined') { 33 | stringExpectedText = falseCase; 34 | boolFalseCase = false; 35 | } else { 36 | boolFalseCase = (falseCase === ' not'); 37 | } 38 | 39 | if (boolFalseCase) { 40 | expect(text).to.not.contain(stringExpectedText); 41 | } else { 42 | expect(text).to.contain(stringExpectedText); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/support/check/checkCookieContent.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the content of a cookie against a given value 5 | * @param {String} name The name of the cookie 6 | * @param {String} falseCase Whether or not to check if the value matches or not 7 | * @param {String} expectedValue The value to check against 8 | */ 9 | export function checkCookieContent(name: string, falseCase: string, expectedValue: string): void { 10 | const cookie = browser.getCookies([name])[0]; 11 | expect(cookie.name).to.equal( 12 | name, 13 | `no cookie found with the name "${name}"`, 14 | ); 15 | 16 | if (falseCase) { 17 | expect(cookie.value).to.not 18 | .equal( 19 | expectedValue, 20 | `expected cookie "${name}" not to have value "${expectedValue}"`, 21 | ); 22 | } else { 23 | expect(cookie.value).to 24 | .equal( 25 | expectedValue, 26 | `expected cookie "${name}" to have value "${expectedValue}" but got "${cookie.value}"`, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/support/check/checkCookieExists.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if a cookie with the given name exists 5 | * @param {String} name The name of the cookie 6 | * @param {String} falseCase Whether or not to check if the cookie exists or not 7 | */ 8 | export function checkCookieExists(name: string, falseCase: string): void { 9 | const cookie = browser.getCookies([name]); 10 | 11 | if (falseCase) { 12 | expect(cookie.length).to.equal( 13 | 0, 14 | `Expected cookie "${name}" not to exists but it does`, 15 | ); 16 | } else { 17 | expect(cookie.length).to.not.equal( 18 | 0, 19 | `Expected cookie "${name}" to exists but it does not`, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/support/check/checkDimension.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the dimensions of the given element 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the dimensions match or not 7 | * @param {String} expectedSize Expected size 8 | * @param {String} dimension Dimension to check (broad or tall) 9 | */ 10 | export function checkDimension(selector: string, falseCase: string, expectedSize: string, dimension: string): void { 11 | const elementSize = $(selector).getSize(); 12 | const intExpectedSize = parseInt(expectedSize, 10); 13 | let originalSize = elementSize.height; 14 | let label = 'height'; 15 | 16 | if (dimension === 'broad') { 17 | originalSize = elementSize.width; 18 | label = 'width'; 19 | } 20 | 21 | if (falseCase) { 22 | expect(originalSize).to.not 23 | .equal( 24 | intExpectedSize, 25 | `Element "${selector}" should not have a ${label} of ` 26 | + `${intExpectedSize}px`, 27 | ); 28 | } else { 29 | expect(originalSize).to 30 | .equal( 31 | intExpectedSize, 32 | `Element "${selector}" should have a ${label} of ` 33 | + `${intExpectedSize}px, but is ${originalSize}px`, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/support/check/checkElementExists.ts: -------------------------------------------------------------------------------- 1 | import { checkIfElementExists } from '../lib/checkIfElementExists'; 2 | 3 | /** 4 | * Check if the given element exists 5 | * @param {String} isExisting Whether the element should be existing or not (an or no) 6 | * @param {String} selector Element selector 7 | */ 8 | export function checkElementExists(isExisting: string, selector: string): void { 9 | let falseCase = true; 10 | 11 | if (isExisting === 'an') { 12 | falseCase = false; 13 | } 14 | 15 | checkIfElementExists(selector, falseCase); 16 | } 17 | -------------------------------------------------------------------------------- /src/support/check/checkEqualsText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given elements text is the same as the given text 5 | * @param {String} elementType Element type (element or button) 6 | * @param {String} selector Element selector 7 | * @param {String} falseCase Whether to check if the content equals the 8 | * given text or not 9 | * @param {String} expectedText The text to validate against 10 | */ 11 | export function checkEqualsText(elementType: string, selector: string, falseCase: string, expectedText: string): void { 12 | let command = 'getValue'; 13 | 14 | if ( 15 | elementType === 'button' 16 | || $(selector).getAttribute('value') === null 17 | ) { 18 | command = 'getText'; 19 | } 20 | 21 | let parsedExpectedText = expectedText; 22 | let boolFalseCase = !!falseCase; 23 | 24 | // Check for empty element 25 | if (typeof parsedExpectedText === 'function') { 26 | parsedExpectedText = ''; 27 | 28 | boolFalseCase = !boolFalseCase; 29 | } 30 | 31 | if (parsedExpectedText === undefined && falseCase === undefined) { 32 | parsedExpectedText = ''; 33 | boolFalseCase = true; 34 | } 35 | 36 | const text = $(selector)[command](); 37 | 38 | if (boolFalseCase) { 39 | expect(parsedExpectedText).to.not.equal(text); 40 | } else { 41 | expect(parsedExpectedText).to.equal(text); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/support/check/checkFocus.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element has the focus 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the given element has focus 7 | * or not 8 | */ 9 | export function checkFocus(selector: string, falseCase: string): void { 10 | const hasFocus = $(selector).isFocused(); 11 | 12 | if (falseCase) { 13 | expect(hasFocus).to.not 14 | .equal(true, 'Expected element to not be focused, but it is'); 15 | } else { 16 | expect(hasFocus).to 17 | .equal(true, 'Expected element to be focused, but it is not'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/support/check/checkFontProperty.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the given property of the given element 5 | * @param {String} isCSS Whether to check for a CSS property or an 6 | * attribute 7 | * @param {String} attrName The name of the attribute to check 8 | * @param {String} elem Element selector 9 | * @param {String} falseCase Whether to check if the value of the 10 | * attribute matches or not 11 | * @param {String} expectedValue The value to match against 12 | */ 13 | export function checkFontProperty( 14 | isCSS: string, 15 | attrName: string, 16 | elem: string, 17 | falseCase: string, 18 | expectedValue: string, 19 | ): void { 20 | const command = isCSS ? 'getCssProperty' : 'getAttribute'; 21 | const attrType = (isCSS ? 'CSS attribute' : 'Attribute'); 22 | let attributeValue = browser[command](elem, attrName); 23 | 24 | // when getting something with a color or font-weight WebdriverIO returns a 25 | // object but we want to assert against a string 26 | if (/(font-size|line-height|display|font-weight)/.exec(attrName)) { 27 | attributeValue = attributeValue.value; 28 | } 29 | 30 | if (falseCase) { 31 | expect(attributeValue).to.not 32 | .equal( 33 | expectedValue, 34 | `${attrType}: ${attrName} of element "${elem}" should not ` 35 | + `contain "${attributeValue}"`, 36 | ); 37 | } else { 38 | expect(attributeValue).to 39 | .equal( 40 | expectedValue, 41 | `${attrType}: ${attrName} of element "${elem}" should contain ` 42 | + `"${attributeValue}", but "${expectedValue}"`, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/support/check/checkInURLPath.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given string is in the URL path 5 | * @param {String} falseCase Whether to check if the given string is in 6 | * the URL path or not 7 | * @param {String} expectedUrlPart The string to check for 8 | */ 9 | export function checkInURLPath(falseCase: string, expectedUrlPart: string): void { 10 | const currentUrl = browser.getUrl(); 11 | 12 | if (falseCase) { 13 | expect(currentUrl).to.not 14 | .contain( 15 | expectedUrlPart, 16 | `Expected URL "${currentUrl}" not to contain ` 17 | + `"${expectedUrlPart}"`, 18 | ); 19 | } else { 20 | expect(currentUrl).to 21 | .contain( 22 | expectedUrlPart, 23 | `Expected URL "${currentUrl}" to contain "${expectedUrlPart}"`, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/support/check/checkIsEmpty.ts: -------------------------------------------------------------------------------- 1 | import { checkContainsAnyText } from './checkContainsAnyText'; 2 | 3 | export function checkIsEmpty(elementType: string, element: string, falseCase: string): void { 4 | let newFalseCase = true; 5 | 6 | if (typeof falseCase === 'function') { 7 | newFalseCase = false; 8 | } else if (falseCase === ' not') { 9 | newFalseCase = false; 10 | } 11 | 12 | checkContainsAnyText(elementType, element, newFalseCase); 13 | } 14 | -------------------------------------------------------------------------------- /src/support/check/checkIsOpenedInNewWindow.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given URL was opened in a new window 5 | * @param {String} expectedUrl The URL to check for 6 | * @param {String} obsolete Indicator for the type (window or tab) unused 7 | */ 8 | /* eslint @typescript-eslint/no-unused-vars: "off" */ 9 | export function checkIsOpenedInNewWindow(expectedUrl: string, obsolete: string): void { 10 | const windowHandles = browser.getWindowHandles(); 11 | 12 | expect(windowHandles).length.to.not.equal(1, 'A popup was not opened'); 13 | 14 | const lastWindowHandle = windowHandles.slice(-1); 15 | 16 | // Make sure we focus on the last opened window handle 17 | browser.switchToWindow(lastWindowHandle[0]); 18 | 19 | const windowUrl = browser.getUrl(); 20 | 21 | expect(windowUrl).to 22 | .contain(expectedUrl, 'The popup has a incorrect getUrl'); 23 | 24 | browser.closeWindow(); 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/checkModal.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if a modal was opened 5 | * @param {String} modalType The type of modal that is expected (alertbox, 6 | * confirmbox or prompt) 7 | * @param {String} falseState Whether to check if the modal was opened or not 8 | */ 9 | export function checkModal(modalType: string, falseState: string): void { 10 | let promptText = ''; 11 | 12 | try { 13 | promptText = browser.getAlertText(); 14 | 15 | if (falseState) { 16 | expect(promptText).to.not 17 | .equal( 18 | null, 19 | `A ${modalType} was opened when it shouldn't`, 20 | ); 21 | } 22 | } catch (e) { 23 | if (!falseState) { 24 | expect(promptText).to 25 | .equal( 26 | null, 27 | `A ${modalType} was not opened when it should have been`, 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/support/check/checkModalText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the text of a modal 5 | * @param {String} modalType The type of modal that is expected 6 | * (alertbox, confirmbox or prompt) 7 | * @param {String} falseState Whether to check if the text matches or not 8 | * @param {String} expectedText The text to check against 9 | */ 10 | export function checkModalText(modalType: string, falseState: string, expectedText: string): void { 11 | try { 12 | const text = browser.getAlertText(); 13 | 14 | if (falseState) { 15 | expect(text).to.not.equal( 16 | expectedText, 17 | `Expected the text of ${modalType} not to equal ` 18 | + `"${expectedText}"`, 19 | ); 20 | } else { 21 | expect(text).to.equal( 22 | expectedText, 23 | `Expected the text of ${modalType} to equal ` 24 | + `"${expectedText}", instead found "${text}"`, 25 | ); 26 | } 27 | } catch (e) { 28 | expect.fail(`A ${modalType} was not opened when it should have been opened`); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/support/check/checkNewWindow.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if a new window or tab is opened 5 | * @param {String} obsolete The type of opened object (window or tab) 6 | * @param {String} falseCase Whether to check if a new window/tab was opened or not 7 | */ 8 | export function checkNewWindow(obsolete: string, falseCase: string): void { 9 | const windowHandles = browser.getWindowHandles(); 10 | 11 | if (falseCase) { 12 | expect(windowHandles.length).to 13 | .equal(1, 'A new window should not have been opened'); 14 | } else { 15 | expect(windowHandles.length).to.not 16 | .equal(1, 'A new window has been opened'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/support/check/checkOffset.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the offset of the given element 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the offset matches or not 7 | * @param {String} expectedPosition The position to check against 8 | * @param {String} axis The axis to check on (x or y) 9 | */ 10 | export function checkOffset(selector: string, falseCase: string, expectedPosition: string, axis: 'x' | 'y'): void { 11 | const location = $(selector).getLocation(axis); 12 | const intExpectedPosition = parseFloat(expectedPosition); 13 | 14 | if (falseCase) { 15 | expect(location).to.not 16 | .equal( 17 | intExpectedPosition, 18 | `Element "${selector}" should not be positioned at ` 19 | + `${intExpectedPosition}px on the ${axis} axis`, 20 | ); 21 | } else { 22 | expect(location).to 23 | .equal( 24 | intExpectedPosition, 25 | `Element "${selector}" should be positioned at ` 26 | + `${intExpectedPosition}px on the ${axis} axis, but was found ` 27 | + `at ${location}px`, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/support/check/checkProperty.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { isNumber } from 'util'; 3 | 4 | /** 5 | * Check the given property of the given element 6 | * @param {String} isCSS Whether to check for a CSS property or an attribute 7 | * @param {String} attrName The name of the attribute to check 8 | * @param {String} selector Element selector 9 | * @param {String} falseCase Whether to check if the value of the 10 | * attribute matches or not 11 | * @param {String} expectedValue The value to match against 12 | */ 13 | export function checkProperty( 14 | isCSS: string, 15 | attrName: string, 16 | selector: string, 17 | falseCase: string, 18 | expectedValue: string, 19 | ): void { 20 | const command = isCSS ? 'getCSSProperty' : 'getAttribute'; 21 | const attrType = (isCSS ? 'CSS attribute' : 'Attribute'); 22 | let attributeValue = $(selector)[command](attrName); 23 | 24 | const value = isNumber(expectedValue) ? parseFloat(expectedValue) : expectedValue; 25 | 26 | // when getting something with a color or font-weight WebdriverIO returns a 27 | // object but we want to assert against a string 28 | if (/(color|font-weight)/.exec(attrName)) { 29 | attributeValue = (attributeValue as WebdriverIO.CSSProperty).value; 30 | } 31 | if (falseCase) { 32 | expect(attributeValue).to.not 33 | .equal(value, 34 | `${attrType}: ${attrName} of element "${selector}" should ` 35 | + `not contain "${attributeValue}"`); 36 | } else { 37 | expect(attributeValue).to 38 | .equal(value, 39 | `${attrType}: ${attrName} of element "${selector}" should ` 40 | + `contain "${attributeValue}", but "${expectedValue}"`); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/support/check/checkSelected.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the selected state of the given element 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the element is elected or not 7 | */ 8 | export function checkSelected(selector: string, falseCase: string): void { 9 | const isSelected = $(selector).isSelected(); 10 | 11 | if (falseCase) { 12 | expect(isSelected).to.not 13 | .equal(true, `"${selector}" should not be selected`); 14 | } else { 15 | expect(isSelected).to 16 | .equal(true, `"${selector}" should be selected`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/support/check/checkTitle.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the title of the current browser window 5 | * @param {Type} falseCase Whether to check if the title matches the 6 | * expected value or not 7 | * @param {Type} expectedTitle The expected title 8 | */ 9 | export function checkTitle(falseCase: string, expectedTitle: string): void { 10 | const title = browser.getTitle(); 11 | 12 | if (falseCase) { 13 | expect(title).to.not 14 | .equal( 15 | expectedTitle, 16 | `Expected title not to be "${expectedTitle}"`, 17 | ); 18 | } else { 19 | expect(title).to 20 | .equal( 21 | expectedTitle, 22 | `Expected title to be "${expectedTitle}" but found "${title}"`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/checkTitleContains.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the title of the current browser window contains expected text/title 5 | * @param {Type} falseCase Whether to check if the title contains the 6 | * expected value or not 7 | * @param {Type} expectedTitle The expected title 8 | */ 9 | export function checkTitleContains(falseCase: string, expectedTitle: string): void { 10 | const title = browser.getTitle(); 11 | 12 | if (falseCase) { 13 | expect(title).to.not 14 | .contain( 15 | expectedTitle, 16 | `Expected title not to contain "${expectedTitle}"`, 17 | ); 18 | } else { 19 | expect(title).to 20 | .contain( 21 | expectedTitle, 22 | `Expected title to contain "${expectedTitle}" but found "${title}"`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/checkUrl.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check the URL of the given browser window 5 | * @param {String} falseCase Whether to check if the URL matches the 6 | * expected value or not 7 | * @param {String} expectedUrl The expected URL to check against 8 | */ 9 | export function checkUrl(falseCase: string, expectedUrl: string): void { 10 | const currentUrl = browser.getUrl(); 11 | 12 | if (falseCase) { 13 | expect(currentUrl).to.not 14 | .equal(expectedUrl, `expected url not to be "${currentUrl}"`); 15 | } else { 16 | expect(currentUrl).to 17 | .equal( 18 | expectedUrl, 19 | `expected url to be "${expectedUrl}" but found "${currentUrl}"`, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/support/check/checkUrlPath.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the current URL path matches the given path 5 | * @param {String} falseCase Whether to check if the path matches the 6 | * expected value or not 7 | * @param {String} expectedPath The expected path to match against 8 | */ 9 | export function checkUrlPath(falseCase: string, expectedPath: string): void { 10 | let currentUrl = browser.getUrl().replace(/http(s?):\/\//, ''); 11 | const domain = `${currentUrl.split('/')[0]}`; 12 | 13 | currentUrl = currentUrl.replace(domain, ''); 14 | 15 | if (falseCase) { 16 | expect(currentUrl).to.not 17 | .equal(expectedPath, `expected path not to be "${currentUrl}"`); 18 | } else { 19 | expect(currentUrl).to 20 | .equal( 21 | expectedPath, 22 | `expected path to be "${expectedPath}" but found "${currentUrl}"`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/checkWithinViewport.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element is visible inside the current viewport 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the element is visible 7 | * within the current viewport or not 8 | */ 9 | export function checkWithinViewport(selector: string, falseCase: string): void { 10 | const isDisplayed = $(selector).isDisplayedInViewport(); 11 | 12 | if (falseCase) { 13 | expect(isDisplayed).to.not 14 | .equal( 15 | true, 16 | `Expected element "${selector}" to be outside the viewport`, 17 | ); 18 | } else { 19 | expect(isDisplayed).to 20 | .equal( 21 | true, 22 | `Expected element "${selector}" to be inside the viewport`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/compareText.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Compare the contents of two elements with each other 5 | * @param {String} selector1 Element selector for the first element 6 | * @param {String} falseCase Whether to check if the contents of both 7 | * elements match or not 8 | * @param {String} selector2 Element selector for the second element 9 | */ 10 | export function compareText(selector1: string, falseCase: string, selector2: string): void { 11 | const text1 = $(selector1).getText(); 12 | const text2 = $(selector2).getText(); 13 | 14 | if (falseCase) { 15 | expect(text1).to.not.equal( 16 | text2, 17 | `Expected text not to be "${text1}"`, 18 | ); 19 | } else { 20 | expect(text1).to.equal( 21 | text2, 22 | `Expected text to be "${text1}" but found "${text2}"`, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/support/check/isDisplayed.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element is (not) visible 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Check for a visible or a hidden element 7 | */ 8 | export function isDisplayed(selector: string, falseCase: string): void { 9 | const displayed = $(selector).isDisplayed(); 10 | 11 | if (falseCase) { 12 | expect(displayed).to.not 13 | .equal(true, `Expected element "${selector}" not to be displayed`); 14 | } else { 15 | expect(displayed).to 16 | .equal(true, `Expected element "${selector}" to be displayed`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/support/check/isEnabled.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given selector is enabled 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the given selector is enabled or not 7 | */ 8 | export function isEnabled(selector: string, falseCase: string): void { 9 | const enabled = $(selector).isEnabled(); 10 | 11 | if (falseCase) { 12 | expect(enabled).to.not 13 | .equal(true, `Expected element "${selector}" not to be enabled`); 14 | } else { 15 | expect(enabled).to 16 | .equal(true, `Expected element "${selector}" to be enabled`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/support/check/isExisting.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element exists in the current DOM 5 | * @param {String} selector Element selector 6 | * @param {String} falseCase Whether to check if the element exists or not 7 | */ 8 | export function isExisting(selector: string, falseCase: string): void { 9 | const elements = $$(selector); 10 | 11 | if (falseCase) { 12 | expect(elements).to.have 13 | .lengthOf(0, `Expected element "${selector}" not to exist`); 14 | } else { 15 | expect(elements).to.have.length 16 | .above(0, `Expected element "${selector}" to exist`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/support/lib/checkIfElementExists.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Check if the given element exists in the DOM one or more times 5 | * @param {String} selector Element selector 6 | * @param {Boolean} falseCase Check if the element (does not) exists 7 | * @param {Number} exactly Check if the element exists exactly this number 8 | * of times 9 | */ 10 | export function checkIfElementExists(selector: string, falseCase?: boolean, exactly?: number): void { 11 | const nrOfElements = $$(selector); 12 | 13 | if (falseCase === true) { 14 | expect(nrOfElements).to.have.lengthOf( 15 | 0, 16 | `Element with selector "${selector}" should not exist on the page`, 17 | ); 18 | } else if (exactly) { 19 | expect(nrOfElements).to.have.lengthOf( 20 | exactly, 21 | `Element with selector "${selector}" should exist exactly ${exactly} time(s)`, 22 | ); 23 | } else { 24 | expect(nrOfElements).to.have.length.of.at.least( 25 | 1, 26 | `Element with selector "${selector}" should exist on the page`, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/support/lib/context.ts: -------------------------------------------------------------------------------- 1 | import cucumberJson from 'wdio-cucumberjs-json-reporter'; 2 | 3 | export function addText(value: string): void { 4 | cucumberJson.attach(value); 5 | } 6 | 7 | export function addObject(value: T): void { 8 | cucumberJson.attach(value, 'application/json'); 9 | } 10 | 11 | export function addScreenshot(): void { 12 | cucumberJson.attach(browser.takeScreenshot(), 'image/png'); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "noImplicitAny": false, 6 | "noImplicitReturns": true, 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "baseUrl": ".", 10 | "outDir": "dist", 11 | "paths": { 12 | "*": [ "./*" ], 13 | }, 14 | "types": ["node", "chai", "@wdio/sync"] 15 | }, 16 | "include": [ 17 | "./src/**/*.ts", 18 | "./config/**/*.ts" 19 | ], 20 | "exclude": [ 21 | "./node_modules" 22 | ], 23 | "compileOnSave": false 24 | } -------------------------------------------------------------------------------- /wdio.conf.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | require('dotenv').config(); 3 | 4 | exports.config = require('./config').config; 5 | --------------------------------------------------------------------------------