├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── default.yml └── workflows │ ├── build.yml │ ├── examples-branch.yml │ └── examples-master.yml ├── .gitignore ├── .mocharc.json ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── augmentations.d.ts ├── cucumber.json ├── declarations.d.ts ├── docs ├── configuration.md ├── cucumber-basics.md ├── dry-run.md ├── event-handlers.md ├── examples │ ├── handling-multiple-configurations.md │ └── handling-multiple-configurations │ │ ├── a-tests-pass.png │ │ ├── b-tests-pass.png │ │ └── example-test-directory.png ├── faq.md ├── html-formatter.md ├── html-report.md ├── json-formatter.md ├── json-report.md ├── localisation.md ├── merging-reports.md ├── messages-report.md ├── mixing-types.md ├── pretty-output.md ├── pretty.gif ├── quick-start.md ├── readme.md ├── source-maps.md ├── state-management.md ├── step-definitions.md ├── tags.md ├── test-configuration.md └── usage-report.md ├── eslint.config.mjs ├── examples ├── browserify-cjs │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.js │ │ └── support │ │ │ └── e2e.js │ └── package.json ├── browserify-esm │ ├── cypress.config.mjs │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.mjs │ │ └── support │ │ │ └── e2e.js │ └── package.json ├── browserify-ts │ ├── cypress.config.ts │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.ts │ │ └── support │ │ │ └── e2e.ts │ ├── package.json │ └── tsconfig.json ├── ct-vite-react-ts │ ├── cypress.config.ts │ ├── cypress │ │ ├── component │ │ │ ├── App.feature │ │ │ └── App.tsx │ │ └── support │ │ │ ├── component-index.html │ │ │ └── component.ts │ ├── package.json │ ├── patches │ │ └── @cypress+vite-dev-server+5.1.0.patch │ ├── src │ │ └── App.tsx │ └── tsconfig.json ├── ct-webpack-react-ts │ ├── cypress.config.ts │ ├── cypress │ │ ├── component │ │ │ ├── App.feature │ │ │ └── App.tsx │ │ └── support │ │ │ ├── component-index.html │ │ │ └── component.ts │ ├── package.json │ ├── src │ │ └── App.tsx │ └── tsconfig.json ├── esbuild-cjs │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.js │ │ └── support │ │ │ └── e2e.js │ └── package.json ├── esbuild-esm │ ├── cypress.config.mjs │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.mjs │ │ └── support │ │ │ └── e2e.js │ └── package.json ├── esbuild-ts │ ├── cypress.config.ts │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.ts │ │ └── support │ │ │ └── e2e.ts │ ├── package.json │ └── tsconfig.json ├── readme.md ├── type-module │ ├── cypress.config.ts │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.ts │ │ └── support │ │ │ └── e2e.ts │ ├── package.json │ └── tsconfig.json ├── webpack-cjs │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.js │ │ └── support │ │ │ └── e2e.js │ └── package.json ├── webpack-esm │ ├── cypress.config.mjs │ ├── cypress │ │ ├── e2e │ │ │ ├── duckduckgo.feature │ │ │ └── duckduckgo.mjs │ │ └── support │ │ │ └── e2e.js │ └── package.json └── webpack-ts │ ├── cypress.config.ts │ ├── cypress │ ├── e2e │ │ ├── duckduckgo.feature │ │ └── duckduckgo.ts │ └── support │ │ └── e2e.ts │ ├── package.json │ └── tsconfig.json ├── features ├── ambiguous_keywords.feature ├── attachments_browser.feature ├── attachments_node.feature ├── browser_crash.feature ├── configuration_overrides.feature ├── custom_parameter_type.feature ├── data_table.feature ├── doc_string.feature ├── dry_run.feature ├── experimental_source_map.feature ├── fixtures │ ├── another-passed-example.ndjson │ ├── attachments │ │ ├── screenshot.json │ │ ├── screenshot.ndjson │ │ └── string.json │ ├── experimental-source-map.json │ ├── failing-after.json │ ├── failing-after.ndjson │ ├── failing-before.json │ ├── failing-before.ndjson │ ├── failing-step.json │ ├── failing-step.ndjson │ ├── multiple-features.json │ ├── multiple-features.ndjson │ ├── multiple-scenarios-reloaded.json │ ├── multiple-scenarios-reloaded.ndjson │ ├── parameterized-scenario-name.json │ ├── passed-example.json │ ├── passed-example.ndjson │ ├── passed-outline.json │ ├── passed-outline.ndjson │ ├── pending-steps.json │ ├── pending-steps.ndjson │ ├── rescued-error.json │ ├── rescued-error.ndjson │ ├── retried.ndjson │ ├── skipped-all-scenarios.json │ ├── skipped-all-scenarios.ndjson │ ├── skipped-first-scenario.json │ ├── skipped-first-scenario.ndjson │ ├── skipped-second-scenario.json │ ├── skipped-second-scenario.ndjson │ ├── skipped-steps.json │ ├── skipped-steps.ndjson │ ├── skipped-third-scenario.json │ ├── skipped-third-scenario.ndjson │ ├── undefined-steps.json │ └── undefined-steps.ndjson ├── hooks_argument.feature ├── hooks_name.feature ├── hooks_ordering.feature ├── issues │ ├── 1017.feature │ ├── 1028.feature │ ├── 1034.feature │ ├── 1041.feature │ ├── 1091.feature │ ├── 1140.feature │ ├── 1142.feature │ ├── 1196 [foo].feature │ ├── 1243.feature │ ├── 1245.feature │ ├── 705.feature │ ├── 713.feature │ ├── 724.feature │ ├── 731.feature │ ├── 736.feature │ ├── 749.feature │ ├── 781.feature │ ├── 785.feature │ ├── 788.feature │ ├── 792.feature │ ├── 795.feature │ ├── 813.feature │ ├── 832.feature │ ├── 856.feature │ ├── 903.feature │ ├── 908.feature │ ├── 922.feature │ ├── 931.feature │ ├── 944.feature │ ├── 946.feature │ ├── 963.feature │ └── 977.feature ├── loaders │ ├── browserify.feature │ ├── esbuild.feature │ └── webpack.feature ├── localisation.feature ├── merge_messages.feature ├── mixing_types.feature ├── nested_steps.feature ├── parse_error.feature ├── pending_scenario_hooks.feature ├── pending_steps.feature ├── pretty_output.feature ├── reporters │ ├── html.feature │ ├── json.feature │ ├── messages.feature │ └── usage.feature ├── scenario_outlines.feature ├── setup.feature ├── skip.feature ├── skipped_scenario_hooks.feature ├── skipped_steps.feature ├── step_definitions.feature ├── step_definitions │ ├── cli_steps.ts │ ├── config_steps.ts │ ├── cypress_steps.ts │ ├── file_steps.ts │ ├── html_steps.ts │ ├── json_steps.ts │ ├── messages_steps.ts │ └── usage_steps.ts ├── suite_only_options.feature ├── suite_options.feature ├── support │ ├── ICustomWorld.ts │ ├── accordion.ts │ ├── configFileUpdater.ts │ ├── helpers.ts │ ├── hooks.ts │ └── world.ts ├── tags │ ├── only_tag.feature │ ├── skip_tag.feature │ ├── spec_filter.feature │ ├── tagged_hooks.feature │ ├── target_specific_scenarios_by_tag.feature │ └── test_filter.feature ├── test_state.feature ├── unambiguous_step_definitions.feature ├── undefined_step.feature ├── world_asynchronous.feature ├── world_example.feature └── world_synchronous.feature ├── lib ├── add-cucumber-preprocessor-plugin.ts ├── bin │ ├── cucumber-html-formatter.ts │ ├── cucumber-json-formatter.ts │ └── cucumber-merge-messages.ts ├── browser-runtime.ts ├── constants.ts ├── cypress-task-definitions.ts ├── data_table.test.ts ├── data_table.ts ├── entrypoint-browser.ts ├── entrypoint-node.ts ├── helpers │ ├── assertions.ts │ ├── ast.ts │ ├── colors.ts │ ├── cypress.ts │ ├── debug.ts │ ├── dry-run.ts │ ├── environment.ts │ ├── error.ts │ ├── formatters.ts │ ├── memoize.ts │ ├── merge.test.ts │ ├── merge.ts │ ├── messages-enums.ts │ ├── messages.ts │ ├── options.ts │ ├── paths.ts │ ├── prepare-registry.ts │ ├── snippets.test.ts │ ├── snippets.ts │ ├── source-map.ts │ ├── strings.ts │ ├── tag-parser.test.ts │ ├── tag-parser │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── parser.ts │ │ └── tokenizer.ts │ └── type-guards.ts ├── plugin-event-handlers.ts ├── preprocessor-configuration.test.ts ├── preprocessor-configuration.ts ├── public-member-types.ts ├── registry.ts ├── step-definitions.test.ts ├── step-definitions.ts ├── subpath-entrypoints │ ├── browserify.ts │ ├── esbuild.ts │ ├── pretty-reporter.ts │ ├── rollup.ts │ └── webpack.ts └── template.ts ├── package.json ├── patches ├── @cucumber+cucumber+10.9.0.patch └── patch.js ├── renovate.json ├── test-d ├── entrypoint-browser.test-d.ts ├── entrypoint-node.test-d.ts └── tsconfig.json ├── test └── run-all-specs.ts ├── tsconfig.eslint.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_style = space 5 | indent_size = 2 6 | insert_final_newline = true 7 | 8 | [*.{js, ts}] 9 | max_line_length = 80 10 | bracket_spacing = true 11 | jsx_bracket_same_line = false 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [badeball] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/default.yml: -------------------------------------------------------------------------------- 1 | name: foo 2 | description: foo 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Current behavior 7 | description: A description including screenshots, stack traces, DEBUG logs, etc. 8 | value: | 9 | Make sure to read the section titled "Reduced test case" under the contributing guidelines [1]. Your 10 | report should contain everything necessary in order for us to reproduce the issue. Preferably link 11 | to another, minimal Git repository that illustrates the issue. 12 | 13 | [1] https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/CONTRIBUTING.md#reduced-test-case 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Desired behavior 19 | description: A clear description of what you want to happen. 20 | validations: 21 | required: true 22 | - type: input 23 | attributes: 24 | description: Don't write "latest", as this information becomes erroneous as soon as another version is released. 25 | label: Cypress version 26 | validations: 27 | required: true 28 | - type: input 29 | attributes: 30 | description: Don't write "latest", as this information becomes erroneous as soon as another version is released. 31 | label: Preprocessor version 32 | validations: 33 | required: true 34 | - type: input 35 | attributes: 36 | description: Don't write "latest", as this information becomes erroneous as soon as another version is released. 37 | label: Node version 38 | validations: 39 | required: true 40 | - type: input 41 | attributes: 42 | label: Operating system 43 | validations: 44 | required: true 45 | - type: checkboxes 46 | id: terms 47 | attributes: 48 | label: Checklist 49 | options: 50 | - label: I've read the [FAQ](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/docs/faq.md). 51 | required: true 52 | - label: I've read [instructions for logging issues](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/CONTRIBUTING.md#bug-reports). 53 | required: true 54 | - label: I'm not using `cypress-cucumber-preprocessor@4.3.1` (package name has changed and it is no longer the most recent version, see [#689](https://github.com/badeball/cypress-cucumber-preprocessor/issues/689)). 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/workflows/examples-master.yml: -------------------------------------------------------------------------------- 1 | name: Examples (master) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | find-examples: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | matrix: ${{ steps.set-matrix.outputs.matrix }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | - id: set-matrix 22 | name: Prepare 23 | run: echo "matrix=$(node -p "JSON.stringify(fs.readdirSync('examples').filter(f => f !== 'readme.md'))")" >> $GITHUB_OUTPUT 24 | 25 | example: 26 | needs: find-examples 27 | runs-on: ubuntu-latest 28 | container: 29 | image: cypress/base:latest 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | example: ${{fromJson(needs.find-examples.outputs.matrix)}} 34 | env: 35 | NPM_CONFIG_PACKAGE_LOCK: "false" 36 | steps: 37 | - uses: actions/setup-node@v4 38 | with: 39 | node-version: 18 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | - name: Cache NPM modules 43 | uses: actions/cache@v4 44 | with: 45 | path: ~/.npm 46 | key: npm@${{ matrix.example }} 47 | - name: Cache Cypress binaries 48 | uses: actions/cache@v4 49 | with: 50 | path: ~/.cache/Cypress 51 | key: cypress-examples 52 | # In lack of native support, https://github.com/actions/checkout/issues/172. 53 | - name: Make checkout sparse 54 | run: | 55 | shopt -s extglob 56 | rm -rf examples/!(${{ matrix.example }}) 57 | rm -rf !(examples) 58 | - name: Install NPM modules 59 | working-directory: examples/${{ matrix.example }} 60 | run: npm install --engine-strict 61 | - name: Run Cypress 62 | working-directory: examples/${{ matrix.example }} 63 | run: | 64 | if [[ "${{ matrix.example }}" == ct-* ]]; then 65 | npx cypress run --component 66 | else 67 | npx cypress run --e2e 68 | fi 69 | - name: Versions 70 | run: | 71 | npx cypress --version 72 | node --version 73 | npm --version 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | 3 | # Generated by genversion 4 | lib/version.ts 5 | 6 | # Temporary directory for test execution 7 | tmp/ 8 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec": "lib/**/*.test.ts", 3 | "require": "ts-node/register" 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jonas Amundsen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /augmentations.d.ts: -------------------------------------------------------------------------------- 1 | import messages from "@cucumber/messages"; 2 | 3 | import { Registry } from "./lib/registry"; 4 | 5 | import { MochaGlobals } from "mocha"; 6 | 7 | declare module "@cucumber/cucumber" { 8 | interface IWorld { 9 | tmpDir: string; 10 | verifiedLastRunError: boolean; 11 | lastRun: { 12 | stdout: string; 13 | stderr: string; 14 | output: string; 15 | exitCode: number; 16 | }; 17 | } 18 | } 19 | 20 | declare module "stream" { 21 | // This is as of this writing, not typed. 22 | function compose(...streams: Stream[]): Duplex; 23 | } 24 | 25 | declare global { 26 | namespace globalThis { 27 | var __cypress_cucumber_preprocessor_registry_dont_use_this: 28 | | Registry 29 | | undefined; 30 | 31 | var __cypress_cucumber_preprocessor_mocha_dont_use_this: 32 | | Pick 33 | | undefined; 34 | } 35 | 36 | interface Window { 37 | testState: { 38 | gherkinDocument: messages.GherkinDocument; 39 | pickles: messages.Pickle[]; 40 | pickle: messages.Pickle; 41 | pickleStep?: messages.PickleStep; 42 | }; 43 | } 44 | } 45 | 46 | export {}; 47 | -------------------------------------------------------------------------------- /cucumber.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "format": ["@cucumber/pretty-formatter"], 4 | "formatOptions": { 5 | "colorsEnabled": true 6 | }, 7 | "requireModule": ["ts-node/register"], 8 | "require": ["features/**/*.ts"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@cypress/browserify-preprocessor"; 2 | 3 | declare module "find-cypress-specs" { 4 | export function getSpecs( 5 | config: Cypress.ConfigOptions, 6 | type: Cypress.TestingType, 7 | returnAbsolute?: boolean, 8 | ): string[]; 9 | 10 | export function getSpecs( 11 | config: Cypress.PluginConfigOptions, 12 | type: Cypress.TestingType, 13 | returnAbsolute?: boolean, 14 | ): string[]; 15 | 16 | export function getConfig(): Cypress.ConfigOptions; 17 | } 18 | -------------------------------------------------------------------------------- /docs/dry-run.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Dry run 4 | 5 | Dry run is a run mode in which no steps or any type of hooks are executed. A few examples where this is useful: 6 | 7 | - Finding unused step definitions with [usage reports](usage-report.md) 8 | - Generating snippets for all undefined steps 9 | - Checking if your path, tag expression, etc. matches the scenarios you expect it to 10 | 11 | Dry run can be enabled using `dryRun`, like seen below. 12 | 13 | ``` 14 | $ cypress run --env dryRun=true 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/examples/handling-multiple-configurations/a-tests-pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/docs/examples/handling-multiple-configurations/a-tests-pass.png -------------------------------------------------------------------------------- /docs/examples/handling-multiple-configurations/b-tests-pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/docs/examples/handling-multiple-configurations/b-tests-pass.png -------------------------------------------------------------------------------- /docs/examples/handling-multiple-configurations/example-test-directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/docs/examples/handling-multiple-configurations/example-test-directory.png -------------------------------------------------------------------------------- /docs/html-formatter.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # HTML formatter 4 | 5 | HTML reports are [natively supported](html-report.md) through configuration. **This is sufficient for almost everyone and you should think twice before reading any further.** You do however have the option to manually convert messages into HTML reports using the following executable. The HTML formatter consumes messages from stdin and outputs its report to stdout, like shown below. 6 | 7 | ``` 8 | $ npx cucumber-html-formatter < cucumber-messages.ndjson 9 | 10 | ... 11 | ``` 12 | 13 | Alternatively you can redirect the output to a new file. 14 | 15 | ``` 16 | $ npx cucumber-html-formatter < cucumber-messages.ndjson > cucumber-report.html 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/html-report.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # HTML reports 4 | 5 | HTML reports are powered by [`@cucumber/html-formatter`](https://github.com/cucumber/html-formatter). They can be enabled using the `html.enabled` property. The preprocessor uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), which means you can place configuration options in EG. `.cypress-cucumber-preprocessorrc.json` or `package.json`. An example configuration is shown below. 6 | 7 | ```json 8 | { 9 | "html": { 10 | "enabled": true 11 | } 12 | } 13 | ``` 14 | 15 | The report is outputted to `cucumber-report.html` in the project directory, but can be configured through the `html.output` property. 16 | 17 | ## Screenshots 18 | 19 | Screenshots are automatically added to HTML reports, including that of failed tests (unless you have disabled `screenshotOnRunFailure`). 20 | 21 | ## Attachments 22 | 23 | Attachments can also be added to HTML reports through an API. This API is further explained in [JSON report](json-report.md), but applies to HTML reports as well. 24 | -------------------------------------------------------------------------------- /docs/json-formatter.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # JSON formatter 4 | 5 | JSON reports are [natively supported](json-report.md) through configuration. **This is sufficient for almost everyone and you should think twice before reading any further.** You do however have the option to manually convert messages into JSON reports using the following executable. The JSON formatter consumes messages from stdin and outputs its report to stdout, like shown below. 6 | 7 | ``` 8 | $ npx cucumber-json-formatter < cucumber-messages.ndjson 9 | { 10 | ... 11 | } 12 | ``` 13 | 14 | Alternatively you can redirect the output to a new file. 15 | 16 | ``` 17 | $ npx cucumber-json-formatter < cucumber-messages.ndjson > cucumber-report.json 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/localisation.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Localisation 4 | 5 | Cucumber keywords have been translated to multiple languages and can be used like shown below. See Cucumber's own [documentation](https://cucumber.io/docs/gherkin/languages/) for supported languages. 6 | 7 | ```gherkin 8 | # language: no 9 | Egenskap: en funksjonalitet 10 | Scenario: et scenario 11 | Gitt et steg 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/messages-report.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Messages reports 4 | 5 | "Messages" refers to [Cucumber Messages](https://github.com/cucumber/common/tree/main/messages) and is a low-level report / protocol for representing results and other information from Cucumber. JSON reports and HTML reports are derived from this report. Hence, messages report will implicitly be enabled if either of the mentioned reports are. 6 | 7 | Messages reports can be enabled using the `messages.enabled` property. The preprocessor uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), which means you can place configuration options in EG. `.cypress-cucumber-preprocessorrc.json` or `package.json`. An example configuration is shown below. 8 | 9 | ```json 10 | { 11 | "messages": { 12 | "enabled": true 13 | } 14 | } 15 | ``` 16 | 17 | The report is outputted to `cucumber-messages.ndjson` in the project directory, but can be configured through the `messages.output` property. 18 | 19 | ## Screenshots 20 | 21 | Screenshots are automatically added to messages reports, including that of failed tests (unless you have disabled `screenshotOnRunFailure`). 22 | 23 | ## Attachments 24 | 25 | Attachments can also be added to messages reports through an API. This API is further explained in [JSON report](json-report.md), but applies to messages reports as well. 26 | -------------------------------------------------------------------------------- /docs/mixing-types.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Mixing Cucumber and non-Cucumber specs 4 | 5 | Mixing Cucumber and non-Cucumber specs are supported. Cypress can be configured with `specPattern` to resolve multiple spec types as shown below. 6 | 7 | ```js 8 | export default defineConfig({ 9 | e2e: { 10 | specPattern: "**/*.{spec.js,feature}" 11 | }, 12 | }); 13 | ``` 14 | 15 | Cucumber hooks, IE. `Before` and `After` as imported from `@badeball/cypress-cucumber-preprocessor`, are *only* run in Cucumber-type specs. 16 | 17 | You can determine spec-type in Cypress' own hooks using `isFeature()`, as shown below. 18 | 19 | ```js 20 | import { isFeature } from "@badeball/cypress-cucumber-preprocessor"; 21 | 22 | beforeEach(() => { 23 | if (isFeature()) { 24 | // This is only run for Cucumber-type specs. 25 | } 26 | }) 27 | ``` 28 | 29 | You can also created conditions based on tags using `doesFeatureMatch(..)`, as shown below. 30 | 31 | ```js 32 | import { isFeature, doesFeatureMatch } from "@badeball/cypress-cucumber-preprocessor"; 33 | 34 | beforeEach(() => { 35 | if (isFeature() && doesFeatureMatch("@foobar")) { 36 | // This is only run for Cucumber-type specs tagged with @foobar. 37 | } 38 | }) 39 | ``` 40 | 41 | :warning: You can however **not** invoke any other member from `@badeball/cypress-cucumber-preprocessor` (such as `Before(..)` or `Given(..)`) within non-Cucumber specs. 42 | -------------------------------------------------------------------------------- /docs/pretty.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/docs/pretty.gif -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | * [Quick start](quick-start.md) 4 | * [Project examples](../examples/) 5 | * [Cucumber basics](cucumber-basics.md) 6 | * [State management](state-management.md) 7 | * [Step definitions](step-definitions.md) 8 | * [Tags](tags.md) 9 | * [Pretty output](pretty-output.md) 10 | * [Source maps](source-maps.md) 11 | * [Dry run](dry-run.md) 12 | * Reports 13 | * [Messages report](messages-report.md) 14 | * [JSON report](json-report.md) 15 | * [HTML report](html-report.md) 16 | * [Usage report](usage-report.md) 17 | * [Localisation](localisation.md) 18 | * [Configuration](configuration.md) 19 | * [Test configuration](test-configuration.md) 20 | * CLI utilities 21 | * [JSON formatter](json-formatter.md) 22 | * [HTML formatter](html-formatter.md) 23 | * [Parallelization using Cypress Cloud & merging reports](merging-reports.md) 24 | * [Mixing Cucumber and non-Cucumber specs](mixing-types.md) 25 | * [:warning: On event handlers](event-handlers.md) 26 | * [Frequently asked questions](faq.md) 27 | 28 | ## User-provided code examples 29 | 30 | * [Handling multiple Cypress configurations with distinct step definition directories](examples/handling-multiple-configurations.md) 31 | -------------------------------------------------------------------------------- /docs/source-maps.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Source maps 4 | 5 | How to enable source maps for each bundler is shown below. 6 | 7 | ## esbuild 8 | 9 | Source maps can be enabled using an optional argument to `createEsbuildPlugin()`, like seen below. 10 | The process of ensuring that source maps are not only enabled, but also "pretty", is somewhat 11 | cumbersome in lieu of [evanw/esbuild#2218](https://github.com/evanw/esbuild/issues/2218). Hence, 12 | this is disabled by default until it has been sufficiently tested. 13 | 14 | ```js 15 | const { defineConfig } = require("cypress"); 16 | const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); 17 | const { 18 | addCucumberPreprocessorPlugin, 19 | } = require("@badeball/cypress-cucumber-preprocessor"); 20 | const { 21 | createEsbuildPlugin, 22 | } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); 23 | 24 | async function setupNodeEvents(on, config) { 25 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 26 | await addCucumberPreprocessorPlugin(on, config); 27 | 28 | on( 29 | "file:preprocessor", 30 | createBundler({ 31 | plugins: [createEsbuildPlugin(config, { prettySourceMap: true })] 32 | }) 33 | ); 34 | 35 | // Make sure to return the config object as it might have been modified by the plugin. 36 | return config; 37 | } 38 | 39 | module.exports = defineConfig({ 40 | e2e: { 41 | baseUrl: "https://duckduckgo.com", 42 | specPattern: "**/*.feature", 43 | setupNodeEvents, 44 | }, 45 | }); 46 | ``` 47 | 48 | ## Webpack 49 | 50 | Source maps are enabled by default. 51 | 52 | ## Browserify 53 | 54 | Source maps are enabled by default. 55 | -------------------------------------------------------------------------------- /docs/test-configuration.md: -------------------------------------------------------------------------------- 1 | [← Back to documentation](readme.md) 2 | 3 | # Test configuration 4 | 5 | Some of Cypress' [configuration options](https://docs.cypress.io/guides/references/configuration) can be overridden per-test by leveraging tags. Below are all supported configuration options shown. 6 | 7 | ```gherkin 8 | @animationDistanceThreshold(5) 9 | @blockHosts('http://www.foo.com','http://www.bar.com') 10 | @defaultCommandTimeout(5) 11 | @env(foo='bar',baz=5,qux=false) 12 | @execTimeout(5) 13 | @includeShadowDom(true) 14 | @includeShadowDom(false) 15 | @keystrokeDelay(5) 16 | @numTestsKeptInMemory(5) 17 | @pageLoadTimeout(5) 18 | @redirectionLimit(5) 19 | @requestTimeout(5) 20 | @responseTimeout(5) 21 | @retries(5) 22 | @retries(runMode=5) 23 | @retries(openMode=5) 24 | @retries(runMode=5,openMode=10) 25 | @retries(openMode=10,runMode=5) 26 | @screenshotOnRunFailure(true) 27 | @screenshotOnRunFailure(false) 28 | @scrollBehavior('center') 29 | @scrollBehavior('top') 30 | @scrollBehavior('bottom') 31 | @scrollBehavior('nearest') 32 | @slowTestThreshold(5) 33 | @viewportHeight(720) 34 | @viewportWidth(1280) 35 | @waitForAnimations(true) 36 | @waitForAnimations(false) 37 | Feature: a feature 38 | Scenario: a scenario 39 | Given a table step 40 | ``` 41 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import js from "@eslint/js"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all, 14 | }); 15 | 16 | export default [ 17 | { 18 | ignores: [ 19 | "examples/**/*", 20 | "tmp/**/*", 21 | "**/*.js", 22 | "**/*.d.ts", 23 | // Can possibly unignored once TypeScript configs becomes supported? Ref. https://github.com/eslint/eslint/pull/18134. 24 | "eslint.config.mjs", 25 | ], 26 | }, 27 | ...compat.extends( 28 | "eslint:recommended", 29 | "plugin:@typescript-eslint/recommended", 30 | ), 31 | { 32 | plugins: { 33 | "@typescript-eslint": typescriptEslint, 34 | }, 35 | 36 | languageOptions: { 37 | parser: tsParser, 38 | ecmaVersion: 5, 39 | sourceType: "script", 40 | 41 | parserOptions: { 42 | project: ["tsconfig.eslint.json"], 43 | }, 44 | }, 45 | 46 | rules: { 47 | "@typescript-eslint/no-unused-vars": [ 48 | "warn", 49 | { 50 | argsIgnorePattern: "^_", 51 | varsIgnorePattern: "^_", 52 | caughtErrorsIgnorePattern: "^_", 53 | }, 54 | ], 55 | 56 | "@typescript-eslint/no-explicit-any": 0, 57 | "@typescript-eslint/no-floating-promises": "error", 58 | }, 59 | }, 60 | ]; 61 | -------------------------------------------------------------------------------- /examples/browserify-cjs/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | const browserify = require("@cypress/browserify-preprocessor"); 3 | const { 4 | addCucumberPreprocessorPlugin, 5 | } = require("@badeball/cypress-cucumber-preprocessor"); 6 | const { 7 | preprendTransformerToOptions, 8 | } = require("@badeball/cypress-cucumber-preprocessor/browserify"); 9 | 10 | async function setupNodeEvents(on, config) { 11 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 12 | await addCucumberPreprocessorPlugin(on, config); 13 | 14 | on( 15 | "file:preprocessor", 16 | browserify(preprendTransformerToOptions(config, browserify.defaultOptions)), 17 | ); 18 | 19 | // Make sure to return the config object as it might have been modified by the plugin. 20 | return config; 21 | } 22 | 23 | module.exports = defineConfig({ 24 | e2e: { 25 | baseUrl: "https://duckduckgo.com", 26 | specPattern: "**/*.feature", 27 | setupNodeEvents, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /examples/browserify-cjs/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/browserify-cjs/cypress/e2e/duckduckgo.js: -------------------------------------------------------------------------------- 1 | const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/browserify-cjs/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/browserify-cjs/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/browserify-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/browserify-preprocessor": "latest", 5 | "cypress": "latest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/browserify-esm/cypress.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import browserify from "@cypress/browserify-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | import { preprendTransformerToOptions } from "@badeball/cypress-cucumber-preprocessor/browserify"; 5 | 6 | export async function setupNodeEvents(on, config) { 7 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 8 | await addCucumberPreprocessorPlugin(on, config); 9 | 10 | on( 11 | "file:preprocessor", 12 | browserify(preprendTransformerToOptions(config, browserify.defaultOptions)), 13 | ); 14 | 15 | // Make sure to return the config object as it might have been modified by the plugin. 16 | return config; 17 | } 18 | 19 | export default defineConfig({ 20 | e2e: { 21 | baseUrl: "https://duckduckgo.com", 22 | specPattern: "**/*.feature", 23 | setupNodeEvents, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /examples/browserify-esm/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/browserify-esm/cypress/e2e/duckduckgo.mjs: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/browserify-esm/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/browserify-esm/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/browserify-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/browserify-preprocessor": "latest", 5 | "cypress": "latest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/browserify-ts/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import browserify from "@cypress/browserify-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | import { preprendTransformerToOptions } from "@badeball/cypress-cucumber-preprocessor/browserify"; 5 | 6 | async function setupNodeEvents( 7 | on: Cypress.PluginEvents, 8 | config: Cypress.PluginConfigOptions, 9 | ): Promise { 10 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 11 | await addCucumberPreprocessorPlugin(on, config); 12 | 13 | on( 14 | "file:preprocessor", 15 | browserify({ 16 | ...preprendTransformerToOptions(config, browserify.defaultOptions), 17 | typescript: require.resolve("typescript"), 18 | }), 19 | ); 20 | 21 | // Make sure to return the config object as it might have been modified by the plugin. 22 | return config; 23 | } 24 | 25 | export default defineConfig({ 26 | e2e: { 27 | baseUrl: "https://duckduckgo.com", 28 | specPattern: "**/*.feature", 29 | setupNodeEvents, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /examples/browserify-ts/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/browserify-ts/cypress/e2e/duckduckgo.ts: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/, 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/browserify-ts/cypress/support/e2e.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/browserify-ts/cypress/support/e2e.ts -------------------------------------------------------------------------------- /examples/browserify-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/browserify-preprocessor": "latest", 5 | "cypress": "latest", 6 | "typescript": "latest" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/browserify-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "nodenext" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import { devServer } from "@cypress/vite-dev-server"; 3 | import react from "@vitejs/plugin-react"; 4 | import { viteCommonjs } from "@originjs/vite-plugin-commonjs"; 5 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 6 | import { createRollupPlugin } from "@badeball/cypress-cucumber-preprocessor/rollup"; 7 | 8 | export default defineConfig({ 9 | component: { 10 | specPattern: "**/*.feature", 11 | devServer(devServerConfig) { 12 | return devServer({ 13 | ...devServerConfig, 14 | framework: "react", 15 | viteConfig: { 16 | plugins: [ 17 | react(), 18 | createRollupPlugin(devServerConfig.cypressConfig), 19 | viteCommonjs(), 20 | ], 21 | }, 22 | }); 23 | }, 24 | async setupNodeEvents(on, config) { 25 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more. 26 | await addCucumberPreprocessorPlugin(on, config); 27 | 28 | // Make sure to return the config object as it might have been modified by the plugin. 29 | return config; 30 | }, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/cypress/component/App.feature: -------------------------------------------------------------------------------- 1 | Feature: App 2 | Scenario: render 3 | Given I render the component 4 | Then I should see the text "Hello world!" 5 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/cypress/component/App.tsx: -------------------------------------------------------------------------------- 1 | import * as preprocessor from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | import { mount } from "cypress/react" 4 | 5 | import App from "../../src/App"; 6 | 7 | const { Given, Then } = preprocessor 8 | 9 | Given("I render the component", () => { 10 | mount(); 11 | }); 12 | 13 | Then("I should see the text {string}", (text: string) => { 14 | cy.contains(text).should("exist"); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/cypress/support/component.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/ct-vite-react-ts/cypress/support/component.ts -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "postinstall": "patch-package" 4 | }, 5 | "dependencies": { 6 | "@badeball/cypress-cucumber-preprocessor": "latest", 7 | "@cypress/vite-dev-server": "latest", 8 | "@originjs/vite-plugin-commonjs": "latest", 9 | "@vitejs/plugin-react": "latest", 10 | "cypress": "latest", 11 | "patch-package": "latest", 12 | "react": "latest", 13 | "react-dom": "latest", 14 | "typescript": "latest" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/patches/@cypress+vite-dev-server+5.1.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@cypress/vite-dev-server/client/initCypressTests.js b/node_modules/@cypress/vite-dev-server/client/initCypressTests.js 2 | index 0fbff85..0799f57 100644 3 | --- a/node_modules/@cypress/vite-dev-server/client/initCypressTests.js 4 | +++ b/node_modules/@cypress/vite-dev-server/client/initCypressTests.js 5 | @@ -51,7 +51,7 @@ if (supportFile) { 6 | // So we use the "@fs" bit to load the test file using its absolute path 7 | // Normalize path to not include a leading slash (different on Win32 vs Unix) 8 | const normalizedAbsolutePath = CypressInstance.spec.absolute.replace(/^\//, '') 9 | -const testFileAbsolutePathRoute = `${devServerPublicPathBase}/@fs/${normalizedAbsolutePath}` 10 | +const testFileAbsolutePathRoute = `${devServerPublicPathBase}/@fs/${normalizedAbsolutePath}?import` 11 | 12 | /* Spec file import logic */ 13 | // We need a slash before /src/my-spec.js, this does not happen by default. 14 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | export default function App () { 2 | return

