├── .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 ├── NOTICE.md ├── README.md ├── SECURITY.md ├── cucumber.js ├── features ├── authentication │ ├── form-based_authentication.feature │ └── readme.md ├── step-definitions │ ├── parameter.steps.ts │ └── the-internet.steps.ts └── support │ └── serenity.config.ts ├── package-lock.json ├── package.json ├── test ├── Actors.ts ├── authentication │ ├── Authenticate.ts │ ├── VerifyAuthentication.ts │ └── index.ts ├── examples │ ├── PickExample.ts │ └── index.ts └── index.ts ├── tsconfig.eslint.json └── tsconfig.json /.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", "^playwright"], 9 | "groupName": "Serenity/JS and Playwright", 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: Install Node Modules 28 | uses: bahmutov/npm-install@v1 29 | with: 30 | install-command: npm ci 31 | - name: Install Playwright Browsers 32 | run: npx playwright install --with-deps 33 | - run: npm run lint 34 | - run: npm test 35 | env: 36 | CI: true 37 | 38 | - name: GitHub Pages 39 | uses: JamesIves/github-pages-deploy-action@v4.7.3 40 | if: matrix.node-version == '22.x' && github.ref == 'refs/heads/main' 41 | with: 42 | branch: gh-pages 43 | folder: target/site/serenity 44 | clean: true 45 | -------------------------------------------------------------------------------- /.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 Node modules' 11 | init: | 12 | nvm install 13 | nvm use 14 | npm ci 15 | npm test 16 | gp sync-done installation 17 | 18 | - name: 'Serenity Reports' 19 | init: gp sync-await installation 20 | command: | 21 | npx http-server -p 8080 target/site/serenity/ 22 | 23 | - name: 'Welcome' 24 | init: gp sync-await installation 25 | command: | 26 | echo "To view Serenity Reports, visit:" 27 | gp url 8080 28 | 29 | vscode: 30 | extensions: 31 | - dbaeumer.vscode-eslint 32 | - EditorConfig.EditorConfig 33 | - CucumberOpen.cucumber-official 34 | 35 | github: 36 | prebuilds: 37 | master: true 38 | branches: false 39 | pullRequests: true 40 | pullRequestsFromForks: false 41 | addCheck: true 42 | addComment: false 43 | addBadge: true 44 | -------------------------------------------------------------------------------- /.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 | 39 | # ###################################################################################################################### 40 | # Install Playwright 41 | # 42 | # Playwright has a peculiar installation procedure, where it fails silently if you try to install it on Gitpod 43 | # as it assumes it's being installed in "dev mode". 44 | # https://github.com/microsoft/playwright/blob/35a9daa4255f2ba556d4d7af6243cc84d1ac4f2a/packages/playwright/install.js#L19-L24 45 | # 46 | # Instead, I need to use the same trick Playwright themselves use, which is to install Playwright using a temporary Npm project 47 | # https://github.com/microsoft/playwright/blob/35a9daa4255f2ba556d4d7af6243cc84d1ac4f2a/utils/docker/Dockerfile.focal 48 | # 49 | # The path to directory cache must be "/ms-playwright" because of the hard-coded path in Playwright: 50 | # https://github.com/microsoft/playwright/blob/35a9daa4255f2ba556d4d7af6243cc84d1ac4f2a/packages/playwright-core/src/server/registry/dependencies.ts#L31 51 | # https://playwright.dev/docs/ci#caching-browsers 52 | 53 | ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright 54 | 55 | ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright:v%version%-focal" 56 | 57 | RUN sudo bash -c "mkdir -p ${PLAYWRIGHT_BROWSERS_PATH} \ 58 | && mkdir -p ${PLAYWRIGHT_BROWSERS_PATH}/agent \ 59 | && chmod -R 777 ${PLAYWRIGHT_BROWSERS_PATH} \ 60 | && rm -rf /var/lib/apt/lists/*" 61 | 62 | # Use our own package.json files so that we install a compatible version of Playwright browsers 63 | COPY package*.json "${PLAYWRIGHT_BROWSERS_PATH}/agent" 64 | 65 | RUN bash -c "cd ${PLAYWRIGHT_BROWSERS_PATH}/agent \ 66 | && npm ci \ 67 | && npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" \ 68 | && npx --yes playwright install --with-deps" \ 69 | && sudo bash -c "chmod -R 777 ${PLAYWRIGHT_BROWSERS_PATH} \ 70 | && rm -rf ${PLAYWRIGHT_BROWSERS_PATH}/agent" \ 71 | && bash -c "npm cache verify" 72 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.16.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: -------------------------------------------------------------------------------- 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. 202 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | Copyright 2016- Jan Molak, smartcode ltd. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serenity/JS Cucumber Playwright Template 2 | 3 | [![Build Status](https://github.com/serenity-js/serenity-js-cucumber-playwright-template/workflows/build/badge.svg)](https://github.com/serenity-js/serenity-js-cucumber-playwright-template/actions) 4 | [![Serenity/JS on GitHub](https://img.shields.io/badge/github-serenity--js-yellow?logo=github)](https://github.com/serenity-js/serenity-js) 5 | [![Serenity/JS on StackOverflow](https://img.shields.io/badge/stackoverflow-serenity--js-important?logo=stackoverflow)](https://stackoverflow.com/questions/tagged/serenity-js) 6 | [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/from-referrer/) 7 | 8 | [![Follow Serenity/JS on LinkedIn](https://img.shields.io/badge/Follow-Serenity%2FJS%20-0077B5?logo=linkedin)](https://www.linkedin.com/company/serenity-js) 9 | [![Watch Serenity/JS on YouTube](https://img.shields.io/badge/Watch-@serenity--js-E62117?logo=youtube)](https://www.youtube.com/@serenity-js) 10 | [![Join Serenity/JS Community Chat](https://img.shields.io/badge/Chat-Serenity%2FJS%20Community-FBD30B?logo=matrix)](https://matrix.to/#/#serenity-js:gitter.im) 11 | [![Support Serenity/JS on GitHub](https://img.shields.io/badge/Support-@serenity--js-703EC8?logo=github)](https://github.com/sponsors/serenity-js) 12 | 13 | Use this [template repository](https://help.github.com/en/articles/creating-a-repository-from-a-template) 14 | to get started with acceptance testing your web applications using [Serenity/JS](https://serenity-js.org), 15 | [Cucumber](https://github.com/cucumber/cucumber-js) and [Playwright](https://playwright.dev/). 16 | 17 | Learn more: 18 | - [Serenity BDD reports for this project](https://serenity-js.github.io/serenity-js-cucumber-playwright-template/) 19 | - [Serenity/JS website, tutorials, and API docs](https://serenity-js.org/) 20 | 21 | ## Usage 22 | 23 | This repository is a GitHub template. You can use it to [create a new GitHub repository](https://help.github.com/en/articles/creating-a-repository-from-a-template), [clone it to your computer](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/cloning-a-repository). 24 | 25 | You can also launch it in a virtual Gitpod workspace without having to install anything on your machine: 26 | 27 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) 28 | 29 | ### Installation 30 | 31 | Once you have the code on your computer, use your computer terminal to run the following command in the directory where you've cloned the project: 32 | ``` 33 | npm ci 34 | ``` 35 | 36 | Running [`npm ci`](https://docs.npmjs.com/cli/v6/commands/npm-ci) downloads the [Node modules](https://docs.npmjs.com/about-packages-and-modules) this project depends on, 37 | as well the [Serenity BDD CLI](https://github.com/serenity-bdd/serenity-cli) reporter jar. 38 | 39 | ### Corporate networks 40 | 41 | If your network administrators require you to use proxy servers or an internal artifact registry (Artifactory, Nexus, etc.), your development environment might require some additional configuration. 42 | 43 | The easiest way to do it is to create an [`.npmrc` file](https://docs.npmjs.com/cli/v6/configuring-npm/npmrc) in your home directory: 44 | 45 | ``` 46 | proxy=http://user:password@host.mycompany.com:8080/ 47 | https-proxy=http://user:password@host.mycompany.com:8080/ 48 | strict-ssl=false 49 | registry=https://artifactory.mycompany.com/artifactory/ 50 | ``` 51 | 52 | If you encounter issues downloading the Serenity BDD CLI jar, please follow the [detailed instructions in the Serenity/JS Handbook](https://serenity-js.org/api/serenity-bdd/#downloading-the-serenity-bdd-reporting-cli). 53 | 54 | Similar instructions are available for the [`chromedriver` module](https://www.npmjs.com/package/chromedriver). 55 | 56 | ### Execution 57 | 58 | The project provides several [NPM scripts](https://docs.npmjs.com/cli/v6/using-npm/scripts) defined in [`package.json`](package.json): 59 | 60 | ``` 61 | npm run lint # runs code linter 62 | npm run lint:fix # attempts to automatically fix linting issues 63 | npm run clean # removes reports from any previous test run 64 | npm test # executes the example test suite 65 | # and generates the report under ./target/site/serenity 66 | npm start # starts a mini HTTP server and serves the test reports 67 | # at http://localhost:8080 68 | ``` 69 | 70 | ## Your feedback matters! 71 | 72 | Do you find Serenity/JS useful? Give it a ⭐ star on GitHub! 73 | 74 | [![GitHub stars](https://img.shields.io/github/stars/serenity-js/serenity-js)](https://github.com/serenity-js/serenity-js) 75 | 76 | Found a bug? Need a feature? Raise [an issue](https://github.com/serenity-js/serenity-js/issues?state=open) 77 | or submit a pull request. 78 | 79 | Have feedback? Let me know on [LinkedIn](https://www.linkedin.com/in/janmolak/) or leave a comment in [Serenity/JS discussions on GitHub](/serenity-js/serenity-js/discussions/categories/comments). 80 | 81 | And if you have any questions about the framework, or simply want to say hello to other Serenity/JS developers, join us on [Serenity/JS Community Chat](https://matrix.to/#/#serenity-js:gitter.im). 82 | 83 | ## Support Serenity/JS 84 | 85 | Serenity/JS is a free open-source framework, so we rely on our [wonderful GitHub sponsors](https://github.com/sponsors/serenity-js) to keep the lights on. 86 | 87 | If you appreciate all the effort that goes into making sophisticated tools easy to work with, please support our work and [become a Serenity/JS GitHub Sponsor](https://github.com/sponsors/serenity-js) today! 88 | 89 | [![LinkedIn Follow](https://img.shields.io/badge/Follow%20Serenity%2FJS-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/serenity-js) 90 | [![YouTube Follow](https://img.shields.io/badge/Watch%20@serenity—JS-FA120F?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/@serenity-js) 91 | [![GitHub Sponsors](https://img.shields.io/badge/Support%20@serenity%2FJS-703EC8?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/serenity-js) 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cucumber.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a Cucumber.js profile 3 | * @see https://github.com/cucumber/cucumber-js/blob/main/docs/profiles.md 4 | * 5 | * @type {{default: string}} 6 | */ 7 | module.exports = { 8 | default: [ 9 | `--require-module 'ts-node/register'`, // use TypeScript in-memory transpiler, ts-node 10 | `--format "@serenity-js/cucumber"`, // use Serenity/JS Cucumber adapter - https://serenity-js.org/modules/cucumber/ 11 | `--format-options '{"specDirectory": "features"}'`, // configure the adapter 12 | `--require "./features/**/*.steps.ts"`, // load step definition libraries 13 | `--require './features/**/*.config.ts'` // load configuration files, such as features/support/ 14 | ].join(' ') 15 | }; 16 | -------------------------------------------------------------------------------- /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 Playwright 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/readme.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/parameter.steps.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 | -------------------------------------------------------------------------------- /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/serenity.config.ts: -------------------------------------------------------------------------------- 1 | import { AfterAll, BeforeAll, setDefaultTimeout } from '@cucumber/cucumber'; 2 | import { configure, Duration } from '@serenity-js/core'; 3 | import path from 'path'; 4 | import * as playwright from 'playwright'; 5 | 6 | import { Actors } from '../../test'; 7 | 8 | const timeouts = { 9 | cucumber: { 10 | step: Duration.ofSeconds(30), // how long to wait for a Cucumber step to complete 11 | }, 12 | playwright: { 13 | defaultNavigationTimeout: Duration.ofSeconds(10), // how long to wait for a page to load 14 | defaultTimeout: Duration.ofSeconds(5), // how long to wait for an element to show up 15 | }, 16 | serenity: { 17 | cueTimeout: Duration.ofSeconds(5), // how long to wait for Serenity/JS to complete any post-test activities, like saving screenshots and reports 18 | } 19 | } 20 | 21 | let browser: playwright.Browser; 22 | 23 | // Configure default Cucumber step timeout 24 | setDefaultTimeout(timeouts.cucumber.step.inMilliseconds()); 25 | 26 | BeforeAll(async () => { 27 | // Launch the browser once before all the tests 28 | // Serenity/JS will take care of managing Playwright browser context and browser tabs. 29 | browser = await playwright.chromium.launch({ 30 | headless: true, 31 | }); 32 | 33 | // Configure Serenity/JS 34 | configure({ 35 | 36 | // Configure Serenity/JS actors to use Playwright browser 37 | actors: new Actors(browser, { 38 | baseURL: 'https://the-internet.herokuapp.com/', 39 | defaultNavigationTimeout: timeouts.playwright.defaultNavigationTimeout.inMilliseconds(), 40 | defaultTimeout: timeouts.playwright.defaultTimeout.inMilliseconds(), 41 | }), 42 | 43 | // Configure Serenity/JS reporting services 44 | crew: [ 45 | [ '@serenity-js/console-reporter', { theme: 'auto' } ], 46 | [ '@serenity-js/web:Photographer', { 47 | // strategy: 'TakePhotosOfInteractions', // capture screenshots of all the interactions; slower but more comprehensive 48 | strategy: 'TakePhotosOfFailures', // capture screenshots of failed interactions; much faster 49 | } ], 50 | [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: path.resolve(__dirname, '../../target/site/serenity') } ], 51 | [ '@serenity-js/serenity-bdd', { specDirectory: path.resolve(__dirname, '../../features') } ], 52 | ], 53 | 54 | cueTimeout: timeouts.serenity.cueTimeout, 55 | }); 56 | }); 57 | 58 | AfterAll(async () => { 59 | // Close the browser after all the tests are finished 60 | if (browser) { 61 | await browser.close(); 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serenity-js-cucumber-playwright-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": "cucumber-js", 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-playwright-template.git" 18 | }, 19 | "keywords": [ 20 | "serenity-js", 21 | "bdd", 22 | "cucumber", 23 | "testing", 24 | "playwright", 25 | "playwright-test" 26 | ], 27 | "author": "Jan Molak ", 28 | "license": "Apache-2.0", 29 | "bugs": { 30 | "url": "https://github.com/serenity-js/serenity-js-cucumber-playwright-template/issues" 31 | }, 32 | "homepage": "https://serenity-js.org", 33 | "engines": { 34 | "node": "^18.12 || ^20 || ^22" 35 | }, 36 | "devDependencies": { 37 | "@cucumber/cucumber": "^11.3.0", 38 | "@serenity-js/assertions": "^3.31.16", 39 | "@serenity-js/console-reporter": "^3.31.16", 40 | "@serenity-js/core": "^3.31.16", 41 | "@serenity-js/cucumber": "^3.31.16", 42 | "@serenity-js/playwright": "^3.31.16", 43 | "@serenity-js/rest": "^3.31.16", 44 | "@serenity-js/serenity-bdd": "^3.31.16", 45 | "@serenity-js/web": "^3.31.16", 46 | "@typescript-eslint/eslint-plugin": "^7.18.0", 47 | "@typescript-eslint/parser": "^7.18.0", 48 | "eslint": "^8.57.1", 49 | "eslint-plugin-import": "^2.31.0", 50 | "eslint-plugin-simple-import-sort": "^12.1.1", 51 | "eslint-plugin-unicorn": "^52.0.0", 52 | "eslint-plugin-unused-imports": "^3.2.0", 53 | "is-ci": "^4.1.0", 54 | "npm-failsafe": "^1.3.0", 55 | "http-server": "^14.1.1", 56 | "mkdirp": "^3.0.1", 57 | "playwright": "^1.52.0", 58 | "rimraf": "^6.0.1", 59 | "ts-node": "^10.9.2", 60 | "typescript": "^5.8.3" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/Actors.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Cast, TakeNotes } from '@serenity-js/core'; 2 | import { BrowseTheWebWithPlaywright, PlaywrightOptions } from '@serenity-js/playwright'; 3 | import { CallAnApi } from '@serenity-js/rest'; 4 | import * as playwright from 'playwright'; 5 | 6 | export class Actors implements Cast { 7 | constructor( 8 | private readonly browser: playwright.Browser, 9 | private readonly options: PlaywrightOptions, 10 | ) { 11 | } 12 | 13 | prepare(actor: Actor): Actor { 14 | return actor.whoCan( 15 | BrowseTheWebWithPlaywright.using(this.browser, this.options), 16 | CallAnApi.at(this.options.baseURL), 17 | TakeNotes.usingAnEmptyNotepad(), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/authentication/Authenticate.ts: -------------------------------------------------------------------------------- 1 | import { Task } from '@serenity-js/core'; 2 | import { By, Click, Enter, PageElement } from '@serenity-js/web'; 3 | 4 | /** 5 | * This is called a "Task". 6 | * Use tasks to compose a sequence of one or more activities and give them domain meaning. 7 | * 8 | * Here, the actor performs three activities: 9 | * - enter username 10 | * - enter password 11 | * - click on the login button 12 | * 13 | * This sequence of activities together means to "log in" 14 | */ 15 | export const Authenticate = { 16 | using: (username: string, password: string) => 17 | Task.where(`#actor logs in as ${ username }`, 18 | Enter.theValue(username).into(LoginForm.usernameField()), 19 | Enter.theValue(password).into(LoginForm.passwordField()), 20 | Click.on(LoginForm.loginButton()), 21 | ), 22 | } 23 | 24 | /** 25 | * This is called a "Lean Page Object". 26 | * Lean Page Objects describe interactive elements of a widget. 27 | * In this case, the login form widget at https://the-internet.herokuapp.com/login 28 | */ 29 | const LoginForm = { 30 | usernameField: () => 31 | PageElement.located(By.id('username')).describedAs('username field'), 32 | 33 | passwordField: () => 34 | PageElement.located(By.id('password')).describedAs('password field'), 35 | 36 | loginButton: () => 37 | PageElement.located(By.css('button[type="submit"]')).describedAs('login button'), 38 | } 39 | -------------------------------------------------------------------------------- /test/authentication/VerifyAuthentication.ts: -------------------------------------------------------------------------------- 1 | import { Ensure, includes } from '@serenity-js/assertions'; 2 | import { Task } from '@serenity-js/core'; 3 | import { By, isVisible, PageElement, Text } from '@serenity-js/web'; 4 | 5 | /** 6 | * VerifyAuthentication aggregates several tasks to make them easier to find: 7 | * - VerifyAuthentication.succeeded() 8 | * - VerifyAuthentication.failed() 9 | * 10 | * Note how both those tasks reuse VerifyAuthentication.hasFlashAlert() 11 | * to avoid code duplication. 12 | */ 13 | export class VerifyAuthentication { 14 | private static hasFlashAlert = () => 15 | Task.where(`#actor verifies that flash alert is present`, 16 | Ensure.that(FlashMessages.flashAlert(), isVisible()), 17 | ) 18 | 19 | static succeeded = () => 20 | Task.where(`#actor verifies that authentication has succeeded`, 21 | VerifyAuthentication.hasFlashAlert(), 22 | Ensure.that(Text.of(FlashMessages.flashAlert()), includes('You logged into a secure area!')), 23 | ) 24 | 25 | static failed = () => 26 | Task.where(`#actor verifies that authentication has failed`, 27 | VerifyAuthentication.hasFlashAlert(), 28 | Ensure.that(Text.of(FlashMessages.flashAlert()), includes('Your username is invalid!')), 29 | ) 30 | } 31 | 32 | /** 33 | * A tiny Lean Page Object, representing the flash messages 34 | * that show up when the user logs submits the authentication form. 35 | */ 36 | const FlashMessages = { 37 | flashAlert: () => 38 | PageElement.located(By.id('flash')).describedAs('flash message'), 39 | } 40 | -------------------------------------------------------------------------------- /test/authentication/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Authenticate'; 2 | export * from './VerifyAuthentication'; 3 | -------------------------------------------------------------------------------- /test/examples/PickExample.ts: -------------------------------------------------------------------------------- 1 | import { equals } from '@serenity-js/assertions'; 2 | import { QuestionAdapter, Task } from '@serenity-js/core'; 3 | import { By, Click, PageElement, PageElements, Text } from '@serenity-js/web'; 4 | 5 | /** 6 | * This is called a "Task". 7 | * Use tasks to compose a sequence of one or more activities and give them domain meaning. 8 | */ 9 | export const PickExample = { 10 | called: (name: string) => 11 | Task.where(`#actor picks example called ${ name }`, 12 | Click.on(Examples.called(name)), 13 | ), 14 | } 15 | 16 | /** 17 | * This is called a "Lean Page Object". 18 | * Lean Page Objects describe interactive elements of a widget. 19 | * In this case, The list of examples at https://the-internet.herokuapp.com/ 20 | */ 21 | class Examples { 22 | static all = () => 23 | PageElements.located(By.css('ul li a')) 24 | .describedAs('available examples') 25 | 26 | /** 27 | * Note how I pick an element which text matches `name` without having to use XPath 28 | * See: https://janmolak.com/xpath-is-dead-advanced-web-element-locators-with-serenity-js-2-25-0-66145ad248d4 29 | * 30 | * @param name 31 | */ 32 | static called = (name: string): QuestionAdapter> => 33 | Examples.all() 34 | .where(Text, equals(name)) 35 | .first() 36 | } 37 | -------------------------------------------------------------------------------- /test/examples/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PickExample'; 2 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Actors'; 2 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "include": [ 4 | "features/**/*.ts" 5 | ], 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["es2019", "dom"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "sourceMap": true, 11 | "declaration": true, 12 | "types": [ 13 | "node" 14 | ] 15 | }, 16 | 17 | "include": [ 18 | "features/**/*.ts", 19 | "test/**/*.ts" 20 | ], 21 | 22 | "exclude": [ 23 | "node_modules" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------