├── .eslintrc.js ├── .gitattributes ├── .github ├── funding.yml ├── renovate.json └── workflows │ └── main.yml ├── .gitignore ├── .gitpod.yml ├── .gitpod └── .gitpod.Dockerfile ├── .nvmrc ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── NOTICE.md ├── README.md ├── SECURITY.md ├── features ├── authentication │ ├── form-based_authentication.feature │ └── narrative.md ├── step-definitions │ └── the-internet.steps.ts └── support │ └── parameters.ts ├── package-lock.json ├── package.json ├── test ├── authentication │ ├── Authenticate.ts │ ├── VerifyAuthentication.ts │ ├── index.ts │ └── ui │ │ ├── Article.ts │ │ ├── FlashMessages.ts │ │ └── LoginForm.ts └── examples │ ├── PickExample.ts │ ├── index.ts │ └── ui │ └── Examples.ts ├── tsconfig.eslint.json ├── tsconfig.json └── wdio.conf.ts /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | ignorePatterns: [ '**/lib/**' ], 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | ecmaVersion: 6, 7 | sourceType: 'module', 8 | project: [ 9 | 'tsconfig.eslint.json', 10 | ], 11 | }, 12 | plugins: [ 13 | '@typescript-eslint', 14 | 'import', 15 | 'simple-import-sort', 16 | 'unused-imports' 17 | ], 18 | extends: [ 19 | 'eslint:recommended', 20 | 'plugin:@typescript-eslint/eslint-recommended', 21 | 'plugin:@typescript-eslint/recommended', 22 | 'plugin:unicorn/recommended', 23 | ], 24 | rules: { 25 | 'simple-import-sort/imports': 'error', 26 | 'simple-import-sort/exports': 'error', 27 | 'sort-imports': 'off', 28 | 'import/order': 'off', 29 | 'unused-imports/no-unused-imports': 'error', 30 | 31 | 'no-multiple-empty-lines': ['warn', { 32 | 'max': 1, 33 | }], 34 | 35 | '@typescript-eslint/explicit-module-boundary-types': 'off', 36 | 37 | 'indent': 'off', 38 | '@typescript-eslint/indent': ['error', 4, { 39 | 'MemberExpression': 'off', 40 | 'SwitchCase': 1, 41 | }], 42 | 43 | 'quotes': 'off', 44 | '@typescript-eslint/quotes': ['error', 'single', { 45 | 'allowTemplateLiterals': true, 46 | 'avoidEscape': true, 47 | }], 48 | 49 | '@typescript-eslint/no-explicit-any': 'off', // todo: review 50 | 51 | '@typescript-eslint/no-unused-vars': ['warn', { 52 | 'args': 'none', 53 | 'vars': 'all', 54 | 'varsIgnorePattern': '^.*_$', 55 | }], 56 | 57 | 'unicorn/empty-brace-spaces': 'off', 58 | 59 | 'unicorn/filename-case': [ 'error', { 60 | 'cases': { 61 | 'kebabCase': true, // packages 62 | 'pascalCase': true, // classes 63 | 'camelCase': true, // functions 64 | } 65 | }], 66 | 67 | 'unicorn/no-array-for-each': 'off', 68 | 'unicorn/no-array-reduce': 'off', 69 | 'unicorn/no-array-callback-reference': 'off', 70 | 'unicorn/no-static-only-class': 'off', 71 | 72 | 'unicorn/numeric-separators-style': 'off', 73 | 74 | 'unicorn/prefer-module': 'off', // fixme disable when we can provide support for ESM 75 | 'unicorn/prefer-node-protocol': 'off', // fixme requires Node 14.13 or newer, disable until we no longer have to support Node 12 76 | 'unicorn/prefer-spread': 'off', 77 | 78 | 'unicorn/prevent-abbreviations': [ 'error', { 79 | 'allowList': { 80 | 'conf': true, 81 | 'wdio': true, 82 | } 83 | }] 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See the below articles for more details: 2 | # - https://help.github.com/articles/dealing-with-line-endings/#platform-all 3 | # - https://stackoverflow.com/questions/9976986/force-lf-eol-in-git-repo-and-working-copy 4 | 5 | # Autodetect text files 6 | * text=auto 7 | 8 | # ... unless the name matches the following overriding patterns: 9 | 10 | # Text files: 11 | .editorconfig text eol=lf 12 | .env text eol=lf 13 | .gitattributes text eol=lf 14 | .gitignore text eol=lf 15 | .npmrc text eol=lf 16 | Dockerfile text eol=lf 17 | *.css text eol=lf 18 | *.hcl text eol=lf 19 | *.html text eol=lf 20 | *.gradle text eol=lf 21 | gradlew text eol=lf 22 | *.java text eol=lf 23 | *.js text eol=lf 24 | *.json text eol=lf 25 | *.md text eol=lf 26 | *.properties text eol=lf 27 | *.scss text eol=lf 28 | *.sh text eol=lf 29 | *.svg text eol=lf 30 | *.tf text eol=lf 31 | *.tfvars text eol=lf 32 | *.ts text eol=lf 33 | *.txt text eol=lf 34 | *.xml text eol=lf 35 | *.yml text eol=lf 36 | 37 | # Windows-specific files: 38 | *.bat text eol=crlf 39 | 40 | # Binary files: 41 | *.data binary 42 | *.eot binary 43 | *.ico binary 44 | *.jar binary 45 | *.jpg binary 46 | *.png binary 47 | *.ttf binary 48 | *.woff binary 49 | *.woff2 binary 50 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ serenity-js ] 4 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "rangeStrategy": "bump", 6 | "packageRules": [ 7 | { 8 | "packagePatterns": ["^@serenity-js"], 9 | "groupName": "Serenity/JS", 10 | "automerge": true 11 | }, 12 | { 13 | "packagePatterns": ["^eslint$", "^eslint-plugin", "^@typescript-eslint"], 14 | "groupName": "ESLint", 15 | "automerge": true 16 | }, 17 | { 18 | "packagePatterns": [ 19 | "^ts-node$", 20 | "^typescript$" 21 | ], 22 | "groupName": "TypeScript" 23 | }, 24 | { 25 | "matchUpdateTypes": ["minor", "patch"], 26 | "matchCurrentVersion": "!/^0/", 27 | "automerge": true 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | types: [ opened, synchronize ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [ 18.x, 20.x, 22.x ] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - uses: actions/setup-java@v4 24 | with: 25 | distribution: 'temurin' 26 | java-version: '17' 27 | - name: Setup Node Modules 28 | uses: bahmutov/npm-install@v1 29 | - run: npm run lint 30 | - run: npm test 31 | env: 32 | CI: true 33 | - name: GitHub Pages 34 | uses: JamesIves/github-pages-deploy-action@v4.7.2 35 | if: matrix.node-version == '22.x' && github.ref == 'refs/heads/main' 36 | with: 37 | branch: gh-pages 38 | folder: target/site/serenity 39 | clean: true 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea 3 | 4 | # Node 5 | node_modules 6 | *.log 7 | 8 | # Build artifacts 9 | target 10 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod/.gitpod.Dockerfile 3 | 4 | ports: 5 | - port: 8080 6 | onOpen: notify 7 | visibility: public 8 | 9 | tasks: 10 | - name: 'Install' 11 | init: | 12 | nvm install 13 | nvm use 14 | npm ci 15 | gp sync-done installation 16 | 17 | - name: 'Serenity Reports' 18 | init: gp sync-await installation 19 | command: | 20 | mkdir -p target/site/serenity 21 | npx http-server -p 8080 target/site/serenity/ 22 | 23 | - name: 'Welcome' 24 | init: gp sync-await installation 25 | command: | 26 | npm test 27 | echo "To view Serenity Reports, visit:" 28 | gp url 8080 29 | 30 | vscode: 31 | extensions: 32 | - dbaeumer.vscode-eslint 33 | - EditorConfig.EditorConfig 34 | - CucumberOpen.cucumber-official 35 | 36 | github: 37 | prebuilds: 38 | master: true 39 | branches: false 40 | pullRequests: true 41 | pullRequestsFromForks: false 42 | addCheck: true 43 | addComment: false 44 | addBadge: true 45 | -------------------------------------------------------------------------------- /.gitpod/.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full-vnc 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | ARG TZ=UTC 5 | ENV SHELL=/bin/bash 6 | 7 | ## https://www.gitpod.io/docs/config-docker 8 | USER gitpod 9 | 10 | # ###################################################################################################################### 11 | # Install Firefox and Microsoft Edge 12 | # https://github.com/webdriverio/webdriverio/blob/36d8c142c6efd3323199819b86e185acc5a5a800/.gitpod/dev.dockerfile 13 | 14 | RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ 15 | && sudo install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/ \ 16 | && sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list' \ 17 | && sudo rm microsoft.gpg \ 18 | && sudo apt-get update \ 19 | && sudo apt-get install -y \ 20 | firefox \ 21 | microsoft-edge-dev 22 | 23 | # ###################################################################################################################### 24 | # Install ChromeDriver 25 | # 26 | # Chromium and ChromeDriver are now distributed via Snap, which is not available on Gitpod.io 27 | # Since Google Chrome is already installed in Gitpod Node workspace, all we need is ChromeDriver 28 | # https://github.com/gitpod-io/workspace-images/blob/e91b47d148d6687703e258a7589b8cba74367a88/dazzle.yaml#L59 29 | # https://github.com/gitpod-io/workspace-images/blob/e91b47d148d6687703e258a7589b8cba74367a88/chunks/tool-chrome/Dockerfile 30 | # 31 | # Running: /usr/bin/google-chrome 32 | # Outputs: Google Chrome 103.0.5060.134 33 | # Extract the major version 34 | RUN bash -c "export GOOGLE_CHROME_VERSION=$(/usr/bin/google-chrome --version | sed -E 's/[[:alpha:]|(|[:space:]]//g' | awk -F. '{print $1}') \ 35 | && npm install --location=global chromedriver@\$GOOGLE_CHROME_VERSION \ 36 | && export NODE_PATH='$(npm root --location=global):\$NODE_PATH' \ 37 | && node -e 'console.log(\`export CHROMEDRIVER_FILEPATH=\${ require(\"chromedriver\").path}\`)' >> /home/gitpod/.bashrc.d/99-chromedriver" 38 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.12.0 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "EditorConfig.EditorConfig", 5 | "CucumberOpen.cucumber-official" 6 | ], 7 | "unwantedRecommendations": [] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "onFocusChange", 3 | "workbench.startupEditor": "readme", 4 | "workbench.tree.indent": 12, 5 | "cucumber.features": [ 6 | "features/**/*.feature" 7 | ], 8 | "cucumber.glue": [ 9 | "features/**/*.steps.ts" 10 | ], 11 | "cucumber.parameterTypes": [ 12 | { "name": "actor", "regexp": "[A-Z][a-z]+" }, 13 | { "name": "pronoun", "regexp": "he|she|they|his|her|their" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering contributing your time and talent to help Serenity/JS move forward! You're awesome 😊 4 | 5 | To learn how and where you can help, check out the ["Contributing" section](https://serenity-js.org/contributing.html) of the [Serenity/JS Handbook](https://serenity-js.org/handbook/). 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | Copyright 2016- Jan Molak, smartcode ltd. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serenity/JS Mocha WebdriverIO Template 2 | 3 | [![Follow Serenity/JS on LinkedIn](https://img.shields.io/badge/Follow-Serenity%2FJS%20-0077B5?logo=linkedin)](https://www.linkedin.com/company/serenity-js) 4 | [![Watch Serenity/JS on YouTube](https://img.shields.io/badge/Watch-@serenity--js-E62117?logo=youtube)](https://www.youtube.com/@serenity-js) 5 | [![Join Serenity/JS Community Chat](https://img.shields.io/badge/Chat-Serenity%2FJS%20Community-FBD30B?logo=matrix)](https://matrix.to/#/#serenity-js:gitter.im) 6 | [![GitHub stars](https://img.shields.io/github/stars/serenity-js/serenity-js?label=Serenity%2FJS&logo=github&style=badge)](https://github.com/serenity-js/serenity-js) 7 | [![Support Serenity/JS on GitHub](https://img.shields.io/badge/Support-@serenity--js-703EC8?logo=github)](https://github.com/sponsors/serenity-js) 8 | 9 | [![Build Status](https://github.com/serenity-js/serenity-js-cucumber-webdriverio-template/actions/workflows/main.yml/badge.svg)](https://github.com/serenity-js/serenity-js-cucumber-webdriverio-template/actions/workflows/main.yml) 10 | [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/from-referrer/) 11 | 12 | 13 | Kickstart your web application acceptance testing with [Serenity/JS](https://serenity-js.org), [Cucumber](https://github.com/cucumber/cucumber-js), 14 | and [WebdriverIO](https://webdriver.io/) using this template repository. 15 | 16 | Learn more about Serenity/JS: 17 | [Serenity/JS and WebdriverIO guide](https://serenity-js.org/handbook/test-runners/webdriverio/) 18 | 19 | ## 🚀 Quick Start 20 | 21 | ### 1. Create a Project 22 | 23 | This repository is a GitHub template. Use it to [create a new GitHub repository](https://help.github.com/en/articles/creating-a-repository-from-a-template) 24 | for your project. 25 | 26 | If you prefer to work in a virtual environment, you can launch this repository in a [Gitpod](https://gitpod.io/from-referrer/) workspace. 27 | 28 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) 29 | 30 | ### 2. Set Up Your Environment 31 | 32 | Install the required tools: 33 | 34 | - [Node.js](https://nodejs.org/en/) (LTS version 18.12 or later) 35 | 36 | - [Java](https://adoptopenjdk.net/) (JRE or JDK version 11 or later) 37 | 38 | Follow the [installation guide](https://serenity-js.org/handbook/getting-started/installation/) to verify your setup. 39 | 40 | ### 3. Install Dependencies 41 | 42 | [Clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) the project and run: 43 | 44 | ``` 45 | npm ci 46 | ``` 47 | 48 | This installs dependencies, including the [Serenity BDD CLI](https://serenity-js.org/handbook/reporting/serenity-bdd-reporter/). 49 | 50 | ### 4. Run Tests 51 | 52 | Use the predefined [NPM scripts](https://docs.npmjs.com/cli/v10/using-npm/scripts) defined in [`package.json`](package.json) to run your tests 53 | and related tasks: 54 | 55 | ``` 56 | npm test # Run tests and generate reports 57 | npm start # Serve test reports at http://localhost:8080 58 | 59 | npm run lint # Run code linter 60 | npm run lint:fix # Try to automatically fix linting issues 61 | npm run clean # Remove test reports from any previous test run 62 | ``` 63 | 64 | ### 5. Write your tests 65 | 66 | Test scenarios are located in the [`./test/specs`](/test/specs) directory. 67 | Modify the examples or write your own scenarios using the [Serenity/JS Screenplay Pattern](https://serenity-js.org/handbook/design/screenplay-pattern/). 68 | 69 | ## 🌟 Features 70 | 71 | - **All automation tools ready to go**: Start testing immediately with WebdriverIO, Serenity/JS and Mocha already integrated and configured. 72 | - **Serenity BDD Reports**: Serenity BDD reporter pre-configured with reports available at `./target/site/serenity` 73 | - **Published Reports**: Reports for this repository are published to GitHub Pages and available at https://serenity-js.github.io/serenity-js-cucumber-webdriverio-template/. 74 | - **Screenplay Pattern**: Out-of-the-box integration with the Serenity/JS actors and Screenplay Pattern APIs 75 | - **GitHub Actions**: Continuous Integration setup included 76 | - **VS Code**: Pre-configured for a seamless developer experience 77 | 78 | ### 🔧 Configuration for Corporate Networks 79 | 80 | If you're behind a proxy or use an internal registry, configure an [`.npmrc` file](https://docs.npmjs.com/cli/v6/configuring-npm/npmrc) in your home directory: 81 | 82 | ``` 83 | proxy=http://user:password@host.mycompany.com:8080/ 84 | https-proxy=http://user:password@host.mycompany.com:8080/ 85 | strict-ssl=false 86 | registry=https://artifactory.mycompany.com/artifactory/ 87 | ``` 88 | 89 | ## 💡️ Learn Serenity/JS 90 | 91 | - [Serenity/JS WebdriverIO integration guide](https://serenity-js.org/handbook/test-runners/webdriverio/) - Integrate Serenity/JS with your WebdriverIO test suite, enable Serenity BDD reports, and start using the Screenplay Pattern 92 | - [Serenity/JS Handbook](https://serenity-js.org/handbook/) - Write high-quality automated acceptance tests with Serenity/JS 93 | - [Serenity/JS API documentation](https://serenity-js.org/api/) - Explore Serenity/JS modules and features 94 | - [Serenity/JS Project Templates](https://serenity-js.org/handbook/getting-started/project-templates/) - Kickstart your projects with best practices built right in 95 | 96 | ## 👋 Join the Serenity/JS Community 97 | 98 | - [Serenity/JS Community chat channel](https://matrix.to/#/#serenity-js:gitter.im) - Meet Serenity/JS developers and maintainers 99 | - [Serenity/JS Forum](https://github.com/orgs/serenity-js/discussions/categories/how-to) - Find answers to your Serenity/JS questions 100 | - [Contribute to Serenity/JS](https://serenity-js.org/community/contributing/) - Learn how to propose features, report bugs, and contribute to the Serenity/JS codebase 101 | 102 | ## 📣 Stay up to date 103 | 104 | - [Serenity/JS on YouTube](https://www.youtube.com/@serenity-js) - Subscribe for tutorials, demos, conference talks, and more 105 | - [Serenity/JS on LinkedIn](https://www.linkedin.com/company/serenity-js) - Follow for release and community event announcements 106 | - [Serenity/JS on GitHub](https://github.com/serenity-js/serenity-js) - Star Serenity/JS to help others discover the framework! 107 | 108 | ## 💛 Support Serenity/JS 109 | 110 | Support our mission to make test automation collaborative and easier to scale. Become a Serenity/JS GitHub Sponsor today! 111 | 112 | [![GitHub Sponsors](https://img.shields.io/badge/Sponsor%20@serenity%2FJS-703EC8?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/serenity-js) 113 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /features/authentication/form-based_authentication.feature: -------------------------------------------------------------------------------- 1 | Feature: Form-Based Authentication 2 | 3 | In order to learn how to use Serenity/JS with Cucumber and WebdriverIO 4 | As a Curious Developer 5 | I'd like to see an example 6 | 7 | Background: 8 | Given Alice starts with the "Form Authentication" example 9 | 10 | Scenario Outline: Using username and password to log in 11 | 12 | ["The Internet"](https://the-internet.herokuapp.com/) is an example application 13 | that captures prominent and ugly functionality found on the web. 14 | Perfect for writing automated acceptance tests against 😎 15 | With **Serenity/JS** you can use [Markdown](https://en.wikipedia.org/wiki/Markdown) 16 | to better describe each `Feature` and `Scenario`. 17 | 18 | When she logs in using "" and "" 19 | Then she should see that authentication has 20 | 21 | Examples: 22 | | username | password | outcome | 23 | | tomsmith | SuperSecretPassword! | succeeded | 24 | | foobar | barfoo | failed | 25 | -------------------------------------------------------------------------------- /features/authentication/narrative.md: -------------------------------------------------------------------------------- 1 | The Internet - Authentication 2 | 3 | Narrative: 4 | In order to learn how to implement *high-quality automated tests* 5 | As a Curious Developer 6 | I'd like to have a place to practice 7 | 8 | This note is called _"the narrative"_. It can be used to provide the context around the business capability of your 9 | product ("Authentication" features in this case) and its features that help to enable this capability. 10 | 11 | **Please note:** While [Cucumber](https://github.com/cucumber/cucumber-js) allows you to capture a description 12 | of each feature in the `.feature` file, [Serenity/JS](https://serenity-js.org) allows you to group those `.feature` 13 | files in directories corresponding to "epics", "themes" or "business capabilities" of your system and provide 14 | each one of those with additional context using this `narrative.md` file. 15 | 16 | **By the way:** Did you notice that you can use **[markdown syntax](https://www.markdownguide.org/)** to better express 17 | your thoughts? 18 | -------------------------------------------------------------------------------- /features/step-definitions/the-internet.steps.ts: -------------------------------------------------------------------------------- 1 | import { Given, Then, When } from '@cucumber/cucumber'; 2 | import { Actor, actorInTheSpotlight } from '@serenity-js/core'; 3 | import { Navigate } from '@serenity-js/web'; 4 | 5 | import { Authenticate, VerifyAuthentication } from '../../test/authentication'; 6 | import { PickExample } from '../../test/examples'; 7 | 8 | /** 9 | * Below step definitions use Cucumber Expressions 10 | * see: https://cucumber.io/docs/cucumber/cucumber-expressions/ 11 | * 12 | * {actor} and {pronoun} are custom expressions defined under support/parameters.ts 13 | */ 14 | Given('{actor} starts with the {string} example', async (actor: Actor, exampleName: string) => 15 | actor.attemptsTo( 16 | Navigate.to('/'), 17 | PickExample.called(exampleName), 18 | ) 19 | ); 20 | 21 | When('{pronoun} log(s) in using {string} and {string}', async (actor: Actor, username: string, password: string) => 22 | actor.attemptsTo( 23 | Authenticate.using(username, password), 24 | ) 25 | ); 26 | 27 | /** 28 | * If you need to use a RegExp instead of Cucumber Expressions like {actor} and {pronoun} 29 | * you can use actorCalled(name) and actorInTheSpotlight() instead 30 | * 31 | * see: https://serenity-js.org/modules/core/function/index.html#static-function-actorCalled 32 | * see: https://serenity-js.org/modules/core/function/index.html#static-function-actorInTheSpotlight 33 | */ 34 | Then(/.* should see that authentication has (succeeded|failed)/, async (expectedOutcome: string) => 35 | actorInTheSpotlight().attemptsTo( 36 | VerifyAuthentication[expectedOutcome](), 37 | ) 38 | ); 39 | 40 | -------------------------------------------------------------------------------- /features/support/parameters.ts: -------------------------------------------------------------------------------- 1 | import { defineParameterType } from '@cucumber/cucumber'; 2 | import { actorCalled, actorInTheSpotlight } from '@serenity-js/core'; 3 | 4 | defineParameterType({ 5 | regexp: /[A-Z][a-z]+/, 6 | transformer(name: string) { 7 | return actorCalled(name); 8 | }, 9 | name: 'actor', 10 | }); 11 | 12 | defineParameterType({ 13 | regexp: /he|she|they|his|her|their/, 14 | transformer() { 15 | return actorInTheSpotlight(); 16 | }, 17 | name: 'pronoun', 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serenity-js-cucumber-webdriverio-template", 3 | "version": "3.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rimraf target", 8 | "test": "failsafe clean test:execute test:report", 9 | "test:execute": "wdio wdio.conf.ts", 10 | "test:report": "serenity-bdd run --features ./features", 11 | "start": "mkdirp target/site/serenity && npx http-server -p 8080 target/site/serenity -s -o", 12 | "lint": "eslint --ext ts --config .eslintrc.js .", 13 | "lint:fix": "npm run lint -- --fix" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/serenity-js/serenity-js-cucumber-webdriverio-template.git" 18 | }, 19 | "keywords": [ 20 | "serenity-js", 21 | "bdd", 22 | "cucumber", 23 | "testing", 24 | "webdriverio" 25 | ], 26 | "author": "Jan Molak ", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://github.com/serenity-js/serenity-js/issues" 30 | }, 31 | "homepage": "https://serenity-js.org", 32 | "engines": { 33 | "node": "^18.12 || ^20 || ^22" 34 | }, 35 | "dependencies": { 36 | "@cucumber/cucumber": "^11.1.1", 37 | "@serenity-js/assertions": "^3.31.0", 38 | "@serenity-js/console-reporter": "^3.31.0", 39 | "@serenity-js/core": "^3.31.0", 40 | "@serenity-js/cucumber": "^3.31.0", 41 | "@serenity-js/serenity-bdd": "^3.31.0", 42 | "@serenity-js/web": "^3.31.0", 43 | "@serenity-js/webdriverio": "^3.31.0", 44 | "@wdio/cli": "^9.4.2", 45 | "@wdio/globals": "^9.4.2", 46 | "@wdio/local-runner": "^9.4.2", 47 | "@wdio/spec-reporter": "^9.4.2", 48 | "webdriverio": "^9.4.2" 49 | }, 50 | "devDependencies": { 51 | "@typescript-eslint/eslint-plugin": "^7.18.0", 52 | "@typescript-eslint/parser": "^7.18.0", 53 | "eslint": "^8.57.1", 54 | "eslint-plugin-import": "^2.31.0", 55 | "eslint-plugin-simple-import-sort": "^12.1.1", 56 | "eslint-plugin-unicorn": "^52.0.0", 57 | "eslint-plugin-unused-imports": "^3.2.0", 58 | "http-server": "^14.1.1", 59 | "mkdirp": "^3.0.1", 60 | "npm-failsafe": "^1.3.0", 61 | "rimraf": "^6.0.1", 62 | "ts-node": "^10.9.2", 63 | "typescript": "^5.7.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/authentication/Authenticate.ts: -------------------------------------------------------------------------------- 1 | import { Task } from '@serenity-js/core'; 2 | import { Click, Enter } from '@serenity-js/web'; 3 | 4 | import { LoginForm } from './ui/LoginForm'; 5 | 6 | /** 7 | * This is called a "Task". 8 | * Use tasks to compose a sequence of one or more activities and give them domain meaning. 9 | * 10 | * Here, the actor performs three activities: 11 | * - enter username 12 | * - enter password 13 | * - click on the login button 14 | * 15 | * This sequence of activities together means to "log in" 16 | */ 17 | export const Authenticate = { 18 | using: (username: string, password: string) => 19 | Task.where(`#actor logs in as ${ username }`, 20 | Enter.theValue(username).into(LoginForm.usernameField()), 21 | Enter.theValue(password).into(LoginForm.passwordField()), 22 | Click.on(LoginForm.loginButton()), 23 | ), 24 | } 25 | -------------------------------------------------------------------------------- /test/authentication/VerifyAuthentication.ts: -------------------------------------------------------------------------------- 1 | import { Ensure, includes } from '@serenity-js/assertions'; 2 | import { Task } from '@serenity-js/core'; 3 | import { isVisible, Text } from '@serenity-js/web'; 4 | 5 | import { FlashMessages } from './ui/FlashMessages'; 6 | 7 | export class VerifyAuthentication { 8 | private static hasFlashAlert = () => 9 | Task.where(`#actor verifies that flash alert is present`, 10 | Ensure.that(FlashMessages.flashAlert(), isVisible()), 11 | ) 12 | 13 | static succeeded = () => 14 | Task.where(`#actor verifies that authentication has succeeded`, 15 | VerifyAuthentication.hasFlashAlert(), 16 | Ensure.that(Text.of(FlashMessages.flashAlert()), includes('You logged into a secure area!')), 17 | ) 18 | 19 | static failed = () => 20 | Task.where(`#actor verifies that authentication has failed`, 21 | VerifyAuthentication.hasFlashAlert(), 22 | Ensure.that(Text.of(FlashMessages.flashAlert()), includes('Your username is invalid!')), 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /test/authentication/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Authenticate'; 2 | export * from './VerifyAuthentication'; 3 | -------------------------------------------------------------------------------- /test/authentication/ui/Article.ts: -------------------------------------------------------------------------------- 1 | import { By, PageElement } from '@serenity-js/web'; 2 | 3 | export const Article = { 4 | title: () => 5 | PageElement.located(By.css('h2')).describedAs('article title'), 6 | } 7 | -------------------------------------------------------------------------------- /test/authentication/ui/FlashMessages.ts: -------------------------------------------------------------------------------- 1 | import { By, PageElement } from '@serenity-js/web'; 2 | 3 | export const FlashMessages = { 4 | flashAlert: () => 5 | PageElement.located(By.id('flash')).describedAs('flash message'), 6 | } 7 | -------------------------------------------------------------------------------- /test/authentication/ui/LoginForm.ts: -------------------------------------------------------------------------------- 1 | import { By, PageElement } from '@serenity-js/web'; 2 | 3 | export const LoginForm = { 4 | usernameField: () => 5 | PageElement.located(By.id('username')).describedAs('username field'), 6 | 7 | passwordField: () => 8 | PageElement.located(By.id('password')).describedAs('password field'), 9 | 10 | loginButton: () => 11 | PageElement.located(By.css('button[type="submit"]')).describedAs('login button'), 12 | } 13 | -------------------------------------------------------------------------------- /test/examples/PickExample.ts: -------------------------------------------------------------------------------- 1 | import { Task } from '@serenity-js/core'; 2 | import { Click } from '@serenity-js/web'; 3 | 4 | import { Examples } from './ui/Examples'; 5 | 6 | /** 7 | * This is called a "Task". 8 | * Use tasks to compose a sequence of one or more activities and give them domain meaning. 9 | */ 10 | export const PickExample = { 11 | called: (name: string) => 12 | Task.where(`#actor picks example called ${ name }`, 13 | Click.on(Examples.called(name)), 14 | ), 15 | } 16 | -------------------------------------------------------------------------------- /test/examples/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PickExample'; 2 | -------------------------------------------------------------------------------- /test/examples/ui/Examples.ts: -------------------------------------------------------------------------------- 1 | import { equals } from '@serenity-js/assertions'; 2 | import { QuestionAdapter } from '@serenity-js/core'; 3 | import { By, PageElement, PageElements, Text } from '@serenity-js/web'; 4 | 5 | /** 6 | * This is called a "Lean Page Object". 7 | * Lean Page Objects describe interactive elements of a widget. 8 | * In this case, The list of examples at https://the-internet.herokuapp.com/ 9 | */ 10 | export class Examples { 11 | static all = () => 12 | PageElements.located(By.css('ul li a')).describedAs('available examples') 13 | 14 | /** 15 | * Note how I pick an element which text matches `name` without having to use XPath 16 | * See: https://janmolak.com/xpath-is-dead-advanced-web-element-locators-with-serenity-js-2-25-0-66145ad248d4 17 | * 18 | * @param name 19 | */ 20 | static called = (name: string): QuestionAdapter> => 21 | Examples.all() 22 | .where(Text, equals(name)) 23 | .first() 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "include": [ 4 | "features/**/*.ts", 5 | "test/**/*.ts", 6 | "wdio.conf.ts" 7 | ], 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "CommonJS", 5 | "types": [ 6 | "node", 7 | "@wdio/globals/types" 8 | ], 9 | "lib": ["es2021", "dom"], 10 | "target": "es2021" 11 | }, 12 | 13 | "include": [ 14 | "features/**/*.ts", 15 | "test/**/*.ts" 16 | ], 17 | 18 | "exclude": [ 19 | "node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /wdio.conf.ts: -------------------------------------------------------------------------------- 1 | import { WebdriverIOConfig } from '@serenity-js/webdriverio'; 2 | 3 | export const config: WebdriverIOConfig = { 4 | 5 | // ========================= 6 | // Serenity/JS Configuration 7 | // ========================= 8 | 9 | // Enable Serenity/JS framework adapter 10 | // see: https://serenity-js.org/handbook/test-runners/webdriverio/ 11 | framework: '@serenity-js/webdriverio', 12 | 13 | serenity: { 14 | // Use Cucumber.js test runner adapter 15 | // see: https://serenity-js.org/api/cucumber/ 16 | runner: 'cucumber', 17 | 18 | // Configure reporting services 19 | // see: https://serenity-js.org/handbook/reporting/ 20 | crew: [ 21 | '@serenity-js/console-reporter', 22 | [ '@serenity-js/serenity-bdd', { specDirectory: './features' } ], 23 | [ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfInteractions' } ], // slower execution, more comprehensive reports 24 | // [ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfFailures' } ], // fast execution, screenshots only when tests fail 25 | [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ], 26 | ] 27 | }, 28 | 29 | automationProtocol: 'webdriver', 30 | // ================== 31 | // Specify Test Files 32 | // ================== 33 | // Define which test specs should run. The pattern is relative to the directory 34 | // from which `wdio` was called. 35 | // 36 | // The specs are defined as an array of spec files (optionally using wildcards 37 | // that will be expanded). The test for each spec file will be run in a separate 38 | // worker process. In order to have a group of spec files run in the same worker 39 | // process simply enclose them in an array within the specs array. 40 | // 41 | // If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script), 42 | // then the current working directory is where your `package.json` resides, so `wdio` 43 | // will be called from there. 44 | specs: [ 45 | './features/**/*.feature' 46 | ], 47 | // Patterns to exclude. 48 | exclude: [ 49 | // 'path/to/excluded/files' 50 | ], 51 | 52 | // ============ 53 | // Capabilities 54 | // ============ 55 | // Define your capabilities here. WebdriverIO can run multiple capabilities at the same 56 | // time. Depending on the number of capabilities, WebdriverIO launches several test 57 | // sessions. Within your capabilities you can overwrite the spec and exclude options in 58 | // order to group specific specs to a specific capability. 59 | // 60 | // First, you can define how many instances should be started at the same time. Let's 61 | // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have 62 | // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec 63 | // files and you set maxInstances to 10, all spec files will get tested at the same time 64 | // and 30 processes will get spawned. The property handles how many capabilities 65 | // from the same test should run tests. 66 | // 67 | maxInstances: 6, 68 | // 69 | // If you have trouble getting all important capabilities together, check out the 70 | // Sauce Labs platform configurator - a great tool to configure your capabilities: 71 | // https://docs.saucelabs.com/reference/platforms-configurator 72 | // 73 | capabilities: [{ 74 | 75 | // maxInstances can get overwritten per capability. So if you have an in-house Selenium 76 | // grid with only 5 firefox instances available you can make sure that not more than 77 | // 5 instances get started at a time. 78 | // maxInstances: 5, 79 | // 80 | browserName: 'chrome', 81 | 82 | // See https://webdriver.io/blog/2023/07/31/driver-management 83 | browserVersion: 'stable', 84 | 85 | acceptInsecureCerts: true, 86 | // If outputDir is provided WebdriverIO can capture driver session logs 87 | // it is possible to configure which logTypes to include/exclude. 88 | // excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs 89 | // excludeDriverLogs: ['bugreport', 'server'], 90 | 91 | 'goog:chromeOptions': { 92 | args: [ 93 | '--disable-web-security', 94 | '--allow-file-access-from-files', 95 | '--allow-file-access', 96 | '--disable-infobars', 97 | '--ignore-certificate-errors', 98 | '--headless', 99 | '--disable-gpu', 100 | '--window-size=1024x768', 101 | ] 102 | } 103 | }], 104 | // 105 | // =================== 106 | // Test Configurations 107 | // =================== 108 | // Define all options that are relevant for the WebdriverIO instance here 109 | // 110 | // Level of logging verbosity: trace | debug | info | warn | error | silent 111 | logLevel: 'error', 112 | 113 | // 114 | // Set specific log levels per logger 115 | // loggers: 116 | // - webdriver, webdriverio 117 | // - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service 118 | // - @wdio/mocha-framework, @wdio/jasmine-framework 119 | // - @wdio/local-runner 120 | // - @wdio/sumologic-reporter 121 | // - @wdio/cli, @wdio/config, @wdio/utils 122 | // Level of logging verbosity: trace | debug | info | warn | error | silent 123 | // logLevels: { 124 | // webdriver: 'info', 125 | // '@wdio/appium-service': 'info' 126 | // }, 127 | // 128 | // If you only want to run your tests until a specific amount of tests have failed use 129 | // bail (default is 0 - don't bail, run all tests). 130 | bail: 0, 131 | // 132 | // Set a base URL in order to shorten url command calls. If your `url` parameter starts 133 | // with `/`, the base url gets prepended, not including the path portion of your baseUrl. 134 | // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url 135 | // gets prepended directly. 136 | baseUrl: 'https://the-internet.herokuapp.com/', 137 | // 138 | // Default timeout for all waitFor* commands. 139 | waitforTimeout: 10000, 140 | // 141 | // Default timeout in milliseconds for request 142 | // if browser driver or grid doesn't send response 143 | connectionRetryTimeout: 120000, 144 | // 145 | // Default request retries count 146 | connectionRetryCount: 3, 147 | // 148 | // Test runner services 149 | // Services take over a specific job you don't want to take care of. They enhance 150 | // your test setup with almost no effort. Unlike plugins, they don't add new 151 | // commands. Instead, they hook themselves up into the test process. 152 | // outputDir: 'target/logs', 153 | 154 | // 155 | // The number of times to retry the entire specfile when it fails as a whole 156 | // specFileRetries: 1, 157 | // 158 | // Delay in seconds between the spec file retry attempts 159 | // specFileRetriesDelay: 0, 160 | // 161 | // Whether or not retried specfiles should be retried immediately or deferred to the end of the queue 162 | // specFileRetriesDeferred: false, 163 | // 164 | // Native WebdriverIO reporter for stdout. 165 | // The only one supported by default is 'dot' 166 | // see also: https://webdriver.io/docs/dot-reporter 167 | // reporters: ['spec'], 168 | 169 | // Cucumber.js configuration 170 | // see: https://serenity-js.org/api/cucumber-adapter/interface/CucumberConfig/ 171 | cucumberOpts: { 172 | // (file/dir) require files before executing features 173 | require: [ 174 | './features/support/*.ts', 175 | './features/step-definitions/*.ts' 176 | ], 177 | // (type[:path]) specify native Cucumber.js output format, if needed. Optionally supply PATH to redirect formatter output (repeatable) 178 | format: [ ], 179 | // (name) specify the profile to use 180 | profile: '', 181 | // fail if there are any undefined or pending steps 182 | strict: false, 183 | // (expression) only execute the features or scenarios with tags matching the expression 184 | tags: [], 185 | // timeout for step definitions 186 | timeout: 60000, 187 | }, 188 | 189 | // 190 | // ===== 191 | // Hooks 192 | // ===== 193 | // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance 194 | // it and to build services around it. You can either apply a single function or an array of 195 | // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got 196 | // resolved to continue. 197 | /** 198 | * Gets executed once before all workers get launched. 199 | * @param {Object} config wdio configuration object 200 | * @param {Array.} capabilities list of capabilities details 201 | */ 202 | // onPrepare: function (config, capabilities) { 203 | // }, 204 | /** 205 | * Gets executed before a worker process is spawned and can be used to initialise specific service 206 | * for that worker as well as modify runtime environments in an async fashion. 207 | * @param {String} cid capability id (e.g 0-0) 208 | * @param {[type]} caps object containing capabilities for session that will be spawn in the worker 209 | * @param {[type]} specs specs to be run in the worker process 210 | * @param {[type]} args object that will be merged with the main configuration once worker is initialised 211 | * @param {[type]} execArgv list of string arguments passed to the worker process 212 | */ 213 | // onWorkerStart: function (cid, caps, specs, args, execArgv) { 214 | // }, 215 | /** 216 | * Gets executed just before initialising the webdriver session and test framework. It allows you 217 | * to manipulate configurations depending on the capability or spec. 218 | * @param {Object} config wdio configuration object 219 | * @param {Array.} capabilities list of capabilities details 220 | * @param {Array.} specs List of spec file paths that are to be run 221 | */ 222 | // beforeSession: function (config, capabilities, specs) { 223 | // }, 224 | /** 225 | * Gets executed before test execution begins. At this point you can access to all global 226 | * variables like `browser`. It is the perfect place to define custom commands. 227 | * @param {Array.} capabilities list of capabilities details 228 | * @param {Array.} specs List of spec file paths that are to be run 229 | * @param {Object} browser instance of created browser/device session 230 | */ 231 | // before: function (capabilities, specs) { 232 | // }, 233 | /** 234 | * Runs before a WebdriverIO command gets executed. 235 | * @param {String} commandName hook command name 236 | * @param {Array} args arguments that command would receive 237 | */ 238 | // beforeCommand: function (commandName, args) { 239 | // }, 240 | /** 241 | * Cucumber Hooks 242 | * 243 | * Runs before a Cucumber Feature. 244 | * @param {String} uri path to feature file 245 | * @param {GherkinDocument.IFeature} feature Cucumber feature object 246 | */ 247 | // beforeFeature: function (uri, feature) { 248 | // }, 249 | /** 250 | * 251 | * Runs before a Cucumber Scenario. 252 | * @param {ITestCaseHookParameter} world world object containing information on pickle and test step 253 | */ 254 | // beforeScenario: function (world) { 255 | // }, 256 | /** 257 | * 258 | * Runs before a Cucumber Step. 259 | * @param {Pickle.IPickleStep} step step data 260 | * @param {IPickle} scenario scenario pickle 261 | */ 262 | // beforeStep: function (step, scenario) { 263 | // }, 264 | /** 265 | * 266 | * Runs after a Cucumber Step. 267 | * @param {Pickle.IPickleStep} step step data 268 | * @param {IPickle} scenario scenario pickle 269 | * @param {Object} result results object containing scenario results 270 | * @param {boolean} result.passed true if scenario has passed 271 | * @param {string} result.error error stack if scenario failed 272 | * @param {number} result.duration duration of scenario in milliseconds 273 | */ 274 | // afterStep: function (step, scenario, result) { 275 | // }, 276 | /** 277 | * 278 | * Runs before a Cucumber Scenario. 279 | * @param {ITestCaseHookParameter} world world object containing information on pickle and test step 280 | * @param {Object} result results object containing scenario results 281 | * @param {boolean} result.passed true if scenario has passed 282 | * @param {string} result.error error stack if scenario failed 283 | * @param {number} result.duration duration of scenario in milliseconds 284 | */ 285 | // afterScenario: function (world, result) { 286 | // }, 287 | /** 288 | * 289 | * Runs after a Cucumber Feature. 290 | * @param {String} uri path to feature file 291 | * @param {GherkinDocument.IFeature} feature Cucumber feature object 292 | */ 293 | // afterFeature: function (uri, feature) { 294 | // }, 295 | 296 | /** 297 | * Runs after a WebdriverIO command gets executed 298 | * @param {String} commandName hook command name 299 | * @param {Array} args arguments that command would receive 300 | * @param {Number} result 0 - command success, 1 - command error 301 | * @param {Object} error error object if any 302 | */ 303 | // afterCommand: function (commandName, args, result, error) { 304 | // }, 305 | /** 306 | * Gets executed after all tests are done. You still have access to all global variables from 307 | * the test. 308 | * @param {Number} result 0 - test pass, 1 - test fail 309 | * @param {Array.} capabilities list of capabilities details 310 | * @param {Array.} specs List of spec file paths that ran 311 | */ 312 | // after: function (result, capabilities, specs) { 313 | // }, 314 | /** 315 | * Gets executed right after terminating the webdriver session. 316 | * @param {Object} config wdio configuration object 317 | * @param {Array.} capabilities list of capabilities details 318 | * @param {Array.} specs List of spec file paths that ran 319 | */ 320 | // afterSession: function (config, capabilities, specs) { 321 | // }, 322 | /** 323 | * Gets executed after all workers got shut down and the process is about to exit. An error 324 | * thrown in the onComplete hook will result in the test run failing. 325 | * @param {Object} exitCode 0 - success, 1 - fail 326 | * @param {Object} config wdio configuration object 327 | * @param {Array.} capabilities list of capabilities details 328 | * @param {} results object containing test results 329 | */ 330 | // onComplete: function(exitCode, config, capabilities, results) { 331 | // }, 332 | /** 333 | * Gets executed when a refresh happens. 334 | * @param {String} oldSessionId session ID of the old session 335 | * @param {String} newSessionId session ID of the new session 336 | */ 337 | //onReload: function(oldSessionId, newSessionId) { 338 | //} 339 | } 340 | --------------------------------------------------------------------------------