Hello world!

3 | } 4 | -------------------------------------------------------------------------------- /examples/ct-vite-react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "nodenext", 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import * as Webpack from "webpack"; 3 | import { devServer } from "@cypress/webpack-dev-server"; 4 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 5 | 6 | const webpackConfig = ( 7 | cypressConfig: Cypress.PluginConfigOptions, 8 | ): Webpack.Configuration => { 9 | return { 10 | resolve: { 11 | extensions: [".js", ".ts", ".tsx"], 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.tsx?$/, 17 | exclude: [/node_modules/], 18 | use: [ 19 | { 20 | loader: "ts-loader", 21 | options: { transpileOnly: true }, 22 | }, 23 | ], 24 | }, 25 | { 26 | test: /\.feature$/, 27 | use: [ 28 | { 29 | loader: "@badeball/cypress-cucumber-preprocessor/webpack", 30 | options: cypressConfig, 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | }; 37 | }; 38 | 39 | export default defineConfig({ 40 | component: { 41 | specPattern: "**/*.feature", 42 | devServer(devServerConfig) { 43 | return devServer({ 44 | ...devServerConfig, 45 | framework: "react", 46 | webpackConfig: webpackConfig(devServerConfig.cypressConfig), 47 | }); 48 | }, 49 | async setupNodeEvents(on, config) { 50 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more. 51 | await addCucumberPreprocessorPlugin(on, config); 52 | 53 | // Make sure to return the config object as it might have been modified by the plugin. 54 | return config; 55 | }, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/cypress/component/App.feature: -------------------------------------------------------------------------------- 1 | Feature: App 2 | Scenario: render 3 | Given I render the component 4 | Then I should see the text "Hello world!" 5 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/cypress/component/App.tsx: -------------------------------------------------------------------------------- 1 | import { Given, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | import { mount } from "cypress/react" 4 | 5 | import App from "../../src/App"; 6 | 7 | Given("I render the component", () => { 8 | mount(); 9 | }); 10 | 11 | Then("I should see the text {string}", (text: string) => { 12 | cy.contains(text).should("exist"); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/cypress/support/component.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/ct-webpack-react-ts/cypress/support/component.ts -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/webpack-dev-server": "latest", 5 | "cypress": "latest", 6 | "react": "latest", 7 | "react-dom": "latest", 8 | "react-scripts": "latest", 9 | "ts-loader": "latest" 10 | }, 11 | "browserslist": { 12 | "production": [ 13 | ">0.2%", 14 | "not dead", 15 | "not op_mini all" 16 | ], 17 | "development": [ 18 | "last 1 chrome version", 19 | "last 1 firefox version", 20 | "last 1 safari version" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | export default function App () { 2 | return

Hello world!

3 | } 4 | -------------------------------------------------------------------------------- /examples/ct-webpack-react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "types": ["cypress"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/esbuild-cjs/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); 3 | const { 4 | addCucumberPreprocessorPlugin, 5 | } = require("@badeball/cypress-cucumber-preprocessor"); 6 | const { 7 | createEsbuildPlugin, 8 | } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); 9 | 10 | async function setupNodeEvents(on, config) { 11 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 12 | await addCucumberPreprocessorPlugin(on, config); 13 | 14 | on( 15 | "file:preprocessor", 16 | createBundler({ 17 | plugins: [createEsbuildPlugin(config)], 18 | }) 19 | ); 20 | 21 | // Make sure to return the config object as it might have been modified by the plugin. 22 | return config; 23 | } 24 | 25 | module.exports = defineConfig({ 26 | e2e: { 27 | baseUrl: "https://duckduckgo.com", 28 | specPattern: "**/*.feature", 29 | setupNodeEvents, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /examples/esbuild-cjs/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/esbuild-cjs/cypress/e2e/duckduckgo.js: -------------------------------------------------------------------------------- 1 | const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/esbuild-cjs/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/esbuild-cjs/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/esbuild-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@bahmutov/cypress-esbuild-preprocessor": "latest", 5 | "cypress": "latest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/esbuild-esm/cypress.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; 5 | 6 | export async function setupNodeEvents(on, config) { 7 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 8 | await addCucumberPreprocessorPlugin(on, config); 9 | 10 | on( 11 | "file:preprocessor", 12 | createBundler({ 13 | plugins: [createEsbuildPlugin(config)], 14 | }) 15 | ); 16 | 17 | // Make sure to return the config object as it might have been modified by the plugin. 18 | return config; 19 | } 20 | 21 | export default defineConfig({ 22 | e2e: { 23 | baseUrl: "https://duckduckgo.com", 24 | specPattern: "**/*.feature", 25 | setupNodeEvents, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/esbuild-esm/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/esbuild-esm/cypress/e2e/duckduckgo.mjs: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/esbuild-esm/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/esbuild-esm/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/esbuild-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@bahmutov/cypress-esbuild-preprocessor": "latest", 5 | "cypress": "latest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/esbuild-ts/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; 5 | 6 | async function setupNodeEvents( 7 | on: Cypress.PluginEvents, 8 | config: Cypress.PluginConfigOptions, 9 | ): Promise { 10 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 11 | await addCucumberPreprocessorPlugin(on, config); 12 | 13 | on( 14 | "file:preprocessor", 15 | createBundler({ 16 | plugins: [createEsbuildPlugin(config)], 17 | }), 18 | ); 19 | 20 | // Make sure to return the config object as it might have been modified by the plugin. 21 | return config; 22 | } 23 | 24 | export default defineConfig({ 25 | e2e: { 26 | baseUrl: "https://duckduckgo.com", 27 | specPattern: "**/*.feature", 28 | setupNodeEvents, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /examples/esbuild-ts/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/esbuild-ts/cypress/e2e/duckduckgo.ts: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/, 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/esbuild-ts/cypress/support/e2e.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/esbuild-ts/cypress/support/e2e.ts -------------------------------------------------------------------------------- /examples/esbuild-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@bahmutov/cypress-esbuild-preprocessor": "latest", 5 | "cypress": "latest", 6 | "typescript": "latest" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/esbuild-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "nodenext" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These are the *only* official examples at the time of writing. Any other example you come across is **not official** and might be severely outdated by now. 4 | 5 | ## Usage with other plugins 6 | 7 | If you're using the preprocessor _with other plugins_, please read [docs/event-handlers.md: On event handlers](event-handlers.md) **carefully**. 8 | 9 | ## E2E testing 10 | 11 | The examples illustrates using each bundler in each language flavor. 12 | 13 | | | CJS | ESM | TS | 14 | |------------|------------------------|------------------------|-----------------------| 15 | | Browserify | [Link](browserify-cjs) | [Link](browserify-esm) | [Link](browserify-ts) | 16 | | Esbuild | [Link](esbuild-cjs) | [Link](esbuild-esm) | [Link](esbuild-ts) | 17 | | Webpack | [Link](webpack-cjs) | [Link](webpack-esm) | [Link](webpack-ts) | 18 | 19 | ## Component testing 20 | 21 | Component testing works with both Webpack and Vite[^1] as a bundler. 22 | 23 | | | CJS | ESM | TS | 24 | |-----------------|------------------------|------------------------|-----------------------------| 25 | | React + Webpack | | | [Link](ct-webpack-react-ts) | 26 | | React + Vite | | | [Link](ct-vite-react-ts) | 27 | 28 | [patch-package]: https://github.com/ds300/patch-package 29 | [^1]: Using Vite requires patching `@cypress/vite-dev-server`, something which is easily achieved using [`patch-package`][patch-package] as the example illustrates. 30 | -------------------------------------------------------------------------------- /examples/type-module/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; 5 | 6 | async function setupNodeEvents( 7 | on: Cypress.PluginEvents, 8 | config: Cypress.PluginConfigOptions, 9 | ): Promise { 10 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 11 | await addCucumberPreprocessorPlugin(on, config); 12 | 13 | on( 14 | "file:preprocessor", 15 | createBundler({ 16 | plugins: [createEsbuildPlugin(config)], 17 | }), 18 | ); 19 | 20 | // Make sure to return the config object as it might have been modified by the plugin. 21 | return config; 22 | } 23 | 24 | export default defineConfig({ 25 | e2e: { 26 | baseUrl: "https://duckduckgo.com", 27 | specPattern: "**/*.feature", 28 | setupNodeEvents, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /examples/type-module/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/type-module/cypress/e2e/duckduckgo.ts: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/, 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/type-module/cypress/support/e2e.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/type-module/cypress/support/e2e.ts -------------------------------------------------------------------------------- /examples/type-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@badeball/cypress-cucumber-preprocessor": "latest", 5 | "@bahmutov/cypress-esbuild-preprocessor": "latest", 6 | "cypress": "latest", 7 | "typescript": "latest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/type-module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/webpack-cjs/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | const webpack = require("@cypress/webpack-preprocessor"); 3 | const { 4 | addCucumberPreprocessorPlugin, 5 | } = require("@badeball/cypress-cucumber-preprocessor"); 6 | 7 | async function setupNodeEvents(on, config) { 8 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 9 | await addCucumberPreprocessorPlugin(on, config); 10 | 11 | on( 12 | "file:preprocessor", 13 | webpack({ 14 | webpackOptions: { 15 | resolve: { 16 | extensions: [".ts", ".js"], 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.feature$/, 22 | use: [ 23 | { 24 | loader: "@badeball/cypress-cucumber-preprocessor/webpack", 25 | options: config, 26 | }, 27 | ], 28 | }, 29 | ], 30 | }, 31 | }, 32 | }) 33 | ); 34 | 35 | // Make sure to return the config object as it might have been modified by the plugin. 36 | return config; 37 | } 38 | 39 | module.exports = defineConfig({ 40 | e2e: { 41 | baseUrl: "https://duckduckgo.com", 42 | specPattern: "**/*.feature", 43 | setupNodeEvents, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /examples/webpack-cjs/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/webpack-cjs/cypress/e2e/duckduckgo.js: -------------------------------------------------------------------------------- 1 | const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/webpack-cjs/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/webpack-cjs/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/webpack-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/webpack-preprocessor": "latest", 5 | "cypress": "latest", 6 | "webpack": "latest" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/webpack-esm/cypress.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import webpack from "@cypress/webpack-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | 5 | export async function setupNodeEvents(on, config) { 6 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 7 | await addCucumberPreprocessorPlugin(on, config); 8 | 9 | on( 10 | "file:preprocessor", 11 | webpack({ 12 | webpackOptions: { 13 | resolve: { 14 | extensions: [".ts", ".js"], 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.feature$/, 20 | use: [ 21 | { 22 | loader: "@badeball/cypress-cucumber-preprocessor/webpack", 23 | options: config, 24 | }, 25 | ], 26 | }, 27 | ], 28 | }, 29 | }, 30 | }) 31 | ); 32 | 33 | // Make sure to return the config object as it might have been modified by the plugin. 34 | return config; 35 | } 36 | 37 | export default defineConfig({ 38 | e2e: { 39 | baseUrl: "https://duckduckgo.com", 40 | specPattern: "**/*.feature", 41 | setupNodeEvents, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /examples/webpack-esm/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/webpack-esm/cypress/e2e/duckduckgo.mjs: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/ 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/webpack-esm/cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/webpack-esm/cypress/support/e2e.js -------------------------------------------------------------------------------- /examples/webpack-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/webpack-preprocessor": "latest", 5 | "cypress": "latest", 6 | "webpack": "latest" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/webpack-ts/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import * as webpack from "@cypress/webpack-preprocessor"; 3 | import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; 4 | 5 | async function setupNodeEvents( 6 | on: Cypress.PluginEvents, 7 | config: Cypress.PluginConfigOptions, 8 | ): Promise { 9 | // This is required for the preprocessor to be able to generate JSON reports after each run, and more, 10 | await addCucumberPreprocessorPlugin(on, config); 11 | 12 | on( 13 | "file:preprocessor", 14 | webpack({ 15 | webpackOptions: { 16 | resolve: { 17 | extensions: [".ts", ".js"], 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.ts$/, 23 | exclude: [/node_modules/], 24 | use: [ 25 | { 26 | loader: "ts-loader", 27 | }, 28 | ], 29 | }, 30 | { 31 | test: /\.feature$/, 32 | use: [ 33 | { 34 | loader: "@badeball/cypress-cucumber-preprocessor/webpack", 35 | options: config, 36 | }, 37 | ], 38 | }, 39 | ], 40 | }, 41 | }, 42 | }), 43 | ); 44 | 45 | // Make sure to return the config object as it might have been modified by the plugin. 46 | return config; 47 | } 48 | 49 | export default defineConfig({ 50 | e2e: { 51 | baseUrl: "https://duckduckgo.com", 52 | specPattern: "**/*.feature", 53 | setupNodeEvents, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /examples/webpack-ts/cypress/e2e/duckduckgo.feature: -------------------------------------------------------------------------------- 1 | Feature: duckduckgo.com 2 | Scenario: visiting the frontpage 3 | When I visit duckduckgo.com 4 | Then I should see a search bar 5 | -------------------------------------------------------------------------------- /examples/webpack-ts/cypress/e2e/duckduckgo.ts: -------------------------------------------------------------------------------- 1 | import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; 2 | 3 | When("I visit duckduckgo.com", () => { 4 | cy.visit("https://duckduckgo.com/"); 5 | }); 6 | 7 | Then("I should see a search bar", () => { 8 | cy.get("input[type=text]") 9 | .should("have.attr", "placeholder") 10 | .and( 11 | "match", 12 | /Search the web without being tracked|Search without being tracked/, 13 | ); 14 | 15 | assert.deepEqual({}, {}); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/webpack-ts/cypress/support/e2e.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badeball/cypress-cucumber-preprocessor/751956b14559245c64f5525cbb2a51c987a61c7c/examples/webpack-ts/cypress/support/e2e.ts -------------------------------------------------------------------------------- /examples/webpack-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@badeball/cypress-cucumber-preprocessor": "latest", 4 | "@cypress/webpack-preprocessor": "latest", 5 | "cypress": "latest", 6 | "ts-loader": "latest", 7 | "typescript": "latest", 8 | "webpack": "latest" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/webpack-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /features/ambiguous_keywords.feature: -------------------------------------------------------------------------------- 1 | Feature: ambiguous keyword 2 | 3 | Scenario: wrongly keyworded step matching 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { When } = require("@badeball/cypress-cucumber-preprocessor"); 13 | When("a step", function() {}); 14 | """ 15 | When I run cypress 16 | Then it passes 17 | -------------------------------------------------------------------------------- /features/attachments_browser.feature: -------------------------------------------------------------------------------- 1 | Feature: attachments 2 | 3 | Background: 4 | Given additional preprocessor configuration 5 | """ 6 | { 7 | "json": { 8 | "enabled": true 9 | } 10 | } 11 | """ 12 | 13 | Rule: it should support a variety of options 14 | 15 | Scenario: string identity 16 | Given a file named "cypress/e2e/a.feature" with: 17 | """ 18 | Feature: a feature 19 | Scenario: a scenario 20 | Given a step 21 | """ 22 | And a file named "cypress/support/step_definitions/steps.js" with: 23 | """ 24 | const { Given, attach } = require("@badeball/cypress-cucumber-preprocessor"); 25 | Given("a step", function() { 26 | attach("foobar") 27 | }) 28 | """ 29 | When I run cypress 30 | Then it passes 31 | And there should be a JSON output similar to "fixtures/attachments/string.json" 32 | 33 | Scenario: array buffer 34 | Given a file named "cypress/e2e/a.feature" with: 35 | """ 36 | Feature: a feature 37 | Scenario: a scenario 38 | Given a step 39 | """ 40 | And a file named "cypress/support/step_definitions/steps.js" with: 41 | """ 42 | const { Given, attach } = require("@badeball/cypress-cucumber-preprocessor"); 43 | Given("a step", function() { 44 | attach(new TextEncoder().encode("foobar").buffer, "text/plain") 45 | }) 46 | """ 47 | When I run cypress 48 | Then it passes 49 | And there should be a JSON output similar to "fixtures/attachments/string.json" 50 | 51 | Scenario: string encoded 52 | Given a file named "cypress/e2e/a.feature" with: 53 | """ 54 | Feature: a feature 55 | Scenario: a scenario 56 | Given a step 57 | """ 58 | And a file named "cypress/support/step_definitions/steps.js" with: 59 | """ 60 | const { fromByteArray } = require("base64-js"); 61 | const { Given, attach } = require("@badeball/cypress-cucumber-preprocessor"); 62 | Given("a step", function() { 63 | attach(fromByteArray(new TextEncoder().encode("foobar")), "base64:text/plain") 64 | }) 65 | """ 66 | When I run cypress 67 | Then it passes 68 | And there should be a JSON output similar to "fixtures/attachments/string.json" 69 | -------------------------------------------------------------------------------- /features/browser_crash.feature: -------------------------------------------------------------------------------- 1 | @cypress>=12 2 | Feature: browser crash 3 | 4 | # Crash-behavior is a mess, ref. https://github.com/cypress-io/cypress/issues/22631. 5 | # Pre-12.17.0, enabling video recording actually made a difference, ref. https://github.com/cypress-io/cypress/pull/27167. 6 | # Post-12.17.0 behavior seems to be consistent enough that it's testable. 7 | 8 | Background: 9 | Given additional preprocessor configuration 10 | """ 11 | { 12 | "json": { 13 | "enabled": true 14 | } 15 | } 16 | """ 17 | 18 | Rule: report generation should fail gracefully in the event of a browser crash 19 | 20 | Scenario: Chromium process crash 21 | Given a file named "cypress/e2e/a.feature" with: 22 | """ 23 | Feature: a feature 24 | Scenario: a scenario 25 | Given a step 26 | """ 27 | And a file named "cypress/support/step_definitions/steps.js" with: 28 | """ 29 | const { Given, attach } = require("@badeball/cypress-cucumber-preprocessor"); 30 | Given("a step", function() { 31 | new Cypress.Promise(() => { 32 | Cypress.automation("remote:debugger:protocol", { 33 | command: "Browser.crash", 34 | }); 35 | }); 36 | }); 37 | """ 38 | When I run cypress with a chromium-family browser 39 | Then it fails 40 | And the output should contain 41 | """ 42 | Due to browser crash, no reports are created for cypress/e2e/a.feature. 43 | """ 44 | And the JSON report shouldn't contain any specs 45 | 46 | Scenario: Renderer process crash 47 | Given a file named "cypress/e2e/a.feature" with: 48 | """ 49 | Feature: a feature 50 | Scenario: a scenario 51 | Given a step 52 | """ 53 | And a file named "cypress/support/step_definitions/steps.js" with: 54 | """ 55 | const { Given, attach } = require("@badeball/cypress-cucumber-preprocessor"); 56 | Given("a step", function() { 57 | new Cypress.Promise(() => { 58 | Cypress.automation("remote:debugger:protocol", { 59 | command: "Page.crash", 60 | }); 61 | }); 62 | }); 63 | """ 64 | When I run cypress with a chromium-family browser 65 | Then it fails 66 | And the output should contain 67 | """ 68 | Due to browser crash, no reports are created for cypress/e2e/a.feature. 69 | """ 70 | And the JSON report shouldn't contain any specs 71 | -------------------------------------------------------------------------------- /features/configuration_overrides.feature: -------------------------------------------------------------------------------- 1 | Feature: configuration overrides 2 | Scenario: overriding stepDefinitions through -e 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | Feature: a feature name 6 | Scenario: a scenario name 7 | Given a step 8 | """ 9 | And a file named "foobar.js" with: 10 | """ 11 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 12 | Given("a step", function() {}); 13 | """ 14 | When I run cypress with "-e stepDefinitions=foobar.js" 15 | Then it passes 16 | 17 | Scenario: overriding stepDefinitions through environment variables 18 | Given a file named "cypress/e2e/a.feature" with: 19 | """ 20 | Feature: a feature name 21 | Scenario: a scenario name 22 | Given a step 23 | """ 24 | And a file named "foobar.js" with: 25 | """ 26 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 27 | Given("a step", function() {}); 28 | """ 29 | When I run cypress with environment variables 30 | | Key | Value | 31 | | CYPRESS_stepDefinitions | foobar.js | 32 | Then it passes 33 | -------------------------------------------------------------------------------- /features/custom_parameter_type.feature: -------------------------------------------------------------------------------- 1 | Feature: custom parameter type 2 | Scenario: definition after usage 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | Feature: a feature 6 | Scenario: a scenario 7 | Given a step in blue 8 | """ 9 | And a file named "cypress/support/step_definitions/steps.js" with: 10 | """ 11 | const { Given, defineParameterType } = require("@badeball/cypress-cucumber-preprocessor"); 12 | Given("a step in {color}", function(color) {}); 13 | defineParameterType({ 14 | name: "color", 15 | regexp: /red|yellow|blue/, 16 | transformer(color) { 17 | return color; 18 | } 19 | }); 20 | """ 21 | When I run cypress 22 | Then it passes 23 | -------------------------------------------------------------------------------- /features/doc_string.feature: -------------------------------------------------------------------------------- 1 | Feature: doc string 2 | 3 | Scenario: as only step definition argument 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature 7 | Scenario: a scenario 8 | Given a doc string step 9 | \"\"\" 10 | The cucumber (Cucumis sativus) is a widely cultivated plant in the gourd family Cucurbitaceae. 11 | \"\"\" 12 | """ 13 | And a file named "cypress/support/step_definitions/steps.js" with: 14 | """ 15 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 16 | Given("a doc string step", function(docString) { 17 | expect(docString).to.equal("The cucumber (Cucumis sativus) is a widely " + 18 | "cultivated plant in the gourd family Cucurbitaceae.") 19 | }) 20 | """ 21 | When I run cypress 22 | Then it passes 23 | 24 | Scenario: with other step definition arguments 25 | Given a file named "cypress/e2e/a.feature" with: 26 | """ 27 | Feature: a feature 28 | Scenario: a scenario 29 | Given a "doc string" step 30 | \"\"\" 31 | The cucumber (Cucumis sativus) is a widely cultivated plant in the gourd family Cucurbitaceae. 32 | \"\"\" 33 | """ 34 | And a file named "cypress/support/step_definitions/steps.js" with: 35 | """ 36 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 37 | Given("a {string} step", function(type, docString) { 38 | expect(type).to.equal("doc string") 39 | expect(docString).to.equal("The cucumber (Cucumis sativus) is a widely " + 40 | "cultivated plant in the gourd family Cucurbitaceae.") 41 | }) 42 | """ 43 | When I run cypress 44 | Then it passes 45 | -------------------------------------------------------------------------------- /features/fixtures/another-passed-example.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: another feature\n Scenario: another scenario\n Given a step","uri":"cypress/e2e/b.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"another feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"another scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/b.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/b.feature","astNodeIds":["id"],"tags":[],"name":"another scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]}]}} 8 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 9 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"PASSED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 11 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 12 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 13 | -------------------------------------------------------------------------------- /features/fixtures/attachments/screenshot.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | }, 24 | "embeddings": [ 25 | { 26 | "data": "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAM0lEQVR4Aa3BAQEAAAiDMKR/51uC7QYjJDGJSUxiEpOYxCQmMYlJTGISk5jEJCYxiUnsARwEAibDACoRAAAAAElFTkSuQmCC", 27 | "mime_type": "image/png" 28 | } 29 | ] 30 | } 31 | ], 32 | "tags": [], 33 | "type": "scenario" 34 | } 35 | ], 36 | "id": "a-feature", 37 | "line": 1, 38 | "keyword": "Feature", 39 | "name": "a feature", 40 | "tags": [], 41 | "uri": "cypress/e2e/a.feature" 42 | } 43 | ] 44 | -------------------------------------------------------------------------------- /features/fixtures/attachments/screenshot.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]}]}} 8 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 9 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 10 | {"attachment":{"testCaseStartedId":"id","testStepId":"id","body":"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAM0lEQVR4Aa3BAQEAAAiDMKR/51uC7QYjJDGJSUxiEpOYxCQmMYlJTGISk5jEJCYxiUnsARwEAibDACoRAAAAAElFTkSuQmCC","mediaType":"image/png","contentEncoding":"BASE64"}} 11 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"PASSED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 12 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 13 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 14 | -------------------------------------------------------------------------------- /features/fixtures/attachments/string.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | }, 24 | "embeddings": [ 25 | { 26 | "data": "Zm9vYmFy", 27 | "mime_type": "text/plain" 28 | } 29 | ] 30 | } 31 | ], 32 | "tags": [], 33 | "type": "scenario" 34 | } 35 | ], 36 | "id": "a-feature", 37 | "line": 1, 38 | "keyword": "Feature", 39 | "name": "a feature", 40 | "tags": [], 41 | "uri": "cypress/e2e/a.feature" 42 | } 43 | ] 44 | -------------------------------------------------------------------------------- /features/fixtures/experimental-source-map.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature-name;a-scenario-name", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario name", 11 | "steps": [ 12 | { 13 | "keyword": "Before", 14 | "hidden": true, 15 | "result": { 16 | "status": "passed", 17 | "duration": 0 18 | } 19 | }, 20 | { 21 | "arguments": [], 22 | "keyword": "Given ", 23 | "line": 3, 24 | "name": "a step", 25 | "match": { 26 | "location": "cypress/support/step_definitions/steps.js:4" 27 | }, 28 | "result": { 29 | "status": "passed", 30 | "duration": 0 31 | } 32 | }, 33 | { 34 | "keyword": "After", 35 | "hidden": true, 36 | "result": { 37 | "status": "passed", 38 | "duration": 0 39 | } 40 | } 41 | ], 42 | "tags": [], 43 | "type": "scenario" 44 | } 45 | ], 46 | "id": "a-feature-name", 47 | "line": 1, 48 | "keyword": "Feature", 49 | "name": "a feature name", 50 | "tags": [], 51 | "uri": "cypress/e2e/a.feature" 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /features/fixtures/failing-after.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "keyword": "After", 27 | "hidden": true, 28 | "result": { 29 | "status": "failed", 30 | "duration": 0, 31 | "error_message": "some error" 32 | } 33 | } 34 | ], 35 | "tags": [], 36 | "type": "scenario" 37 | } 38 | ], 39 | "id": "a-feature", 40 | "line": 1, 41 | "keyword": "Feature", 42 | "name": "a feature", 43 | "tags": [], 44 | "uri": "cypress/e2e/a.feature" 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /features/fixtures/failing-after.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"hook":{"id":"id","sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 8 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]},{"id":"id","hookId":"id"}]}} 9 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 11 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"PASSED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 12 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 13 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"FAILED","message":"some error","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 14 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 15 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 16 | -------------------------------------------------------------------------------- /features/fixtures/failing-before.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "keyword": "Before", 14 | "hidden": true, 15 | "result": { 16 | "status": "failed", 17 | "duration": 0, 18 | "error_message": "some error" 19 | } 20 | }, 21 | { 22 | "arguments": [], 23 | "keyword": "Given ", 24 | "line": 3, 25 | "name": "a step", 26 | "match": { 27 | "location": "not available:0" 28 | }, 29 | "result": { 30 | "status": "skipped", 31 | "duration": 0 32 | } 33 | } 34 | ], 35 | "tags": [], 36 | "type": "scenario" 37 | } 38 | ], 39 | "id": "a-feature", 40 | "line": 1, 41 | "keyword": "Feature", 42 | "name": "a feature", 43 | "tags": [], 44 | "uri": "cypress/e2e/a.feature" 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /features/fixtures/failing-before.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"hook":{"id":"id","sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 8 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","hookId":"id"},{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]}]}} 9 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 11 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"FAILED","message":"some error","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 12 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 13 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"SKIPPED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 14 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 15 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 16 | -------------------------------------------------------------------------------- /features/fixtures/failing-step.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a preceding step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "arguments": [], 27 | "keyword": "And ", 28 | "line": 4, 29 | "name": "a failing step", 30 | "match": { 31 | "location": "not available:0" 32 | }, 33 | "result": { 34 | "status": "failed", 35 | "duration": 0, 36 | "error_message": "some error" 37 | } 38 | }, 39 | { 40 | "arguments": [], 41 | "keyword": "And ", 42 | "line": 5, 43 | "name": "a succeeding step", 44 | "match": { 45 | "location": "not available:0" 46 | }, 47 | "result": { 48 | "status": "skipped", 49 | "duration": 0 50 | } 51 | } 52 | ], 53 | "tags": [], 54 | "type": "scenario" 55 | } 56 | ], 57 | "id": "a-feature", 58 | "line": 1, 59 | "keyword": "Feature", 60 | "name": "a feature", 61 | "tags": [], 62 | "uri": "cypress/e2e/a.feature" 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /features/fixtures/multiple-features.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | } 29 | ], 30 | "id": "a-feature", 31 | "line": 1, 32 | "keyword": "Feature", 33 | "name": "a feature", 34 | "tags": [], 35 | "uri": "cypress/e2e/a.feature" 36 | }, 37 | { 38 | "description": "", 39 | "elements": [ 40 | { 41 | "description": "", 42 | "id": "another-feature;another-scenario", 43 | "keyword": "Scenario", 44 | "line": 2, 45 | "name": "another scenario", 46 | "steps": [ 47 | { 48 | "arguments": [], 49 | "keyword": "Given ", 50 | "line": 3, 51 | "name": "a step", 52 | "match": { 53 | "location": "not available:0" 54 | }, 55 | "result": { 56 | "status": "passed", 57 | "duration": 0 58 | } 59 | } 60 | ], 61 | "tags": [], 62 | "type": "scenario" 63 | } 64 | ], 65 | "id": "another-feature", 66 | "line": 1, 67 | "keyword": "Feature", 68 | "name": "another feature", 69 | "tags": [], 70 | "uri": "cypress/e2e/b.feature" 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /features/fixtures/multiple-scenarios-reloaded.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 3, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 4, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [ 27 | { 28 | "name": "@env(origin=\"https://duckduckgo.com/\")", 29 | "line": 2 30 | } 31 | ], 32 | "type": "scenario" 33 | }, 34 | { 35 | "description": "", 36 | "id": "a-feature;another-scenario", 37 | "keyword": "Scenario", 38 | "line": 7, 39 | "name": "another scenario", 40 | "steps": [ 41 | { 42 | "arguments": [], 43 | "keyword": "Given ", 44 | "line": 8, 45 | "name": "another step", 46 | "match": { 47 | "location": "not available:0" 48 | }, 49 | "result": { 50 | "status": "passed", 51 | "duration": 0 52 | } 53 | } 54 | ], 55 | "tags": [ 56 | { 57 | "name": "@env(origin=\"https://google.com/\")", 58 | "line": 6 59 | } 60 | ], 61 | "type": "scenario" 62 | } 63 | ], 64 | "id": "a-feature", 65 | "line": 1, 66 | "keyword": "Feature", 67 | "name": "a feature", 68 | "tags": [], 69 | "uri": "cypress/e2e/a.feature" 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /features/fixtures/parameterized-scenario-name.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;foo", 8 | "keyword": "Scenario Outline", 9 | "line": 7, 10 | "name": "foo", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | } 29 | ], 30 | "id": "a-feature", 31 | "line": 1, 32 | "keyword": "Feature", 33 | "name": "a feature", 34 | "tags": [], 35 | "uri": "cypress/e2e/a.feature" 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /features/fixtures/passed-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | } 29 | ], 30 | "id": "a-feature", 31 | "line": 1, 32 | "keyword": "Feature", 33 | "name": "a feature", 34 | "tags": [], 35 | "uri": "cypress/e2e/a.feature" 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /features/fixtures/passed-example.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]}]}} 8 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 9 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"PASSED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 11 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 12 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 13 | -------------------------------------------------------------------------------- /features/fixtures/passed-outline.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario Outline", 9 | "line": 6, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | }, 29 | { 30 | "description": "", 31 | "id": "a-feature;a-scenario", 32 | "keyword": "Scenario Outline", 33 | "line": 7, 34 | "name": "a scenario", 35 | "steps": [ 36 | { 37 | "arguments": [], 38 | "keyword": "Given ", 39 | "line": 3, 40 | "name": "a step", 41 | "match": { 42 | "location": "not available:0" 43 | }, 44 | "result": { 45 | "status": "passed", 46 | "duration": 0 47 | } 48 | } 49 | ], 50 | "tags": [], 51 | "type": "scenario" 52 | } 53 | ], 54 | "id": "a-feature", 55 | "line": 1, 56 | "keyword": "Feature", 57 | "name": "a feature", 58 | "tags": [], 59 | "uri": "cypress/e2e/a.feature" 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /features/fixtures/pending-steps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a preceding step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "arguments": [], 27 | "keyword": "And ", 28 | "line": 4, 29 | "name": "a pending step", 30 | "match": { 31 | "location": "not available:0" 32 | }, 33 | "result": { 34 | "status": "pending", 35 | "duration": 0 36 | } 37 | }, 38 | { 39 | "arguments": [], 40 | "keyword": "And ", 41 | "line": 5, 42 | "name": "a succeeding step", 43 | "match": { 44 | "location": "not available:0" 45 | }, 46 | "result": { 47 | "status": "skipped", 48 | "duration": 0 49 | } 50 | } 51 | ], 52 | "tags": [], 53 | "type": "scenario" 54 | } 55 | ], 56 | "id": "a-feature", 57 | "line": 1, 58 | "keyword": "Feature", 59 | "name": "a feature", 60 | "tags": [], 61 | "uri": "cypress/e2e/a.feature" 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /features/fixtures/rescued-error.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a failing step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "unknown", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "arguments": [], 27 | "keyword": "And ", 28 | "line": 4, 29 | "name": "an unimplemented step", 30 | "result": { 31 | "status": "unknown", 32 | "duration": 0 33 | } 34 | } 35 | ], 36 | "tags": [], 37 | "type": "scenario" 38 | } 39 | ], 40 | "id": "a-feature", 41 | "line": 1, 42 | "keyword": "Feature", 43 | "name": "a feature", 44 | "tags": [], 45 | "uri": "cypress/e2e/a.feature" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /features/fixtures/rescued-error.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a failing step\n And an unimplemented step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a failing step"},{"id":"id","location":{"line":4,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an unimplemented step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a failing step","type":"Context","astNodeIds":["id"]},{"id":"id","text":"an unimplemented step","type":"Context","astNodeIds":["id"]}]}} 6 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a failing step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]},{"id":"id","pickleStepId":"id","stepDefinitionIds":[]}]}} 8 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 9 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 11 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"UNKNOWN","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 12 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 13 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"UNKNOWN","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 14 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 15 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 16 | -------------------------------------------------------------------------------- /features/fixtures/retried.ndjson: -------------------------------------------------------------------------------- 1 | {"meta":"meta"} 2 | {"testRunStarted":{"timestamp":{"seconds":0,"nanos":0}}} 3 | {"source":{"data":"Feature: a feature\n Scenario: a scenario\n Given a step","uri":"cypress/e2e/a.feature","mediaType":"text/x.cucumber.gherkin+plain"}} 4 | {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","description":"","children":[{"scenario":{"id":"id","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"id","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"cypress/e2e/a.feature"}} 5 | {"pickle":{"id":"id","uri":"cypress/e2e/a.feature","astNodeIds":["id"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"id","text":"a step","type":"Context","astNodeIds":["id"]}]}} 6 | {"stepDefinition":{"id":"id","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"not available","location":{"line":0,"column":0}}}} 7 | {"testCase":{"id":"id","pickleId":"id","testSteps":[{"id":"id","pickleStepId":"id","stepDefinitionIds":["id"]}]}} 8 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":0,"timestamp":{"seconds":0,"nanos":0}}} 9 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 10 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"FAILED","message":"some error","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 11 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":true}} 12 | {"testCaseStarted":{"id":"id","testCaseId":"id","attempt":1,"timestamp":{"seconds":0,"nanos":0}}} 13 | {"testStepStarted":{"testStepId":"id","testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0}}} 14 | {"testStepFinished":{"testStepId":"id","testCaseStartedId":"id","testStepResult":{"status":"PASSED","duration":0},"timestamp":{"seconds":0,"nanos":0}}} 15 | {"testCaseFinished":{"testCaseStartedId":"id","timestamp":{"seconds":0,"nanos":0},"willBeRetried":false}} 16 | {"testRunFinished":{"timestamp":{"seconds":0,"nanos":0}}} 17 | -------------------------------------------------------------------------------- /features/fixtures/skipped-first-scenario.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;first-scenario", 8 | "keyword": "Scenario", 9 | "line": 3, 10 | "name": "first scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 4, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "skipped", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [ 27 | { 28 | "name": "@skip", 29 | "line": 2 30 | } 31 | ], 32 | "type": "scenario" 33 | }, 34 | { 35 | "description": "", 36 | "id": "a-feature;second-scenario", 37 | "keyword": "Scenario", 38 | "line": 5, 39 | "name": "second scenario", 40 | "steps": [ 41 | { 42 | "arguments": [], 43 | "keyword": "Given ", 44 | "line": 6, 45 | "name": "a step", 46 | "match": { 47 | "location": "not available:0" 48 | }, 49 | "result": { 50 | "status": "passed", 51 | "duration": 0 52 | } 53 | } 54 | ], 55 | "tags": [], 56 | "type": "scenario" 57 | }, 58 | { 59 | "description": "", 60 | "id": "a-feature;third-scenario", 61 | "keyword": "Scenario", 62 | "line": 7, 63 | "name": "third scenario", 64 | "steps": [ 65 | { 66 | "arguments": [], 67 | "keyword": "Given ", 68 | "line": 8, 69 | "name": "a step", 70 | "match": { 71 | "location": "not available:0" 72 | }, 73 | "result": { 74 | "status": "passed", 75 | "duration": 0 76 | } 77 | } 78 | ], 79 | "tags": [], 80 | "type": "scenario" 81 | } 82 | ], 83 | "id": "a-feature", 84 | "line": 1, 85 | "keyword": "Feature", 86 | "name": "a feature", 87 | "tags": [], 88 | "uri": "cypress/e2e/a.feature" 89 | } 90 | ] 91 | -------------------------------------------------------------------------------- /features/fixtures/skipped-second-scenario.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;first-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "first scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | }, 29 | { 30 | "description": "", 31 | "id": "a-feature;second-scenario", 32 | "keyword": "Scenario", 33 | "line": 5, 34 | "name": "second scenario", 35 | "steps": [ 36 | { 37 | "arguments": [], 38 | "keyword": "Given ", 39 | "line": 6, 40 | "name": "a step", 41 | "match": { 42 | "location": "not available:0" 43 | }, 44 | "result": { 45 | "status": "skipped", 46 | "duration": 0 47 | } 48 | } 49 | ], 50 | "tags": [ 51 | { 52 | "name": "@skip", 53 | "line": 4 54 | } 55 | ], 56 | "type": "scenario" 57 | }, 58 | { 59 | "description": "", 60 | "id": "a-feature;third-scenario", 61 | "keyword": "Scenario", 62 | "line": 7, 63 | "name": "third scenario", 64 | "steps": [ 65 | { 66 | "arguments": [], 67 | "keyword": "Given ", 68 | "line": 8, 69 | "name": "a step", 70 | "match": { 71 | "location": "not available:0" 72 | }, 73 | "result": { 74 | "status": "passed", 75 | "duration": 0 76 | } 77 | } 78 | ], 79 | "tags": [], 80 | "type": "scenario" 81 | } 82 | ], 83 | "id": "a-feature", 84 | "line": 1, 85 | "keyword": "Feature", 86 | "name": "a feature", 87 | "tags": [], 88 | "uri": "cypress/e2e/a.feature" 89 | } 90 | ] 91 | -------------------------------------------------------------------------------- /features/fixtures/skipped-steps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a preceding step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "arguments": [], 27 | "keyword": "And ", 28 | "line": 4, 29 | "name": "a skipped step", 30 | "match": { 31 | "location": "not available:0" 32 | }, 33 | "result": { 34 | "status": "skipped", 35 | "duration": 0 36 | } 37 | }, 38 | { 39 | "arguments": [], 40 | "keyword": "And ", 41 | "line": 5, 42 | "name": "a succeeding step", 43 | "match": { 44 | "location": "not available:0" 45 | }, 46 | "result": { 47 | "status": "skipped", 48 | "duration": 0 49 | } 50 | } 51 | ], 52 | "tags": [], 53 | "type": "scenario" 54 | } 55 | ], 56 | "id": "a-feature", 57 | "line": 1, 58 | "keyword": "Feature", 59 | "name": "a feature", 60 | "tags": [], 61 | "uri": "cypress/e2e/a.feature" 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /features/fixtures/skipped-third-scenario.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;first-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "first scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | } 25 | ], 26 | "tags": [], 27 | "type": "scenario" 28 | }, 29 | { 30 | "description": "", 31 | "id": "a-feature;second-scenario", 32 | "keyword": "Scenario", 33 | "line": 4, 34 | "name": "second scenario", 35 | "steps": [ 36 | { 37 | "arguments": [], 38 | "keyword": "Given ", 39 | "line": 5, 40 | "name": "a step", 41 | "match": { 42 | "location": "not available:0" 43 | }, 44 | "result": { 45 | "status": "passed", 46 | "duration": 0 47 | } 48 | } 49 | ], 50 | "tags": [], 51 | "type": "scenario" 52 | }, 53 | { 54 | "description": "", 55 | "id": "a-feature;third-scenario", 56 | "keyword": "Scenario", 57 | "line": 7, 58 | "name": "third scenario", 59 | "steps": [ 60 | { 61 | "arguments": [], 62 | "keyword": "Given ", 63 | "line": 8, 64 | "name": "a step", 65 | "match": { 66 | "location": "not available:0" 67 | }, 68 | "result": { 69 | "status": "skipped", 70 | "duration": 0 71 | } 72 | } 73 | ], 74 | "tags": [ 75 | { 76 | "name": "@skip", 77 | "line": 6 78 | } 79 | ], 80 | "type": "scenario" 81 | } 82 | ], 83 | "id": "a-feature", 84 | "line": 1, 85 | "keyword": "Feature", 86 | "name": "a feature", 87 | "tags": [], 88 | "uri": "cypress/e2e/a.feature" 89 | } 90 | ] 91 | -------------------------------------------------------------------------------- /features/fixtures/undefined-steps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "", 4 | "elements": [ 5 | { 6 | "description": "", 7 | "id": "a-feature;a-scenario", 8 | "keyword": "Scenario", 9 | "line": 2, 10 | "name": "a scenario", 11 | "steps": [ 12 | { 13 | "arguments": [], 14 | "keyword": "Given ", 15 | "line": 3, 16 | "name": "a preceding step", 17 | "match": { 18 | "location": "not available:0" 19 | }, 20 | "result": { 21 | "status": "passed", 22 | "duration": 0 23 | } 24 | }, 25 | { 26 | "arguments": [], 27 | "keyword": "And ", 28 | "line": 4, 29 | "name": "an undefined step", 30 | "result": { 31 | "status": "undefined", 32 | "duration": 0 33 | } 34 | }, 35 | { 36 | "arguments": [], 37 | "keyword": "And ", 38 | "line": 5, 39 | "name": "a succeeding step", 40 | "match": { 41 | "location": "not available:0" 42 | }, 43 | "result": { 44 | "status": "skipped", 45 | "duration": 0 46 | } 47 | } 48 | ], 49 | "tags": [], 50 | "type": "scenario" 51 | } 52 | ], 53 | "id": "a-feature", 54 | "line": 1, 55 | "keyword": "Feature", 56 | "name": "a feature", 57 | "tags": [], 58 | "uri": "cypress/e2e/a.feature" 59 | } 60 | ] 61 | -------------------------------------------------------------------------------- /features/issues/1017.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1017 2 | 3 | @network 4 | Feature: JSON report 5 | Scenario: with after hook and reload-behavior 6 | Given additional preprocessor configuration 7 | """ 8 | { 9 | "json": { 10 | "enabled": true 11 | } 12 | } 13 | """ 14 | And a file named "cypress/e2e/a.feature" with: 15 | """ 16 | Feature: a feature 17 | Scenario: a scenario 18 | When I navigate to "https://duckduckgo.com/" 19 | """ 20 | And a file named "cypress/support/step_definitions/steps.js" with: 21 | """ 22 | const { When, After } = require("@badeball/cypress-cucumber-preprocessor"); 23 | When("I navigate to {string}", function(url) { 24 | cy.visit(url) 25 | }) 26 | After(() => {}) 27 | """ 28 | When I run cypress 29 | Then it passes 30 | -------------------------------------------------------------------------------- /features/issues/1028.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1028 2 | 3 | @network 4 | Feature: non-feature specs 5 | Scenario: with messages enabled + reload-behavior 6 | Given additional preprocessor configuration 7 | """ 8 | { 9 | "messages": { 10 | "enabled": true 11 | } 12 | } 13 | """ 14 | And additional Cypress configuration 15 | """ 16 | { 17 | "e2e": { 18 | "specPattern": "**/spec.js" 19 | } 20 | } 21 | """ 22 | And a file named "cypress/e2e/spec.js" with: 23 | """ 24 | it("should work", () => { 25 | cy.visit("https://duckduckgo.com/") 26 | }) 27 | """ 28 | When I run cypress 29 | Then it passes 30 | -------------------------------------------------------------------------------- /features/issues/1034.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1034 2 | 3 | Feature: HTML report 4 | Scenario: one failed, one passed 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "html": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a step 18 | Scenario: another scenario 19 | Given a step 20 | """ 21 | And a file named "cypress/support/step_definitions/steps.js" with: 22 | """ 23 | const { After, Given } = require("@badeball/cypress-cucumber-preprocessor"); 24 | let i = 0; 25 | Given("a step", function() { 26 | if (i++ === 0) { 27 | throw "some error"; 28 | } 29 | }); 30 | After(function() {}); 31 | """ 32 | When I run cypress 33 | Then it fails 34 | And the HTML should display 50% passed scenarios 35 | -------------------------------------------------------------------------------- /features/issues/1041.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1041 2 | 3 | Feature: HTML report 4 | Scenario: skipped test 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "html": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | @skip 17 | Scenario: a scenario 18 | Given a step 19 | """ 20 | And a file named "cypress/support/step_definitions/steps.js" with: 21 | """ 22 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 23 | Given("a step", function() {}); 24 | """ 25 | When I run cypress 26 | Then it passes 27 | And the HTML report should display 1 "skipped" step 28 | -------------------------------------------------------------------------------- /features/issues/1091.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1091 2 | 3 | @network 4 | Feature: pretty output 5 | Scenario: reload-behavior in before hook 6 | Given additional Cypress configuration 7 | """ 8 | { 9 | "reporter": "@badeball/cypress-cucumber-preprocessor/dist/subpath-entrypoints/pretty-reporter.js" 10 | } 11 | """ 12 | And a file named "cypress/e2e/a.feature" with: 13 | """ 14 | Feature: a feature 15 | Scenario: a scenario 16 | Given a step 17 | """ 18 | And a file named "cypress/support/step_definitions/steps.js" with: 19 | """ 20 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 21 | Given("a step", function() {}) 22 | """ 23 | And a file named "cypress/support/e2e.js" with: 24 | """ 25 | before(() => { 26 | cy.visit("https://duckduckgo.com/"); 27 | }); 28 | """ 29 | When I run cypress 30 | Then it passes 31 | -------------------------------------------------------------------------------- /features/issues/1140.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1140 2 | 3 | Feature: no implicit report 4 | Scenario: JSON enabled 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "json": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a step 18 | """ 19 | And a file named "cypress/support/step_definitions/steps.js" with: 20 | """ 21 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 22 | Given("a step", function() {}) 23 | """ 24 | When I run cypress 25 | Then it passes 26 | And there should be a JSON output similar to "fixtures/passed-example.json" 27 | And there should be no messages report 28 | -------------------------------------------------------------------------------- /features/issues/1142.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1142 2 | 3 | @network 4 | Feature: JSON report 5 | Scenario: reload-behavior in beforeEach hook 6 | Given additional preprocessor configuration 7 | """ 8 | { 9 | "json": { 10 | "enabled": true 11 | } 12 | } 13 | """ 14 | And a file named "cypress/e2e/a.feature" with: 15 | """ 16 | Feature: a feature 17 | Scenario: a scenario 18 | Given a step 19 | """ 20 | And a file named "cypress/support/step_definitions/steps.js" with: 21 | """ 22 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 23 | Given("a step", function() {}) 24 | """ 25 | And a file named "cypress/support/e2e.js" with: 26 | """ 27 | beforeEach(() => { 28 | cy.visit("https://duckduckgo.com/"); 29 | }); 30 | """ 31 | When I run cypress 32 | Then it passes 33 | -------------------------------------------------------------------------------- /features/issues/1196 [foo].feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/discussions/1196 2 | 3 | Feature: square brackets in directory name 4 | Scenario: 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a step 10 | """ 11 | And a file named "cypress/support/step_definitions/steps.js" with: 12 | """ 13 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Given("a step", function() {}) 15 | """ 16 | When I run cypress 17 | Then it passes 18 | -------------------------------------------------------------------------------- /features/issues/1243.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/1243 2 | 3 | Feature: custom config location 4 | Scenario: custom config location 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a step 10 | """ 11 | And a file named "cypress/support/step_definitions/steps.js" with: 12 | """ 13 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Given("a step", function() {}) 15 | """ 16 | And a file named "config/cypress.config.js" with: 17 | """ 18 | module.exports = require("../cypress.config.js"); 19 | """ 20 | When I run cypress with "--config-file config/cypress.config.js" 21 | Then it passes 22 | -------------------------------------------------------------------------------- /features/issues/1245.feature: -------------------------------------------------------------------------------- 1 | Feature: cached source maps 2 | 3 | Ideally, I would have liked to test this in a more behavior driven way, like counting the number 4 | of times that the bundler, whichever was configured, was invoked. However, it turns out that 5 | Cypress does in fact cache [1]. Unfortunately, this cache is dead slow and requires higher-order 6 | caching. The only way to verify that Cypress' cache isn't invoked, is by interpreting stderr. 7 | 8 | [1] https://github.com/cypress-io/cypress/blob/v13.15.0/packages/server/lib/plugins/preprocessor.js#L94-L98 9 | 10 | Scenario: 11 | Given a file named "cypress/e2e/a.feature" with: 12 | """ 13 | Feature: a feature name 14 | Scenario: a scenario name 15 | Given a step 16 | """ 17 | And a file named "cypress/support/step_definitions/steps.ts" with: 18 | """ 19 | import { Given } from "@badeball/cypress-cucumber-preprocessor"; 20 | Given("a step", function(this: Mocha.Context) {}); 21 | for (let i = 0; i < 10; i++) { 22 | Given(`an unused step (${i + 1})`, function(this: Mocha.Context) {}); 23 | }; 24 | """ 25 | When I run cypress with environment variables 26 | | name | value | 27 | | DEBUG | cypress:server:preprocessor | 28 | Then it passes 29 | # Why two? Who knows. Cypress requests this file twice and the library once. 30 | And I should see exactly 2 instances of "headless and already processed" in stderr 31 | -------------------------------------------------------------------------------- /features/issues/705.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/705 2 | 3 | @no-default-plugin 4 | Feature: overriding event handlers 5 | Background: 6 | Given additional preprocessor configuration 7 | """ 8 | { 9 | "json": { 10 | "enabled": true 11 | } 12 | } 13 | """ 14 | 15 | Scenario: overriding after:screenshot 16 | Given a file named "cypress/e2e/a.feature" with: 17 | """ 18 | Feature: a feature 19 | Scenario: a scenario 20 | Given a failing step 21 | """ 22 | And a file named "cypress/support/step_definitions/steps.js" with: 23 | """ 24 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 25 | Given("a failing step", function() { 26 | throw "some error" 27 | }) 28 | """ 29 | And a file named "setupNodeEvents.js" with: 30 | """ 31 | const { addCucumberPreprocessorPlugin, afterScreenshotHandler } = require("@badeball/cypress-cucumber-preprocessor"); 32 | const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); 33 | const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); 34 | 35 | module.exports = async (on, config) => { 36 | await addCucumberPreprocessorPlugin(on, config, { omitAfterScreenshotHandler: true }); 37 | 38 | on("after:screenshot", (details) => afterScreenshotHandler(config, details)) 39 | 40 | on( 41 | "file:preprocessor", 42 | createBundler({ 43 | plugins: [createEsbuildPlugin(config)] 44 | }) 45 | ); 46 | 47 | return config; 48 | } 49 | """ 50 | 51 | When I run cypress 52 | Then it fails 53 | And the JSON report should contain an image attachment for what appears to be a screenshot 54 | -------------------------------------------------------------------------------- /features/issues/713.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/713 2 | 3 | Feature: returning chains 4 | Scenario: returning a chain 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a step 10 | """ 11 | And a file named "cypress/support/step_definitions/steps.js" with: 12 | """ 13 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Given("a step", () => cy.log("foo")) 15 | """ 16 | When I run cypress 17 | Then it passes 18 | -------------------------------------------------------------------------------- /features/issues/724.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/724 2 | 3 | Feature: outputting merely messages 4 | Scenario: enabling *only* messages 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "messages": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a step 18 | """ 19 | And a file named "cypress/support/step_definitions/steps.js" with: 20 | """ 21 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 22 | Given("a step", function() {}) 23 | """ 24 | When I run cypress 25 | Then it passes 26 | And there should be no JSON output 27 | But there should be a messages report 28 | -------------------------------------------------------------------------------- /features/issues/731.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/731 2 | 3 | Feature: blank titles 4 | Scenario: blank scenario outline title 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario Outline: 9 | Given a step 10 | 11 | Examples: 12 | | value | 13 | | foo | 14 | """ 15 | And a file named "cypress/support/step_definitions/steps.js" with: 16 | """ 17 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 18 | Given("a step", () => {}) 19 | """ 20 | When I run cypress 21 | Then it passes 22 | -------------------------------------------------------------------------------- /features/issues/736.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/736 2 | 3 | Feature: create output directories 4 | Background: 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "messages": { 9 | "enabled": true, 10 | "output": "foo/cucumber-messages.ndjson" 11 | }, 12 | "json": { 13 | "enabled": true, 14 | "output": "bar/cucumber-report.json" 15 | }, 16 | "html": { 17 | "enabled": true, 18 | "output": "baz/cucumber-report.html" 19 | }, 20 | "usage": { 21 | "enabled": true, 22 | "output": "qux/usage-report.html" 23 | } 24 | } 25 | """ 26 | 27 | Scenario: 28 | Given a file named "cypress/e2e/a.feature" with: 29 | """ 30 | Feature: a feature 31 | Scenario: a scenario 32 | Given a step 33 | """ 34 | And a file named "cypress/support/step_definitions/steps.js" with: 35 | """ 36 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 37 | Given("a step", function() {}) 38 | """ 39 | When I run cypress 40 | Then it passes 41 | -------------------------------------------------------------------------------- /features/issues/749.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/749 2 | 3 | Feature: retried steps 4 | Scenario: forever failing 5 | Given additional Cypress configuration 6 | """ 7 | { 8 | "retries": 2 9 | } 10 | """ 11 | And a file named "cypress/e2e/a.feature" with: 12 | """ 13 | Feature: a feature 14 | Scenario: a scenario 15 | Given a failing step 16 | """ 17 | And a file named "cypress/support/step_definitions/steps.js" with: 18 | """ 19 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 20 | Given("a failing step", function() { 21 | throw "some error" 22 | }) 23 | """ 24 | When I run cypress 25 | Then it fails 26 | -------------------------------------------------------------------------------- /features/issues/781.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/781 2 | 3 | Feature: non-array step definitions, not matching 4 | Scenario: non-array step definitions, not matching 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "stepDefinitions": "foo/bar" 9 | } 10 | """ 11 | And a file named "cypress/e2e/a.feature" with: 12 | """ 13 | Feature: a feature 14 | Scenario: a scenario 15 | Given an undefined step 16 | """ 17 | When I run cypress 18 | Then it fails 19 | And the output should contain 20 | """ 21 | Step implementation missing for "an undefined step". 22 | 23 | We tried searching for files containing step definitions using the following search pattern templates: 24 | 25 | - foo/bar 26 | 27 | These templates resolved to the following search patterns: 28 | 29 | - foo/bar 30 | 31 | These patterns matched **no files** containing step definitions. This almost certainly means that you have misconfigured `stepDefinitions`. 32 | """ 33 | -------------------------------------------------------------------------------- /features/issues/785.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/785 2 | 3 | Feature: omit filtered tests, creating a different common ancestor path 4 | Background: 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "stepDefinitions": "cypress/e2e/[filepath].js", 9 | "filterSpecs": true 10 | } 11 | """ 12 | And a file named "cypress/e2e/foo/bar.feature" with: 13 | """ 14 | Feature: a feature 15 | Scenario: a scenario 16 | Given a step 17 | """ 18 | And a file named "cypress/e2e/foo/bar.js" with: 19 | """ 20 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 21 | Given("a step", () => {}) 22 | """ 23 | And a file named "cypress/e2e/baz/qux.feature" with: 24 | """ 25 | @qux 26 | Feature: a feature 27 | Scenario: a scenario 28 | Given a step 29 | """ 30 | And a file named "cypress/e2e/baz/qux.js" with: 31 | """ 32 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 33 | Given("a step", () => {}) 34 | """ 35 | 36 | Scenario: no filtering 37 | When I run cypress 38 | Then it passes 39 | 40 | Scenario: with filtering 41 | When I run cypress with "--env tags=@qux" 42 | Then it passes 43 | -------------------------------------------------------------------------------- /features/issues/788.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/788 2 | 3 | Feature: calculating common ancestor path 4 | Scenario: 5 | Given a file named "cypress/e2e/1/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a step 10 | """ 11 | And a file named "cypress/e2e/2/a.feature" with: 12 | """ 13 | Feature: a feature 14 | Scenario: a scenario 15 | Given a step 16 | """ 17 | And a file named "cypress/e2e/3/a.feature" with: 18 | """ 19 | Feature: a feature 20 | Scenario: a scenario 21 | Given a step 22 | """ 23 | And a file named "cypress/support/step_definitions/steps.js" with: 24 | """ 25 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 26 | Given("a step", function() {}) 27 | """ 28 | When I run cypress 29 | Then it passes 30 | -------------------------------------------------------------------------------- /features/issues/792.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/792 2 | 3 | Feature: overriding env 4 | Scenario: overriding env 5 | Given a file named "cypress/e2e/foo/bar.feature" with: 6 | """ 7 | @env(foo="bar") 8 | Feature: a feature 9 | Scenario: a scenario 10 | Given a step 11 | """ 12 | And a file named "cypress/e2e/foo/bar.js" with: 13 | """ 14 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 15 | Given("a step", () => { 16 | expect(Cypress.env("foo")).to.equal("bar"); 17 | }); 18 | """ 19 | When I run cypress 20 | Then it passes 21 | -------------------------------------------------------------------------------- /features/issues/795.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/795 2 | 3 | Feature: scenario outline with parameterized name 4 | Scenario: scenario outline with parameterized name 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "json": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario Outline: 17 | Given a step 18 | 19 | Examples: 20 | | value | 21 | | foo | 22 | """ 23 | And a file named "cypress/support/step_definitions/steps.js" with: 24 | """ 25 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 26 | Given("a step", () => {}) 27 | """ 28 | When I run cypress 29 | Then it passes 30 | And there should be a JSON output similar to "fixtures/parameterized-scenario-name.json" 31 | -------------------------------------------------------------------------------- /features/issues/813.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/813 2 | 3 | Feature: messages report 4 | Scenario: it should only ever contain one 'testRunStarted' and 'testRunFinished' 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "messages": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a step 18 | """ 19 | And a file named "cypress/e2e/b.feature" with: 20 | """ 21 | Feature: a feature 22 | Scenario: a scenario 23 | Given a step 24 | """ 25 | And a file named "cypress/support/step_definitions/steps.js" with: 26 | """ 27 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 28 | Given("a step", function() {}) 29 | """ 30 | When I run cypress 31 | Then it passes 32 | And the messages should only contain a single "testRunStarted" and a single "testRunFinished" 33 | -------------------------------------------------------------------------------- /features/issues/832.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/832 2 | 3 | @no-default-preprocessor-config 4 | Feature: absolute paths in `stepDefinitions` 5 | Scenario: absolute paths in `stepDefinitions` 6 | Given a file named "cypress-cucumber-preprocessor.config.js" with: 7 | """ 8 | const path = require("path") 9 | 10 | module.exports = { 11 | stepDefinitions: [ 12 | path.join(process.cwd(), "cypress/support/step_definitions/**/*.{js,ts}") 13 | ] 14 | }; 15 | """ 16 | And a file named "cypress/e2e/a.feature" with: 17 | """ 18 | Feature: a feature 19 | Scenario: 20 | Given a step 21 | """ 22 | And a file named "cypress/support/step_definitions/steps.js" with: 23 | """ 24 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 25 | Given("a step", () => {}) 26 | """ 27 | When I run cypress 28 | Then it passes 29 | -------------------------------------------------------------------------------- /features/issues/856.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/856 2 | 3 | Feature: swallowing exceptions 4 | Background: 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a failing step 10 | """ 11 | And a file named "cypress/support/step_definitions/steps.js" with: 12 | """ 13 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Given("a failing step", function() { 15 | throw new Error("foobar") 16 | }) 17 | """ 18 | 19 | Scenario: without swallowing 20 | When I run cypress 21 | Then it fails 22 | 23 | Scenario: with swallowing 24 | Given a file named "cypress/support/e2e.js" with: 25 | """ 26 | Cypress.on("fail", (err) => { 27 | if (err.message.includes("foobar")) { 28 | return; 29 | } 30 | 31 | throw err; 32 | }) 33 | """ 34 | When I run cypress 35 | Then it passes 36 | -------------------------------------------------------------------------------- /features/issues/908.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/908 2 | 3 | Feature: hide internals from cypress environment 4 | Scenario: 5 | And a file named "cypress/e2e/a.feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: hide internal state by default 9 | Then the visible internal state should be stringified to a replacement text 10 | """ 11 | And a file named "cypress/support/step_definitions/steps.js" with: 12 | """ 13 | const { Then } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Then("the visible internal state should be stringified to a replacement text", () => { 15 | const { 16 | __cypress_cucumber_preprocessor_dont_use_this_spec: internalProperties 17 | } = JSON.parse(JSON.stringify(Cypress.env())); 18 | 19 | expect(internalProperties).to.equal("Internal properties of cypress-cucumber-preprocessor omitted from report."); 20 | }); 21 | """ 22 | When I run cypress 23 | Then it passes 24 | -------------------------------------------------------------------------------- /features/issues/922.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/922 2 | 3 | Feature: visualizing hook with filter 4 | Scenario: visualizing hook with filter 5 | Given a file named "cypress/e2e/a.feature" with: 6 | """ 7 | @foo 8 | Feature: a feature 9 | Scenario: a scenario 10 | Given a step 11 | """ 12 | And a file named "cypress/support/step_definitions/steps.js" with: 13 | """ 14 | const { Before, Given } = require("@badeball/cypress-cucumber-preprocessor"); 15 | Before(() => {}) 16 | Before({ tags: "@foo or @bar" }, () => {}) 17 | Given("a step", function() { 18 | cy.expectCommandLogEntry({ 19 | method: "Before", 20 | message: "@foo or @bar" 21 | }); 22 | }) 23 | """ 24 | When I run cypress 25 | Then it passes 26 | -------------------------------------------------------------------------------- /features/issues/931.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/931 2 | 3 | Feature: screenshot of last, failed attempt 4 | Background: 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "html": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a failing step 18 | """ 19 | And a file named "cypress/support/step_definitions/steps.js" with: 20 | """ 21 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 22 | Given("a failing step", function() { 23 | throw new Error("foobar") 24 | }) 25 | """ 26 | 27 | Scenario: retries: 0 28 | When I run cypress 29 | Then it fails 30 | And the report should have an image attachment 31 | 32 | Scenario: retries: 1 33 | Given additional Cypress configuration 34 | """ 35 | { 36 | "retries": 1 37 | } 38 | """ 39 | When I run cypress 40 | Then it fails 41 | # «Still have», despite having been retried 42 | And the report should have an image attachment 43 | -------------------------------------------------------------------------------- /features/issues/944.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/944 2 | 3 | @network 4 | Feature: changing domain during a spec 5 | Background: 6 | Given additional preprocessor configuration 7 | """ 8 | { 9 | "json": { 10 | "enabled": true 11 | } 12 | } 13 | """ 14 | 15 | Scenario: 16 | Given a file named "cypress/e2e/a.feature" with: 17 | """ 18 | Feature: a feature 19 | Scenario: a scenario 20 | When I navigate to "https://duckduckgo.com/" 21 | 22 | Scenario: a scenario 23 | When I navigate to "https://google.com/" 24 | """ 25 | And a file named "cypress/support/step_definitions/steps.js" with: 26 | """ 27 | const { When } = require("@badeball/cypress-cucumber-preprocessor"); 28 | When("I navigate to {string}", function(url) { 29 | cy.visit(url) 30 | }) 31 | """ 32 | When I run cypress 33 | Then it passes 34 | And the JSON report should contain 2 tests 35 | -------------------------------------------------------------------------------- /features/issues/946.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/946 2 | 3 | Feature: spec names containing glob specific characters 4 | Scenario: spec names containing glob specific characters 5 | Given a file named "cypress/e2e/[foo].feature" with: 6 | """ 7 | Feature: a feature 8 | Scenario: a scenario 9 | Given a step 10 | """ 11 | And a file named "cypress/e2e/[foo].js" with: 12 | """ 13 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 14 | Given("a step", function() {}) 15 | """ 16 | When I run cypress 17 | Then it passes 18 | -------------------------------------------------------------------------------- /features/issues/963.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/963 2 | 3 | Feature: message timestamps 4 | Scenario: failing step 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "messages": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | Feature: a feature 16 | Scenario: a scenario 17 | Given a failing step 18 | """ 19 | And a file named "cypress/support/step_definitions/steps.js" with: 20 | """ 21 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 22 | Given("a failing step", function() { 23 | cy.wait(1000).then(() => { throw "some error" }) 24 | }) 25 | """ 26 | When I run cypress 27 | Then it fails 28 | And the message report should contain a non-zero duration of the step 29 | -------------------------------------------------------------------------------- /features/issues/977.feature: -------------------------------------------------------------------------------- 1 | # https://github.com/badeball/cypress-cucumber-preprocessor/issues/977 2 | 3 | Feature: JSON report 4 | Scenario: a successful test not being retried 5 | Given additional preprocessor configuration 6 | """ 7 | { 8 | "json": { 9 | "enabled": true 10 | } 11 | } 12 | """ 13 | And a file named "cypress/e2e/a.feature" with: 14 | """ 15 | @retries(1) 16 | Feature: a feature 17 | Scenario: a scenario 18 | Given a step 19 | """ 20 | And a file named "cypress/support/step_definitions/steps.js" with: 21 | """ 22 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 23 | Given("a step", function() {}) 24 | """ 25 | When I run cypress 26 | Then it passes 27 | And the JSON report should contain a spec 28 | -------------------------------------------------------------------------------- /features/loaders/browserify.feature: -------------------------------------------------------------------------------- 1 | @no-default-plugin 2 | Feature: browserify + typescript 3 | Scenario: 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "setupNodeEvents.js" with: 11 | """ 12 | const browserify = require("@cypress/browserify-preprocessor"); 13 | const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); 14 | const { preprendTransformerToOptions } = require("@badeball/cypress-cucumber-preprocessor/browserify"); 15 | 16 | module.exports = async (on, config) => { 17 | await addCucumberPreprocessorPlugin(on, config); 18 | on( 19 | "file:preprocessor", 20 | browserify({ 21 | ...preprendTransformerToOptions(config, browserify.defaultOptions), 22 | typescript: require.resolve("typescript") 23 | }) 24 | ); 25 | return config; 26 | }; 27 | """ 28 | And a file named "cypress/support/step_definitions/steps.ts" with: 29 | """ 30 | import { Given } from "@badeball/cypress-cucumber-preprocessor"; 31 | Given("a step", function(this: Mocha.Context) {}); 32 | """ 33 | When I run cypress 34 | Then it passes 35 | -------------------------------------------------------------------------------- /features/loaders/esbuild.feature: -------------------------------------------------------------------------------- 1 | @no-default-plugin 2 | Feature: esbuild + typescript 3 | Scenario: 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "setupNodeEvents.js" with: 11 | """ 12 | const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); 13 | const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); 14 | const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); 15 | 16 | module.exports = async (on, config) => { 17 | await addCucumberPreprocessorPlugin(on, config); 18 | on( 19 | "file:preprocessor", 20 | createBundler({ 21 | plugins: [createEsbuildPlugin(config)] 22 | }) 23 | ); 24 | return config; 25 | }; 26 | """ 27 | And a file named "cypress/support/step_definitions/steps.ts" with: 28 | """ 29 | import { Given } from "@badeball/cypress-cucumber-preprocessor"; 30 | Given("a step", function(this: Mocha.Context) {}); 31 | """ 32 | When I run cypress 33 | Then it passes 34 | -------------------------------------------------------------------------------- /features/loaders/webpack.feature: -------------------------------------------------------------------------------- 1 | @no-default-plugin 2 | Feature: webpack + typescript 3 | Scenario: 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "setupNodeEvents.js" with: 11 | """ 12 | const webpack = require("@cypress/webpack-preprocessor"); 13 | const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); 14 | 15 | module.exports = async (on, config) => { 16 | await addCucumberPreprocessorPlugin(on, config); 17 | on( 18 | "file:preprocessor", 19 | webpack({ 20 | webpackOptions: { 21 | resolve: { 22 | extensions: [".ts", ".js"] 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | exclude: [/node_modules/], 29 | use: [ 30 | { 31 | loader: "ts-loader", 32 | options: { 33 | transpileOnly: true 34 | } 35 | } 36 | ] 37 | }, 38 | { 39 | test: /\.feature$/, 40 | use: [ 41 | { 42 | loader: "@badeball/cypress-cucumber-preprocessor/webpack", 43 | options: config 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | } 50 | }) 51 | ); 52 | return config; 53 | }; 54 | """ 55 | And a file named "cypress/support/step_definitions/steps.ts" with: 56 | """ 57 | import { Given } from "@badeball/cypress-cucumber-preprocessor"; 58 | Given("a step", function(this: Mocha.Context) {}); 59 | """ 60 | When I run cypress 61 | Then it passes 62 | -------------------------------------------------------------------------------- /features/localisation.feature: -------------------------------------------------------------------------------- 1 | Feature: localisation 2 | Scenario: norwegian 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | # language: no 6 | Egenskap: en funksjonalitet 7 | Scenario: et scenario 8 | Gitt et steg 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 13 | Given("et steg", function() {}); 14 | """ 15 | When I run cypress 16 | Then it passes 17 | -------------------------------------------------------------------------------- /features/merge_messages.feature: -------------------------------------------------------------------------------- 1 | Feature: merging reports 2 | 3 | Scenario: two features 4 | Given additional preprocessor configuration 5 | """ 6 | { 7 | "messages": { 8 | "enabled": true 9 | } 10 | } 11 | """ 12 | And a file named "cypress/e2e/a.feature" with: 13 | """ 14 | Feature: a feature 15 | Scenario: a scenario 16 | Given a step 17 | """ 18 | And a file named "cypress/e2e/b.feature" with: 19 | """ 20 | Feature: another feature 21 | Scenario: another scenario 22 | Given a step 23 | """ 24 | And a file named "cypress/support/step_definitions/steps.js" with: 25 | """ 26 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 27 | Given("a step", function() {}) 28 | """ 29 | When I run cypress with "--spec cypress/e2e/a.feature --env messagesOutput=cucumber-messages-a.ndjson" (expecting exit code 0) 30 | And I run cypress with "--spec cypress/e2e/b.feature --env messagesOutput=cucumber-messages-b.ndjson" (expecting exit code 0) 31 | And I merge the messages reports 32 | Then there should be a messages similar to "fixtures/multiple-features.ndjson" 33 | -------------------------------------------------------------------------------- /features/mixing_types.feature: -------------------------------------------------------------------------------- 1 | Feature: mixing feature and non-feature specs: API 2 | Background: 3 | Given additional Cypress configuration 4 | """ 5 | { 6 | "e2e": { 7 | "specPattern": "**/*.{spec.js,feature}" 8 | } 9 | } 10 | """ 11 | 12 | Scenario: feature 13 | Given a file named "cypress/e2e/a.feature" with: 14 | """ 15 | @foo 16 | Feature: a feature name 17 | Scenario: a scenario name 18 | Given a step 19 | """ 20 | And a file named "cypress/support/step_definitions/steps.js" with: 21 | """ 22 | const { When, isFeature, doesFeatureMatch } = require("@badeball/cypress-cucumber-preprocessor"); 23 | When("a step", function() { 24 | expect(isFeature()).to.be.true; 25 | expect(doesFeatureMatch("@foo")).to.be.true; 26 | }); 27 | """ 28 | And a file named "cypress/support/e2e.js" with: 29 | """ 30 | const { isFeature, doesFeatureMatch } = require("@badeball/cypress-cucumber-preprocessor"); 31 | beforeEach(() => { 32 | expect(isFeature()).to.be.true; 33 | expect(doesFeatureMatch("@foo")).to.be.true; 34 | }); 35 | """ 36 | When I run cypress 37 | Then it passes 38 | 39 | Scenario: non-feature 40 | Given a file named "cypress/e2e/a.spec.js" with: 41 | """ 42 | const { isFeature, doesFeatureMatch } = require("@badeball/cypress-cucumber-preprocessor"); 43 | it("should work", () => { 44 | expect(isFeature()).to.be.false; 45 | expect(doesFeatureMatch).to.throw("Expected to find internal properties, but didn't. This is likely because you're calling doesFeatureMatch() in a non-feature spec. Use doesFeatureMatch() in combination with isFeature() if you have both feature and non-feature specs"); 46 | }); 47 | """ 48 | And a file named "cypress/support/e2e.js" with: 49 | """ 50 | const { isFeature, doesFeatureMatch } = require("@badeball/cypress-cucumber-preprocessor"); 51 | beforeEach(() => { 52 | expect(isFeature()).to.be.false; 53 | expect(doesFeatureMatch).to.throw("Expected to find internal properties, but didn't. This is likely because you're calling doesFeatureMatch() in a non-feature spec. Use doesFeatureMatch() in combination with isFeature() if you have both feature and non-feature specs"); 54 | }); 55 | """ 56 | When I run cypress 57 | Then it passes 58 | -------------------------------------------------------------------------------- /features/nested_steps.feature: -------------------------------------------------------------------------------- 1 | Feature: nesten steps 2 | 3 | Scenario: invoking step from another step 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a nested step 9 | """ 10 | And a file named "cypress/e2e/a.js" with: 11 | """ 12 | const { Given, Step } = require("@badeball/cypress-cucumber-preprocessor"); 13 | Given("a nested step", function() { 14 | Step(this, "another step"); 15 | cy.get("@bar").should("equal", "foo"); 16 | }); 17 | Given("another step", function() { 18 | cy.wrap("foo").as("bar"); 19 | }); 20 | """ 21 | When I run cypress 22 | Then it passes 23 | -------------------------------------------------------------------------------- /features/parse_error.feature: -------------------------------------------------------------------------------- 1 | Feature: parse errors 2 | 3 | Scenario: tagged rules 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | @tag 8 | Background: a backgrund name 9 | """ 10 | When I run cypress 11 | Then it fails 12 | And the output should contain 13 | """ 14 | (3:3): expected: #TagLine, #RuleLine, #Comment, #Empty, got 'Background: a backgrund name' 15 | """ 16 | -------------------------------------------------------------------------------- /features/pending_scenario_hooks.feature: -------------------------------------------------------------------------------- 1 | Feature: pending scenario hooks 2 | 3 | Scenario: pending Before() hook 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature 7 | Scenario: a scenario 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { Before, Given } = require("@badeball/cypress-cucumber-preprocessor") 13 | Before(() => { 14 | return "pending" 15 | }) 16 | Given("a step", function() {}) 17 | """ 18 | When I run cypress 19 | Then it passes 20 | And it should appear to have skipped the scenario "a scenario" 21 | 22 | Scenario: pending After() hook 23 | Given a file named "cypress/e2e/a.feature" with: 24 | """ 25 | Feature: a feature 26 | Scenario: a scenario 27 | Given a step 28 | """ 29 | And a file named "cypress/support/step_definitions/steps.js" with: 30 | """ 31 | const { After, Given } = require("@badeball/cypress-cucumber-preprocessor") 32 | After(() => { 33 | return "pending" 34 | }) 35 | Given("a step", function() {}) 36 | """ 37 | When I run cypress 38 | Then it passes 39 | And it should appear to have skipped the scenario "a scenario" 40 | -------------------------------------------------------------------------------- /features/pending_steps.feature: -------------------------------------------------------------------------------- 1 | Feature: pending steps 2 | Background: 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | Feature: a feature 6 | Scenario: a scenario 7 | Given a pending step 8 | """ 9 | And a file named "cypress/support/step_definitions/steps.js" with: 10 | """ 11 | const { Given } = require("@badeball/cypress-cucumber-preprocessor") 12 | Given("a pending step", function() { 13 | return "pending" 14 | }) 15 | """ 16 | 17 | Rule: pending steps make the test skipped 18 | 19 | Scenario: basic pending step 20 | When I run cypress 21 | Then it passes 22 | And it should appear to have skipped the scenario "a scenario" 23 | 24 | Scenario: with step hooks returning strings 25 | Given a file named "cypress/support/step_definitions/hooks.js" with: 26 | """ 27 | const { BeforeStep, AfterStep } = require("@badeball/cypress-cucumber-preprocessor") 28 | BeforeStep(function() { 29 | return "foobar" 30 | }) 31 | AfterStep(function() { 32 | return "foobar" 33 | }) 34 | """ 35 | When I run cypress 36 | Then it passes 37 | And it should appear to have skipped the scenario "a scenario" 38 | 39 | Scenario: with step hooks returning chains 40 | Given a file named "cypress/support/step_definitions/hooks.js" with: 41 | """ 42 | const { BeforeStep, AfterStep } = require("@badeball/cypress-cucumber-preprocessor") 43 | BeforeStep(function() { 44 | return cy.wrap("foobar") 45 | }) 46 | AfterStep(function() { 47 | return cy.wrap("foobar") 48 | }) 49 | """ 50 | When I run cypress 51 | Then it passes 52 | And it should appear to have skipped the scenario "a scenario" 53 | 54 | -------------------------------------------------------------------------------- /features/scenario_outlines.feature: -------------------------------------------------------------------------------- 1 | Feature: scenario outlines and examples 2 | 3 | Scenario: placeholder in step 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature 7 | Scenario Outline: a scenario 8 | Given a step 9 | Examples: 10 | | value | 11 | | foo | 12 | """ 13 | And a file named "cypress/support/step_definitions/steps.js" with: 14 | """ 15 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 16 | Given("a foo step", function() {}) 17 | """ 18 | When I run cypress 19 | Then it passes 20 | 21 | Scenario: placeholder in docstring 22 | Given a file named "cypress/e2e/a.feature" with: 23 | """ 24 | Feature: a feature 25 | Scenario Outline: a scenario 26 | Given a doc string step 27 | \"\"\" 28 | a doc string 29 | \"\"\" 30 | Examples: 31 | | value | 32 | | foo | 33 | """ 34 | And a file named "cypress/support/step_definitions/steps.js" with: 35 | """ 36 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 37 | Given("a doc string step", function(docString) { 38 | expect(docString).to.equal("a foo doc string") 39 | }) 40 | """ 41 | When I run cypress 42 | Then it passes 43 | 44 | Scenario: placeholder in table 45 | Given a file named "cypress/e2e/a.feature" with: 46 | """ 47 | Feature: a feature 48 | Scenario Outline: a scenario 49 | Given a table step 50 | | | 51 | Examples: 52 | | value | 53 | | foo | 54 | """ 55 | And a file named "cypress/support/step_definitions/steps.js" with: 56 | """ 57 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 58 | Given("a table step", function(tableData) { 59 | expect(tableData.raw()[0][0]).to.equal("foo") 60 | }) 61 | """ 62 | When I run cypress 63 | Then it passes 64 | -------------------------------------------------------------------------------- /features/setup.feature: -------------------------------------------------------------------------------- 1 | @no-default-plugin 2 | Feature: setup 3 | Scenario: missing addCucumberPreprocessorPlugin in setupNodeEvents 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "setupNodeEvents.js" with: 11 | """ 12 | const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); 13 | const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); 14 | 15 | module.exports = async (on, config) => { 16 | on( 17 | "file:preprocessor", 18 | createBundler({ 19 | plugins: [createEsbuildPlugin(config)] 20 | }) 21 | ); 22 | }; 23 | """ 24 | And a file named "cypress/support/step_definitions/steps.js" with: 25 | """ 26 | import { Given } from "@badeball/cypress-cucumber-preprocessor"; 27 | Given("a step", function() {}); 28 | """ 29 | When I run cypress 30 | Then it fails 31 | And the output should contain 32 | """ 33 | Missing preprocessor event handlers (this usually means you've not invoked `addCucumberPreprocessorPlugin()` or not returned the config object in `setupNodeEvents()`) 34 | """ 35 | -------------------------------------------------------------------------------- /features/skip.feature: -------------------------------------------------------------------------------- 1 | Feature: skip 2 | 3 | Scenario: calling skip() 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { When } = require("@badeball/cypress-cucumber-preprocessor"); 13 | When("a step", function() { 14 | this.skip(); 15 | }); 16 | """ 17 | When I run cypress 18 | Then it passes 19 | And it should appear to have skipped the scenario "a scenario name" 20 | -------------------------------------------------------------------------------- /features/skipped_scenario_hooks.feature: -------------------------------------------------------------------------------- 1 | Feature: skipped scenario hooks 2 | 3 | Scenario: skipped Before() hook 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature 7 | Scenario: a scenario 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { Before, Given } = require("@badeball/cypress-cucumber-preprocessor") 13 | Before(() => { 14 | return "skipped" 15 | }) 16 | Given("a step", function() {}) 17 | """ 18 | When I run cypress 19 | Then it passes 20 | And it should appear to have skipped the scenario "a scenario" 21 | 22 | Scenario: skipped After() hook 23 | Given a file named "cypress/e2e/a.feature" with: 24 | """ 25 | Feature: a feature 26 | Scenario: a scenario 27 | Given a step 28 | """ 29 | And a file named "cypress/support/step_definitions/steps.js" with: 30 | """ 31 | const { After, Given } = require("@badeball/cypress-cucumber-preprocessor") 32 | After(() => { 33 | return "skipped" 34 | }) 35 | Given("a step", function() {}) 36 | """ 37 | When I run cypress 38 | Then it passes 39 | And it should appear to have skipped the scenario "a scenario" 40 | -------------------------------------------------------------------------------- /features/skipped_steps.feature: -------------------------------------------------------------------------------- 1 | Feature: skipped steps 2 | Background: 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | Feature: a feature 6 | Scenario: a scenario 7 | Given a skipped step 8 | """ 9 | And a file named "cypress/support/step_definitions/steps.js" with: 10 | """ 11 | const { Given } = require("@badeball/cypress-cucumber-preprocessor") 12 | Given("a skipped step", function() { 13 | return "skipped" 14 | }) 15 | """ 16 | 17 | Rule: skipped steps make the test skipped 18 | 19 | Scenario: basic skipped step 20 | When I run cypress 21 | Then it passes 22 | And it should appear to have skipped the scenario "a scenario" 23 | 24 | Scenario: with step hooks returning strings 25 | Given a file named "cypress/support/step_definitions/hooks.js" with: 26 | """ 27 | const { BeforeStep, AfterStep } = require("@badeball/cypress-cucumber-preprocessor") 28 | BeforeStep(function() { 29 | return "foobar" 30 | }) 31 | AfterStep(function() { 32 | return "foobar" 33 | }) 34 | """ 35 | When I run cypress 36 | Then it passes 37 | And it should appear to have skipped the scenario "a scenario" 38 | 39 | Scenario: with step hooks returning chains 40 | Given a file named "cypress/support/step_definitions/hooks.js" with: 41 | """ 42 | const { BeforeStep, AfterStep } = require("@badeball/cypress-cucumber-preprocessor") 43 | BeforeStep(function() { 44 | return cy.wrap("foobar") 45 | }) 46 | AfterStep(function() { 47 | return cy.wrap("foobar") 48 | }) 49 | """ 50 | When I run cypress 51 | Then it passes 52 | And it should appear to have skipped the scenario "a scenario" 53 | -------------------------------------------------------------------------------- /features/step_definitions.feature: -------------------------------------------------------------------------------- 1 | Feature: step definitions 2 | 3 | Rule: it should by default look for step definitions in a couple of locations 4 | 5 | Example: step definitions with same filename 6 | Given a file named "cypress/e2e/a.feature" with: 7 | """ 8 | Feature: a feature name 9 | Scenario: a scenario name 10 | Given a step 11 | """ 12 | And a file named "cypress/e2e/a.js" with: 13 | """ 14 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 15 | Given("a step", function() {}); 16 | """ 17 | When I run cypress 18 | Then it passes 19 | 20 | Example: step definitions in a directory with same name 21 | Given a file named "cypress/e2e/a.feature" with: 22 | """ 23 | Feature: a feature name 24 | Scenario: a scenario name 25 | Given a step 26 | """ 27 | And a file named "cypress/e2e/a/steps.js" with: 28 | """ 29 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 30 | Given("a step", function() {}); 31 | """ 32 | When I run cypress 33 | Then it passes 34 | 35 | Example: step definitions in a common directory 36 | Given a file named "cypress/e2e/a.feature" with: 37 | """ 38 | Feature: a feature name 39 | Scenario: a scenario name 40 | Given a step 41 | """ 42 | And a file named "cypress/support/step_definitions/steps.js" with: 43 | """ 44 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 45 | Given("a step", function() {}); 46 | """ 47 | When I run cypress 48 | Then it passes 49 | -------------------------------------------------------------------------------- /features/step_definitions/config_steps.ts: -------------------------------------------------------------------------------- 1 | import { Given } from "@cucumber/cucumber"; 2 | import path from "path"; 3 | import { promises as fs } from "fs"; 4 | import { insertValuesInConfigFile } from "../support/configFileUpdater"; 5 | import ICustomWorld from "../support/ICustomWorld"; 6 | 7 | async function updateJsonConfiguration( 8 | absoluteConfigPath: string, 9 | additionalJsonContent: any, 10 | ) { 11 | const existingConfig = JSON.parse( 12 | (await fs.readFile(absoluteConfigPath)).toString(), 13 | ); 14 | 15 | await fs.writeFile( 16 | absoluteConfigPath, 17 | JSON.stringify( 18 | { 19 | ...existingConfig, 20 | ...additionalJsonContent, 21 | }, 22 | null, 23 | 2, 24 | ), 25 | ); 26 | } 27 | 28 | Given( 29 | "additional preprocessor configuration", 30 | async function (this: ICustomWorld, jsonContent) { 31 | const absoluteConfigPath = path.join( 32 | this.tmpDir, 33 | ".cypress-cucumber-preprocessorrc", 34 | ); 35 | 36 | await updateJsonConfiguration(absoluteConfigPath, JSON.parse(jsonContent)); 37 | }, 38 | ); 39 | 40 | Given( 41 | "additional Cypress configuration", 42 | async function (this: ICustomWorld, jsonContent) { 43 | await insertValuesInConfigFile( 44 | path.join(this.tmpDir, "cypress.config.js"), 45 | JSON.parse(jsonContent), 46 | ); 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /features/step_definitions/file_steps.ts: -------------------------------------------------------------------------------- 1 | import { Given } from "@cucumber/cucumber"; 2 | import stripIndent from "strip-indent"; 3 | import path from "path"; 4 | import { writeFile } from "../support/helpers"; 5 | import ICustomWorld from "../support/ICustomWorld"; 6 | 7 | Given( 8 | "a file named {string} with:", 9 | async function (this: ICustomWorld, filePath, fileContent) { 10 | const absoluteFilePath = path.join(this.tmpDir, filePath); 11 | 12 | await writeFile(absoluteFilePath, stripIndent(fileContent)); 13 | }, 14 | ); 15 | 16 | Given( 17 | "an empty file named {string}", 18 | async function (this: ICustomWorld, filePath) { 19 | const absoluteFilePath = path.join(this.tmpDir, filePath); 20 | 21 | await writeFile(absoluteFilePath, ""); 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /features/step_definitions/usage_steps.ts: -------------------------------------------------------------------------------- 1 | import { Then } from "@cucumber/cucumber"; 2 | import path from "path"; 3 | import { promises as fs } from "fs"; 4 | import assert from "assert"; 5 | import { expectLastRun, rescape } from "../support/helpers"; 6 | import ICustomWorld from "../support/ICustomWorld"; 7 | 8 | const normalizeUsageOutput = (content: string) => 9 | content.replaceAll("\\", "/").replaceAll(/\d+\.\d+ms/g, (match: string) => { 10 | const replaceWith = "0.00ms"; 11 | return replaceWith + " ".repeat(match.length - replaceWith.length); 12 | }); 13 | 14 | Then( 15 | "there should be a usage report named {string} containing", 16 | async function (file, expectedContent) { 17 | const absoluteFilePath = path.join(this.tmpDir, file); 18 | 19 | const actualContent = (await fs.readFile(absoluteFilePath)).toString(); 20 | 21 | assert.equal(normalizeUsageOutput(actualContent), expectedContent + "\n"); 22 | }, 23 | ); 24 | 25 | Then( 26 | "the output should contain a usage report", 27 | function (this: ICustomWorld, expectedContent) { 28 | assert.match( 29 | normalizeUsageOutput(expectLastRun(this).stdout), 30 | new RegExp(rescape(expectedContent)), 31 | ); 32 | }, 33 | ); 34 | -------------------------------------------------------------------------------- /features/suite_options.feature: -------------------------------------------------------------------------------- 1 | Feature: suite options 2 | Scenario: suite specific retry 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | @retries(2) 6 | Feature: a feature 7 | Scenario: a scenario 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 13 | let attempt = 0; 14 | Given("a step", () => { 15 | if (attempt++ === 0) { 16 | throw "some error"; 17 | } 18 | }); 19 | """ 20 | When I run cypress 21 | Then it passes 22 | -------------------------------------------------------------------------------- /features/support/ICustomWorld.ts: -------------------------------------------------------------------------------- 1 | export interface ExtraOptions { 2 | extraArgs?: string[]; 3 | extraEnv?: Record; 4 | expectedExitCode?: number; 5 | } 6 | 7 | export default interface ICustomWorld { 8 | tmpDir: string; 9 | verifiedLastRunError: boolean | undefined; 10 | lastRun: 11 | | { 12 | stdout: string; 13 | stderr: string; 14 | output: string; 15 | exitCode: number; 16 | } 17 | | undefined; 18 | 19 | runCypress(options?: ExtraOptions): Promise; 20 | 21 | runMergeMessages(options?: ExtraOptions): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /features/support/accordion.ts: -------------------------------------------------------------------------------- 1 | import { 2 | queryHelpers, 3 | buildQueries, 4 | Matcher, 5 | MatcherOptions, 6 | } from "@testing-library/dom"; 7 | 8 | const queryAllByAccordionComponent = ( 9 | container: HTMLElement, 10 | id: Matcher, 11 | options?: MatcherOptions | undefined, 12 | ) => 13 | queryHelpers.queryAllByAttribute( 14 | "data-accordion-component", 15 | container, 16 | id, 17 | options, 18 | ); 19 | 20 | const getMultipleError = (c: any, dataAccordionComponent: string) => 21 | `Found multiple elements with the data-accordion-component attribute of: ${dataAccordionComponent}`; 22 | const getMissingError = (c: any, dataAccordionComponent: string) => 23 | `Unable to find an element with the data-accordion-component attribute of: ${dataAccordionComponent}`; 24 | 25 | const [ 26 | queryByAccordionComponent, 27 | getAllByAccordionComponent, 28 | getByAccordionComponent, 29 | findAllByAccordionComponent, 30 | findByAccordionComponent, 31 | ] = buildQueries( 32 | queryAllByAccordionComponent, 33 | getMultipleError, 34 | getMissingError, 35 | ); 36 | 37 | export { 38 | queryByAccordionComponent, 39 | queryAllByAccordionComponent, 40 | getByAccordionComponent, 41 | getAllByAccordionComponent, 42 | findAllByAccordionComponent, 43 | findByAccordionComponent, 44 | }; 45 | -------------------------------------------------------------------------------- /features/tags/target_specific_scenarios_by_tag.feature: -------------------------------------------------------------------------------- 1 | Feature: target specific scenario 2 | 3 | Background: 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: some feature 7 | @a 8 | Scenario: first scenario 9 | Given a step 10 | @b 11 | Scenario Outline: second scenario - 12 | Given a step 13 | @c 14 | Examples: 15 | | ID | 16 | | X | 17 | | Y | 18 | @d 19 | Examples: 20 | | ID | 21 | | Z | 22 | """ 23 | And a file named "cypress/support/step_definitions/steps.js" with: 24 | """ 25 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 26 | Given("a step", function() {}) 27 | """ 28 | 29 | Scenario: run a single scenario 30 | When I run cypress with "--env tags=@a" 31 | Then it passes 32 | And it should appear to have run the scenario "first scenario" 33 | 34 | Scenario: filter out scenarios with ~ 35 | When I run cypress with "--env 'tags=not @b'" 36 | Then it passes 37 | And it should appear to have run the scenario "first scenario" 38 | 39 | Scenario: run a single scenario outline 40 | When I run cypress with "--env tags=@b" 41 | Then it passes 42 | And it should appear to have run the scenarios 43 | | Name | 44 | | second scenario - X (example #1) | 45 | | second scenario - Y (example #2) | 46 | | second scenario - Z (example #3) | 47 | 48 | Scenario: run a single scenario outline examples 49 | When I run cypress with "--env tags=@d" 50 | Then it passes 51 | And it should appear to have run the scenario "second scenario - Z (example #3)" 52 | But it should appear to not have run the scenarios 53 | | Name | 54 | | second scenario - X (example #1) | 55 | | second scenario - Y (example #2) | 56 | -------------------------------------------------------------------------------- /features/tags/test_filter.feature: -------------------------------------------------------------------------------- 1 | Feature: test filter 2 | 3 | Scenario: with omitFiltered = false (default) 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: some feature 7 | Rule: first rule 8 | @foo 9 | Scenario: first scenario 10 | Given a step 11 | 12 | Rule: second rule 13 | Scenario: second scenario 14 | Given a step 15 | """ 16 | And a file named "cypress/support/step_definitions/steps.js" with: 17 | """ 18 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 19 | Given("a step", function() {}) 20 | """ 21 | When I run cypress with "--env tags=@foo" 22 | Then it passes 23 | And it should appear to have skipped the scenario "second scenario" 24 | 25 | Scenario: with omitFiltered = true 26 | Given additional preprocessor configuration 27 | """ 28 | { 29 | "omitFiltered": true 30 | } 31 | """ 32 | And a file named "cypress/e2e/a.feature" with: 33 | """ 34 | Feature: some feature 35 | Rule: first rule 36 | @foo 37 | Scenario: first scenario 38 | Given a step 39 | 40 | Rule: second rule 41 | Scenario: second scenario 42 | Given a step 43 | """ 44 | And a file named "cypress/support/step_definitions/steps.js" with: 45 | """ 46 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 47 | Given("a step", function() {}) 48 | """ 49 | When I run cypress with "--env tags=@foo" 50 | Then it passes 51 | And it should appear as if only a single test ran 52 | And I should not see "second rule" in the output 53 | -------------------------------------------------------------------------------- /features/test_state.feature: -------------------------------------------------------------------------------- 1 | Feature: window.testState 2 | Scenario: gherkin document 3 | Given a file named "cypress/e2e/a.feature" with: 4 | """ 5 | Feature: a feature name 6 | Scenario: a scenario name 7 | Given a step 8 | """ 9 | And a file named "cypress/e2e/a.js" with: 10 | """ 11 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 12 | Given("a step", function() { 13 | expect(testState.gherkinDocument.feature.name).to.equal("a feature name"); 14 | }); 15 | """ 16 | When I run cypress 17 | Then it passes 18 | 19 | Scenario: pickles 20 | Given a file named "cypress/e2e/a.feature" with: 21 | """ 22 | Feature: a feature name 23 | Scenario: a scenario name 24 | Given a step 25 | """ 26 | And a file named "cypress/e2e/a.js" with: 27 | """ 28 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 29 | Given("a step", function() { 30 | expect(testState.pickles[0].name).to.equal("a scenario name"); 31 | }); 32 | """ 33 | When I run cypress 34 | Then it passes 35 | 36 | Scenario: pickle 37 | Given a file named "cypress/e2e/a.feature" with: 38 | """ 39 | Feature: a feature name 40 | Scenario: a scenario name 41 | Given a step 42 | """ 43 | And a file named "cypress/e2e/a.js" with: 44 | """ 45 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 46 | Given("a step", function() { 47 | expect(testState.pickle.name).to.equal("a scenario name"); 48 | }); 49 | """ 50 | When I run cypress 51 | Then it passes 52 | 53 | Scenario: pickleStep 54 | Given a file named "cypress/e2e/a.feature" with: 55 | """ 56 | Feature: a feature name 57 | Scenario: a scenario name 58 | Given a step 59 | And another step 60 | """ 61 | And a file named "cypress/e2e/a.js" with: 62 | """ 63 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 64 | Given("a step", function() { 65 | expect(testState.pickleStep.text).to.equal("a step"); 66 | }); 67 | Given("another step", function() { 68 | expect(testState.pickleStep.text).to.equal("another step"); 69 | }); 70 | """ 71 | When I run cypress 72 | Then it passes 73 | -------------------------------------------------------------------------------- /features/unambiguous_step_definitions.feature: -------------------------------------------------------------------------------- 1 | Feature: unambiguous step definitions 2 | 3 | Scenario: step matching two definitions 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: a feature name 7 | Scenario: a scenario name 8 | Given a step 9 | """ 10 | And a file named "cypress/support/step_definitions/steps.js" with: 11 | """ 12 | const { Given } = require("@badeball/cypress-cucumber-preprocessor"); 13 | Given("a step", function() {}); 14 | Given(/a step/, function() {}); 15 | """ 16 | When I run cypress 17 | Then it fails 18 | And the output should contain 19 | """ 20 | Error: Multiple matching step definitions for: a step 21 | a step 22 | /a step/ 23 | """ 24 | -------------------------------------------------------------------------------- /features/world_example.feature: -------------------------------------------------------------------------------- 1 | Feature: world 2 | 3 | Scenario: example of an World 4 | Given a file named "cypress/e2e/a.feature" with: 5 | """ 6 | Feature: Simple maths 7 | In order to do maths 8 | As a developer 9 | I want to increment variables 10 | 11 | Scenario: easy maths 12 | Given a variable set to 1 13 | When I increment the variable by 1 14 | Then the variable should contain 2 15 | 16 | Scenario Outline: much more complex stuff 17 | Given a variable set to 18 | When I increment the variable by 19 | Then the variable should contain 20 | 21 | Examples: 22 | | var | increment | result | 23 | | 100 | 5 | 105 | 24 | | 99 | 1234 | 1333 | 25 | | 12 | 5 | 17 | 26 | """ 27 | And a file named "cypress/support/e2e.js" with: 28 | """ 29 | beforeEach(function () { 30 | // This mimics setWorldConstructor of cucumber-js. 31 | const world = { 32 | variable: 0, 33 | 34 | setTo(number) { 35 | this.variable = number; 36 | }, 37 | 38 | incrementBy(number) { 39 | this.variable += number; 40 | } 41 | }; 42 | 43 | Object.assign(this, world); 44 | }); 45 | """ 46 | And a file named "cypress/support/step_definitions/steps.js" with: 47 | """ 48 | const { Given, When, Then } = require("@badeball/cypress-cucumber-preprocessor"); 49 | 50 | Given("a variable set to {int}", function(number) { 51 | this.setTo(number); 52 | }); 53 | 54 | When("I increment the variable by {int}", function(number) { 55 | this.incrementBy(number); 56 | }); 57 | 58 | Then("the variable should contain {int}", function(number) { 59 | expect(this.variable).to.equal(number); 60 | }); 61 | """ 62 | When I run cypress 63 | Then it passes 64 | -------------------------------------------------------------------------------- /lib/bin/cucumber-html-formatter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import stream from "stream/promises"; 4 | 5 | import { NdjsonToMessageStream } from "@cucumber/message-streams"; 6 | 7 | import { createHtmlStream } from "../helpers/formatters"; 8 | 9 | stream 10 | .pipeline( 11 | process.stdin, 12 | new NdjsonToMessageStream(), 13 | createHtmlStream(), 14 | process.stdout, 15 | ) 16 | .catch((err) => { 17 | console.error(err.stack); 18 | process.exitCode = 1; 19 | }); 20 | -------------------------------------------------------------------------------- /lib/bin/cucumber-json-formatter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { pipeline } from "stream/promises"; 4 | 5 | import { Writable } from "stream"; 6 | 7 | import { NdjsonToMessageStream } from "@cucumber/message-streams"; 8 | 9 | import type * as messages from "@cucumber/messages"; 10 | 11 | import { createJsonFormatter } from "../helpers/formatters"; 12 | 13 | import { assertIsString } from "../helpers/assertions"; 14 | 15 | const envelopes: messages.Envelope[] = []; 16 | 17 | pipeline( 18 | process.stdin, 19 | new NdjsonToMessageStream(), 20 | new Writable({ 21 | objectMode: true, 22 | write(envelope: messages.Envelope, _: BufferEncoding, callback) { 23 | envelopes.push(envelope); 24 | callback(); 25 | }, 26 | }), 27 | ) 28 | .then(() => { 29 | let jsonOutput: string | undefined; 30 | 31 | const eventBroadcaster = createJsonFormatter(envelopes, (chunk) => { 32 | jsonOutput = chunk; 33 | }); 34 | 35 | for (const message of envelopes) { 36 | eventBroadcaster.emit("envelope", message); 37 | } 38 | 39 | assertIsString( 40 | jsonOutput, 41 | "Expected JSON formatter to have finished, but it never returned", 42 | ); 43 | 44 | console.log(jsonOutput); 45 | }) 46 | .catch((err) => { 47 | console.error(err.stack); 48 | process.exitCode = 1; 49 | }); 50 | -------------------------------------------------------------------------------- /lib/bin/cucumber-merge-messages.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { mergeMessagesArgs } from "../helpers/merge"; 4 | 5 | mergeMessagesArgs(process).catch((err) => { 6 | console.error(err.stack); 7 | process.exitCode = 1; 8 | }); 9 | -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const INTERNAL_PROPERTY_NAME = 2 | "__cypress_cucumber_preprocessor_dont_use_this"; 3 | 4 | export const INTERNAL_SPEC_PROPERTIES = INTERNAL_PROPERTY_NAME + "_spec"; 5 | 6 | export const INTERNAL_SUITE_PROPERTIES = INTERNAL_PROPERTY_NAME + "_suite"; 7 | 8 | export const HOOK_FAILURE_EXPR = 9 | /Because this error occurred during a `[^`]+` hook we are skipping all of the remaining tests\./; 10 | 11 | export const TEST_ISOLATION_CONFIGURATION_OPTION = "testIsolation"; 12 | -------------------------------------------------------------------------------- /lib/cypress-task-definitions.ts: -------------------------------------------------------------------------------- 1 | import type * as messages from "@cucumber/messages"; 2 | 3 | export const TASK_SPEC_ENVELOPES = 4 | "cypress-cucumber-preprocessor:spec-envelopes"; 5 | 6 | export interface ITaskSpecEnvelopes { 7 | messages: messages.Envelope[]; 8 | } 9 | 10 | export const TASK_TEST_CASE_STARTED = 11 | "cypress-cucumber-preprocessor:test-case-started"; 12 | 13 | export type ITaskTestCaseStarted = messages.TestCaseStarted; 14 | 15 | export const TASK_TEST_CASE_FINISHED = 16 | "cypress-cucumber-preprocessor:test-case-finished"; 17 | 18 | export type ITaskTestCaseFinished = messages.TestCaseFinished; 19 | 20 | export const TASK_TEST_STEP_STARTED = 21 | "cypress-cucumber-preprocessor:test-step-started"; 22 | 23 | export type ITaskTestStepStarted = messages.TestStepStarted; 24 | 25 | export const TASK_TEST_STEP_FINISHED = 26 | "cypress-cucumber-preprocessor:test-step-finished"; 27 | 28 | export type ITaskTestStepFinished = messages.TestStepFinished; 29 | 30 | export const TASK_CREATE_STRING_ATTACHMENT = 31 | "cypress-cucumber-preprocessor:create-string-attachment"; 32 | 33 | export interface ITaskCreateStringAttachment { 34 | data: string; 35 | mediaType: string; 36 | encoding: messages.AttachmentContentEncoding; 37 | } 38 | -------------------------------------------------------------------------------- /lib/data_table.ts: -------------------------------------------------------------------------------- 1 | import type * as messages from "@cucumber/messages"; 2 | 3 | import { assert, assertAndReturn } from "./helpers/assertions"; 4 | 5 | function zip(collectionA: A[], collectionB: B[]) { 6 | return collectionA.map<[A, B]>((element, index) => [ 7 | element, 8 | collectionB[index], 9 | ]); 10 | } 11 | 12 | export default class DataTable { 13 | private readonly rawTable: string[][]; 14 | 15 | constructor(sourceTable: messages.PickleTable | string[][]) { 16 | if (sourceTable instanceof Array) { 17 | this.rawTable = sourceTable; 18 | } else { 19 | this.rawTable = assertAndReturn( 20 | sourceTable.rows, 21 | "Expected a PicleTable to have rows", 22 | ).map((row) => 23 | assertAndReturn( 24 | row.cells, 25 | "Expected a PicleTableRow to have cells", 26 | ).map((cell) => { 27 | const { value } = cell; 28 | assert(value != null, "Expected a PicleTableCell to have a value"); 29 | return value; 30 | }), 31 | ); 32 | } 33 | } 34 | 35 | hashes(): Record[] { 36 | const copy = this.raw(); 37 | const keys = copy[0]; 38 | const valuesArray = copy.slice(1); 39 | return valuesArray.map((values) => Object.fromEntries(zip(keys, values))); 40 | } 41 | 42 | raw(): string[][] { 43 | return this.rawTable.slice(0); 44 | } 45 | 46 | rows(): string[][] { 47 | const copy = this.raw(); 48 | copy.shift(); 49 | return copy; 50 | } 51 | 52 | rowsHash(): Record { 53 | return Object.fromEntries( 54 | this.raw().map<[string, string]>((values) => { 55 | const [first, second, ...rest] = values; 56 | 57 | if (first == null || second == null || rest.length !== 0) { 58 | throw new Error( 59 | "rowsHash can only be called on a data table where all rows have exactly two columns", 60 | ); 61 | } 62 | 63 | return [first, second]; 64 | }), 65 | ); 66 | } 67 | 68 | transpose(): DataTable { 69 | const transposed = this.rawTable[0].map((x, i) => 70 | this.rawTable.map((y) => y[i]), 71 | ); 72 | 73 | return new DataTable(transposed); 74 | } 75 | 76 | toString() { 77 | return "[object DataTable]"; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/helpers/assertions.ts: -------------------------------------------------------------------------------- 1 | import { createError } from "./error"; 2 | 3 | import { isString } from "./type-guards"; 4 | 5 | export function assertNever(value: never): never { 6 | throw new Error("Illegal value: " + value); 7 | } 8 | 9 | export function fail(message: string) { 10 | throw createError(message); 11 | } 12 | 13 | export function assert(value: unknown, message: string): asserts value { 14 | if (value != null) { 15 | return; 16 | } 17 | 18 | fail(message); 19 | } 20 | 21 | export function assertAndReturn( 22 | value: T, 23 | message: string, 24 | ): Exclude { 25 | assert(value, message); 26 | return value as Exclude; 27 | } 28 | 29 | export function assertIsString( 30 | value: unknown, 31 | message: string, 32 | ): asserts value is string { 33 | assert(isString(value), message); 34 | } 35 | -------------------------------------------------------------------------------- /lib/helpers/colors.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | /** 4 | * Copied from Cypress [1] as this isn't exposed. 5 | * 6 | * [1] https://github.com/cypress-io/cypress/blob/v12.13.0/cli/lib/util.js#L348-L363 7 | */ 8 | export function useColors() { 9 | // if we've been explicitly told not to support 10 | // color then turn this off 11 | if (process.env.NO_COLOR) { 12 | return false; 13 | } 14 | 15 | // https://github.com/cypress-io/cypress/issues/1747 16 | // always return true in CI providers 17 | if (process.env.CI) { 18 | return true; 19 | } 20 | 21 | // ensure that both stdout and stderr support color 22 | return Boolean(chalk.supportsColor) && Boolean(chalk.stderr.supportsColor); 23 | } 24 | -------------------------------------------------------------------------------- /lib/helpers/cypress.ts: -------------------------------------------------------------------------------- 1 | import DataTable from "../data_table"; 2 | 3 | import { CypressCucumberError } from "./error"; 4 | 5 | const ensureChain = (value: unknown): Cypress.Chainable => 6 | Cypress.isCy(value) ? value : cy.wrap(value, { log: false }); 7 | 8 | const nativePromiseConstructor = (async () => {})().constructor; 9 | 10 | function getMaxColumnWidths(tableArray: any) { 11 | const maxColumnWidths: any[] = []; 12 | for (const row of tableArray) { 13 | for (let i = 0; i < row.length; i++) { 14 | const cell = row[i]; 15 | const cellWidth = cell.length; 16 | maxColumnWidths[i] = Math.max(maxColumnWidths[i] || 0, cellWidth); 17 | } 18 | } 19 | return maxColumnWidths; 20 | } 21 | 22 | function padElements(tableArray: any[], maxColumnWidths: any[]) { 23 | return tableArray.map((row) => 24 | row.map( 25 | (cell: string, index: any) => 26 | (cell = cell + " ".repeat(maxColumnWidths[index] - cell.length)), 27 | ), 28 | ); 29 | } 30 | 31 | function convertArrayToTableString(tableArray: any[]) { 32 | const maxColumnWidths = getMaxColumnWidths(tableArray); 33 | const paddedTableArray = padElements(tableArray, maxColumnWidths); 34 | const lines = paddedTableArray.map((row) => row.join(" | ")); 35 | const tableString = lines.join(" |\n| "); 36 | return tableString; 37 | } 38 | 39 | export function runStepWithLogGroup(options: { 40 | fn: () => unknown; 41 | keyword: string; 42 | argument?: DataTable | string; 43 | text?: string; 44 | }) { 45 | const log = Cypress.log({ 46 | name: options.keyword, 47 | message: options.text == null ? "" : `**${options.text}**`, 48 | groupStart: true, 49 | type: "parent", 50 | } as object); 51 | 52 | if (options.argument instanceof DataTable) { 53 | Cypress.log({ 54 | name: "DataTable", 55 | message: 56 | " \n| " + 57 | convertArrayToTableString(options.argument.raw()) + 58 | " |", 59 | }); 60 | } else if (typeof options.argument === "string") { 61 | // TODO: Log docstring here. 62 | } 63 | 64 | const ret = options.fn(); 65 | 66 | if (ret instanceof nativePromiseConstructor) { 67 | throw new CypressCucumberError( 68 | "Cucumber preprocessor detected that you returned a native promise from a function handler, this is not supported. Using async / await is generally speaking not supported when using Cypress, period, preprocessor or not.", 69 | ); 70 | } 71 | 72 | return ensureChain(ret).then((result) => { 73 | (log as any).endGroup(); 74 | return result; 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /lib/helpers/debug.ts: -------------------------------------------------------------------------------- 1 | import debug from "debug"; 2 | 3 | export default debug("cypress-cucumber-preprocessor"); 4 | -------------------------------------------------------------------------------- /lib/helpers/dry-run.ts: -------------------------------------------------------------------------------- 1 | const globalPropertyName = 2 | "__cypress_cucumber_preprocessor_mocha_dont_use_this"; 3 | 4 | globalThis[globalPropertyName] = { 5 | before: globalThis.before, 6 | beforeEach: globalThis.beforeEach, 7 | after: globalThis.after, 8 | afterEach: globalThis.afterEach, 9 | }; 10 | 11 | /** 12 | * {} isn't strictly speaking a Mocha.Hook, so if Cypress decides to update their shipped Mocha 13 | * version to v11, which introduces #5231 [1], then this might become problematic. The 14 | * @types/mocha package did however update their types within its v10 line. 15 | * 16 | * [1] https://github.com/mochajs/mocha/issues/5231 17 | */ 18 | window.before = () => ({}) as Mocha.Hook; 19 | window.beforeEach = () => ({}) as Mocha.Hook; 20 | window.after = () => ({}) as Mocha.Hook; 21 | window.afterEach = () => ({}) as Mocha.Hook; 22 | -------------------------------------------------------------------------------- /lib/helpers/environment.ts: -------------------------------------------------------------------------------- 1 | export function getTags(env: Record): string | null { 2 | const tags = env.TAGS ?? env.tags; 3 | 4 | return tags == null ? null : String(tags); 5 | } 6 | -------------------------------------------------------------------------------- /lib/helpers/error.ts: -------------------------------------------------------------------------------- 1 | export const homepage = 2 | "https://github.com/badeball/cypress-cucumber-preprocessor"; 3 | 4 | export class CypressCucumberError extends Error {} 5 | 6 | export function createError(message: string) { 7 | return new CypressCucumberError( 8 | `${message} (this might be a bug, please report at ${homepage})`, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lib/helpers/memoize.ts: -------------------------------------------------------------------------------- 1 | export function memoize any>( 2 | fn: T, 3 | ): (...args: Parameters) => ReturnType { 4 | let result: ReturnType; 5 | 6 | return (...args: Parameters) => { 7 | if (result) { 8 | return result; 9 | } 10 | 11 | return (result = fn(...args)); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /lib/helpers/merge.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | 3 | import assert from "assert/strict"; 4 | 5 | import { WritableStreamBuffer } from "stream-buffers"; 6 | 7 | import { mergeMessagesArgs } from "./merge"; 8 | 9 | import { stringToNdJson } from "../../features/support/helpers"; 10 | 11 | // TODO: Implement these. 12 | describe.skip("mergeMessages()", () => { 13 | it("should select the first meta", () => {}); 14 | 15 | it("should select the earliest testRunStarted", () => {}); 16 | 17 | it("should select the latest testRunFinished", () => {}); 18 | 19 | it("it should fail upon merging unrelated reports", () => {}); 20 | 21 | it("it should fail upon receiving zero reports", () => {}); 22 | }); 23 | 24 | describe("mergeMessagesArgs()", () => { 25 | it("should merge two, basic and unrelated reports", async () => { 26 | const stdout = new WritableStreamBuffer(); 27 | 28 | await mergeMessagesArgs({ 29 | stdout, 30 | argv: [ 31 | "/usr/bin/node", 32 | "./node_modules/.bin/merge-messages", 33 | "features/fixtures/passed-example.ndjson", 34 | "features/fixtures/another-passed-example.ndjson", 35 | ], 36 | }); 37 | 38 | const actual = stringToNdJson(stdout.getContentsAsString() || ""); 39 | 40 | const expected = stringToNdJson( 41 | ( 42 | await fs.readFile("features/fixtures/multiple-features.ndjson") 43 | ).toString(), 44 | ); 45 | 46 | assert.deepEqual(actual, expected); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /lib/helpers/messages-enums.ts: -------------------------------------------------------------------------------- 1 | export enum PickleStepType { 2 | UNKNOWN = "Unknown", 3 | CONTEXT = "Context", 4 | ACTION = "Action", 5 | OUTCOME = "Outcome", 6 | } 7 | 8 | export enum AttachmentContentEncoding { 9 | IDENTITY = "IDENTITY", 10 | BASE64 = "BASE64", 11 | } 12 | 13 | export enum StepDefinitionPatternType { 14 | CUCUMBER_EXPRESSION = "CUCUMBER_EXPRESSION", 15 | REGULAR_EXPRESSION = "REGULAR_EXPRESSION", 16 | } 17 | 18 | export enum TestStepResultStatus { 19 | UNKNOWN = "UNKNOWN", 20 | PASSED = "PASSED", 21 | SKIPPED = "SKIPPED", 22 | PENDING = "PENDING", 23 | UNDEFINED = "UNDEFINED", 24 | AMBIGUOUS = "AMBIGUOUS", 25 | FAILED = "FAILED", 26 | } 27 | 28 | export enum SourceMediaType { 29 | TEXT_X_CUCUMBER_GHERKIN_PLAIN = "text/x.cucumber.gherkin+plain", 30 | TEXT_X_CUCUMBER_GHERKIN_MARKDOWN = "text/x.cucumber.gherkin+markdown", 31 | } 32 | -------------------------------------------------------------------------------- /lib/helpers/options.ts: -------------------------------------------------------------------------------- 1 | import type * as messages from "@cucumber/messages"; 2 | 3 | import { collectTagNames } from "./ast"; 4 | 5 | import { 6 | ConfigurationEntry, 7 | looksLikeOptions, 8 | tagToCypressOptions, 9 | } from "./tag-parser"; 10 | import { TEST_ISOLATION_CONFIGURATION_OPTION } from "../constants"; 11 | 12 | export function tagsToOptions(tags: readonly messages.Tag[]) { 13 | return collectTagNames(tags) 14 | .filter(looksLikeOptions) 15 | .map(tagToCypressOptions); 16 | } 17 | 18 | export function isExclusivelySuiteConfiguration(entry: ConfigurationEntry) { 19 | // TODO: Remove type cast once support for v10 is removed. 20 | return entry[0] === (TEST_ISOLATION_CONFIGURATION_OPTION as string); 21 | } 22 | 23 | export function isNotExclusivelySuiteConfiguration(entry: ConfigurationEntry) { 24 | // TODO: Remove type cast once support for v10 is removed. 25 | return entry[0] !== (TEST_ISOLATION_CONFIGURATION_OPTION as string); 26 | } 27 | 28 | export function hasExclusivelySuiteConfiguration( 29 | config: Cypress.TestConfigOverrides, 30 | ) { 31 | return Object.keys(config).includes(TEST_ISOLATION_CONFIGURATION_OPTION); 32 | } 33 | -------------------------------------------------------------------------------- /lib/helpers/paths.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export function ensureIsAbsolute(root: string, maybeRelativePath: string) { 4 | if (path.isAbsolute(maybeRelativePath)) { 5 | return maybeRelativePath; 6 | } else { 7 | return path.join(root, maybeRelativePath); 8 | } 9 | } 10 | 11 | export function ensureIsRelative(root: string, maybeRelativePath: string) { 12 | if (path.isAbsolute(maybeRelativePath)) { 13 | return path.relative(root, maybeRelativePath); 14 | } else { 15 | return maybeRelativePath; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/helpers/prepare-registry.ts: -------------------------------------------------------------------------------- 1 | import { Registry, assignRegistry, freeRegistry } from "../registry"; 2 | 3 | const registry = new Registry(); 4 | 5 | assignRegistry(registry); 6 | 7 | export function getAndFreeRegistry() { 8 | freeRegistry(); 9 | return registry; 10 | } 11 | -------------------------------------------------------------------------------- /lib/helpers/snippets.ts: -------------------------------------------------------------------------------- 1 | import { GeneratedExpression } from "@cucumber/cucumber-expressions"; 2 | 3 | import type * as messages from "@cucumber/messages"; 4 | 5 | import { PickleStepType } from "./messages-enums"; 6 | 7 | const TEMPLATE = ` 8 | [function]("[definition]", function ([arguments]) { 9 | return "pending"; 10 | }); 11 | `.trim(); 12 | 13 | export function getFunctionName(type: messages.PickleStepType) { 14 | switch (type) { 15 | case PickleStepType.CONTEXT: 16 | return "Given"; 17 | case PickleStepType.ACTION: 18 | return "When"; 19 | case PickleStepType.OUTCOME: 20 | return "Then"; 21 | case PickleStepType.UNKNOWN: 22 | return "Given"; 23 | default: 24 | throw "Unknown PickleStepType: " + type; 25 | } 26 | } 27 | 28 | export function generateSnippet( 29 | expression: GeneratedExpression, 30 | type: messages.PickleStepType, 31 | parameter: "dataTable" | "docString" | null, 32 | ) { 33 | const definition = expression.source 34 | .replace(/\\/g, "\\\\") 35 | .replace(/"/g, '\\"'); 36 | 37 | const stepParameterNames = parameter ? [parameter] : []; 38 | 39 | const args = expression.parameterNames.concat(stepParameterNames).join(", "); 40 | 41 | return TEMPLATE.replace("[function]", getFunctionName(type)) 42 | .replace("[definition]", definition) 43 | .replace("[arguments]", args); 44 | } 45 | -------------------------------------------------------------------------------- /lib/helpers/strings.ts: -------------------------------------------------------------------------------- 1 | export function minIndent(content: string) { 2 | const match = content.match(/^[ \t]*(?=\S)/gm); 3 | 4 | if (!match) { 5 | return 0; 6 | } 7 | 8 | return match.reduce((r, a) => Math.min(r, a.length), Infinity); 9 | } 10 | 11 | export function stripIndent(content: string) { 12 | const indent = minIndent(content); 13 | 14 | if (indent === 0) { 15 | return content; 16 | } 17 | 18 | const regex = new RegExp(`^[ \\t]{${indent}}`, "gm"); 19 | 20 | return content.replace(regex, ""); 21 | } 22 | 23 | export function indent( 24 | string: string, 25 | options: { 26 | count?: number; 27 | indent?: string; 28 | includeEmptyLines?: boolean; 29 | } = {}, 30 | ) { 31 | const { count = 1, indent = " ", includeEmptyLines = false } = options; 32 | 33 | if (count === 0) { 34 | return string; 35 | } 36 | 37 | const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; 38 | 39 | return string.replace(regex, indent.repeat(count)); 40 | } 41 | -------------------------------------------------------------------------------- /lib/helpers/tag-parser/errors.ts: -------------------------------------------------------------------------------- 1 | export class TagError extends Error {} 2 | 3 | export class TagTokenizerError extends TagError {} 4 | 5 | export class TagParserError extends TagError {} 6 | -------------------------------------------------------------------------------- /lib/helpers/tag-parser/index.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./parser"; 2 | 3 | type Entry = { 4 | [K in keyof T]: [K, T[K]]; 5 | }[keyof T]; 6 | 7 | export type ConfigurationEntry = Entry & {}; 8 | 9 | export function tagToCypressOptions(tag: string): ConfigurationEntry { 10 | return new Parser(tag).parse() as any; 11 | } 12 | 13 | export function looksLikeOptions(tag: string) { 14 | return tag.includes("("); 15 | } 16 | -------------------------------------------------------------------------------- /lib/helpers/type-guards.ts: -------------------------------------------------------------------------------- 1 | export function isString(value: unknown): value is string { 2 | return typeof value === "string"; 3 | } 4 | 5 | export function isBoolean(value: unknown): value is boolean { 6 | return typeof value === "boolean"; 7 | } 8 | 9 | export function isFalse(value: unknown): value is false { 10 | return value === false; 11 | } 12 | 13 | export function isStringOrFalse(value: unknown): value is string | false { 14 | return isString(value) || isFalse(value); 15 | } 16 | 17 | export function isStringOrStringArray( 18 | value: unknown, 19 | ): value is string | string[] { 20 | return ( 21 | typeof value === "string" || (Array.isArray(value) && value.every(isString)) 22 | ); 23 | } 24 | 25 | export function notNull(value: T | null | undefined): value is T { 26 | return value != null; 27 | } 28 | -------------------------------------------------------------------------------- /lib/public-member-types.ts: -------------------------------------------------------------------------------- 1 | import type * as messages from "@cucumber/messages"; 2 | 3 | export interface IParameterTypeDefinition { 4 | name: string; 5 | regexp: RegExp; 6 | transformer?: (this: C, ...match: string[]) => T; 7 | } 8 | 9 | export interface IRunHookOptions { 10 | order?: number; 11 | } 12 | 13 | export interface IRunHookBody { 14 | (this: Mocha.Context): void; 15 | } 16 | 17 | export interface ICaseHookOptions { 18 | name?: string; 19 | tags?: string; 20 | order?: number; 21 | } 22 | 23 | export interface ICaseHookParameter { 24 | pickle: messages.Pickle; 25 | gherkinDocument: messages.GherkinDocument; 26 | testCaseStartedId: string; 27 | } 28 | 29 | export interface ICaseHookBody { 30 | (this: Mocha.Context, options: ICaseHookParameter): void; 31 | } 32 | 33 | export interface IStepHookOptions { 34 | name?: string; 35 | tags?: string; 36 | order?: number; 37 | } 38 | 39 | export interface IStepHookParameter { 40 | pickle: messages.Pickle; 41 | pickleStep: messages.PickleStep; 42 | gherkinDocument: messages.GherkinDocument; 43 | testCaseStartedId: string; 44 | testStepId: string; 45 | } 46 | 47 | export interface IStepHookBody { 48 | (this: Mocha.Context, options: IStepHookParameter): void; 49 | } 50 | 51 | export interface IStepDefinitionBody< 52 | T extends unknown[], 53 | C extends Mocha.Context, 54 | > { 55 | (this: C, ...args: T): void; 56 | } 57 | -------------------------------------------------------------------------------- /lib/subpath-entrypoints/browserify.ts: -------------------------------------------------------------------------------- 1 | import { PassThrough, Transform, TransformCallback } from "stream"; 2 | 3 | import debug from "../helpers/debug"; 4 | 5 | import { compile } from "../template"; 6 | 7 | export default function transform( 8 | configuration: Cypress.PluginConfigOptions, 9 | filepath: string, 10 | ) { 11 | if (!filepath.match(".feature$")) { 12 | return new PassThrough(); 13 | } 14 | 15 | debug(`compiling ${filepath}`); 16 | 17 | let buffer = Buffer.alloc(0); 18 | 19 | return new Transform({ 20 | transform(chunk, encoding, done) { 21 | buffer = Buffer.concat([buffer, chunk]); 22 | done(); 23 | }, 24 | async flush(done: TransformCallback) { 25 | try { 26 | done( 27 | null, 28 | await compile(configuration, buffer.toString("utf8"), filepath), 29 | ); 30 | 31 | debug(`compiled ${filepath}`); 32 | } catch (e: any) { 33 | done(e); 34 | } 35 | }, 36 | }); 37 | } 38 | 39 | export { transform }; 40 | 41 | export function preprendTransformerToOptions( 42 | configuration: Cypress.PluginConfigOptions, 43 | options: any, 44 | ) { 45 | let wrappedTransform; 46 | 47 | if ( 48 | !options.browserifyOptions || 49 | !Array.isArray(options.browserifyOptions.transform) 50 | ) { 51 | wrappedTransform = [transform.bind(null, configuration)]; 52 | } else { 53 | wrappedTransform = [ 54 | transform.bind(null, configuration), 55 | ...options.browserifyOptions.transform, 56 | ]; 57 | } 58 | 59 | return { 60 | ...options, 61 | browserifyOptions: { 62 | ...(options.browserifyOptions || {}), 63 | transform: wrappedTransform, 64 | }, 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /lib/subpath-entrypoints/pretty-reporter.ts: -------------------------------------------------------------------------------- 1 | import Mocha from "mocha"; 2 | 3 | export = class CucumberReporter extends Mocha.reporters.Base { 4 | constructor(runner: Mocha.Runner, options?: Mocha.MochaOptions) { 5 | super(runner, options); 6 | 7 | runner.once(Mocha.Runner.constants.EVENT_RUN_BEGIN, () => { 8 | console.log(); 9 | }); 10 | 11 | runner.once(Mocha.Runner.constants.EVENT_RUN_END, this.epilogue.bind(this)); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/subpath-entrypoints/rollup.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "rollup"; 2 | 3 | import { compile } from "../template"; 4 | 5 | export function createRollupPlugin( 6 | config: Cypress.PluginConfigOptions, 7 | ): Plugin { 8 | return { 9 | name: "transform-feature", 10 | async transform(src: string, id: string) { 11 | if (/\.feature$/.test(id)) { 12 | return { 13 | code: await compile(config, src, id), 14 | map: null, 15 | }; 16 | } 17 | }, 18 | }; 19 | } 20 | 21 | export default createRollupPlugin; 22 | -------------------------------------------------------------------------------- /lib/subpath-entrypoints/webpack.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderDefinition } from "webpack"; 2 | 3 | import { compile } from "../template"; 4 | 5 | const loader: LoaderDefinition = function (data) { 6 | const callback = this.async(); 7 | 8 | const config: Cypress.PluginConfigOptions = this.query as any; 9 | 10 | compile(config, data, this.resourcePath).then( 11 | (result) => callback(null, result), 12 | (error) => callback(error), 13 | ); 14 | }; 15 | 16 | export default loader; 17 | -------------------------------------------------------------------------------- /patches/@cucumber+cucumber+10.9.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@cucumber/cucumber/lib/formatter/helpers/usage_helpers/index.js b/node_modules/@cucumber/cucumber/lib/formatter/helpers/usage_helpers/index.js 2 | index 35339d7..c6001c0 100644 3 | --- a/node_modules/@cucumber/cucumber/lib/formatter/helpers/usage_helpers/index.js 4 | +++ b/node_modules/@cucumber/cucumber/lib/formatter/helpers/usage_helpers/index.js 5 | @@ -86,10 +86,7 @@ function buildResult(mapping) { 6 | .map((stepDefinitionId) => { 7 | const { matches, ...rest } = mapping[stepDefinitionId]; 8 | const sortedMatches = matches.sort((a, b) => { 9 | - if (a.duration === b.duration) { 10 | - return a.text < b.text ? -1 : 1; 11 | - } 12 | - return normalizeDuration(b.duration) - normalizeDuration(a.duration); 13 | + return a.text.localeCompare(b.text); 14 | }); 15 | const result = { matches: sortedMatches, ...rest }; 16 | const durations = matches 17 | @@ -101,7 +98,7 @@ function buildResult(mapping) { 18 | } 19 | return result; 20 | }) 21 | - .sort((a, b) => normalizeDuration(b.meanDuration) - normalizeDuration(a.meanDuration)); 22 | + .sort((a, b) => a.uri.localeCompare(b.uri)); 23 | } 24 | function getUsage({ stepDefinitions, eventDataCollector, }) { 25 | const mapping = buildMapping({ stepDefinitions, eventDataCollector }); 26 | -------------------------------------------------------------------------------- /patches/patch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Even the simplest shell script fails in Windows, hence this file. 3 | * 4 | * The intention here is to only run `patch-package` during local installation, IE. during 5 | * development of the library and not upon installation by the users of the library. 6 | */ 7 | if (process.cwd() === process.env.INIT_CWD) { 8 | require("patch-package"); 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", "npm:unpublishSafe", "schedule:daily"], 4 | "automerge": true, 5 | "automergeType": "branch", 6 | "packageRules": [ 7 | { 8 | "matchManagers": ["npm"], 9 | "matchDepTypes": ["devDependencies"], 10 | "enabled": false 11 | } 12 | ], 13 | "ignoreDeps": ["source-map"] 14 | } 15 | -------------------------------------------------------------------------------- /test-d/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "types": ["node", "mocha", "cypress"], 6 | }, 7 | "include": [ 8 | "*.test-d.ts", 9 | "../augmentations.d.ts", 10 | "../declarations.d.ts", 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "features/**/*.ts", 5 | "lib/**/*.ts", 6 | "test/**/*.ts", 7 | "test-d/**/*.ts", 8 | ], 9 | "exclude": [] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "files": true 4 | }, 5 | "compilerOptions": { 6 | "outDir": "dist", 7 | "declaration": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "target": "ES2017", 13 | "module": "nodenext", 14 | "types": ["node", "cypress"], 15 | "lib": ["dom", "ESNext"] 16 | }, 17 | "include": [ 18 | "lib/**/*.ts", 19 | "augmentations.d.ts", 20 | "declarations.d.ts" 21 | ], 22 | "exclude": [ 23 | "lib/**/*.test.ts" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------