From 11f89a3e65661e7c63a4cabc46020759114a9cb5 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 13 Apr 2022 15:18:52 +0200 Subject: [PATCH 01/62] Only run examples in master --- .github/workflows/examples.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 4722f07e..ec65b4b5 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -1,11 +1,9 @@ name: Examples -on: [push, pull_request] - -# on: -# push: -# branches: -# - master +on: + push: + branches: + - master defaults: run: From 92e12cc4f6f1d5b0d3ed2c8fd9de29efd0152434 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 13 Apr 2022 15:27:31 +0200 Subject: [PATCH 02/62] v9.0.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31844d02..5124e25d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.0.1 + +- Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/694) when running all specs. + ## v9.0.0 This is the point where [badeball](https://github.com/badeball)'s fork becomes the mainline and replaces [TheBrainFamily](https://github.com/TheBrainFamily)'s implementation. This implementation has been re-written from scratch in TypeScript, has more thorough test coverage and is filled with a bunch of new feature. Read more about the transfer of ownership [here](https://github.com/badeball/cypress-cucumber-preprocessor/issues/689). diff --git a/package.json b/package.json index 0928c8cd..5c8687e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.0", + "version": "9.0.1", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 49e9723ae8c21a225962a64dcf588aa91bd6eec5 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 13 Apr 2022 14:55:30 +0200 Subject: [PATCH 03/62] Fix "All Integration Specs" ish This feature will load # Please enter the commit message for your changes. Lines starting --- lib/constants.ts | 2 +- lib/create-tests.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/constants.ts b/lib/constants.ts index 7750cd18..4edd24cd 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -8,7 +8,7 @@ export const TASK_CREATE_STRING_ATTACHMENT = "cypress-cucumber-preprocessor:create-string-attachment"; export const INTERNAL_PROPERTY_NAME = - "_cypress-cucumber-preprocessor-do-not-use"; + "__cypress_cucumber_preprocessor_dont_use_this"; export const HOOK_FAILURE_EXPR = /Because this error occurred during a `[^`]+` hook we are skipping all of the remaining tests\./; diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 8910cf52..4321a955 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -25,6 +25,12 @@ import { getTags } from "./environment-helpers"; import { notNull } from "./type-guards"; +declare global { + namespace globalThis { + var __cypress_cucumber_preprocessor_dont_use_this: true | undefined; + } +} + type Node = ReturnType; interface CompositionContext { @@ -562,6 +568,14 @@ export default function createTests( ); } + const isHooksAttached = globalThis[INTERNAL_PROPERTY_NAME]; + + if (isHooksAttached) { + return; + } else { + globalThis[INTERNAL_PROPERTY_NAME] = true; + } + afterEach(function () { freeRegistry(); From ffd8948a857ef24da6e8a12b67008e0efe9c14e8 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 13 Apr 2022 15:42:08 +0200 Subject: [PATCH 04/62] v9.0.2 --- CHANGELOG.md | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5124e25d..23c5116e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ All notable changes to this project will be documented in this file. -## v9.0.1 +## v9.0.2 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/694) when running all specs. +## v9.0.1 + +Due to an publishing error from my side, this version is identical to v9.0.0. + ## v9.0.0 This is the point where [badeball](https://github.com/badeball)'s fork becomes the mainline and replaces [TheBrainFamily](https://github.com/TheBrainFamily)'s implementation. This implementation has been re-written from scratch in TypeScript, has more thorough test coverage and is filled with a bunch of new feature. Read more about the transfer of ownership [here](https://github.com/badeball/cypress-cucumber-preprocessor/issues/689). diff --git a/package.json b/package.json index 5c8687e1..00c66b9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.1", + "version": "9.0.2", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 8419238b22726b28c97c352dd02c1824f4173b0f Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 19 Apr 2022 10:23:53 +0200 Subject: [PATCH 05/62] Never return undefined in tasks --- CHANGELOG.md | 4 ++++ lib/add-cucumber-preprocessor-plugin.ts | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c5116e..e7294b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.0.3 + +- Fixed an issue where the preprocessor was throwing in interactive mode when JSON reports was enabled. + ## v9.0.2 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/694) when running all specs. diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index 0251c5be..d65e670c 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -163,7 +163,7 @@ export default async function addCucumberPreprocessorPlugin( on("task", { [TASK_APPEND_MESSAGES]: (messages: messages.IEnvelope[]) => { if (!currentSpecMessages) { - return; + return true; } currentSpecMessages.push(...messages); @@ -173,7 +173,7 @@ export default async function addCucumberPreprocessorPlugin( [TASK_TEST_STEP_STARTED]: (testStepStartedId) => { if (!currentSpecMessages) { - return; + return true; } currentTestStepStartedId = testStepStartedId; @@ -183,7 +183,7 @@ export default async function addCucumberPreprocessorPlugin( [TASK_CREATE_STRING_ATTACHMENT]: ({ data, mediaType, encoding }) => { if (!currentSpecMessages) { - return; + return true; } const message: messages.IEnvelope = { From 9ebfc0271967aa826a444b16adfc2a77999e4364 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 19 Apr 2022 10:31:06 +0200 Subject: [PATCH 06/62] v9.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 00c66b9f..665a9f31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.2", + "version": "9.0.3", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 9436fe7a73cfaf2f91b43053433923b0f7b5fed9 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 19 Apr 2022 21:54:25 +0200 Subject: [PATCH 07/62] Prevent error when experimentalInteractiveRunEvents is enabled This ensures after:spec is invoked even in run mode, but then without the 2nd argument, as per documentation [1]. [1] https://docs.cypress.io/api/plugins/after-spec-api --- CHANGELOG.md | 4 ++++ lib/add-cucumber-preprocessor-plugin.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7294b7d..1d36829a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.0.4 + +- Prevent an error when `experimentalInteractiveRunEvents` is enabled. + ## v9.0.3 - Fixed an issue where the preprocessor was throwing in interactive mode when JSON reports was enabled. diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index d65e670c..d3508cc6 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -105,7 +105,8 @@ export default async function addCucumberPreprocessorPlugin( }); on("after:spec", async (_spec, results) => { - if (!preprocessor.messages.enabled || !currentSpecMessages) { + // `results` is undefined when running via `cypress open`. + if (!preprocessor.messages.enabled || !currentSpecMessages || !results) { return; } From 989d49c7b1acc31f3735a23294eeb984f917c72b Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 19 Apr 2022 22:03:03 +0200 Subject: [PATCH 08/62] v9.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 665a9f31..594c6b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.3", + "version": "9.0.4", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 6cff64701d6066efb0b75bb0b76bb2f4d4b11004 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 20 Apr 2022 10:38:14 +0200 Subject: [PATCH 09/62] Correct some types --- CHANGELOG.md | 4 ++++ lib/index.ts | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d36829a..8e4826d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.0.5 + +- Correct types for `isFeature` and `doesFeatureMatch`. + ## v9.0.4 - Prevent an error when `experimentalInteractiveRunEvents` is enabled. diff --git a/lib/index.ts b/lib/index.ts index 25017112..fa455016 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -36,11 +36,11 @@ function createUnimplemented() { export { NOT_FEATURE_ERROR } from "./methods"; -export function isFeature() { +export function isFeature(): boolean { throw createUnimplemented(); } -export function doesFeatureMatch(expression: string) { +export function doesFeatureMatch(expression: string): boolean { throw createUnimplemented(); } diff --git a/package.json b/package.json index 594c6b06..bd8ce2e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.4", + "version": "9.0.5", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 068892aa3fd1b0bbe6faa5852dacab24e526030f Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 23 Apr 2022 22:19:49 +0200 Subject: [PATCH 10/62] Extend test coverage of @focus --- features/tags/smart_tagging.feature | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/features/tags/smart_tagging.feature b/features/tags/smart_tagging.feature index e425f0bd..20914f80 100644 --- a/features/tags/smart_tagging.feature +++ b/features/tags/smart_tagging.feature @@ -3,6 +3,7 @@ Feature: smart tagging Rules: - In presence of any @focus tag, only tests tagged with this should be run - This behavior is scoped per file + - Presence of this tag override any other tag filter Scenario: 1 / 2 scenarios tagged with @focus Given a file named "cypress/integration/a.feature" with: @@ -119,3 +120,24 @@ Feature: smart tagging When I run cypress Then it should appear to have run the scenario "a scenario" And it should appear to have run the scenario "another scenario" + + Scenario: specifying tags + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + @focus + Scenario: a scenario + Given a step + + @foo + Scenario: another scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress with "-e tags=@foo" + Then it should appear to have run the scenario "a scenario" + And it should appear to have skipped the scenario "another scenario" From 2c4b73f2497f988ca83076f048eb177e6ae04269 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 23 Apr 2022 22:22:28 +0200 Subject: [PATCH 11/62] Rename @focus ~> @only --- docs/tags.md | 2 +- ...smart_tagging.feature => only_tag.feature} | 30 +++++++++---------- lib/create-tests.ts | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) rename features/tags/{smart_tagging.feature => only_tag.feature} (89%) diff --git a/docs/tags.md b/docs/tags.md index 81232e5f..312bf688 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -58,4 +58,4 @@ By default, all filtered tests are made *pending* using `it.skip` method. If you ## Smart tagging -In the absence of a `tags` value and presence of a scenario with `@focus`, only said scenario will run. You can in other words use this tag as you would use `.only()` in Mocha. +In the absence of a `tags` value and presence of a scenario with `@only`, only said scenario will run. You can in other words use this tag as you would use `.only()` in Mocha. diff --git a/features/tags/smart_tagging.feature b/features/tags/only_tag.feature similarity index 89% rename from features/tags/smart_tagging.feature rename to features/tags/only_tag.feature index 20914f80..f2f806ec 100644 --- a/features/tags/smart_tagging.feature +++ b/features/tags/only_tag.feature @@ -1,15 +1,15 @@ -Feature: smart tagging +Feature: @only tag Rules: - - In presence of any @focus tag, only tests tagged with this should be run + - In presence of any @only tag, only tests tagged with this should be run - This behavior is scoped per file - Presence of this tag override any other tag filter - Scenario: 1 / 2 scenarios tagged with @focus + Scenario: 1 / 2 scenarios tagged with @only Given a file named "cypress/integration/a.feature" with: """ Feature: a feature - @focus + @only Scenario: a scenario Given a step @@ -24,15 +24,15 @@ Feature: smart tagging Then it should appear to have run the scenario "a scenario" And it should appear to have skipped the scenario "another scenario" - Scenario: 2 / 2 scenarios tagged with @focus + Scenario: 2 / 2 scenarios tagged with @only Given a file named "cypress/integration/a.feature" with: """ Feature: a feature - @focus + @only Scenario: a scenario Given a step - @focus + @only Scenario: another scenario """ And a file named "cypress/support/step_definitions/steps.js" with: @@ -43,14 +43,14 @@ Feature: smart tagging When I run cypress Then it should appear as if both tests ran - Scenario: 1 / 2 example table tagged with @focus + Scenario: 1 / 2 example table tagged with @only Given a file named "cypress/integration/a.feature" with: """ Feature: a feature Scenario Outline: a scenario Given a step - @focus + @only Examples: | value | | foo | @@ -70,19 +70,19 @@ Feature: smart tagging Then it should appear to have run the scenario "a scenario (example #1)" And it should appear to have skipped the scenario "a scenario (example #2)" - Scenario: 2 / 2 example table tagged with @focus + Scenario: 2 / 2 example table tagged with @only Given a file named "cypress/integration/a.feature" with: """ Feature: a feature Scenario Outline: a scenario Given a step - @focus + @only Examples: | value | | foo | - @focus + @only Examples: | value | | bar | @@ -98,11 +98,11 @@ Feature: smart tagging Then it should appear to have run the scenario "a scenario (example #1)" And it should appear to have run the scenario "a scenario (example #2)" - Scenario: one file with @focus, one without + Scenario: one file with @only, one without Given a file named "cypress/integration/a.feature" with: """ Feature: a feature - @focus + @only Scenario: a scenario Given a step """ @@ -125,7 +125,7 @@ Feature: smart tagging Given a file named "cypress/integration/a.feature" with: """ Feature: a feature - @focus + @only Scenario: a scenario Given a step diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 4321a955..a9433115 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -545,8 +545,8 @@ export default function createTests( const testFilter = collectTagNamesFromGherkinDocument( gherkinDocument - ).includes("@focus") - ? parse("@focus") + ).includes("@only") + ? parse("@only") : environmentTags ? parse(environmentTags) : noopNode; From c7b8cb10cc50ea288813c0300f243cea2ed94235 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 23 Apr 2022 22:25:46 +0200 Subject: [PATCH 12/62] Add support for @focus for backwards compatibility Maybe this will raise a warning in the future. --- features/tags/only_tag.feature | 19 +++++++++++++++++++ lib/create-tests.ts | 15 ++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/features/tags/only_tag.feature b/features/tags/only_tag.feature index f2f806ec..f89f8efe 100644 --- a/features/tags/only_tag.feature +++ b/features/tags/only_tag.feature @@ -141,3 +141,22 @@ Feature: @only tag When I run cypress with "-e tags=@foo" Then it should appear to have run the scenario "a scenario" And it should appear to have skipped the scenario "another scenario" + + Scenario: @focus (backwards compatibility) + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + @focus + Scenario: a scenario + Given a step + + Scenario: another scenario + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress + Then it should appear to have run the scenario "a scenario" + And it should appear to have skipped the scenario "another scenario" diff --git a/lib/create-tests.ts b/lib/create-tests.ts index a9433115..49029c52 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -543,13 +543,14 @@ export default function createTests( }); } - const testFilter = collectTagNamesFromGherkinDocument( - gherkinDocument - ).includes("@only") - ? parse("@only") - : environmentTags - ? parse(environmentTags) - : noopNode; + const tagsInDocument = collectTagNamesFromGherkinDocument(gherkinDocument); + + const testFilter = + tagsInDocument.includes("@only") || tagsInDocument.includes("@focus") + ? parse("@only or @focus") + : environmentTags + ? parse(environmentTags) + : noopNode; if (gherkinDocument.feature) { createFeature( From e9d455cf536f9c0c123ee40cd1e63e0464369120 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 23 Apr 2022 22:37:58 +0200 Subject: [PATCH 13/62] Skip tests marked with @skip This is similar to how .skip() works in Mocha. --- docs/tags.md | 2 + features/step_definitions/cli_steps.ts | 7 ++ features/tags/skip_tag.feature | 115 +++++++++++++++++++++++++ lib/create-tests.ts | 2 +- 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 features/tags/skip_tag.feature diff --git a/docs/tags.md b/docs/tags.md index 312bf688..18b759b1 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -59,3 +59,5 @@ By default, all filtered tests are made *pending* using `it.skip` method. If you ## Smart tagging In the absence of a `tags` value and presence of a scenario with `@only`, only said scenario will run. You can in other words use this tag as you would use `.only()` in Mocha. + +Similarly, scenarios tagged with `@skip` will always be skipped, despite being tagged with something else matching a tag filter. diff --git a/features/step_definitions/cli_steps.ts b/features/step_definitions/cli_steps.ts index 31ed2065..6388d432 100644 --- a/features/step_definitions/cli_steps.ts +++ b/features/step_definitions/cli_steps.ts @@ -54,6 +54,13 @@ Then("it should appear as if both tests ran", function () { assert.match(this.lastRun.stdout, /All specs passed!\s+\d+ms\s+2\s+2\D/); }); +Then("it should appear as if both tests were skipped", function () { + assert.match( + this.lastRun.stdout, + /All specs passed!\s+\d+ms\s+2\s+-\s+-\s+2\D/ + ); +}); + Then("it should appear to not have ran spec {string}", function (spec) { assert.doesNotMatch( this.lastRun.stdout, diff --git a/features/tags/skip_tag.feature b/features/tags/skip_tag.feature new file mode 100644 index 00000000..5d585b56 --- /dev/null +++ b/features/tags/skip_tag.feature @@ -0,0 +1,115 @@ +Feature: @skip tag + + Rules: + - Tests tagged with @skip are skipped + - Presence of this tag override any other tag filter + + Scenario: 1 / 2 scenarios tagged with @skip + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a step + + @skip + Scenario: another scenario + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress + Then it should appear to have run the scenario "a scenario" + And it should appear to have skipped the scenario "another scenario" + + Scenario: 2 / 2 scenarios tagged with @skip + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + @skip + Scenario: a scenario + Given a step + + @skip + Scenario: another scenario + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress + Then it should appear as if both tests were skipped + + Scenario: 1 / 2 example table tagged with @skip + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario Outline: a scenario + Given a step + + Examples: + | value | + | foo | + + @skip + Examples: + | value | + | bar | + + Scenario: another scenario + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress + Then it should appear to have run the scenario "a scenario (example #1)" + And it should appear to have skipped the scenario "a scenario (example #2)" + + Scenario: 2 / 2 example table tagged with @skip + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario Outline: a scenario + Given a step + + @skip + Examples: + | value | + | foo | + + @skip + Examples: + | value | + | bar | + + Scenario: another scenario + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress + Then it should appear to have skipped the scenario "a scenario (example #1)" + And it should appear to have skipped the scenario "a scenario (example #2)" + + Scenario: specifying tags + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + @foo + @skip + Scenario: a scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function(table) {}); + """ + When I run cypress with "-e tags=@foo" + Then it should appear to have skipped the scenario "a scenario" diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 49029c52..b0e7b3f7 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -303,7 +303,7 @@ function createPickle( const astIdMap = gherkinDocumentsAstIdMaps.get(gherkinDocument); - if (!testFilter.evaluate(tags)) { + if (!testFilter.evaluate(tags) || tags.includes("@skip")) { if (!context.omitFiltered) { it.skip(scenarioName); } From 219de7abdf8cb386442014904dafcf2b9c08a3ef Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 23 Apr 2022 22:59:08 +0200 Subject: [PATCH 14/62] v9.1.0 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4826d0..9cf7ecd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.1.0 + +- Automatically skip tests marked with `@skip`. + ## v9.0.5 - Correct types for `isFeature` and `doesFeatureMatch`. diff --git a/package.json b/package.json index bd8ce2e8..dfda256b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.0.5", + "version": "9.1.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 721d715674267f92d51b69681e920a70ffd60eff Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 26 Apr 2022 18:01:06 +0200 Subject: [PATCH 15/62] We-write fixtures This merely re-orders properties. --- features/fixtures/failing-after.json | 16 ++++++++-------- features/fixtures/failing-before.json | 16 ++++++++-------- features/fixtures/failing-step.json | 16 ++++++++-------- features/fixtures/passed-outline.json | 20 ++++++++++---------- features/fixtures/pending-steps.json | 16 ++++++++-------- features/fixtures/undefined-steps.json | 16 ++++++++-------- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/features/fixtures/failing-after.json b/features/fixtures/failing-after.json index 1e25cdda..54fa8a2f 100644 --- a/features/fixtures/failing-after.json +++ b/features/fixtures/failing-after.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario", "line": 2, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -37,8 +31,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] diff --git a/features/fixtures/failing-before.json b/features/fixtures/failing-before.json index e2c6f0aa..a5fd5e1a 100644 --- a/features/fixtures/failing-before.json +++ b/features/fixtures/failing-before.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario", "line": 2, "name": "a scenario", - "type": "scenario", "before": [ { "result": { @@ -37,8 +31,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] diff --git a/features/fixtures/failing-step.json b/features/fixtures/failing-step.json index b472aaea..9c86025e 100644 --- a/features/fixtures/failing-step.json +++ b/features/fixtures/failing-step.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario", "line": 2, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -38,8 +32,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] diff --git a/features/fixtures/passed-outline.json b/features/fixtures/passed-outline.json index ccf1d0ab..62039da4 100644 --- a/features/fixtures/passed-outline.json +++ b/features/fixtures/passed-outline.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario Outline", "line": 6, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -26,7 +20,8 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" }, { "description": "", @@ -34,7 +29,6 @@ "keyword": "Scenario Outline", "line": 7, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -47,8 +41,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] diff --git a/features/fixtures/pending-steps.json b/features/fixtures/pending-steps.json index db1d126e..6e337165 100644 --- a/features/fixtures/pending-steps.json +++ b/features/fixtures/pending-steps.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario", "line": 2, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -48,8 +42,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] diff --git a/features/fixtures/undefined-steps.json b/features/fixtures/undefined-steps.json index 64f6bbb7..9d66ca4f 100644 --- a/features/fixtures/undefined-steps.json +++ b/features/fixtures/undefined-steps.json @@ -1,10 +1,5 @@ [ { - "keyword": "Feature", - "name": "a feature", - "line": 1, - "id": "a-feature", - "uri": "cypress/integration/a.feature", "description": "", "elements": [ { @@ -13,7 +8,6 @@ "keyword": "Scenario", "line": 2, "name": "a scenario", - "type": "scenario", "steps": [ { "keyword": "Given ", @@ -37,8 +31,14 @@ "location": "not available:0" } } - ] + ], + "type": "scenario" } - ] + ], + "id": "a-feature", + "keyword": "Feature", + "line": 1, + "name": "a feature", + "uri": "cypress/integration/a.feature" } ] From bc17781a397ac1dd1099ae26f02741f9cb8b6186 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 26 Apr 2022 18:03:05 +0200 Subject: [PATCH 16/62] Add timestamps and durations to messages --- features/fixtures/attachments/screenshot.json | 1 + features/fixtures/attachments/string.json | 1 + features/fixtures/failing-after.json | 1 + features/fixtures/multiple-features.json | 2 + features/fixtures/passed-example.json | 1 + features/fixtures/passed-outline.json | 2 + features/fixtures/pending-steps.json | 1 + features/fixtures/retried.json | 1 + features/step_definitions/json_steps.ts | 2 +- lib/create-tests.ts | 68 ++++++++++++++++++- lib/registry.ts | 2 +- 11 files changed, 77 insertions(+), 5 deletions(-) diff --git a/features/fixtures/attachments/screenshot.json b/features/fixtures/attachments/screenshot.json index a72e2d7c..789b677b 100644 --- a/features/fixtures/attachments/screenshot.json +++ b/features/fixtures/attachments/screenshot.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/attachments/string.json b/features/fixtures/attachments/string.json index 93ed02c8..20437769 100644 --- a/features/fixtures/attachments/string.json +++ b/features/fixtures/attachments/string.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/failing-after.json b/features/fixtures/failing-after.json index 54fa8a2f..8101437a 100644 --- a/features/fixtures/failing-after.json +++ b/features/fixtures/failing-after.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/multiple-features.json b/features/fixtures/multiple-features.json index 9937152d..2fabdb5f 100644 --- a/features/fixtures/multiple-features.json +++ b/features/fixtures/multiple-features.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { @@ -45,6 +46,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/passed-example.json b/features/fixtures/passed-example.json index b64b91fa..a5646a73 100644 --- a/features/fixtures/passed-example.json +++ b/features/fixtures/passed-example.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/passed-outline.json b/features/fixtures/passed-outline.json index 62039da4..0ff51995 100644 --- a/features/fixtures/passed-outline.json +++ b/features/fixtures/passed-outline.json @@ -14,6 +14,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { @@ -35,6 +36,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/fixtures/pending-steps.json b/features/fixtures/pending-steps.json index 6e337165..ef75b328 100644 --- a/features/fixtures/pending-steps.json +++ b/features/fixtures/pending-steps.json @@ -14,6 +14,7 @@ "line": 3, "name": "a pending step", "result": { + "duration": 0, "status": "pending" }, "match": { diff --git a/features/fixtures/retried.json b/features/fixtures/retried.json index bcd76227..dd25be83 100644 --- a/features/fixtures/retried.json +++ b/features/fixtures/retried.json @@ -36,6 +36,7 @@ "line": 3, "name": "a step", "result": { + "duration": 0, "status": "passed" }, "match": { diff --git a/features/step_definitions/json_steps.ts b/features/step_definitions/json_steps.ts index 5ef42ca3..a910686b 100644 --- a/features/step_definitions/json_steps.ts +++ b/features/step_definitions/json_steps.ts @@ -34,7 +34,7 @@ function* traverseTree(object: any): Generator { function prepareJsonReport(tree: any) { for (const node of traverseTree(tree)) { if (hasOwnProperty(node, "duration")) { - delete node.duration; + node.duration = 0; } else if (hasOwnProperty(node, "uri") && typeof node.uri === "string") { node.uri = node.uri.replace(/\\/g, "/"); } diff --git a/lib/create-tests.ts b/lib/create-tests.ts index b0e7b3f7..08b95750 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -104,6 +104,34 @@ function collectExampleIds( .reduce((acum, el) => acum.concat(el), []); } +type StrictTimestamp = { + seconds: number; + nanos: number; +}; + +function createTimestamp(): StrictTimestamp { + const now = new Date().getTime(); + + const seconds = Math.floor(now / 1000); + + const nanos = (now - seconds * 1000) * 1000000; + + return { + seconds, + nanos, + }; +} + +function duration( + start: StrictTimestamp, + end: StrictTimestamp +): StrictTimestamp { + return { + seconds: end.seconds - start.seconds, + nanos: end.nanos - start.nanos, + }; +} + function createFeature( context: CompositionContext, feature: messages.GherkinDocument.IFeature @@ -332,6 +360,7 @@ function createPickle( id: testCaseStartedId, testCaseId, attempt: attempt++, + timestamp: createTimestamp(), }, }); @@ -352,28 +381,37 @@ function createPickle( message: "", }); + const start = createTimestamp(); + messages.stack.push({ testStepStarted: { testStepId: hook.id, testCaseStartedId, + timestamp: start, }, }); if (messages.enabled) { cy.task(TASK_TEST_STEP_STARTED, hook.id, { log: false }); } + + return cy.wrap(start, { log: false }); }) .then(() => { registry.runHook(this, hook); }) - .then(() => { + .then((start) => { + const end = createTimestamp(); + messages.stack.push({ testStepFinished: { testStepId: hook.id, testCaseStartedId, testStepResult: { status: Status.Passed, + duration: duration(start, end), }, + timestamp: end, }, }); @@ -418,19 +456,37 @@ function createPickle( cy.then(() => { internalProperties.currentStep = { pickleStep }; + const start = createTimestamp(); + messages.stack.push({ testStepStarted: { testStepId: pickleStep.id, testCaseStartedId, + timestamp: start, }, }); if (messages.enabled) { cy.task(TASK_TEST_STEP_STARTED, pickleStep.id, { log: false }); } + + return cy.wrap(start, { log: false }); }) - .then(() => registry.runStepDefininition(this, text, argument)) - .then((result) => { + .then((start) => { + return cy + .wrap(registry.runStepDefininition(this, text, argument), { + log: false, + }) + .then((result: any) => { + return { + start, + result, + }; + }); + }) + .then(({ start, result }) => { + const end = createTimestamp(); + if (result === "pending") { messages.stack.push({ testStepFinished: { @@ -438,7 +494,9 @@ function createPickle( testCaseStartedId, testStepResult: { status: Status.Pending, + duration: duration(start, end), }, + timestamp: end, }, }); @@ -480,7 +538,9 @@ function createPickle( testCaseStartedId, testStepResult: { status: Status.Passed, + duration: duration(start, end), }, + timestamp: end, }, }); @@ -624,6 +684,7 @@ export default function createTests( status: Status.Failed, message: this.currentTest?.err?.message, }, + timestamp: createTimestamp(), }, }; @@ -657,6 +718,7 @@ export default function createTests( messages.push({ testCaseFinished: { testCaseStartedId, + timestamp: createTimestamp(), }, }); diff --git a/lib/registry.ts b/lib/registry.ts index cb433ea1..89b9ad3b 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -155,7 +155,7 @@ export class Registry { world: Mocha.Context, text: string, argument?: DataTable | string - ) { + ): any { const stepDefinition = this.resolveStepDefintion(text); const args = stepDefinition.expression From 22940b7fdf06c5c045c16ac240b3f863c243e6f2 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 26 Apr 2022 18:07:55 +0200 Subject: [PATCH 17/62] v9.1.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf7ecd6..71dd77aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.1.1 + +- Add timestamps and durations to messages. + ## v9.1.0 - Automatically skip tests marked with `@skip`. diff --git a/package.json b/package.json index dfda256b..85d37992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.1.0", + "version": "9.1.1", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 87c8d5d04ebab1ef2c1661de2cffa90a8eaf2acb Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 29 Apr 2022 14:30:06 +0200 Subject: [PATCH 18/62] Be explicit about returning start time This would break if the hooks contained basically anything. --- lib/create-tests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 08b95750..85321908 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -397,8 +397,9 @@ function createPickle( return cy.wrap(start, { log: false }); }) - .then(() => { + .then((start) => { registry.runHook(this, hook); + return cy.wrap(start, { log: false }); }) .then((start) => { const end = createTimestamp(); From a768e2e9a79433551cebb7cdc74ce33cb861e598 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 29 Apr 2022 14:31:33 +0200 Subject: [PATCH 19/62] v9.1.2 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71dd77aa..7fe7ce98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.1.2 + +- Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/701) where Before hooks would error. + ## v9.1.1 - Add timestamps and durations to messages. diff --git a/package.json b/package.json index 85d37992..8d3b5930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.1.1", + "version": "9.1.2", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 6c50f1e11738e63260d46da7929bd54bb6c41cc3 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 5 May 2022 19:32:38 +0200 Subject: [PATCH 20/62] Don't error on programmatic skip --- features/skip.feature | 19 +++++++++++++++++++ lib/create-tests.ts | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 features/skip.feature diff --git a/features/skip.feature b/features/skip.feature new file mode 100644 index 00000000..3875659a --- /dev/null +++ b/features/skip.feature @@ -0,0 +1,19 @@ +Feature: skip + + Scenario: calling skip() + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature name + Scenario: a scenario name + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { When } = require("@badeball/cypress-cucumber-preprocessor"); + When("a step", function() { + this.skip(); + }); + """ + When I run cypress + Then it passes + And it should appear to have skipped the scenario "a scenario name" diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 85321908..847964f0 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -645,7 +645,10 @@ export default function createTests( const { testCaseStartedId, remainingSteps } = properties; - if (remainingSteps.length > 0) { + if ( + remainingSteps.length > 0 && + (this.currentTest?.state as any) !== "pending" + ) { const error = assertAndReturn( this.currentTest?.err?.message, "Expected to find an error message" From 3dc38f8a12ff6aca95cf885c93f755c4e4fa839e Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 5 May 2022 19:35:05 +0200 Subject: [PATCH 21/62] Document screenshots auto-attach --- docs/json-report.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/json-report.md b/docs/json-report.md index 620781ce..f7e554d7 100644 --- a/docs/json-report.md +++ b/docs/json-report.md @@ -32,6 +32,10 @@ The location of the executable is configurable through the `json.formatter` prop The report is outputted to `cucumber-report.json` in the project directory, but can be configured through the `json.output` property. +## Screenshots + +Screenshots are automatically added to JSON reports, including that of failed tests (unless you have disabled `screenshotOnRunFailure`). + ## Attachments Text, images and other data can be added to the output of the messages and JSON reports with attachments. From 3ea1581636864be5e7add2608be41107a0f7d355 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 5 May 2022 19:36:11 +0200 Subject: [PATCH 22/62] v9.1.3 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe7ce98..b3dfb988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.1.3 + +- Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/704) where programmatically skipping a test would error. + ## v9.1.2 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/701) where Before hooks would error. diff --git a/package.json b/package.json index 8d3b5930..05e95aa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.1.2", + "version": "9.1.3", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 09f3c06518542484ed7ae3e7dcb4149317c72abb Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Tue, 10 May 2022 17:54:09 +0200 Subject: [PATCH 23/62] Test auto-attach of failed test screenshot --- declarations.d.ts | 2 + features/json_report.feature | 20 ++++++- features/step_definitions/json_steps.ts | 69 ++++++++++++++++++++++++- package.json | 1 + tsconfig.json | 3 ++ 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/declarations.d.ts b/declarations.d.ts index 0f680735..2e8e197a 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -1 +1,3 @@ declare module "@cypress/browserify-preprocessor"; + +declare module "pngjs"; diff --git a/features/json_report.feature b/features/json_report.feature index 1e6e9a4f..7075a2fe 100644 --- a/features/json_report.feature +++ b/features/json_report.feature @@ -148,7 +148,7 @@ Feature: JSON formatter Then it passes And there should be a JSON output similar to "fixtures/pending-steps.json" - Scenario: screenshot + Scenario: explicit screenshot Given a file named "cypress/integration/a.feature" with: """ Feature: a feature @@ -179,6 +179,24 @@ Feature: JSON formatter Then it passes And there should be a JSON output similar to "fixtures/attachments/screenshot.json" + Scenario: screenshot of failed test + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a failing step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a failing step", function() { + throw "some error" + }) + """ + When I run cypress + Then it fails + And the JSON report should contain an image attachment for what appears to be a screenshot + Scenario: retried Given additional Cypress configuration """ diff --git a/features/step_definitions/json_steps.ts b/features/step_definitions/json_steps.ts index a910686b..4e9f6b7e 100644 --- a/features/step_definitions/json_steps.ts +++ b/features/step_definitions/json_steps.ts @@ -1,10 +1,11 @@ import { Given, Then } from "@cucumber/cucumber"; -import { WritableStreamBuffer } from "stream-buffers"; -import { Readable } from "stream"; import path from "path"; import { promises as fs } from "fs"; import assert from "assert"; import child_process from "child_process"; +import { toByteArray } from "base64-js"; +import { PNG } from "pngjs"; +import { version as cypressVersion } from "cypress/package.json"; function isObject(object: any): object is object { return typeof object === "object" && object != null; @@ -97,3 +98,67 @@ Then( } } ); + +Then( + "the JSON report should contain an image attachment for what appears to be a screenshot", + async function () { + const absolutejsonPath = path.join(this.tmpDir, "cucumber-report.json"); + + const jsonFile = await fs.readFile(absolutejsonPath); + + const actualJsonOutput = JSON.parse(jsonFile.toString()); + + const embeddings: { data: string; mime_type: string }[] = actualJsonOutput + .flatMap((feature: any) => feature.elements) + .flatMap((element: any) => element.steps) + .flatMap((step: any) => step.embeddings); + + if (embeddings.length === 0) { + throw new Error("Expected to find an embedding in JSON, but found none"); + } else if (embeddings.length > 1) { + throw new Error( + "Expected to find a single embedding in JSON, but found " + + embeddings.length + ); + } + + const [embedding] = embeddings; + + assert.strictEqual(embedding.mime_type, "image/png"); + + const png = await new Promise((resolve, reject) => { + new PNG().parse( + toByteArray(embedding.data).buffer, + function (error: any, data: any) { + if (error) { + reject(error); + } else { + resolve(data); + } + } + ); + }); + + let expectedDimensions; + + /** + * See https://github.com/cypress-io/cypress/pull/15686 and https://github.com/cypress-io/cypress/pull/17309. + */ + if (cypressVersion.startsWith("7.")) { + expectedDimensions = { + width: 1920, + height: 1080, + }; + } else { + expectedDimensions = { + width: 1280, + height: 720, + }; + } + + const { width: actualWidth, height: actualHeight } = png; + + assert.strictEqual(actualWidth, expectedDimensions.width); + assert.strictEqual(actualHeight, expectedDimensions.height); + } +); diff --git a/package.json b/package.json index 05e95aa0..28af787e 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "dtslint": "^4.2.1", "esbuild": "^0.14.23", "mocha": "^9.2.1", + "pngjs": "^6.0.0", "prettier": "^2.5.1", "stream-buffers": "^3.0.2", "strip-indent": "^3.0.0", diff --git a/tsconfig.json b/tsconfig.json index a4853d3b..37bfbeb9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,7 @@ { + "ts-node": { + "files": true + }, "compilerOptions": { "declaration": true, "skipLibCheck": true, From 73eaf2e40a2e4a66175d34bd1b990936d47255c1 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 13 May 2022 18:58:27 +0200 Subject: [PATCH 24/62] Document common error when node is not sufficiently recent --- docs/faq.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 7cde1931..6b127adf 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -3,3 +3,7 @@ ### `--env` / `tags` isn't picked up This might be because you're trying to specify `-e / --env` multiple times, but [multiple values should be comma-separated](https://docs.cypress.io/guides/guides/command-line#cypress-run-env-lt-env-gt). + +### I get `fs_1.promises.rm is not a function` + +Upgrade your node version to at least [v14.14.0](https://nodejs.org/api/fs.html#fspromisesrmpath-options). From 190078d91baae4e41298d6119cd7067eae544398 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 13 May 2022 19:03:15 +0200 Subject: [PATCH 25/62] Specify required node version --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 28af787e..82007fe3 100644 --- a/package.json +++ b/package.json @@ -89,5 +89,8 @@ "esbuild": { "optional": true } + }, + "engines": { + "node": ">=14.14.0" } } From 15ae6d0ae3b30fe3034804a2826ba5411a5de102 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 13 May 2022 20:03:16 +0200 Subject: [PATCH 26/62] Allow handlers to be omitted --- features/issues/705.feature | 52 +++++ features/step_definitions/json_steps.ts | 2 +- lib/add-cucumber-preprocessor-plugin.ts | 257 +++++++++++++++--------- lib/index.ts | 9 +- 4 files changed, 219 insertions(+), 101 deletions(-) create mode 100644 features/issues/705.feature diff --git a/features/issues/705.feature b/features/issues/705.feature new file mode 100644 index 00000000..ed2b8673 --- /dev/null +++ b/features/issues/705.feature @@ -0,0 +1,52 @@ +# https://github.com/badeball/cypress-cucumber-preprocessor/issues/705 + +@no-default-plugin +Feature: overriding event handlers + Background: + Given additional preprocessor configuration + """ + { + "json": { + "enabled": true + } + } + """ + + Scenario: overriding after:screenshot + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a failing step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a failing step", function() { + throw "some error" + }) + """ + And a file named "cypress/plugins/index.js" with: + """ + const { addCucumberPreprocessorPlugin, afterScreenshotHandler } = require("@badeball/cypress-cucumber-preprocessor"); + const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); + const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); + + module.exports = async (on, config) => { + await addCucumberPreprocessorPlugin(on, config, { omitAfterScreenshotHandler: true }); + + on("after:screenshot", (details) => afterScreenshotHandler(config, details)) + + on( + "file:preprocessor", + createBundler({ + plugins: [createEsbuildPlugin(config)] + }) + ); + + return config; + } + """ + When I run cypress + Then it fails + And the JSON report should contain an image attachment for what appears to be a screenshot diff --git a/features/step_definitions/json_steps.ts b/features/step_definitions/json_steps.ts index 4e9f6b7e..55becb69 100644 --- a/features/step_definitions/json_steps.ts +++ b/features/step_definitions/json_steps.ts @@ -111,7 +111,7 @@ Then( const embeddings: { data: string; mime_type: string }[] = actualJsonOutput .flatMap((feature: any) => feature.elements) .flatMap((element: any) => element.steps) - .flatMap((step: any) => step.embeddings); + .flatMap((step: any) => step.embeddings ?? []); if (embeddings.length === 0) { throw new Error("Expected to find an embedding in JSON, but found none"); diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index d3508cc6..43f81dd8 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -32,134 +32,193 @@ import { notNull } from "./type-guards"; import { getTags } from "./environment-helpers"; -export default async function addCucumberPreprocessorPlugin( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions -) { - const preprocessor = await resolve(); +const preprocessorP = resolve(); + +let currentTestStepStartedId: string; +let currentSpecMessages: messages.IEnvelope[]; + +export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { + const preprocessor = await preprocessorP; + + if (!preprocessor.messages.enabled) { + return; + } const messagesPath = path.join( config.projectRoot, preprocessor.messages.output ); - const jsonPath = path.join(config.projectRoot, preprocessor.json.output); + await fs.rm(messagesPath, { force: true }); +} - on("before:run", async () => { - if (!preprocessor.messages.enabled) { - return; - } +export async function afterRunHandler(config: Cypress.PluginConfigOptions) { + const preprocessor = await preprocessorP; - await fs.rm(messagesPath, { force: true }); - }); + if (!preprocessor.messages.enabled) { + return; + } - on("after:run", async () => { - if (!preprocessor.messages.enabled) { - return; - } + const messagesPath = path.join( + config.projectRoot, + preprocessor.messages.output + ); - try { - await fs.access(messagesPath, fsConstants.F_OK); - } catch { - return; - } + const jsonPath = path.join(config.projectRoot, preprocessor.json.output); - const messages = await fs.open(messagesPath, "r"); + try { + await fs.access(messagesPath, fsConstants.F_OK); + } catch { + return; + } - try { - const json = await fs.open(jsonPath, "w"); + const messages = await fs.open(messagesPath, "r"); - try { - const child = child_process.spawn(preprocessor.json.formatter, { - stdio: [messages.fd, json.fd, "inherit"], - }); + try { + const json = await fs.open(jsonPath, "w"); - await new Promise((resolve, reject) => { - child.on("exit", (code) => { - if (code === 0) { - resolve(); - } else { - reject( - new Error( - `${preprocessor.json.formatter} exited non-successfully` - ) - ); - } - }); - - child.on("error", reject); + try { + const child = child_process.spawn(preprocessor.json.formatter, { + stdio: [messages.fd, json.fd, "inherit"], + }); + + await new Promise((resolve, reject) => { + child.on("exit", (code) => { + if (code === 0) { + resolve(); + } else { + reject( + new Error( + `${preprocessor.json.formatter} exited non-successfully` + ) + ); + } }); - } finally { - await json.close(); - } + + child.on("error", reject); + }); } finally { - await messages.close(); + await json.close(); } - }); + } finally { + await messages.close(); + } +} - let currentTestStepStartedId: string; - let currentSpecMessages: messages.IEnvelope[]; +export async function beforeSpecHandler(config: Cypress.PluginConfigOptions) { + currentSpecMessages = []; +} - on("before:spec", () => { - currentSpecMessages = []; - }); +export async function afterSpecHandler( + config: Cypress.PluginConfigOptions, + spec: Cypress.Spec, + results: CypressCommandLine.RunResult +) { + const preprocessor = await preprocessorP; - on("after:spec", async (_spec, results) => { - // `results` is undefined when running via `cypress open`. - if (!preprocessor.messages.enabled || !currentSpecMessages || !results) { - return; - } + const messagesPath = path.join( + config.projectRoot, + preprocessor.messages.output + ); + + // `results` is undefined when running via `cypress open`. + if (!preprocessor.messages.enabled || !currentSpecMessages || !results) { + return; + } - const wasRemainingSkipped = results.tests.some((test) => - test.displayError?.match(HOOK_FAILURE_EXPR) + const wasRemainingSkipped = results.tests.some((test) => + test.displayError?.match(HOOK_FAILURE_EXPR) + ); + + if (wasRemainingSkipped) { + console.log( + chalk.yellow( + ` Hook failures can't be represented in JSON reports, thus none is created for ${spec.relative}.` + ) ); + } else { + await fs.writeFile( + messagesPath, + currentSpecMessages.map((message) => JSON.stringify(message)).join("\n") + + "\n", + { + flag: "a", + } + ); + } +} - if (wasRemainingSkipped) { - console.log( - chalk.yellow( - ` Hook failures can't be represented in JSON reports, thus none is created for ${_spec.relative}.` - ) - ); - } else { - await fs.writeFile( - messagesPath, - currentSpecMessages - .map((message) => JSON.stringify(message)) - .join("\n") + "\n", - { - flag: "a", - } - ); - } - }); +export async function afterScreenshotHandler( + config: Cypress.PluginConfigOptions, + details: Cypress.ScreenshotDetails +) { + const preprocessor = await preprocessorP; - on("after:screenshot", async (details) => { - if (!preprocessor.messages.enabled || !currentSpecMessages) { - return details; - } + if (!preprocessor.messages.enabled || !currentSpecMessages) { + return details; + } - let buffer; + let buffer; - try { - buffer = await fs.readFile(details.path); - } catch { - return details; - } + try { + buffer = await fs.readFile(details.path); + } catch { + return details; + } - const message: messages.IEnvelope = { - attachment: { - testStepId: currentTestStepStartedId, - body: buffer.toString("base64"), - mediaType: "image/png", - contentEncoding: - "BASE64" as unknown as messages.Attachment.ContentEncoding, - }, - }; + const message: messages.IEnvelope = { + attachment: { + testStepId: currentTestStepStartedId, + body: buffer.toString("base64"), + mediaType: "image/png", + contentEncoding: + "BASE64" as unknown as messages.Attachment.ContentEncoding, + }, + }; - currentSpecMessages.push(message); + currentSpecMessages.push(message); - return details; - }); + return details; +} + +type AddOptions = { + omitBeforeRunHandler?: boolean; + omitAfterRunHandler?: boolean; + omitBeforeSpecHandler?: boolean; + omitAfterSpecHandler?: boolean; + omitAfterScreenshotHandler?: boolean; +}; + +export default async function addCucumberPreprocessorPlugin( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions, + options: AddOptions = {} +) { + const preprocessor = await preprocessorP; + + if (!options.omitBeforeRunHandler) { + on("before:run", () => beforeRunHandler(config)); + } + + if (!options.omitAfterRunHandler) { + on("after:run", () => afterRunHandler(config)); + } + + if (!options.omitBeforeSpecHandler) { + on("before:spec", () => beforeSpecHandler(config)); + } + + if (!options.omitAfterSpecHandler) { + on("after:spec", (spec, results) => + afterSpecHandler(config, spec, results) + ); + } + + if (!options.omitAfterScreenshotHandler) { + on("after:screenshot", (details) => + afterScreenshotHandler(config, details) + ); + } on("task", { [TASK_APPEND_MESSAGES]: (messages: messages.IEnvelope[]) => { diff --git a/lib/index.ts b/lib/index.ts index fa455016..4fce4796 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -24,7 +24,14 @@ export { resolve as resolvePreprocessorConfiguration } from "./preprocessor-conf export { getStepDefinitionPaths } from "./step-definitions"; -export { default as addCucumberPreprocessorPlugin } from "./add-cucumber-preprocessor-plugin"; +export { + default as addCucumberPreprocessorPlugin, + beforeRunHandler, + afterRunHandler, + beforeSpecHandler, + afterSpecHandler, + afterScreenshotHandler, +} from "./add-cucumber-preprocessor-plugin"; /** * Everything below exist merely for the purpose of being nice with TypeScript. All of these methods From d85500728092b5e8c0762f1163259138449aed0c Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 13 May 2022 20:50:36 +0200 Subject: [PATCH 27/62] v9.2.0 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3dfb988..093dc1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.2.0 + +- Allow handlers to be omitted and attached explicitly, fixes [#705](https://github.com/badeball/cypress-cucumber-preprocessor/issues/705) (undocumented, experimental and API is subject to change anytime). + ## v9.1.3 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/704) where programmatically skipping a test would error. diff --git a/package.json b/package.json index 82007fe3..279d34f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.1.3", + "version": "9.2.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From bb18697a8c5a5b6a49f0436384bcbc0ae7ef8572 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Mon, 16 May 2022 10:48:03 +0200 Subject: [PATCH 28/62] Allow chains to be returned from step definitions --- features/issues/713.feature | 17 +++++++++++++++++ lib/create-tests.ts | 21 +++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 features/issues/713.feature diff --git a/features/issues/713.feature b/features/issues/713.feature new file mode 100644 index 00000000..0a4fe150 --- /dev/null +++ b/features/issues/713.feature @@ -0,0 +1,17 @@ +# https://github.com/badeball/cypress-cucumber-preprocessor/issues/713 + +Feature: returning chains + Scenario: returning a chain + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", () => cy.log("foo")) + """ + When I run cypress + Then it passes diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 847964f0..4edba359 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -474,16 +474,17 @@ function createPickle( return cy.wrap(start, { log: false }); }) .then((start) => { - return cy - .wrap(registry.runStepDefininition(this, text, argument), { - log: false, - }) - .then((result: any) => { - return { - start, - result, - }; - }); + const ensureChain = (value: any): Cypress.Chainable => + Cypress.isCy(value) ? value : cy.wrap(value); + + return ensureChain( + registry.runStepDefininition(this, text, argument) + ).then((result: any) => { + return { + start, + result, + }; + }); }) .then(({ start, result }) => { const end = createTimestamp(); From 4ef5f8d192a6e79be423b7e1e171b32702a61906 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Mon, 16 May 2022 11:40:37 +0200 Subject: [PATCH 29/62] v9.2.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093dc1fb..617a16ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v9.2.1 + +- Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/713) with returning chainables from step definitions. + ## v9.2.0 - Allow handlers to be omitted and attached explicitly, fixes [#705](https://github.com/badeball/cypress-cucumber-preprocessor/issues/705) (undocumented, experimental and API is subject to change anytime). diff --git a/package.json b/package.json index 279d34f0..4ab7a390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.2.0", + "version": "9.2.1", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From d102a4b39d7944110ce8e1e2702814f74528fe91 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 20 May 2022 17:29:05 +0200 Subject: [PATCH 30/62] Add addCucumberPreprocessorPlugin() to the examples --- examples/browserify/cypress/plugins/index.ts | 10 ++++++++-- examples/esbuild/cypress/plugins/index.ts | 10 ++++++++-- examples/webpack/cypress/plugins/index.ts | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/browserify/cypress/plugins/index.ts b/examples/browserify/cypress/plugins/index.ts index e646237b..348cddb0 100644 --- a/examples/browserify/cypress/plugins/index.ts +++ b/examples/browserify/cypress/plugins/index.ts @@ -1,10 +1,13 @@ import * as browserify from "@cypress/browserify-preprocessor"; +import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; import { preprocessor } from "@badeball/cypress-cucumber-preprocessor/browserify"; -export default ( +export default async ( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): void => { +): Promise => { + await addCucumberPreprocessorPlugin(on, config); + on( "file:preprocessor", preprocessor(config, { @@ -12,4 +15,7 @@ export default ( typescript: require.resolve("typescript"), }) ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; }; diff --git a/examples/esbuild/cypress/plugins/index.ts b/examples/esbuild/cypress/plugins/index.ts index b42dc781..3b159218 100644 --- a/examples/esbuild/cypress/plugins/index.ts +++ b/examples/esbuild/cypress/plugins/index.ts @@ -1,14 +1,20 @@ import * as createBundler from "@bahmutov/cypress-esbuild-preprocessor"; +import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; -export default ( +export default async ( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): void => { +): Promise => { + await addCucumberPreprocessorPlugin(on, config); + on( "file:preprocessor", createBundler({ plugins: [createEsbuildPlugin(config)], }) ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; }; diff --git a/examples/webpack/cypress/plugins/index.ts b/examples/webpack/cypress/plugins/index.ts index 25d682c3..63875ea9 100644 --- a/examples/webpack/cypress/plugins/index.ts +++ b/examples/webpack/cypress/plugins/index.ts @@ -1,9 +1,12 @@ import * as webpack from "@cypress/webpack-preprocessor"; +import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -export default ( +export default async ( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): void => { +): Promise => { + await addCucumberPreprocessorPlugin(on, config); + on( "file:preprocessor", webpack({ @@ -36,4 +39,7 @@ export default ( }, }) ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; }; From 0215236263ad27b5d9ea5c0051a67c204431ec85 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 21 May 2022 20:23:03 +0200 Subject: [PATCH 31/62] Memoize resolve() to resolve on demand I like this better. --- lib/add-cucumber-preprocessor-plugin.ts | 28 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index 43f81dd8..d78f93fe 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -26,19 +26,33 @@ import { TASK_TEST_STEP_STARTED, } from "./constants"; -import { resolve } from "./preprocessor-configuration"; +import { resolve as origResolve } from "./preprocessor-configuration"; import { notNull } from "./type-guards"; import { getTags } from "./environment-helpers"; -const preprocessorP = resolve(); +function memoize any>( + fn: T +): (...args: Parameters) => ReturnType { + let result: ReturnType; + + return (...args: Parameters) => { + if (result) { + return result; + } + + return (result = fn(...args)); + }; +} + +const resolve = memoize(origResolve); let currentTestStepStartedId: string; let currentSpecMessages: messages.IEnvelope[]; export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await preprocessorP; + const preprocessor = await resolve(); if (!preprocessor.messages.enabled) { return; @@ -53,7 +67,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { } export async function afterRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await preprocessorP; + const preprocessor = await resolve(); if (!preprocessor.messages.enabled) { return; @@ -114,7 +128,7 @@ export async function afterSpecHandler( spec: Cypress.Spec, results: CypressCommandLine.RunResult ) { - const preprocessor = await preprocessorP; + const preprocessor = await resolve(); const messagesPath = path.join( config.projectRoot, @@ -152,7 +166,7 @@ export async function afterScreenshotHandler( config: Cypress.PluginConfigOptions, details: Cypress.ScreenshotDetails ) { - const preprocessor = await preprocessorP; + const preprocessor = await resolve(); if (!preprocessor.messages.enabled || !currentSpecMessages) { return details; @@ -194,7 +208,7 @@ export default async function addCucumberPreprocessorPlugin( config: Cypress.PluginConfigOptions, options: AddOptions = {} ) { - const preprocessor = await preprocessorP; + const preprocessor = await resolve(); if (!options.omitBeforeRunHandler) { on("before:run", () => beforeRunHandler(config)); From 257eee173294f0ba0bb5bf28ad77f7971b3ea731 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 21 May 2022 20:31:55 +0200 Subject: [PATCH 32/62] Make projectRoot of resolve() required --- lib/add-cucumber-preprocessor-plugin.ts | 10 +++++----- lib/preprocessor-configuration.ts | 4 ++-- lib/template.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index d78f93fe..f023b44c 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -52,7 +52,7 @@ let currentTestStepStartedId: string; let currentSpecMessages: messages.IEnvelope[]; export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await resolve(); + const preprocessor = await resolve(config.projectRoot); if (!preprocessor.messages.enabled) { return; @@ -67,7 +67,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { } export async function afterRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await resolve(); + const preprocessor = await resolve(config.projectRoot); if (!preprocessor.messages.enabled) { return; @@ -128,7 +128,7 @@ export async function afterSpecHandler( spec: Cypress.Spec, results: CypressCommandLine.RunResult ) { - const preprocessor = await resolve(); + const preprocessor = await resolve(config.projectRoot); const messagesPath = path.join( config.projectRoot, @@ -166,7 +166,7 @@ export async function afterScreenshotHandler( config: Cypress.PluginConfigOptions, details: Cypress.ScreenshotDetails ) { - const preprocessor = await resolve(); + const preprocessor = await resolve(config.projectRoot); if (!preprocessor.messages.enabled || !currentSpecMessages) { return details; @@ -208,7 +208,7 @@ export default async function addCucumberPreprocessorPlugin( config: Cypress.PluginConfigOptions, options: AddOptions = {} ) { - const preprocessor = await resolve(); + const preprocessor = await resolve(config.projectRoot); if (!options.omitBeforeRunHandler) { on("before:run", () => beforeRunHandler(config)); diff --git a/lib/preprocessor-configuration.ts b/lib/preprocessor-configuration.ts index f64753c7..81eb8429 100644 --- a/lib/preprocessor-configuration.ts +++ b/lib/preprocessor-configuration.ts @@ -178,9 +178,9 @@ export class PreprocessorConfiguration implements IPreprocessorConfiguration { } } -export async function resolve(searchFrom?: string) { +export async function resolve(projectRoot: string) { const result = await cosmiconfig("cypress-cucumber-preprocessor").search( - searchFrom + projectRoot ); if (result) { diff --git a/lib/template.ts b/lib/template.ts index a2ff2c99..2f4285c0 100644 --- a/lib/template.ts +++ b/lib/template.ts @@ -49,7 +49,7 @@ export async function compile( const pickles = envelopes.map((envelope) => envelope.pickle).filter(notNull); - const preprocessor = await resolve(); + const preprocessor = await resolve(configuration.projectRoot); const stepDefinitions = await getStepDefinitionPaths( { From 36e15f0fe9c706712dad8466c6594232027e955b Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 21 May 2022 21:14:32 +0200 Subject: [PATCH 33/62] Allow config options to be overriden through environment --- docs/configuration.md | 53 ++++++ docs/quick-start.md | 2 + docs/readme.md | 1 + features/configuration_overrides.feature | 32 ++++ features/step_definitions/cli_steps.ts | 8 + features/support/world.ts | 3 +- lib/add-cucumber-preprocessor-plugin.ts | 10 +- lib/preprocessor-configuration.test.ts | 85 +++++++++ lib/preprocessor-configuration.ts | 217 +++++++++++++++++++++-- lib/step-definitions.test.ts | 5 +- lib/template.ts | 5 +- package.json | 2 +- 12 files changed, 398 insertions(+), 25 deletions(-) create mode 100644 docs/configuration.md create mode 100644 features/configuration_overrides.feature create mode 100644 lib/preprocessor-configuration.test.ts diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..8beb8b7d --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,53 @@ +# Configuration + +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`, with corresponding examples shown below. + +``` +// .cypress-cucumber-preprocessorrc.json +{ + "json": { + "enabled": true + } +} +``` + +``` +// package.json +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest" + }, + "cypress-cucumber-preprocessor": { + "json": { + "enabled": true + } + } +} +``` + +## Configuration overrides + +Configuration options can be overriden using (Cypress-) [environment variable](https://docs.cypress.io/guides/guides/environment-variables). The `filterSpecs` options (described in [docs/tags.md](tags.md)) can for instance be overriden by running Cypress like shown below. + +``` +$ cypress run -e filterSpecs=true +``` + +Cypress environment variables can also be configured through ordinary environment variables, like shown below. + +``` +$ CYPRESS_filterSpecs=true cypress run +``` + +Every configuration option has a similar key which can be use to override it, shown in the table below. + +| JSON path | Environment key | Example(s) | +|--------------------|-------------------|------------------------------------------| +| `stepDefinitions` | `stepDefinitions` | `cypress/integration/[filepath].{js,ts}` | +| `messages.enabled` | `messagesEnabled` | `true`, `false` | +| `messages.output` | `messagesOutput` | `cucumber-messages.ndjson` | +| `json.enabled` | `jsonEnabled` | `true`, `false` | +| `json.formatter` | `jsonFormatter` | `/usr/bin/cucumber-json-formatter` | +| `json.output` | `jsonOutput` | `cucumber-report.json` | +| `filterSpecs` | `filterSpecs` | `true`, `false` | +| `omitFiltered` | `omitFiltered` | `true`, `false` | diff --git a/docs/quick-start.md b/docs/quick-start.md index d756b063..dae64cd5 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -20,6 +20,8 @@ Configure your preferred bundler to process features files, with examples for * [Webpack](../examples/webpack) * [Esbuild](../examples/esbuild) +Read more about configuration options at [docs/configuration.md](configuration.md). + # Write a test Write Gherkin documents anywhere in your configured integration folder (defaults to `cypress/integration`) and add a file for type definitions with a corresponding name (read more about how step definitions are resolved in [docs/step-definitions.md](step-definitions.md)). Reading [docs/cucumber-basics.md](cucumber-basics.md) is highly recommended. diff --git a/docs/readme.md b/docs/readme.md index c6ae2e76..3646828e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -7,4 +7,5 @@ * [Tags](tags.md) * [JSON report](json-report.md) * [Localisation](localisation.md) +* [Configuration](configuration.md) * [Frequently asked questions](faq.md) diff --git a/features/configuration_overrides.feature b/features/configuration_overrides.feature new file mode 100644 index 00000000..903802c2 --- /dev/null +++ b/features/configuration_overrides.feature @@ -0,0 +1,32 @@ +Feature: configuration overrides + Scenario: overriding stepDefinitions through -e + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature name + Scenario: a scenario name + Given a step + """ + And a file named "foobar.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function() {}); + """ + When I run cypress with "-e stepDefinitions=foobar.js" + Then it passes + + Scenario: overriding stepDefinitions through environment variables + Given a file named "cypress/integration/a.feature" with: + """ + Feature: a feature name + Scenario: a scenario name + Given a step + """ + And a file named "foobar.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function() {}); + """ + When I run cypress with environment variables + | Key | Value | + | CYPRESS_stepDefinitions | foobar.js | + Then it passes diff --git a/features/step_definitions/cli_steps.ts b/features/step_definitions/cli_steps.ts index 6388d432..44423647 100644 --- a/features/step_definitions/cli_steps.ts +++ b/features/step_definitions/cli_steps.ts @@ -37,6 +37,14 @@ When( } ); +When( + "I run cypress with environment variables", + { timeout: 60 * 1000 }, + async function (table) { + await this.run([], Object.fromEntries(table.rows())); + } +); + Then("it passes", function () { assert.equal(this.lastRun.exitCode, 0, "Expected a zero exit code"); }); diff --git a/features/support/world.ts b/features/support/world.ts index fa000155..9d1bc02e 100644 --- a/features/support/world.ts +++ b/features/support/world.ts @@ -20,7 +20,7 @@ function combine(...streams: Readable[]) { } class World { - async run(this: IWorld, extraArgs = []) { + async run(this: IWorld, extraArgs = [], extraEnv = {}) { const child = childProcess.spawn( path.join( projectPath, @@ -35,6 +35,7 @@ class World { env: { ...process.env, NO_COLOR: "1", + ...extraEnv, }, } ); diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index f023b44c..d4bb3c21 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -52,7 +52,7 @@ let currentTestStepStartedId: string; let currentSpecMessages: messages.IEnvelope[]; export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await resolve(config.projectRoot); + const preprocessor = await resolve(config.projectRoot, config.env); if (!preprocessor.messages.enabled) { return; @@ -67,7 +67,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { } export async function afterRunHandler(config: Cypress.PluginConfigOptions) { - const preprocessor = await resolve(config.projectRoot); + const preprocessor = await resolve(config.projectRoot, config.env); if (!preprocessor.messages.enabled) { return; @@ -128,7 +128,7 @@ export async function afterSpecHandler( spec: Cypress.Spec, results: CypressCommandLine.RunResult ) { - const preprocessor = await resolve(config.projectRoot); + const preprocessor = await resolve(config.projectRoot, config.env); const messagesPath = path.join( config.projectRoot, @@ -166,7 +166,7 @@ export async function afterScreenshotHandler( config: Cypress.PluginConfigOptions, details: Cypress.ScreenshotDetails ) { - const preprocessor = await resolve(config.projectRoot); + const preprocessor = await resolve(config.projectRoot, config.env); if (!preprocessor.messages.enabled || !currentSpecMessages) { return details; @@ -208,7 +208,7 @@ export default async function addCucumberPreprocessorPlugin( config: Cypress.PluginConfigOptions, options: AddOptions = {} ) { - const preprocessor = await resolve(config.projectRoot); + const preprocessor = await resolve(config.projectRoot, config.env); if (!options.omitBeforeRunHandler) { on("before:run", () => beforeRunHandler(config)); diff --git a/lib/preprocessor-configuration.test.ts b/lib/preprocessor-configuration.test.ts new file mode 100644 index 00000000..7cdc2715 --- /dev/null +++ b/lib/preprocessor-configuration.test.ts @@ -0,0 +1,85 @@ +import assert from "assert"; + +import { resolve } from "./preprocessor-configuration"; + +describe("resolve()", () => { + it("overriding stepDefinitions", async () => { + const { stepDefinitions } = await resolve( + "/foo/bar", + { stepDefinitions: "foo/bar/**" }, + () => null + ); + + assert.strictEqual(stepDefinitions, "foo/bar/**"); + }); + + it("overriding messages.enabled (1)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: "" }, () => ({ + messages: { enabled: true }, + })); + + assert.strictEqual(enabled, true); + }); + + it("overriding messages.enabled (2)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: "true" }, () => ({ + messages: { enabled: false }, + })); + + assert.strictEqual(enabled, true); + }); + + it("overriding messages.enabled (3)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: "foobar" }, () => ({ + messages: { enabled: false }, + })); + + assert.strictEqual(enabled, true); + }); + + it("overriding messages.enabled (4)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: true }, () => ({ + messages: { enabled: false }, + })); + + assert.strictEqual(enabled, true); + }); + + it("overriding messages.enabled (5)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: "false" }, () => ({ + messages: { enabled: true }, + })); + + assert.strictEqual(enabled, false); + }); + + it("overriding messages.enabled (6)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: "false" }, () => ({ + messages: { enabled: true }, + })); + + assert.strictEqual(enabled, false); + }); + + it("overriding messages.enabled (7)", async () => { + const { + messages: { enabled }, + } = await resolve("/foo/bar", { messagesEnabled: false }, () => ({ + messages: { enabled: true }, + })); + + assert.strictEqual(enabled, false); + }); +}); diff --git a/lib/preprocessor-configuration.ts b/lib/preprocessor-configuration.ts index 81eb8429..c15ee26b 100644 --- a/lib/preprocessor-configuration.ts +++ b/lib/preprocessor-configuration.ts @@ -123,6 +123,142 @@ function validateConfigurationEntry( } } +function validateEnvironmentOverrides( + environment: Record +): IEnvironmentOverrides { + const overrides: IEnvironmentOverrides = {}; + + if (hasOwnProperty(environment, "stepDefinitions")) { + const { stepDefinitions } = environment; + + if (isStringOrStringArray(stepDefinitions)) { + overrides.stepDefinitions = stepDefinitions; + } else { + throw new Error( + `Expected a string or array of strings (stepDefinitions), but got ${util.inspect( + stepDefinitions + )}` + ); + } + } + + if (hasOwnProperty(environment, "messagesEnabled")) { + const { messagesEnabled } = environment; + + if (isBoolean(messagesEnabled)) { + overrides.messagesEnabled = messagesEnabled; + } else if (isString(messagesEnabled)) { + overrides.messagesEnabled = stringToMaybeBoolean(messagesEnabled); + } else { + throw new Error( + `Expected a boolean (messagesEnabled), but got ${util.inspect( + messagesEnabled + )}` + ); + } + } + + if (hasOwnProperty(environment, "messagesOutput")) { + const { messagesOutput } = environment; + + if (isString(messagesOutput)) { + overrides.messagesOutput = messagesOutput; + } else { + throw new Error( + `Expected a string (messagesOutput), but got ${util.inspect( + messagesOutput + )}` + ); + } + } + + if (hasOwnProperty(environment, "jsonEnabled")) { + const { jsonEnabled } = environment; + + if (isBoolean(jsonEnabled)) { + overrides.jsonEnabled = jsonEnabled; + } else if (isString(jsonEnabled)) { + overrides.jsonEnabled = stringToMaybeBoolean(jsonEnabled); + } else { + throw new Error( + `Expected a boolean (jsonEnabled), but got ${util.inspect(jsonEnabled)}` + ); + } + } + + if (hasOwnProperty(environment, "jsonFormatter")) { + const { jsonFormatter } = environment; + + if (isString(jsonFormatter)) { + overrides.jsonFormatter = jsonFormatter; + } else { + throw new Error( + `Expected a string (jsonFormatter), but got ${util.inspect( + jsonFormatter + )}` + ); + } + } + + if (hasOwnProperty(environment, "jsonOutput")) { + const { jsonOutput } = environment; + + if (isString(jsonOutput)) { + overrides.jsonOutput = jsonOutput; + } else { + throw new Error( + `Expected a string (jsonOutput), but got ${util.inspect(jsonOutput)}` + ); + } + } + + if (hasOwnProperty(environment, "filterSpecs")) { + const { filterSpecs } = environment; + + if (isBoolean(filterSpecs)) { + overrides.filterSpecs = filterSpecs; + } else if (isString(filterSpecs)) { + overrides.filterSpecs = stringToMaybeBoolean(filterSpecs); + } else { + throw new Error( + `Expected a boolean (filterSpecs), but got ${util.inspect(filterSpecs)}` + ); + } + } + + if (hasOwnProperty(environment, "omitFiltered")) { + const { omitFiltered } = environment; + + if (isBoolean(omitFiltered)) { + overrides.omitFiltered = omitFiltered; + } else if (isString(omitFiltered)) { + overrides.omitFiltered = stringToMaybeBoolean(omitFiltered); + } else { + throw new Error( + `Expected a boolean (omitFiltered), but got ${util.inspect( + omitFiltered + )}` + ); + } + } + + return overrides; +} + +export function stringToMaybeBoolean(value: string): boolean | undefined { + if (value === "") { + return; + } + + const falsyValues = ["0", "false"]; + + if (falsyValues.includes(value)) { + return false; + } else { + return true; + } +} + export interface IPreprocessorConfiguration { readonly stepDefinitions: string | string[]; readonly messages?: { @@ -138,11 +274,26 @@ export interface IPreprocessorConfiguration { readonly omitFiltered?: boolean; } +export interface IEnvironmentOverrides { + stepDefinitions?: string | string[]; + messagesEnabled?: boolean; + messagesOutput?: string; + jsonEnabled?: boolean; + jsonFormatter?: string; + jsonOutput?: string; + filterSpecs?: boolean; + omitFiltered?: boolean; +} + export class PreprocessorConfiguration implements IPreprocessorConfiguration { - constructor(private explicitValues: Partial) {} + constructor( + private explicitValues: Partial, + private environmentOverrides: IEnvironmentOverrides + ) {} get stepDefinitions() { return ( + this.environmentOverrides.stepDefinitions ?? this.explicitValues.stepDefinitions ?? [ "cypress/integration/[filepath]/**/*.{js,ts}", "cypress/integration/[filepath].{js,ts}", @@ -154,59 +305,93 @@ export class PreprocessorConfiguration implements IPreprocessorConfiguration { get messages() { return { enabled: - this.json.enabled || (this.explicitValues.messages?.enabled ?? false), + this.json.enabled || + (this.environmentOverrides.messagesEnabled ?? + this.explicitValues.messages?.enabled ?? + false), output: - this.explicitValues.messages?.output ?? "cucumber-messages.ndjson", + this.environmentOverrides.messagesOutput ?? + this.explicitValues.messages?.output ?? + "cucumber-messages.ndjson", }; } get json() { return { - enabled: this.explicitValues.json?.enabled ?? false, + enabled: + this.environmentOverrides.jsonEnabled ?? + this.explicitValues.json?.enabled ?? + false, formatter: - this.explicitValues.json?.formatter ?? "cucumber-json-formatter", - output: this.explicitValues.json?.output || "cucumber-report.json", + this.environmentOverrides.jsonFormatter ?? + this.explicitValues.json?.formatter ?? + "cucumber-json-formatter", + output: + this.environmentOverrides.jsonOutput ?? + (this.explicitValues.json?.output || "cucumber-report.json"), }; } get filterSpecs() { - return this.explicitValues.filterSpecs ?? false; + return ( + this.environmentOverrides.filterSpecs ?? + this.explicitValues.filterSpecs ?? + false + ); } get omitFiltered() { - return this.explicitValues.omitFiltered ?? false; + return ( + this.environmentOverrides.omitFiltered ?? + this.explicitValues.omitFiltered ?? + false + ); } } -export async function resolve(projectRoot: string) { +async function cosmiconfigResolver(projectRoot: string) { const result = await cosmiconfig("cypress-cucumber-preprocessor").search( projectRoot ); - if (result) { - const { config: rawConfig } = result; + return result?.config; +} - if (typeof rawConfig !== "object" || rawConfig == null) { +export type ConfigurationFileResolver = ( + projectRoot: string +) => any | Promise; + +export async function resolve( + projectRoot: string, + environment: Record, + configurationFileResolver: ConfigurationFileResolver = cosmiconfigResolver +) { + const result = await configurationFileResolver(projectRoot); + + const environmentOverrides = validateEnvironmentOverrides(environment); + + if (result) { + if (typeof result !== "object" || result == null) { throw new Error( `Malformed configuration, expected an object, but got ${util.inspect( - rawConfig + result )}` ); } const config: Partial = Object.assign( {}, - ...Object.entries(rawConfig).map((entry) => + ...Object.entries(result).map((entry) => validateConfigurationEntry(...entry) ) ); debug(`resolved configuration ${util.inspect(config)}`); - return new PreprocessorConfiguration(config); + return new PreprocessorConfiguration(config, environmentOverrides); } else { debug("resolved no configuration"); - return new PreprocessorConfiguration({}); + return new PreprocessorConfiguration({}, environmentOverrides); } } diff --git a/lib/step-definitions.test.ts b/lib/step-definitions.test.ts index 47e38de4..57faa0bd 100644 --- a/lib/step-definitions.test.ts +++ b/lib/step-definitions.test.ts @@ -28,7 +28,10 @@ function example( const actual = getStepDefinitionPatterns( { cypress: cypressConfiguration, - preprocessor: new PreprocessorConfiguration(preprocessorConfiguration), + preprocessor: new PreprocessorConfiguration( + preprocessorConfiguration, + {} + ), }, filepath ); diff --git a/lib/template.ts b/lib/template.ts index 2f4285c0..5fe8e0ab 100644 --- a/lib/template.ts +++ b/lib/template.ts @@ -49,7 +49,10 @@ export async function compile( const pickles = envelopes.map((envelope) => envelope.pickle).filter(notNull); - const preprocessor = await resolve(configuration.projectRoot); + const preprocessor = await resolve( + configuration.projectRoot, + configuration.env + ); const stepDefinitions = await getStepDefinitionPaths( { diff --git a/package.json b/package.json index 4ab7a390..9bcf8f0d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "prepublishOnly": "npm run clean && npm run build && npm run test" }, "dependencies": { - "@badeball/cypress-configuration": "^2.0.0", + "@badeball/cypress-configuration": "^2.1.0", "@cucumber/cucumber-expressions": "^15.0.1", "@cucumber/gherkin": "^15.0.2", "@cucumber/messages": "^13.2.1", From 686f72e0cdebbeb77476f5dc7904fedca5755a81 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sat, 21 May 2022 22:52:04 +0200 Subject: [PATCH 34/62] v10.0.0 --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 617a16ae..0cd3bd67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## v10.0.0 + +Breaking changes: + +- Exported member `resolvePreprocessorConfiguration` now *requires* a `projectRoot` variable and a `environment` variable. + +Other changes: + +- Configuration values can now be overriden using (Cypress-) [environment variable](https://docs.cypress.io/guides/guides/environment-variables). + ## v9.2.1 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/713) with returning chainables from step definitions. diff --git a/package.json b/package.json index 9bcf8f0d..0233843d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "9.2.1", + "version": "10.0.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 2dc70c361752c54f804a6482de528dcd4cdaf415 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 22 May 2022 19:33:28 +0200 Subject: [PATCH 35/62] Avoid logging certain calls to cy.wrap --- lib/create-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 4edba359..e930600a 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -475,7 +475,7 @@ function createPickle( }) .then((start) => { const ensureChain = (value: any): Cypress.Chainable => - Cypress.isCy(value) ? value : cy.wrap(value); + Cypress.isCy(value) ? value : cy.wrap(value, { log: false }); return ensureChain( registry.runStepDefininition(this, text, argument) From ea9ba4df03b35c4205d28b9528d3260ff7854a85 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 22 May 2022 19:34:52 +0200 Subject: [PATCH 36/62] v10.0.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd3bd67..d80f99b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v10.0.1 + +- Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/720) where internal calls to `cy.wrap` was being logged. + ## v10.0.0 Breaking changes: diff --git a/package.json b/package.json index 0233843d..0b3ce3da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "10.0.0", + "version": "10.0.1", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 67815ac979c2961a921add16a035d03c90cd5dd6 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Mon, 23 May 2022 19:30:00 +0200 Subject: [PATCH 37/62] Step definiitions need not be within the project root It might even be that they don't need to be within the integration folder, but one thing at the time I suppose. --- lib/step-definitions.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/step-definitions.ts b/lib/step-definitions.ts index 3b791d71..6cb18023 100644 --- a/lib/step-definitions.ts +++ b/lib/step-definitions.ts @@ -79,12 +79,6 @@ export function getStepDefinitionPatterns( throw new Error(`${filepath} is not inside ${fullIntegrationFolder}`); } - if (!isPathInside(filepath, configuration.cypress.projectRoot)) { - throw new Error( - `${filepath} is not inside ${configuration.cypress.projectRoot}` - ); - } - debug( `looking for step definitions using ${util.inspect( configuration.preprocessor.stepDefinitions From be404b90d0905684750e380546ee4fd606bbd997 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Mon, 23 May 2022 19:32:25 +0200 Subject: [PATCH 38/62] v10.0.2 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d80f99b0..2842f6d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v10.0.2 + +- Allow integration folders outside of project root, fixes [#719](https://github.com/badeball/cypress-cucumber-preprocessor/issues/719). + ## v10.0.1 - Fixed an [issue](https://github.com/badeball/cypress-cucumber-preprocessor/issues/720) where internal calls to `cy.wrap` was being logged. diff --git a/package.json b/package.json index 0b3ce3da..1f4a29ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "10.0.1", + "version": "10.0.2", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 37779508e463cdc6e6041476afd17b93447027eb Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Mon, 6 Jun 2022 21:53:49 +0200 Subject: [PATCH 39/62] Update link to cucumber-json-formatter --- docs/json-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/json-report.md b/docs/json-report.md index f7e554d7..59292720 100644 --- a/docs/json-report.md +++ b/docs/json-report.md @@ -26,7 +26,7 @@ export default async ( } ``` -This also **requires** you to have downloaded and installed the [cucumber-json-formatter](https://github.com/cucumber/common/tree/main/json-formatter) **yourself**. Arch Linux users can install it from [AUR](https://aur.archlinux.org/packages/cucumber-json-formatter). +This also **requires** you to have downloaded and installed the [cucumber-json-formatter](https://github.com/cucumber/json-formatter) **yourself**. Arch Linux users can install it from [AUR](https://aur.archlinux.org/packages/cucumber-json-formatter). The location of the executable is configurable through the `json.formatter` property, but it will by default search for `cucumber-json-formatter` in your `PATH`. From afc9965749ab3a72d063f1390b9cf4bfb3cb1965 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 12 Jun 2022 00:15:35 +0200 Subject: [PATCH 40/62] Add support for Cypress v10 Co-authored-by: Anthony Champagne --- README.md | 4 +- docs/configuration.md | 2 +- docs/quick-start.md | 20 +- docs/state-management.md | 4 +- docs/step-definitions.md | 22 +- .../plugins/index.ts => cypress.config.ts} | 15 +- examples/browserify/cypress.json | 3 - .../{integration => e2e}/duckduckgo.feature | 0 .../{integration => e2e}/duckduckgo.ts | 0 .../plugins/index.ts => cypress.config.ts} | 17 +- examples/esbuild/cypress.json | 3 - .../{integration => e2e}/duckduckgo.feature | 0 .../{integration => e2e}/duckduckgo.ts | 0 .../plugins/index.ts => cypress.config.ts} | 15 +- examples/webpack/cypress.json | 3 - .../{integration => e2e}/duckduckgo.feature | 0 .../{integration => e2e}/duckduckgo.ts | 0 features/ambiguous_keywords.feature | 2 +- features/asynchronous_world.feature | 2 +- features/attachments.feature | 6 +- features/configuration_overrides.feature | 4 +- features/custom_parameter_type.feature | 2 +- features/data_table.feature | 10 +- features/doc_string.feature | 4 +- features/fixtures/attachments/screenshot.json | 2 +- features/fixtures/attachments/string.json | 2 +- features/fixtures/failing-after.json | 2 +- features/fixtures/failing-before.json | 2 +- features/fixtures/failing-step.json | 2 +- features/fixtures/multiple-features.json | 4 +- features/fixtures/passed-example.json | 2 +- features/fixtures/passed-outline.json | 2 +- features/fixtures/pending-steps.json | 2 +- features/fixtures/retried.json | 2 +- features/fixtures/undefined-steps.json | 2 +- features/hooks_ordering.feature | 2 +- features/issues/705.feature | 5 +- features/issues/713.feature | 2 +- features/json_report.feature | 56 +-- features/loaders/browserify.feature | 13 +- features/loaders/esbuild.feature | 13 +- features/loaders/webpack.feature | 11 +- features/localisation.feature | 2 +- features/mixing_types.feature | 14 +- features/nested_steps.feature | 4 +- features/parse_error.feature | 2 +- features/scenario_outlines.feature | 6 +- features/skip.feature | 2 +- features/step_definitions.feature | 10 +- features/step_definitions/cli_steps.ts | 4 + features/step_definitions/config_steps.ts | 64 ++- features/step_definitions/file_steps.ts | 12 +- features/support/configFileUpdater.ts | 400 ++++++++++++++++++ features/support/helpers.ts | 9 + features/support/hooks.ts | 128 ++++-- features/tags/only_tag.feature | 16 +- features/tags/skip_tag.feature | 10 +- features/tags/spec_filter.feature | 4 +- features/tags/tagged_hooks.feature | 4 +- .../target_specific_scenarios_by_tag.feature | 2 +- features/tags/test_filter.feature | 4 +- features/unambiguous_step_definitions.feature | 2 +- features/undefined_step.feature | 2 +- features/world_example.feature | 4 +- lib/add-cucumber-preprocessor-plugin.ts | 6 +- lib/helpers.ts | 17 + lib/preprocessor-configuration.ts | 20 +- lib/registry.ts | 2 +- lib/step-definitions.test.ts | 129 +++++- lib/step-definitions.ts | 118 +++++- package.json | 11 +- 71 files changed, 1015 insertions(+), 257 deletions(-) rename examples/browserify/{cypress/plugins/index.ts => cypress.config.ts} (71%) delete mode 100644 examples/browserify/cypress.json rename examples/browserify/cypress/{integration => e2e}/duckduckgo.feature (100%) rename examples/browserify/cypress/{integration => e2e}/duckduckgo.ts (100%) rename examples/esbuild/{cypress/plugins/index.ts => cypress.config.ts} (61%) delete mode 100644 examples/esbuild/cypress.json rename examples/esbuild/cypress/{integration => e2e}/duckduckgo.feature (100%) rename examples/esbuild/cypress/{integration => e2e}/duckduckgo.ts (100%) rename examples/webpack/{cypress/plugins/index.ts => cypress.config.ts} (80%) delete mode 100644 examples/webpack/cypress.json rename examples/webpack/cypress/{integration => e2e}/duckduckgo.feature (100%) rename examples/webpack/cypress/{integration => e2e}/duckduckgo.ts (100%) create mode 100644 features/support/configFileUpdater.ts create mode 100644 lib/helpers.ts diff --git a/README.md b/README.md index fe41e601..81b9c30a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ $ npm install @badeball/cypress-cucumber-preprocessor The preprocessor (with its dependencies) parses Gherkin documents and allows you to write tests as shown below. ```cucumber -# cypress/integration/duckduckgo.feature +# cypress/e2e/duckduckgo.feature Feature: duckduckgo.com Scenario: visting the frontpage When I visit duckduckgo.com @@ -26,7 +26,7 @@ Feature: duckduckgo.com ``` ```ts -// cypress/integration/duckduckgo.ts +// cypress/e2e/duckduckgo.ts import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; When("I visit duckduckgo.com", () => { diff --git a/docs/configuration.md b/docs/configuration.md index 8beb8b7d..70d3fbb8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -43,7 +43,7 @@ Every configuration option has a similar key which can be use to override it, sh | JSON path | Environment key | Example(s) | |--------------------|-------------------|------------------------------------------| -| `stepDefinitions` | `stepDefinitions` | `cypress/integration/[filepath].{js,ts}` | +| `stepDefinitions` | `stepDefinitions` | `[filepath].{js,ts}` | | `messages.enabled` | `messagesEnabled` | `true`, `false` | | `messages.output` | `messagesOutput` | `cucumber-messages.ndjson` | | `json.enabled` | `jsonEnabled` | `true`, `false` | diff --git a/docs/quick-start.md b/docs/quick-start.md index dae64cd5..3876f07c 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -6,12 +6,16 @@ $ npm install @badeball/cypress-cucumber-preprocessor # Configuration -[Configure](https://docs.cypress.io/guides/references/configuration) `testFiles` with `"**/*.feature"`, using EG. `cypress.json`. +[Configure](https://docs.cypress.io/guides/references/configuration) `specPattern` with `"**/*.feature"`, using EG. `cypress.config.ts`. -```json -{ - "testFiles": "**/*.feature" -} +```js +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature" + } +}); ``` Configure your preferred bundler to process features files, with examples for @@ -24,10 +28,10 @@ Read more about configuration options at [docs/configuration.md](configuration.m # Write a test -Write Gherkin documents anywhere in your configured integration folder (defaults to `cypress/integration`) and add a file for type definitions with a corresponding name (read more about how step definitions are resolved in [docs/step-definitions.md](step-definitions.md)). Reading [docs/cucumber-basics.md](cucumber-basics.md) is highly recommended. +Write Gherkin documents and add a file for type definitions with a corresponding name (read more about how step definitions are resolved in [docs/step-definitions.md](step-definitions.md)). Reading [docs/cucumber-basics.md](cucumber-basics.md) is highly recommended. ```cucumber -# cypress/integration/duckduckgo.feature +# cypress/e2e/duckduckgo.feature Feature: duckduckgo.com Scenario: visting the frontpage When I visit duckduckgo.com @@ -35,7 +39,7 @@ Feature: duckduckgo.com ``` ```ts -// cypress/integration/duckduckgo.ts +// cypress/e2e/duckduckgo.ts import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; When("I visit duckduckgo.com", () => { diff --git a/docs/state-management.md b/docs/state-management.md index cdaecd41..f1cba2ed 100644 --- a/docs/state-management.md +++ b/docs/state-management.md @@ -23,7 +23,7 @@ Please note that if you use arrow functions, you won’t be able to share state Even though `setWorldConstructor` isn't implemented, it's behavior can be closely replicated like shown below. ```gherkin -# cypress/integration/math.feature +# cypress/e2e/math.feature Feature: Replicating setWorldConstructor() Scenario: easy maths Given a variable set to 1 @@ -32,7 +32,7 @@ Feature: Replicating setWorldConstructor() ``` ```ts -// cypress/support/index.ts +// cypress/support/e2e.ts beforeEach(function () { const world = { variable: 0, diff --git a/docs/step-definitions.md b/docs/step-definitions.md index 8e2fb4cc..5d167691 100644 --- a/docs/step-definitions.md +++ b/docs/step-definitions.md @@ -5,17 +5,17 @@ Step definitions are resolved using search paths that are configurable through t ```json { "stepDefinitions": [ - "cypress/integration/[filepath]/**/*.{js,ts}", - "cypress/integration/[filepath].{js,ts}", + "[filepath]/**/*.{js,ts}", + "[filepath].{js,ts}", "cypress/support/step_definitions/**/*.{js,ts}", ] } ``` -This means that if you have a file `cypress/integration/duckduckgo.feature`, it will match step definitions found in +This means that if you have a file `cypress/e2e/duckduckgo.feature`, it will match step definitions found in -* `cypress/integration/duckduckgo/steps.ts` -* `cypress/integration/duckduckgo.ts` +* `cypress/e2e/duckduckgo/steps.ts` +* `cypress/e2e/duckduckgo.ts` * `cypress/support/step_definitions/duckduckgo.ts` ## Hierarchy @@ -25,14 +25,14 @@ There's also a `[filepart]` option available. Given a configuration shown below ```json { "stepDefinitions": [ - "cypress/integration/[filepart]/step_definitions/**/*.{js,ts}" + "[filepart]/step_definitions/**/*.{js,ts}" ] } ``` -... and a feature file `cypress/integration/foo/bar/baz.feature`, the preprocessor would look for step definitions in +... and a feature file `cypress/e2e/foo/bar/baz.feature`, the preprocessor would look for step definitions in -* `cypress/integration/foo/bar/baz/step_definitions/**/*.{js,ts}` -* `cypress/integration/foo/bar/step_definitions/**/*.{js,ts}` -* `cypress/integration/foo/step_definitions/**/*.{js,ts}` -* `cypress/integration/step_definitions/**/*.{js,ts}` +* `cypress/e2e/foo/bar/baz/step_definitions/**/*.{js,ts}` +* `cypress/e2e/foo/bar/step_definitions/**/*.{js,ts}` +* `cypress/e2e/foo/step_definitions/**/*.{js,ts}` +* `cypress/e2e/step_definitions/**/*.{js,ts}` diff --git a/examples/browserify/cypress/plugins/index.ts b/examples/browserify/cypress.config.ts similarity index 71% rename from examples/browserify/cypress/plugins/index.ts rename to examples/browserify/cypress.config.ts index 348cddb0..0870872c 100644 --- a/examples/browserify/cypress/plugins/index.ts +++ b/examples/browserify/cypress.config.ts @@ -1,11 +1,12 @@ +import { defineConfig } from "cypress"; import * as browserify from "@cypress/browserify-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; import { preprocessor } from "@badeball/cypress-cucumber-preprocessor/browserify"; -export default async ( +export async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): Promise => { +): Promise { await addCucumberPreprocessorPlugin(on, config); on( @@ -18,4 +19,12 @@ export default async ( // Make sure to return the config object as it might have been modified by the plugin. return config; -}; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/browserify/cypress.json b/examples/browserify/cypress.json deleted file mode 100644 index 5481e206..00000000 --- a/examples/browserify/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "testFiles": "**/*.feature" -} diff --git a/examples/browserify/cypress/integration/duckduckgo.feature b/examples/browserify/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/browserify/cypress/integration/duckduckgo.feature rename to examples/browserify/cypress/e2e/duckduckgo.feature diff --git a/examples/browserify/cypress/integration/duckduckgo.ts b/examples/browserify/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/browserify/cypress/integration/duckduckgo.ts rename to examples/browserify/cypress/e2e/duckduckgo.ts diff --git a/examples/esbuild/cypress/plugins/index.ts b/examples/esbuild/cypress.config.ts similarity index 61% rename from examples/esbuild/cypress/plugins/index.ts rename to examples/esbuild/cypress.config.ts index 3b159218..d0745e44 100644 --- a/examples/esbuild/cypress/plugins/index.ts +++ b/examples/esbuild/cypress.config.ts @@ -1,11 +1,12 @@ -import * as createBundler from "@bahmutov/cypress-esbuild-preprocessor"; +import { defineConfig } from "cypress"; +import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; -export default async ( +export async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): Promise => { +): Promise { await addCucumberPreprocessorPlugin(on, config); on( @@ -17,4 +18,12 @@ export default async ( // Make sure to return the config object as it might have been modified by the plugin. return config; -}; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/esbuild/cypress.json b/examples/esbuild/cypress.json deleted file mode 100644 index 5481e206..00000000 --- a/examples/esbuild/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "testFiles": "**/*.feature" -} diff --git a/examples/esbuild/cypress/integration/duckduckgo.feature b/examples/esbuild/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/esbuild/cypress/integration/duckduckgo.feature rename to examples/esbuild/cypress/e2e/duckduckgo.feature diff --git a/examples/esbuild/cypress/integration/duckduckgo.ts b/examples/esbuild/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/esbuild/cypress/integration/duckduckgo.ts rename to examples/esbuild/cypress/e2e/duckduckgo.ts diff --git a/examples/webpack/cypress/plugins/index.ts b/examples/webpack/cypress.config.ts similarity index 80% rename from examples/webpack/cypress/plugins/index.ts rename to examples/webpack/cypress.config.ts index 63875ea9..9690b132 100644 --- a/examples/webpack/cypress/plugins/index.ts +++ b/examples/webpack/cypress.config.ts @@ -1,10 +1,11 @@ +import { defineConfig } from "cypress"; import * as webpack from "@cypress/webpack-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -export default async ( +export async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions -): Promise => { +): Promise { await addCucumberPreprocessorPlugin(on, config); on( @@ -42,4 +43,12 @@ export default async ( // Make sure to return the config object as it might have been modified by the plugin. return config; -}; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/webpack/cypress.json b/examples/webpack/cypress.json deleted file mode 100644 index 5481e206..00000000 --- a/examples/webpack/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "testFiles": "**/*.feature" -} diff --git a/examples/webpack/cypress/integration/duckduckgo.feature b/examples/webpack/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/webpack/cypress/integration/duckduckgo.feature rename to examples/webpack/cypress/e2e/duckduckgo.feature diff --git a/examples/webpack/cypress/integration/duckduckgo.ts b/examples/webpack/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/webpack/cypress/integration/duckduckgo.ts rename to examples/webpack/cypress/e2e/duckduckgo.ts diff --git a/features/ambiguous_keywords.feature b/features/ambiguous_keywords.feature index 4018e8e6..5fec4e07 100644 --- a/features/ambiguous_keywords.feature +++ b/features/ambiguous_keywords.feature @@ -1,7 +1,7 @@ Feature: ambiguous keyword Scenario: wrongly keyworded step matching - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/asynchronous_world.feature b/features/asynchronous_world.feature index bd4a81c5..45362cda 100644 --- a/features/asynchronous_world.feature +++ b/features/asynchronous_world.feature @@ -1,7 +1,7 @@ Feature: asynchronous world Scenario: Assigning to world asynchronously - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/attachments.feature b/features/attachments.feature index 4339c108..cd7b9070 100644 --- a/features/attachments.feature +++ b/features/attachments.feature @@ -12,7 +12,7 @@ Feature: attachments And I've ensured cucumber-json-formatter is installed Scenario: string identity - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -30,7 +30,7 @@ Feature: attachments And there should be a JSON output similar to "fixtures/attachments/string.json" Scenario: array buffer - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -48,7 +48,7 @@ Feature: attachments And there should be a JSON output similar to "fixtures/attachments/string.json" Scenario: string encoded - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario diff --git a/features/configuration_overrides.feature b/features/configuration_overrides.feature index 903802c2..9e27b7b1 100644 --- a/features/configuration_overrides.feature +++ b/features/configuration_overrides.feature @@ -1,6 +1,6 @@ Feature: configuration overrides Scenario: overriding stepDefinitions through -e - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name @@ -15,7 +15,7 @@ Feature: configuration overrides Then it passes Scenario: overriding stepDefinitions through environment variables - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/custom_parameter_type.feature b/features/custom_parameter_type.feature index d2abb51e..69558fe6 100644 --- a/features/custom_parameter_type.feature +++ b/features/custom_parameter_type.feature @@ -1,6 +1,6 @@ Feature: custom parameter type Scenario: definition after usage - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario diff --git a/features/data_table.feature b/features/data_table.feature index 508f149c..e6cb0396 100644 --- a/features/data_table.feature +++ b/features/data_table.feature @@ -1,7 +1,7 @@ Feature: data tables Scenario: raw - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -24,7 +24,7 @@ Feature: data tables Then it passes Scenario: rows - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -50,7 +50,7 @@ Feature: data tables Then it passes Scenario: rowsHash - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -73,7 +73,7 @@ Feature: data tables Then it passes Scenario: hashes - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -99,7 +99,7 @@ Feature: data tables Then it passes Scenario: empty cells - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario diff --git a/features/doc_string.feature b/features/doc_string.feature index 7b47139d..740e7b84 100644 --- a/features/doc_string.feature +++ b/features/doc_string.feature @@ -1,7 +1,7 @@ Feature: doc string Scenario: as only step definition argument - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -22,7 +22,7 @@ Feature: doc string Then it passes Scenario: with other step definition arguments - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario diff --git a/features/fixtures/attachments/screenshot.json b/features/fixtures/attachments/screenshot.json index 789b677b..0fc89bec 100644 --- a/features/fixtures/attachments/screenshot.json +++ b/features/fixtures/attachments/screenshot.json @@ -35,6 +35,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/attachments/string.json b/features/fixtures/attachments/string.json index 20437769..713ca830 100644 --- a/features/fixtures/attachments/string.json +++ b/features/fixtures/attachments/string.json @@ -35,6 +35,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/failing-after.json b/features/fixtures/failing-after.json index 8101437a..6fab6aad 100644 --- a/features/fixtures/failing-after.json +++ b/features/fixtures/failing-after.json @@ -40,6 +40,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/failing-before.json b/features/fixtures/failing-before.json index a5fd5e1a..5859904d 100644 --- a/features/fixtures/failing-before.json +++ b/features/fixtures/failing-before.json @@ -39,6 +39,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/failing-step.json b/features/fixtures/failing-step.json index 9c86025e..44bd84f4 100644 --- a/features/fixtures/failing-step.json +++ b/features/fixtures/failing-step.json @@ -40,6 +40,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/multiple-features.json b/features/fixtures/multiple-features.json index 2fabdb5f..2be251ae 100644 --- a/features/fixtures/multiple-features.json +++ b/features/fixtures/multiple-features.json @@ -29,7 +29,7 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" }, { "description": "", @@ -61,6 +61,6 @@ "keyword": "Feature", "line": 1, "name": "another feature", - "uri": "cypress/integration/b.feature" + "uri": "cypress/e2e/b.feature" } ] diff --git a/features/fixtures/passed-example.json b/features/fixtures/passed-example.json index a5646a73..6583582d 100644 --- a/features/fixtures/passed-example.json +++ b/features/fixtures/passed-example.json @@ -29,6 +29,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/passed-outline.json b/features/fixtures/passed-outline.json index 0ff51995..429c4c75 100644 --- a/features/fixtures/passed-outline.json +++ b/features/fixtures/passed-outline.json @@ -51,6 +51,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/pending-steps.json b/features/fixtures/pending-steps.json index ef75b328..ca076344 100644 --- a/features/fixtures/pending-steps.json +++ b/features/fixtures/pending-steps.json @@ -51,6 +51,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/retried.json b/features/fixtures/retried.json index dd25be83..7fa6b498 100644 --- a/features/fixtures/retried.json +++ b/features/fixtures/retried.json @@ -51,6 +51,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/fixtures/undefined-steps.json b/features/fixtures/undefined-steps.json index 9d66ca4f..fdf51a31 100644 --- a/features/fixtures/undefined-steps.json +++ b/features/fixtures/undefined-steps.json @@ -39,6 +39,6 @@ "keyword": "Feature", "line": 1, "name": "a feature", - "uri": "cypress/integration/a.feature" + "uri": "cypress/e2e/a.feature" } ] diff --git a/features/hooks_ordering.feature b/features/hooks_ordering.feature index 1f6a0a24..c30ec85b 100644 --- a/features/hooks_ordering.feature +++ b/features/hooks_ordering.feature @@ -11,7 +11,7 @@ Feature: hooks ordering - after Scenario: with all hooks incrementing a counter - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Background: diff --git a/features/issues/705.feature b/features/issues/705.feature index ed2b8673..b89d7e00 100644 --- a/features/issues/705.feature +++ b/features/issues/705.feature @@ -13,7 +13,7 @@ Feature: overriding event handlers """ Scenario: overriding after:screenshot - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -26,7 +26,7 @@ Feature: overriding event handlers throw "some error" }) """ - And a file named "cypress/plugins/index.js" with: + And a file named "cypress/plugins/index.js" or "setupNodeEvents.js" (depending on Cypress era) with: """ const { addCucumberPreprocessorPlugin, afterScreenshotHandler } = require("@badeball/cypress-cucumber-preprocessor"); const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); @@ -47,6 +47,7 @@ Feature: overriding event handlers return config; } """ + When I run cypress Then it fails And the JSON report should contain an image attachment for what appears to be a screenshot diff --git a/features/issues/713.feature b/features/issues/713.feature index 0a4fe150..72e90e7c 100644 --- a/features/issues/713.feature +++ b/features/issues/713.feature @@ -2,7 +2,7 @@ Feature: returning chains Scenario: returning a chain - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario diff --git a/features/json_report.feature b/features/json_report.feature index 7075a2fe..1c46112a 100644 --- a/features/json_report.feature +++ b/features/json_report.feature @@ -12,7 +12,7 @@ Feature: JSON formatter And I've ensured cucumber-json-formatter is installed Scenario: passed example - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -28,7 +28,7 @@ Feature: JSON formatter And there should be a JSON output similar to "fixtures/passed-example.json" Scenario: passed outline - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -48,13 +48,13 @@ Feature: JSON formatter And there should be a JSON output similar to "fixtures/passed-outline.json" Scenario: multiple features - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario Given a step """ - And a file named "cypress/integration/b.feature" with: + And a file named "cypress/e2e/b.feature" with: """ Feature: another feature Scenario: another scenario @@ -76,7 +76,7 @@ Feature: JSON formatter "screenshotOnRunFailure": false } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -102,7 +102,7 @@ Feature: JSON formatter "screenshotOnRunFailure": false } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -125,7 +125,7 @@ Feature: JSON formatter "screenshotOnRunFailure": false } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -149,7 +149,7 @@ Feature: JSON formatter And there should be a JSON output similar to "fixtures/pending-steps.json" Scenario: explicit screenshot - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -180,7 +180,7 @@ Feature: JSON formatter And there should be a JSON output similar to "fixtures/attachments/screenshot.json" Scenario: screenshot of failed test - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -205,7 +205,7 @@ Feature: JSON formatter "retries": 1 } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -233,7 +233,7 @@ Feature: JSON formatter "screenshotOnRunFailure": false } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -258,7 +258,7 @@ Feature: JSON formatter "screenshotOnRunFailure": false } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -277,7 +277,7 @@ Feature: JSON formatter And there should be a JSON output similar to "fixtures/failing-after.json" Scenario: failing before hook - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -288,7 +288,7 @@ Feature: JSON formatter const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}) """ - And a file named "cypress/support/index.ts" with: + And a file named "cypress/support/e2e.js" with: """ before(() => { throw "some error" @@ -297,13 +297,13 @@ Feature: JSON formatter When I run cypress Then it fails And there should be no JSON output - And the output should contain + And the output should match """ - Hook failures can't be represented in JSON reports, thus none is created for cypress/integration/a.feature. + Hook failures can't be represented in JSON reports, thus none is created for cypress[\\\/]e2e[\\\/]a\.feature\. """ Scenario: failing beforeEach hook - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -314,7 +314,7 @@ Feature: JSON formatter const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}) """ - And a file named "cypress/support/index.ts" with: + And a file named "cypress/support/e2e.js" with: """ beforeEach(() => { throw "some error" @@ -323,13 +323,13 @@ Feature: JSON formatter When I run cypress Then it fails And there should be no JSON output - And the output should contain + And the output should match """ - Hook failures can't be represented in JSON reports, thus none is created for cypress/integration/a.feature. + Hook failures can't be represented in JSON reports, thus none is created for cypress[\\\/]e2e[\\\/]a\.feature\. """ Scenario: failing afterEach hook - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -340,7 +340,7 @@ Feature: JSON formatter const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}) """ - And a file named "cypress/support/index.ts" with: + And a file named "cypress/support/e2e.js" with: """ afterEach(() => { throw "some error" @@ -349,13 +349,13 @@ Feature: JSON formatter When I run cypress Then it fails And there should be no JSON output - And the output should contain + And the output should match """ - Hook failures can't be represented in JSON reports, thus none is created for cypress/integration/a.feature. + Hook failures can't be represented in JSON reports, thus none is created for cypress[\\\/]e2e[\\\/]a\.feature\. """ Scenario: failing after hook - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -366,7 +366,7 @@ Feature: JSON formatter const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}) """ - And a file named "cypress/support/index.ts" with: + And a file named "cypress/support/e2e.js" with: """ after(() => { throw "some error" @@ -375,7 +375,7 @@ Feature: JSON formatter When I run cypress Then it fails And there should be no JSON output - And the output should contain + And the output should match """ - Hook failures can't be represented in JSON reports, thus none is created for cypress/integration/a.feature. + Hook failures can't be represented in JSON reports, thus none is created for cypress[\\\/]e2e[\\\/]a\.feature\. """ diff --git a/features/loaders/browserify.feature b/features/loaders/browserify.feature index 7eec4f31..5c7f8cbf 100644 --- a/features/loaders/browserify.feature +++ b/features/loaders/browserify.feature @@ -1,21 +1,18 @@ @no-default-plugin Feature: browserify + typescript Scenario: - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a step """ - And a file named "cypress/plugins/index.ts" with: + And a file named "cypress/plugins/index.js" or "setupNodeEvents.js" (depending on Cypress era) with: """ - import browserify from "@cypress/browserify-preprocessor"; - import { preprocessor } from "@badeball/cypress-cucumber-preprocessor/browserify"; + const browserify = require("@cypress/browserify-preprocessor"); + const { preprocessor } = require("@badeball/cypress-cucumber-preprocessor/browserify"); - export default ( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions - ): void => { + module.exports = async (on, config) => { on( "file:preprocessor", preprocessor(config, { diff --git a/features/loaders/esbuild.feature b/features/loaders/esbuild.feature index 7b86cde4..7cfe3e98 100644 --- a/features/loaders/esbuild.feature +++ b/features/loaders/esbuild.feature @@ -1,21 +1,18 @@ @no-default-plugin Feature: esbuild + typescript Scenario: - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a step """ - And a file named "cypress/plugins/index.ts" with: + And a file named "cypress/plugins/index.js" or "setupNodeEvents.js" (depending on Cypress era) with: """ - import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; - import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; + const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); + const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); - export default ( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions - ): void => { + module.exports = async (on, config) => { on( "file:preprocessor", createBundler({ diff --git a/features/loaders/webpack.feature b/features/loaders/webpack.feature index 497008e9..354a0d67 100644 --- a/features/loaders/webpack.feature +++ b/features/loaders/webpack.feature @@ -1,20 +1,17 @@ @no-default-plugin Feature: webpack + typescript Scenario: - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a step """ - And a file named "cypress/plugins/index.ts" with: + And a file named "cypress/plugins/index.js" or "setupNodeEvents.js" (depending on Cypress era) with: """ - import webpack from "@cypress/webpack-preprocessor"; + const webpack = require("@cypress/webpack-preprocessor"); - export default ( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions - ): void => { + module.exports = async (on, config) => { on( "file:preprocessor", webpack({ diff --git a/features/localisation.feature b/features/localisation.feature index 9fe04479..c241947f 100644 --- a/features/localisation.feature +++ b/features/localisation.feature @@ -1,6 +1,6 @@ Feature: localisation Scenario: norwegian - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ # language: no Egenskap: en funksjonalitet diff --git a/features/mixing_types.feature b/features/mixing_types.feature index 70497e6c..e6df6cb6 100644 --- a/features/mixing_types.feature +++ b/features/mixing_types.feature @@ -1,14 +1,22 @@ Feature: mixing feature and non-feature specs Background: - Given additional Cypress configuration + Given if pre-v10, additional Cypress configuration """ { "testFiles": "**/*.{spec.js,feature}" } """ + And if post-v10, additional Cypress configuration + """ + { + "e2e": { + "specPattern": "**/*.{spec.js,feature}" + } + } + """ Scenario: one feature and non-feature specs - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ @foo Feature: a feature name @@ -23,7 +31,7 @@ Feature: mixing feature and non-feature specs expect(doesFeatureMatch("@foo")).to.be.true; }); """ - And a file named "cypress/integration/b.spec.js" with: + And a file named "cypress/e2e/b.spec.js" with: """ const { isFeature } = require("@badeball/cypress-cucumber-preprocessor"); it("should work", () => { diff --git a/features/nested_steps.feature b/features/nested_steps.feature index fe4c6935..e21f35e4 100644 --- a/features/nested_steps.feature +++ b/features/nested_steps.feature @@ -1,13 +1,13 @@ Feature: nesten steps Scenario: invoking step from another step - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a nested step """ - And a file named "cypress/integration/a.js" with: + And a file named "cypress/e2e/a.js" with: """ const { Given, Step } = require("@badeball/cypress-cucumber-preprocessor"); Given("a nested step", function() { diff --git a/features/parse_error.feature b/features/parse_error.feature index 6533ed34..85249262 100644 --- a/features/parse_error.feature +++ b/features/parse_error.feature @@ -1,7 +1,7 @@ Feature: parse errors Scenario: tagged rules - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name @tag diff --git a/features/scenario_outlines.feature b/features/scenario_outlines.feature index a74414a1..96bd7ad5 100644 --- a/features/scenario_outlines.feature +++ b/features/scenario_outlines.feature @@ -1,7 +1,7 @@ Feature: scenario outlines and examples Scenario: placeholder in step - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -19,7 +19,7 @@ Feature: scenario outlines and examples Then it passes Scenario: placeholder in docstring - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -42,7 +42,7 @@ Feature: scenario outlines and examples Then it passes Scenario: placeholder in table - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario diff --git a/features/skip.feature b/features/skip.feature index 3875659a..c7c7a718 100644 --- a/features/skip.feature +++ b/features/skip.feature @@ -1,7 +1,7 @@ Feature: skip Scenario: calling skip() - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/step_definitions.feature b/features/step_definitions.feature index feeb10b5..ac883c31 100644 --- a/features/step_definitions.feature +++ b/features/step_definitions.feature @@ -3,13 +3,13 @@ Feature: step definitions Rule: it should by default look for step definitions in a couple of locations Example: step definitions with same filename - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a step """ - And a file named "cypress/integration/a.js" with: + And a file named "cypress/e2e/a.js" with: """ const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}); @@ -18,13 +18,13 @@ Feature: step definitions Then it passes Example: step definitions in a directory with same name - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name Given a step """ - And a file named "cypress/integration/a/steps.js" with: + And a file named "cypress/e2e/a/steps.js" with: """ const { Given } = require("@badeball/cypress-cucumber-preprocessor"); Given("a step", function() {}); @@ -33,7 +33,7 @@ Feature: step definitions Then it passes Example: step definitions in a common directory - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/step_definitions/cli_steps.ts b/features/step_definitions/cli_steps.ts index 44423647..541e1aa1 100644 --- a/features/step_definitions/cli_steps.ts +++ b/features/step_definitions/cli_steps.ts @@ -138,6 +138,10 @@ Then("the output should contain", function (content) { assert.match(this.lastRun.stdout, new RegExp(rescape(content))); }); +Then("the output should match", function (content) { + assert.match(this.lastRun.stdout, new RegExp(content)); +}); + Then( "it should appear to have skipped the scenario {string}", function (scenarioName) { diff --git a/features/step_definitions/config_steps.ts b/features/step_definitions/config_steps.ts index 4c235cda..875c9c71 100644 --- a/features/step_definitions/config_steps.ts +++ b/features/step_definitions/config_steps.ts @@ -1,31 +1,23 @@ import { Given } from "@cucumber/cucumber"; import path from "path"; import { promises as fs } from "fs"; +import { isPost10, isPre10 } from "../support/helpers"; +import { insertValuesInConfigFile } from "../support/configFileUpdater"; -async function addOrUpdateConfiguration( +async function updateJsonConfiguration( absoluteConfigPath: string, - additionalJsonContent: string + additionalJsonContent: any ) { - let existingConfig: any; - - try { - existingConfig = JSON.parse( - (await fs.readFile(absoluteConfigPath)).toString() - ); - } catch (e: any) { - if (e.code === "ENOENT") { - existingConfig = {}; - } else { - throw e; - } - } + const existingConfig = JSON.parse( + (await fs.readFile(absoluteConfigPath)).toString() + ); await fs.writeFile( absoluteConfigPath, JSON.stringify( { ...existingConfig, - ...JSON.parse(additionalJsonContent), + ...additionalJsonContent, }, null, 2 @@ -39,11 +31,43 @@ Given("additional preprocessor configuration", async function (jsonContent) { ".cypress-cucumber-preprocessorrc" ); - await addOrUpdateConfiguration(absoluteConfigPath, jsonContent); + await updateJsonConfiguration(absoluteConfigPath, JSON.parse(jsonContent)); }); Given("additional Cypress configuration", async function (jsonContent) { - const absoluteConfigPath = path.join(this.tmpDir, "cypress.json"); - - await addOrUpdateConfiguration(absoluteConfigPath, jsonContent); + if (isPost10()) { + await insertValuesInConfigFile( + path.join(this.tmpDir, "cypress.config.js"), + JSON.parse(jsonContent) + ); + } else { + await updateJsonConfiguration( + path.join(this.tmpDir, "cypress.json"), + JSON.parse(jsonContent) + ); + } }); + +Given( + "if post-v10, additional Cypress configuration", + async function (jsonContent) { + if (isPost10()) { + await insertValuesInConfigFile( + path.join(this.tmpDir, "cypress.config.js"), + JSON.parse(jsonContent) + ); + } + } +); + +Given( + "if pre-v10, additional Cypress configuration", + async function (jsonContent) { + if (isPre10()) { + await updateJsonConfiguration( + path.join(this.tmpDir, "cypress.json"), + JSON.parse(jsonContent) + ); + } + } +); diff --git a/features/step_definitions/file_steps.ts b/features/step_definitions/file_steps.ts index 34d33c43..783fbe97 100644 --- a/features/step_definitions/file_steps.ts +++ b/features/step_definitions/file_steps.ts @@ -1,7 +1,7 @@ import { Given } from "@cucumber/cucumber"; import stripIndent from "strip-indent"; import path from "path"; -import { writeFile } from "../support/helpers"; +import { isPost10, isPre10, writeFile } from "../support/helpers"; Given("a file named {string} with:", async function (filePath, fileContent) { const absoluteFilePath = path.join(this.tmpDir, filePath); @@ -9,6 +9,16 @@ Given("a file named {string} with:", async function (filePath, fileContent) { await writeFile(absoluteFilePath, stripIndent(fileContent)); }); +Given( + "a file named {string} or {string} \\(depending on Cypress era) with:", + async function (filePathPre10, filePathPost10, fileContent) { + await writeFile( + path.join(this.tmpDir, isPre10() ? filePathPre10 : filePathPost10), + stripIndent(fileContent) + ); + } +); + Given("an empty file named {string}", async function (filePath) { const absoluteFilePath = path.join(this.tmpDir, filePath); diff --git a/features/support/configFileUpdater.ts b/features/support/configFileUpdater.ts new file mode 100644 index 00000000..bf3ffa33 --- /dev/null +++ b/features/support/configFileUpdater.ts @@ -0,0 +1,400 @@ +import { parse } from "@babel/parser"; +import type { File } from "@babel/types"; +import type { NodePath } from "ast-types/lib/node-path"; +import { visit } from "recast"; +import type { namedTypes } from "ast-types"; +import * as fs from "fs-extra"; +import prettier from "prettier"; + +/** + * Shamelessly copied from Cypress. + */ + +export async function insertValuesInConfigFile( + filePath: string, + obj: Record = {} +) { + await insertValuesInJavaScript(filePath, obj); + + return true; +} + +export async function insertValuesInJavaScript( + filePath: string, + obj: Record +) { + const fileContents = await fs.readFile(filePath, { encoding: "utf8" }); + + const finalCode = await insertValueInJSString(fileContents, obj); + + const prettifiedCode = prettier.format(finalCode, { parser: "babel" }); + + await fs.writeFile(filePath, prettifiedCode); +} + +export async function insertValueInJSString( + fileContents: string, + obj: Record +): Promise { + const ast = parse(fileContents, { + plugins: ["typescript"], + sourceType: "module", + }); + + let objectLiteralNode: namedTypes.ObjectExpression | undefined; + + function handleExport( + nodePath: + | NodePath + | NodePath + ): void { + if ( + nodePath.node.type === "CallExpression" && + nodePath.node.callee.type === "Identifier" + ) { + const functionName = nodePath.node.callee.name; + + if (isDefineConfigFunction(ast, functionName)) { + return handleExport(nodePath.get("arguments", 0)); + } + } + + if ( + nodePath.node.type === "ObjectExpression" && + !nodePath.node.properties.find((prop) => prop.type !== "ObjectProperty") + ) { + objectLiteralNode = nodePath.node; + + return; + } + + throw new Error( + "Cypress was unable to add/update values in your configuration file." + ); + } + + visit(ast, { + visitAssignmentExpression(nodePath) { + if (nodePath.node.left.type === "MemberExpression") { + if ( + nodePath.node.left.object.type === "Identifier" && + nodePath.node.left.object.name === "module" && + nodePath.node.left.property.type === "Identifier" && + nodePath.node.left.property.name === "exports" + ) { + handleExport(nodePath.get("right")); + } + } + + return false; + }, + visitExportDefaultDeclaration(nodePath) { + handleExport(nodePath.get("declaration")); + + return false; + }, + }); + + const splicers: Splicer[] = []; + + if (!objectLiteralNode) { + // if the export is no object litteral + throw new Error( + "Cypress was unable to add/update values in your configuration file." + ); + } + + setRootKeysSplicers(splicers, obj, objectLiteralNode!, " "); + setSubKeysSplicers(splicers, obj, objectLiteralNode!, " ", " "); + + // sort splicers to keep the order of the original file + const sortedSplicers = splicers.sort((a, b) => + a.start === b.start ? 0 : a.start > b.start ? 1 : -1 + ); + + if (!sortedSplicers.length) return fileContents; + + let nextStartingIndex = 0; + let resultCode = ""; + + sortedSplicers.forEach((splicer) => { + resultCode += + fileContents.slice(nextStartingIndex, splicer.start) + + splicer.replaceString; + nextStartingIndex = splicer.end; + }); + + return resultCode + fileContents.slice(nextStartingIndex); +} + +export function isDefineConfigFunction( + ast: File, + functionName: string +): boolean { + let value = false; + + visit(ast, { + visitVariableDeclarator(nodePath) { + // if this is a require of cypress + if ( + nodePath.node.init?.type === "CallExpression" && + nodePath.node.init.callee.type === "Identifier" && + nodePath.node.init.callee.name === "require" && + nodePath.node.init.arguments[0].type === "StringLiteral" && + nodePath.node.init.arguments[0].value === "cypress" + ) { + if (nodePath.node.id?.type === "ObjectPattern") { + const defineConfigFunctionNode = nodePath.node.id.properties.find( + (prop) => { + return ( + prop.type === "ObjectProperty" && + prop.key.type === "Identifier" && + prop.key.name === "defineConfig" + ); + } + ); + + if (defineConfigFunctionNode) { + value = + (defineConfigFunctionNode as any).value?.name === functionName; + } + } + } + + return false; + }, + visitImportDeclaration(nodePath) { + if ( + nodePath.node.source.type === "StringLiteral" && + nodePath.node.source.value === "cypress" + ) { + const defineConfigFunctionNode = nodePath.node.specifiers?.find( + (specifier) => { + return ( + specifier.type === "ImportSpecifier" && + specifier.imported.type === "Identifier" && + specifier.imported.name === "defineConfig" + ); + } + ); + + if (defineConfigFunctionNode) { + value = + (defineConfigFunctionNode as any).local?.name === functionName; + } + } + + return false; + }, + }); + + return value; +} + +function setRootKeysSplicers( + splicers: Splicer[], + obj: Record, + objectLiteralNode: namedTypes.ObjectExpression, + lineStartSpacer: string +) { + const objectLiteralStartIndex = (objectLiteralNode as any).start + 1; + // add values + const objKeys = Object.keys(obj).filter((key) => + ["boolean", "number", "string"].includes(typeof obj[key]) + ); + + // update values + const keysToUpdate = objKeys.filter((key) => { + return objectLiteralNode.properties.find((prop) => { + return ( + prop.type === "ObjectProperty" && + prop.key.type === "Identifier" && + prop.key.name === key + ); + }); + }); + + keysToUpdate.forEach((key) => { + const propertyToUpdate = propertyFromKey(objectLiteralNode, key); + + if (propertyToUpdate) { + setSplicerToUpdateProperty( + splicers, + propertyToUpdate, + obj[key], + key, + obj + ); + } + }); + + const keysToInsert = objKeys.filter((key) => !keysToUpdate.includes(key)); + + if (keysToInsert.length) { + const valuesInserted = `\n${lineStartSpacer}${keysToInsert + .map((key) => `${key}: ${JSON.stringify(obj[key])},`) + .join(`\n${lineStartSpacer}`)}`; + + splicers.push({ + start: objectLiteralStartIndex, + end: objectLiteralStartIndex, + replaceString: valuesInserted, + }); + } +} + +function setSubKeysSplicers( + splicers: Splicer[], + obj: Record, + objectLiteralNode: namedTypes.ObjectExpression, + lineStartSpacer: string, + parentLineStartSpacer: string +) { + const objectLiteralStartIndex = (objectLiteralNode as any).start + 1; + + const keysToUpdateWithObjects: string[] = []; + + const objSubkeys = Object.keys(obj) + .filter((key) => typeof obj[key] === "object") + .reduce((acc: Array<{ parent: string; subkey: string }>, key) => { + keysToUpdateWithObjects.push(key); + Object.entries(obj[key]).forEach(([subkey, value]) => { + if (["boolean", "number", "string"].includes(typeof value)) { + acc.push({ parent: key, subkey }); + } + }); + + return acc; + }, []); + + // add values where the parent key needs to be created + const subkeysToInsertWithoutKey = objSubkeys.filter(({ parent }) => { + return !objectLiteralNode.properties.find((prop) => { + return ( + prop.type === "ObjectProperty" && + prop.key.type === "Identifier" && + prop.key.name === parent + ); + }); + }); + const keysToInsertForSubKeys: Record = {}; + + subkeysToInsertWithoutKey.forEach((keyTuple) => { + const subkeyList = keysToInsertForSubKeys[keyTuple.parent] || []; + + subkeyList.push(keyTuple.subkey); + keysToInsertForSubKeys[keyTuple.parent] = subkeyList; + }); + + let subvaluesInserted = ""; + + for (const key in keysToInsertForSubKeys) { + subvaluesInserted += `\n${parentLineStartSpacer}${key}: {`; + keysToInsertForSubKeys[key].forEach((subkey) => { + subvaluesInserted += `\n${parentLineStartSpacer}${lineStartSpacer}${subkey}: ${JSON.stringify( + obj[key][subkey] + )},`; + }); + + subvaluesInserted += `\n${parentLineStartSpacer}},`; + } + + if (subkeysToInsertWithoutKey.length) { + splicers.push({ + start: objectLiteralStartIndex, + end: objectLiteralStartIndex, + replaceString: subvaluesInserted, + }); + } + + // add/update values where parent key already exists + keysToUpdateWithObjects + .filter((parent) => { + return objectLiteralNode.properties.find((prop) => { + return ( + prop.type === "ObjectProperty" && + prop.key.type === "Identifier" && + prop.key.name === parent + ); + }); + }) + .forEach((key) => { + const propertyToUpdate = propertyFromKey(objectLiteralNode, key); + + if (propertyToUpdate?.value.type === "ObjectExpression") { + setRootKeysSplicers( + splicers, + obj[key], + propertyToUpdate.value, + parentLineStartSpacer + lineStartSpacer + ); + } + }); +} + +function setSplicerToUpdateProperty( + splicers: Splicer[], + propertyToUpdate: namedTypes.ObjectProperty, + updatedValue: any, + key: string, + obj: Record +) { + if ( + propertyToUpdate && + (isPrimitive(propertyToUpdate.value) || + isUndefinedOrNull(propertyToUpdate.value)) + ) { + splicers.push({ + start: (propertyToUpdate.value as any).start, + end: (propertyToUpdate.value as any).end, + replaceString: + typeof updatedValue === "function" + ? updatedValue.toString() + : JSON.stringify(updatedValue), + }); + } else { + throw new Error( + "Cypress was unable to add/update values in your configuration file." + ); + } +} + +function propertyFromKey( + objectLiteralNode: namedTypes.ObjectExpression | undefined, + key: string +): namedTypes.ObjectProperty | undefined { + return objectLiteralNode?.properties.find((prop) => { + return ( + prop.type === "ObjectProperty" && + prop.key.type === "Identifier" && + prop.key.name === key + ); + }) as namedTypes.ObjectProperty; +} + +function isPrimitive( + value: NodePath["node"] +): value is + | namedTypes.NumericLiteral + | namedTypes.StringLiteral + | namedTypes.BooleanLiteral { + return ( + value.type === "NumericLiteral" || + value.type === "StringLiteral" || + value.type === "BooleanLiteral" + ); +} + +function isUndefinedOrNull( + value: NodePath["node"] +): value is namedTypes.Identifier { + return ( + value.type === "Identifier" && ["undefined", "null"].includes(value.name) + ); +} + +interface Splicer { + start: number; + end: number; + replaceString: string; +} diff --git a/features/support/helpers.ts b/features/support/helpers.ts index 2a7fe94c..d045bbcb 100644 --- a/features/support/helpers.ts +++ b/features/support/helpers.ts @@ -1,7 +1,16 @@ import path from "path"; import { promises as fs } from "fs"; +import { version as cypressVersion } from "cypress/package.json"; export async function writeFile(filePath: string, fileContent: string) { await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, fileContent); } + +export function isPost10() { + return cypressVersion.startsWith("10."); +} + +export function isPre10() { + return !isPost10(); +} diff --git a/features/support/hooks.ts b/features/support/hooks.ts index 51d8263d..3b34d053 100644 --- a/features/support/hooks.ts +++ b/features/support/hooks.ts @@ -1,8 +1,9 @@ +import { version as cypressVersion } from "cypress/package.json"; import { After, Before, formatterHelpers } from "@cucumber/cucumber"; import path from "path"; import assert from "assert"; import { promises as fs } from "fs"; -import { writeFile } from "./helpers"; +import { isPost10, writeFile } from "./helpers"; const projectPath = path.join(__dirname, "..", ".."); @@ -21,18 +22,45 @@ Before(async function ({ gherkinDocument, pickle }) { await fs.rm(this.tmpDir, { recursive: true, force: true }); await writeFile( - path.join(this.tmpDir, "cypress.json"), - JSON.stringify( - { - testFiles: "**/*.feature", - video: false, - nodeVersion: "system", - }, - null, - 2 - ) + path.join(this.tmpDir, ".cypress-cucumber-preprocessorrc"), + "{}" ); + await writeFile(path.join(this.tmpDir, "cypress", "support", "e2e.js"), ""); + + if (isPost10()) { + await writeFile( + path.join(this.tmpDir, "cypress.config.js"), + ` + const { defineConfig } = require("cypress"); + const setupNodeEvents = require("./setupNodeEvents.js"); + + module.exports = defineConfig({ + e2e: { + specPattern: "cypress/e2e/**/*.feature", + video: false, + setupNodeEvents + } + }) + ` + ); + } else { + await writeFile( + path.join(this.tmpDir, "cypress.json"), + JSON.stringify( + { + testFiles: "**/*.feature", + integrationFolder: "cypress/e2e", + supportFile: "cypress/support/e2e.js", + video: false, + nodeVersion: "system", + }, + null, + 2 + ) + ); + } + await fs.mkdir(path.join(this.tmpDir, "node_modules", "@badeball"), { recursive: true, }); @@ -50,27 +78,51 @@ Before(async function ({ gherkinDocument, pickle }) { }); Before({ tags: "not @no-default-plugin" }, async function () { - await writeFile( - path.join(this.tmpDir, "cypress", "plugins", "index.js"), - ` - const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); - const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); - const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); - - module.exports = async (on, config) => { - await addCucumberPreprocessorPlugin(on, config); - - on( - "file:preprocessor", - createBundler({ - plugins: [createEsbuildPlugin(config)] - }) - ); - - return config; - } - ` - ); + if (isPost10()) { + await writeFile( + path.join(this.tmpDir, "setupNodeEvents.js"), + ` + const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); + const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); + const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); + + module.exports = async function setupNodeEvents(on, config) { + await addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + createBundler({ + plugins: [createEsbuildPlugin(config)], + }) + ); + + return config; + }; + ` + ); + } else { + await writeFile( + path.join(this.tmpDir, "cypress", "plugins", "index.js"), + ` + const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor"); + const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild"); + const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); + + module.exports = async (on, config) => { + await addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + createBundler({ + plugins: [createEsbuildPlugin(config)] + }) + ); + + return config; + } + ` + ); + } }); After(function () { @@ -84,3 +136,15 @@ After(function () { ); } }); + +function addCucumberPreprocessorPlugin(on: any, config: any) { + throw new Error("Function not implemented."); +} + +function createBundler(arg0: { plugins: any[] }): any { + throw new Error("Function not implemented."); +} + +function createEsbuildPlugin(config: any) { + throw new Error("Function not implemented."); +} diff --git a/features/tags/only_tag.feature b/features/tags/only_tag.feature index f89f8efe..be7a6b94 100644 --- a/features/tags/only_tag.feature +++ b/features/tags/only_tag.feature @@ -6,7 +6,7 @@ Feature: @only tag - Presence of this tag override any other tag filter Scenario: 1 / 2 scenarios tagged with @only - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @only @@ -25,7 +25,7 @@ Feature: @only tag And it should appear to have skipped the scenario "another scenario" Scenario: 2 / 2 scenarios tagged with @only - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @only @@ -44,7 +44,7 @@ Feature: @only tag Then it should appear as if both tests ran Scenario: 1 / 2 example table tagged with @only - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -71,7 +71,7 @@ Feature: @only tag And it should appear to have skipped the scenario "a scenario (example #2)" Scenario: 2 / 2 example table tagged with @only - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -99,14 +99,14 @@ Feature: @only tag And it should appear to have run the scenario "a scenario (example #2)" Scenario: one file with @only, one without - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @only Scenario: a scenario Given a step """ - And a file named "cypress/integration/b.feature" with: + And a file named "cypress/e2e/b.feature" with: """ Feature: b feature Scenario: another scenario @@ -122,7 +122,7 @@ Feature: @only tag And it should appear to have run the scenario "another scenario" Scenario: specifying tags - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @only @@ -143,7 +143,7 @@ Feature: @only tag And it should appear to have skipped the scenario "another scenario" Scenario: @focus (backwards compatibility) - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @focus diff --git a/features/tags/skip_tag.feature b/features/tags/skip_tag.feature index 5d585b56..6ebd5ac0 100644 --- a/features/tags/skip_tag.feature +++ b/features/tags/skip_tag.feature @@ -5,7 +5,7 @@ Feature: @skip tag - Presence of this tag override any other tag filter Scenario: 1 / 2 scenarios tagged with @skip - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario: a scenario @@ -24,7 +24,7 @@ Feature: @skip tag And it should appear to have skipped the scenario "another scenario" Scenario: 2 / 2 scenarios tagged with @skip - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @skip @@ -43,7 +43,7 @@ Feature: @skip tag Then it should appear as if both tests were skipped Scenario: 1 / 2 example table tagged with @skip - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -70,7 +70,7 @@ Feature: @skip tag And it should appear to have skipped the scenario "a scenario (example #2)" Scenario: 2 / 2 example table tagged with @skip - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature Scenario Outline: a scenario @@ -98,7 +98,7 @@ Feature: @skip tag And it should appear to have skipped the scenario "a scenario (example #2)" Scenario: specifying tags - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature @foo diff --git a/features/tags/spec_filter.feature b/features/tags/spec_filter.feature index 336cc687..0d2f6ff5 100644 --- a/features/tags/spec_filter.feature +++ b/features/tags/spec_filter.feature @@ -7,14 +7,14 @@ Feature: filter spec "filterSpecs": true } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ @foo Feature: some feature Scenario: first scenario Given a step """ - And a file named "cypress/integration/b.feature" with: + And a file named "cypress/e2e/b.feature" with: """ @bar Feature: some other feature diff --git a/features/tags/tagged_hooks.feature b/features/tags/tagged_hooks.feature index c42132ad..2e48c791 100644 --- a/features/tags/tagged_hooks.feature +++ b/features/tags/tagged_hooks.feature @@ -29,7 +29,7 @@ Feature: tagged Hooks """ Scenario: hooks filtered by tags on scenario - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: @foo @@ -41,7 +41,7 @@ Feature: tagged Hooks Then it passes Scenario: tags cascade from feature to scenario - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ @foo Feature: diff --git a/features/tags/target_specific_scenarios_by_tag.feature b/features/tags/target_specific_scenarios_by_tag.feature index 3613c249..0a431ad1 100644 --- a/features/tags/target_specific_scenarios_by_tag.feature +++ b/features/tags/target_specific_scenarios_by_tag.feature @@ -1,7 +1,7 @@ Feature: target specific scenario Background: - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: some feature @a diff --git a/features/tags/test_filter.feature b/features/tags/test_filter.feature index 9ab0f00e..235ed92b 100644 --- a/features/tags/test_filter.feature +++ b/features/tags/test_filter.feature @@ -1,7 +1,7 @@ Feature: test filter Scenario: with omitFiltered = false (default) - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: some feature Rule: first rule @@ -29,7 +29,7 @@ Feature: test filter "omitFiltered": true } """ - And a file named "cypress/integration/a.feature" with: + And a file named "cypress/e2e/a.feature" with: """ Feature: some feature Rule: first rule diff --git a/features/unambiguous_step_definitions.feature b/features/unambiguous_step_definitions.feature index b5c59ec1..a1d2ac36 100644 --- a/features/unambiguous_step_definitions.feature +++ b/features/unambiguous_step_definitions.feature @@ -1,7 +1,7 @@ Feature: unambiguous step definitions Scenario: step matching two definitions - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/undefined_step.feature b/features/undefined_step.feature index c4a9fc0d..4307d26f 100644 --- a/features/undefined_step.feature +++ b/features/undefined_step.feature @@ -1,7 +1,7 @@ Feature: undefined Steps Scenario: - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: a feature name Scenario: a scenario name diff --git a/features/world_example.feature b/features/world_example.feature index 858d5945..5265ea22 100644 --- a/features/world_example.feature +++ b/features/world_example.feature @@ -1,7 +1,7 @@ Feature: world Scenario: example of an World - Given a file named "cypress/integration/a.feature" with: + Given a file named "cypress/e2e/a.feature" with: """ Feature: Simple maths In order to do maths @@ -24,7 +24,7 @@ Feature: world | 99 | 1234 | 1333 | | 12 | 5 | 17 | """ - And a file named "cypress/support/index.js" with: + And a file named "cypress/support/e2e.js" with: """ beforeEach(function () { // This mimics setWorldConstructor of cucumber-js. diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index d4bb3c21..e9de50e6 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -32,6 +32,8 @@ import { notNull } from "./type-guards"; import { getTags } from "./environment-helpers"; +import { ensureIsAbsolute } from "./helpers"; + function memoize any>( fn: T ): (...args: Parameters) => ReturnType { @@ -280,7 +282,9 @@ export default async function addCucumberPreprocessorPlugin( if (tags !== null && preprocessor.filterSpecs) { const node = parse(tags); - (config as any).testFiles = getTestFiles( + const propertyName = "specPattern" in config ? "specPattern" : "testFiles"; + + (config as any)[propertyName] = getTestFiles( config as unknown as ICypressConfiguration ).filter((testFile) => { const content = syncFs.readFileSync(testFile).toString("utf-8"); diff --git a/lib/helpers.ts b/lib/helpers.ts new file mode 100644 index 00000000..6186de17 --- /dev/null +++ b/lib/helpers.ts @@ -0,0 +1,17 @@ +import path from "path"; + +export function ensureIsAbsolute(root: string, maybeRelativePath: string) { + if (path.isAbsolute(maybeRelativePath)) { + return maybeRelativePath; + } else { + return path.join(root, maybeRelativePath); + } +} + +export function ensureIsRelative(root: string, maybeRelativePath: string) { + if (path.isAbsolute(maybeRelativePath)) { + return path.relative(root, maybeRelativePath); + } else { + return maybeRelativePath; + } +} diff --git a/lib/preprocessor-configuration.ts b/lib/preprocessor-configuration.ts index c15ee26b..12b2f045 100644 --- a/lib/preprocessor-configuration.ts +++ b/lib/preprocessor-configuration.ts @@ -260,7 +260,7 @@ export function stringToMaybeBoolean(value: string): boolean | undefined { } export interface IPreprocessorConfiguration { - readonly stepDefinitions: string | string[]; + readonly stepDefinitions?: string | string[]; readonly messages?: { enabled: boolean; output?: string; @@ -285,6 +285,18 @@ export interface IEnvironmentOverrides { omitFiltered?: boolean; } +export const DEFAULT_PRE_10_STEP_DEFINITIONS = [ + "[integration-directory]/[filepath]/**/*.{js,ts}", + "[integration-directory]/[filepath].{js,ts}", + "cypress/support/step_definitions/**/*.{js,ts}", +]; + +export const DEFAULT_POST_10_STEP_DEFINITIONS = [ + "[filepath]/**/*.{js,ts}", + "[filepath].{js,ts}", + "cypress/support/step_definitions/**/*.{js,ts}", +]; + export class PreprocessorConfiguration implements IPreprocessorConfiguration { constructor( private explicitValues: Partial, @@ -294,11 +306,7 @@ export class PreprocessorConfiguration implements IPreprocessorConfiguration { get stepDefinitions() { return ( this.environmentOverrides.stepDefinitions ?? - this.explicitValues.stepDefinitions ?? [ - "cypress/integration/[filepath]/**/*.{js,ts}", - "cypress/integration/[filepath].{js,ts}", - "cypress/support/step_definitions/**/*.{js,ts}", - ] + this.explicitValues.stepDefinitions ); } diff --git a/lib/registry.ts b/lib/registry.ts index 89b9ad3b..48066826 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -216,6 +216,6 @@ export function freeRegistry() { export function getRegistry() { return assertAndReturn( globalThis[globalPropertyName], - "Expected to find a global registry (this usually means you are trying to define steps or hooks in support/index.js, which is not supported)" + "Expected to find a global registry (this usually means you are trying to define steps or hooks in support/e2e.js, which is not supported)" ); } diff --git a/lib/step-definitions.test.ts b/lib/step-definitions.test.ts index 57faa0bd..ba2edf72 100644 --- a/lib/step-definitions.test.ts +++ b/lib/step-definitions.test.ts @@ -2,19 +2,26 @@ import util from "util"; import assert from "assert"; -import { ICypressConfiguration } from "@badeball/cypress-configuration"; +import { + ICypressPost10Configuration, + ICypressPre10Configuration, +} from "@badeball/cypress-configuration"; import { IPreprocessorConfiguration, PreprocessorConfiguration, } from "./preprocessor-configuration"; -import { getStepDefinitionPatterns, pathParts } from "./step-definitions"; +import { + getStepDefinitionPatternsPost10, + getStepDefinitionPatternsPre10, + pathParts, +} from "./step-definitions"; -function example( +function pre10example( filepath: string, cypressConfiguration: Pick< - ICypressConfiguration, + ICypressPre10Configuration, "projectRoot" | "integrationFolder" >, preprocessorConfiguration: Partial, @@ -25,7 +32,47 @@ function example( )}] for ${filepath} with ${util.inspect(preprocessorConfiguration)} in ${ cypressConfiguration.projectRoot }`, () => { - const actual = getStepDefinitionPatterns( + const actual = getStepDefinitionPatternsPre10( + { + cypress: cypressConfiguration, + preprocessor: new PreprocessorConfiguration( + preprocessorConfiguration, + {} + ), + }, + filepath + ); + + const throwUnequal = () => { + throw new Error( + `Expected ${util.inspect(expected)}, but got ${util.inspect(actual)}` + ); + }; + + if (expected.length !== actual.length) { + throwUnequal(); + } + + for (let i = 0; i < expected.length; i++) { + if (expected[i] !== actual[i]) { + throwUnequal(); + } + } + }); +} + +function post10example( + filepath: string, + cypressConfiguration: Pick, + preprocessorConfiguration: Partial, + expected: string[] +) { + it(`should return [${expected.join( + ", " + )}] for ${filepath} with ${util.inspect(preprocessorConfiguration)} in ${ + cypressConfiguration.projectRoot + }`, () => { + const actual = getStepDefinitionPatternsPost10( { cypress: cypressConfiguration, preprocessor: new PreprocessorConfiguration( @@ -65,8 +112,8 @@ describe("pathParts()", () => { }); }); -describe("getStepDefinitionPatterns()", () => { - example( +describe("getStepDefinitionPatternsPre10()", () => { + pre10example( "/foo/bar/cypress/integration/baz.feature", { projectRoot: "/foo/bar", @@ -80,7 +127,7 @@ describe("getStepDefinitionPatterns()", () => { ] ); - example( + pre10example( "/cypress/integration/foo/bar/baz.feature", { projectRoot: "/", @@ -92,7 +139,7 @@ describe("getStepDefinitionPatterns()", () => { ["/cypress/integration/foo/bar/baz/step_definitions/*.ts"] ); - example( + pre10example( "/cypress/integration/foo/bar/baz.feature", { projectRoot: "/", @@ -111,7 +158,7 @@ describe("getStepDefinitionPatterns()", () => { it("should error when provided a path not within integrationFolder", () => { assert.throws(() => { - getStepDefinitionPatterns( + getStepDefinitionPatternsPre10( { cypress: { projectRoot: "/foo/bar", @@ -128,7 +175,7 @@ describe("getStepDefinitionPatterns()", () => { it("should error when provided a path not within cwd", () => { assert.throws(() => { - getStepDefinitionPatterns( + getStepDefinitionPatternsPre10( { cypress: { projectRoot: "/baz", @@ -143,3 +190,63 @@ describe("getStepDefinitionPatterns()", () => { }, "/foo/bar/cypress/features/baz.feature is not within /baz"); }); }); + +describe("getStepDefinitionPatternsPost10()", () => { + post10example( + "/foo/bar/cypress/e2e/baz.feature", + { + projectRoot: "/foo/bar", + }, + {}, + [ + "/foo/bar/cypress/e2e/baz/**/*.{js,ts}", + "/foo/bar/cypress/e2e/baz.{js,ts}", + "/foo/bar/cypress/support/step_definitions/**/*.{js,ts}", + ] + ); + + post10example( + "/cypress/e2e/foo/bar/baz.feature", + { + projectRoot: "/", + }, + { + stepDefinitions: "[filepath]/step_definitions/*.ts", + }, + ["/cypress/e2e/foo/bar/baz/step_definitions/*.ts"] + ); + + post10example( + "/cypress/e2e/foo/bar/baz.feature", + { + projectRoot: "/", + }, + { + stepDefinitions: "[filepart]/step_definitions/*.ts", + }, + [ + "/cypress/e2e/foo/bar/baz/step_definitions/*.ts", + "/cypress/e2e/foo/bar/step_definitions/*.ts", + "/cypress/e2e/foo/step_definitions/*.ts", + "/cypress/e2e/step_definitions/*.ts", + "/cypress/step_definitions/*.ts", + "/step_definitions/*.ts", + ] + ); + + it("should error when provided a path not within cwd", () => { + assert.throws(() => { + getStepDefinitionPatternsPost10( + { + cypress: { + projectRoot: "/baz", + }, + preprocessor: { + stepDefinitions: [], + }, + }, + "/foo/bar/cypress/e2e/baz.feature" + ); + }, "/foo/bar/cypress/features/baz.feature is not within /baz"); + }); +}); diff --git a/lib/step-definitions.ts b/lib/step-definitions.ts index 6cb18023..c9171d13 100644 --- a/lib/step-definitions.ts +++ b/lib/step-definitions.ts @@ -12,7 +12,17 @@ import { ICypressConfiguration } from "@badeball/cypress-configuration"; import debug from "./debug"; -import { IPreprocessorConfiguration } from "./preprocessor-configuration"; +import { + DEFAULT_POST_10_STEP_DEFINITIONS, + DEFAULT_PRE_10_STEP_DEFINITIONS, + IPreprocessorConfiguration, +} from "./preprocessor-configuration"; +import { + ICypressPost10Configuration, + ICypressPre10Configuration, +} from "@badeball/cypress-configuration/lib/cypress-configuration"; + +import { ensureIsAbsolute, ensureIsRelative } from "./helpers"; export async function getStepDefinitionPaths( configuration: { @@ -61,22 +71,31 @@ export function pathParts(relativePath: string): string[] { export function getStepDefinitionPatterns( configuration: { - cypress: Pick; + cypress: ICypressConfiguration; preprocessor: IPreprocessorConfiguration; }, filepath: string ): string[] { - const fullIntegrationFolder = path.isAbsolute( - configuration.cypress.integrationFolder - ) - ? configuration.cypress.integrationFolder - : path.join( - configuration.cypress.projectRoot, - configuration.cypress.integrationFolder - ); + const { cypress, preprocessor } = configuration; - if (!isPathInside(filepath, fullIntegrationFolder)) { - throw new Error(`${filepath} is not inside ${fullIntegrationFolder}`); + if ("specPattern" in cypress) { + return getStepDefinitionPatternsPost10({ cypress, preprocessor }, filepath); + } else { + return getStepDefinitionPatternsPre10({ cypress, preprocessor }, filepath); + } +} + +export function getStepDefinitionPatternsPost10( + configuration: { + cypress: Pick; + preprocessor: IPreprocessorConfiguration; + }, + filepath: string +): string[] { + const projectRoot = configuration.cypress.projectRoot; + + if (!isPathInside(filepath, projectRoot)) { + throw new Error(`${filepath} is not inside ${projectRoot}`); } debug( @@ -85,6 +104,61 @@ export function getStepDefinitionPatterns( )}` ); + const filepathReplacement = trimFeatureExtension( + path.relative(projectRoot, filepath) + ); + + debug(`replacing [filepath] with ${util.inspect(filepathReplacement)}`); + + const parts = pathParts(filepathReplacement); + + debug(`replacing [filepart] with ${util.inspect(parts)}`); + + const stepDefinitions = configuration.preprocessor.stepDefinitions + ? [configuration.preprocessor.stepDefinitions].flat() + : DEFAULT_POST_10_STEP_DEFINITIONS; + + return stepDefinitions + .flatMap((pattern) => { + if (pattern.includes("[filepath]") && pattern.includes("[filepart]")) { + throw new Error( + `Pattern cannot contain both [filepath] and [filepart], but got ${util.inspect( + pattern + )}` + ); + } else if (pattern.includes("[filepath]")) { + return pattern.replace("[filepath]", filepathReplacement); + } else if (pattern.includes("[filepart]")) { + return [ + ...parts.map((part) => pattern.replace("[filepart]", part)), + path.normalize(pattern.replace("[filepart]", ".")), + ]; + } else { + return pattern; + } + }) + .map((pattern) => path.join(projectRoot, pattern)); +} + +export function getStepDefinitionPatternsPre10( + configuration: { + cypress: Pick< + ICypressPre10Configuration, + "projectRoot" | "integrationFolder" + >; + preprocessor: IPreprocessorConfiguration; + }, + filepath: string +): string[] { + const fullIntegrationFolder = ensureIsAbsolute( + configuration.cypress.projectRoot, + configuration.cypress.integrationFolder + ); + + if (!isPathInside(filepath, fullIntegrationFolder)) { + throw new Error(`${filepath} is not inside ${fullIntegrationFolder}`); + } + const filepathReplacement = trimFeatureExtension( path.relative(fullIntegrationFolder, filepath) ); @@ -95,11 +169,21 @@ export function getStepDefinitionPatterns( debug(`replacing [filepart] with ${util.inspect(parts)}`); - return ( - typeof configuration.preprocessor.stepDefinitions === "string" - ? [configuration.preprocessor.stepDefinitions] - : configuration.preprocessor.stepDefinitions - ) + const stepDefinitions = configuration.preprocessor.stepDefinitions + ? [configuration.preprocessor.stepDefinitions].flat() + : DEFAULT_PRE_10_STEP_DEFINITIONS.map((pattern) => + pattern.replace( + "[integration-directory]", + ensureIsRelative( + configuration.cypress.projectRoot, + configuration.cypress.integrationFolder + ) + ) + ); + + debug(`looking for step definitions using ${util.inspect(stepDefinitions)}`); + + return stepDefinitions .flatMap((pattern) => { if (pattern.includes("[filepath]") && pattern.includes("[filepart]")) { throw new Error( diff --git a/package.json b/package.json index 1f4a29ff..79dfc811 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "prepublishOnly": "npm run clean && npm run build && npm run test" }, "dependencies": { - "@badeball/cypress-configuration": "^2.1.0", + "@badeball/cypress-configuration": "^3.0.0", "@cucumber/cucumber-expressions": "^15.0.1", "@cucumber/gherkin": "^15.0.2", "@cucumber/messages": "^13.2.1", @@ -62,14 +62,19 @@ "@cypress/browserify-preprocessor": "^3.0.2", "@cypress/webpack-preprocessor": "^5.11.1", "@types/debug": "^4.1.7", + "@types/fs-extra": "^9.0.13", "@types/glob": "^7.2.0", + "@types/prettier": "^2.6.3", "@types/stream-buffers": "^3.0.4", - "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "ast-types": "^0.15.2", + "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "dtslint": "^4.2.1", "esbuild": "^0.14.23", + "fs-extra": "^10.1.0", "mocha": "^9.2.1", "pngjs": "^6.0.0", "prettier": "^2.5.1", + "recast": "^0.21.1", "stream-buffers": "^3.0.2", "strip-indent": "^3.0.0", "ts-loader": "^9.2.6", @@ -79,7 +84,7 @@ }, "peerDependencies": { "@cypress/browserify-preprocessor": "^3.0.1", - "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "esbuild": "^0.14.23" }, "peerDependenciesMeta": { From 8143065949be09cd959a6ff1cee2b603bb79195c Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 15 Jun 2022 22:38:25 +0200 Subject: [PATCH 41/62] Drop support for Cypress v6 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 79dfc811..7f239043 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@types/prettier": "^2.6.3", "@types/stream-buffers": "^3.0.4", "ast-types": "^0.15.2", - "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "cypress": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "dtslint": "^4.2.1", "esbuild": "^0.14.23", "fs-extra": "^10.1.0", @@ -84,7 +84,7 @@ }, "peerDependencies": { "@cypress/browserify-preprocessor": "^3.0.1", - "cypress": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "cypress": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "esbuild": "^0.14.23" }, "peerDependenciesMeta": { From 5676bc88ecb5765728881b8953f7de5271ffe04e Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 12 Jun 2022 22:29:09 +0200 Subject: [PATCH 42/62] Correctly run untitled scenario outlines This fixes #731. --- features/issues/731.feature | 21 +++++++++++++++++++++ lib/create-tests.ts | 5 +---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 features/issues/731.feature diff --git a/features/issues/731.feature b/features/issues/731.feature new file mode 100644 index 00000000..0ac67699 --- /dev/null +++ b/features/issues/731.feature @@ -0,0 +1,21 @@ +# https://github.com/badeball/cypress-cucumber-preprocessor/issues/731 + +Feature: blank titles + Scenario: blank scenario outline title + Given a file named "cypress/e2e/a.feature" with: + """ + Feature: a feature + Scenario Outline: + Given a step + + Examples: + | value | + | foo | + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", () => {}) + """ + When I run cypress + Then it passes diff --git a/lib/create-tests.ts b/lib/create-tests.ts index e930600a..6c2fdfa2 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -241,10 +241,7 @@ function createScenario( const pickle = findPickleById(context, exampleId); - const baseName = assertAndReturn( - pickle.name, - "Expected pickle to have a name" - ); + const baseName = pickle.name || ""; const exampleName = `${baseName} (example #${i + 1})`; From a295532d3b9372d8c1e0fd979a2f32b36c8fbbc1 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 12 Jun 2022 22:38:02 +0200 Subject: [PATCH 43/62] Allow outputting *only* messages This fixes #724. --- features/issues/724.feature | 27 +++++++++++++++++++++++++ features/step_definitions/json_steps.ts | 9 ++++++++- lib/add-cucumber-preprocessor-plugin.ts | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 features/issues/724.feature diff --git a/features/issues/724.feature b/features/issues/724.feature new file mode 100644 index 00000000..62b388fa --- /dev/null +++ b/features/issues/724.feature @@ -0,0 +1,27 @@ +# https://github.com/badeball/cypress-cucumber-preprocessor/issues/724 + +Feature: outputting merely messages + Scenario: enabling *only* messages + Given additional preprocessor configuration + """ + { + "messages": { + "enabled": true + } + } + """ + And a file named "cypress/e2e/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function() {}) + """ + When I run cypress + Then it passes + And there should be no JSON output + But there should be a messages report diff --git a/features/step_definitions/json_steps.ts b/features/step_definitions/json_steps.ts index 55becb69..59470195 100644 --- a/features/step_definitions/json_steps.ts +++ b/features/step_definitions/json_steps.ts @@ -60,9 +60,16 @@ Given("I've ensured cucumber-json-formatter is installed", async () => { }); }); +Then("there should be a messages report", async function () { + await assert.doesNotReject( + () => fs.access(path.join(this.tmpDir, "cucumber-messages.ndjson")), + "Expected there to be a messages file" + ); +}); + Then("there should be no JSON output", async function () { await assert.rejects( - () => fs.readFile(path.join(this.tmpDir, "cucumber-messages.ndjson")), + () => fs.readFile(path.join(this.tmpDir, "cucumber-report.json")), { code: "ENOENT", }, diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index e9de50e6..5dce30ef 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -71,7 +71,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { export async function afterRunHandler(config: Cypress.PluginConfigOptions) { const preprocessor = await resolve(config.projectRoot, config.env); - if (!preprocessor.messages.enabled) { + if (!preprocessor.json.enabled) { return; } From f4974166b9f11330888c6df8cd92cc5f15b833d6 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 15 Jun 2022 22:28:39 +0200 Subject: [PATCH 44/62] Allow absolute paths to be used This partially fixes #736. --- lib/add-cucumber-preprocessor-plugin.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index 5dce30ef..01047a1c 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -60,7 +60,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) { return; } - const messagesPath = path.join( + const messagesPath = ensureIsAbsolute( config.projectRoot, preprocessor.messages.output ); @@ -75,12 +75,15 @@ export async function afterRunHandler(config: Cypress.PluginConfigOptions) { return; } - const messagesPath = path.join( + const messagesPath = ensureIsAbsolute( config.projectRoot, preprocessor.messages.output ); - const jsonPath = path.join(config.projectRoot, preprocessor.json.output); + const jsonPath = ensureIsAbsolute( + config.projectRoot, + preprocessor.json.output + ); try { await fs.access(messagesPath, fsConstants.F_OK); @@ -132,7 +135,7 @@ export async function afterSpecHandler( ) { const preprocessor = await resolve(config.projectRoot, config.env); - const messagesPath = path.join( + const messagesPath = ensureIsAbsolute( config.projectRoot, preprocessor.messages.output ); From 9a89d8a1568882fbaddc93b387855ec1ab9314ad Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 15 Jun 2022 22:37:19 +0200 Subject: [PATCH 45/62] Ensure output directory existence This partially fixes #736. --- features/issues/736.feature | 33 +++++++++++++++++++++++++ lib/add-cucumber-preprocessor-plugin.ts | 4 +++ 2 files changed, 37 insertions(+) create mode 100644 features/issues/736.feature diff --git a/features/issues/736.feature b/features/issues/736.feature new file mode 100644 index 00000000..6c78d880 --- /dev/null +++ b/features/issues/736.feature @@ -0,0 +1,33 @@ +# https://github.com/badeball/cypress-cucumber-preprocessor/issues/736 + +Feature: create output directories + Background: + Given additional preprocessor configuration + """ + { + "messages": { + "enabled": true, + "output": "foo/cucumber-messages.ndjson" + }, + "json": { + "enabled": true, + "output": "bar/cucumber-report.json" + } + } + """ + And I've ensured cucumber-json-formatter is installed + + Scenario: + Given a file named "cypress/e2e/a.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + Given("a step", function() {}) + """ + When I run cypress + Then it passes diff --git a/lib/add-cucumber-preprocessor-plugin.ts b/lib/add-cucumber-preprocessor-plugin.ts index 01047a1c..0bd2c9ef 100644 --- a/lib/add-cucumber-preprocessor-plugin.ts +++ b/lib/add-cucumber-preprocessor-plugin.ts @@ -91,6 +91,8 @@ export async function afterRunHandler(config: Cypress.PluginConfigOptions) { return; } + await fs.mkdir(path.dirname(jsonPath), { recursive: true }); + const messages = await fs.open(messagesPath, "r"); try { @@ -156,6 +158,8 @@ export async function afterSpecHandler( ) ); } else { + await fs.mkdir(path.dirname(messagesPath), { recursive: true }); + await fs.writeFile( messagesPath, currentSpecMessages.map((message) => JSON.stringify(message)).join("\n") + From e617f85895d566e28212c222f54881d00d925d09 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Wed, 15 Jun 2022 23:49:59 +0200 Subject: [PATCH 46/62] v11.0.0 --- CHANGELOG.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2842f6d2..a365d905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. +## v11.0.0 + +Breaking changes: + +- Dropped support for Cypress v6. + +Other changes: + +- Added support for Cypress v11. :tada: + +- Untitled scenario outline no longer errors, fixes [#731](https://github.com/badeball/cypress-cucumber-preprocessor/issues/731). + +- Outputting *only* messages is now possible, fixes [#724](https://github.com/badeball/cypress-cucumber-preprocessor/issues/724). + +- Allow absolute output paths, partially fixes [#736](https://github.com/badeball/cypress-cucumber-preprocessor/issues/736). + +- Output directories are automatically created recursively, partially fixes [#736](https://github.com/badeball/cypress-cucumber-preprocessor/issues/736). + ## v10.0.2 - Allow integration folders outside of project root, fixes [#719](https://github.com/badeball/cypress-cucumber-preprocessor/issues/719). diff --git a/package.json b/package.json index 7f239043..c353855c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "10.0.2", + "version": "11.0.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From dce39d8609960d9e24f76d4bf2f9f0f569c87575 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 16 Jun 2022 00:41:02 +0200 Subject: [PATCH 47/62] Add a simple upgrade guide --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a365d905..a0b0954a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,30 @@ Other changes: - Output directories are automatically created recursively, partially fixes [#736](https://github.com/badeball/cypress-cucumber-preprocessor/issues/736). +### Upgrading to Cypress v10 + +There's no changes to configuration options, but if your configuration looked like this pre-10 + +```json +{ + "stepDefinitions": [ + "cypress/integration/[filepath].{js,ts}", + "cypress/support/step_definitions/**/*.{js,ts}" + ] +} +``` + +.. then it should look like this post-10 (notice the removal of `cypress/integration`) + +```json +{ + "stepDefinitions": [ + "[filepath].{js,ts}", + "cypress/support/step_definitions/**/*.{js,ts}" + ] +} +``` + ## v10.0.2 - Allow integration folders outside of project root, fixes [#719](https://github.com/badeball/cypress-cucumber-preprocessor/issues/719). From 302dd7e03b6578194570424d248aaae58561b70e Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 17 Jun 2022 23:39:05 +0200 Subject: [PATCH 48/62] Fix changelog typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b0954a..9ec8dc2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Breaking changes: Other changes: -- Added support for Cypress v11. :tada: +- Added support for Cypress v10. :tada: - Untitled scenario outline no longer errors, fixes [#731](https://github.com/badeball/cypress-cucumber-preprocessor/issues/731). From bdac86e60c91bf6103cd5dc4507a849f04f1a4c2 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 17 Jun 2022 23:45:09 +0200 Subject: [PATCH 49/62] Install instructions in FAQ This comes up from time to time. --- docs/faq.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 6b127adf..4593836d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -4,6 +4,14 @@ This might be because you're trying to specify `-e / --env` multiple times, but [multiple values should be comma-separated](https://docs.cypress.io/guides/guides/command-line#cypress-run-env-lt-env-gt). +--- + ### I get `fs_1.promises.rm is not a function` Upgrade your node version to at least [v14.14.0](https://nodejs.org/api/fs.html#fspromisesrmpath-options). + +--- + +### I get `spawn cucumber-json-formatter ENOENT` + +You need to install `cucumber-json-formatter` **yourself**, as per [documentation](json-report.md). From eb74502b708ef16684426e0475a3a80bb0360343 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 17 Jun 2022 23:47:03 +0200 Subject: [PATCH 50/62] Update link to FAQ --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40fee3ba..f75e5923 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## 1. Read the FAQ -Please [read the FAQ](user-guide/faq.md) before logging new issues, even if you think you have found a bug. +Please [read the FAQ](docs/faq.md) before logging new issues, even if you think you have found a bug. Issues that ask questions answered in the FAQ will be closed without elaboration. From 5dc1fa25c07524443501c397aa78e43096df66ca Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 17 Jun 2022 23:51:34 +0200 Subject: [PATCH 51/62] Simplify issue template with a checklist I hope this will reduce some frequent questions and issues that arise from time to time. --- .github/ISSUE_TEMPLATE.md | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f3f46e27..0d3e8415 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,22 +1,3 @@ - - ### Current behavior @@ -27,10 +8,16 @@ ### Test code to reproduce - + ### Versions * **Cypress version**: * **Preprocessor version**: * **Node version**: + +### Checklist + +- [ ] I've read the [FAQ](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/docs/faq.md). +- [ ] I've read [Instructions for logging issues](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/CONTRIBUTING.md#instructions-for-logging-issues). +- [ ] I'm not using v4.3.1 (package name has changed, see [#689](https://github.com/badeball/cypress-cucumber-preprocessor/issues/689)). From bba057614c2c102c8c270000a2ca0e82c1e18082 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Fri, 17 Jun 2022 23:56:19 +0200 Subject: [PATCH 52/62] Clearer description --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0d3e8415..46a2b98c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -20,4 +20,4 @@ - [ ] I've read the [FAQ](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/docs/faq.md). - [ ] I've read [Instructions for logging issues](https://github.com/badeball/cypress-cucumber-preprocessor/blob/master/CONTRIBUTING.md#instructions-for-logging-issues). -- [ ] I'm not using v4.3.1 (package name has changed, see [#689](https://github.com/badeball/cypress-cucumber-preprocessor/issues/689)). +- [ ] 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)). From 760342adcefee8deae07f37e637cab4b27045464 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 19 Jun 2022 12:27:50 +0200 Subject: [PATCH 53/62] Allow config overrides per suite / test This fixes #697. --- docs/configuration.md | 4 + docs/readme.md | 1 + docs/test-configuration.md | 37 +++++++ features/suite_options.feature | 21 ++++ lib/create-tests.ts | 9 +- lib/tag-parser.test.ts | 60 ++++++++++ lib/tag-parser/errors.ts | 5 + lib/tag-parser/index.ts | 9 ++ lib/tag-parser/parser.ts | 196 +++++++++++++++++++++++++++++++++ lib/tag-parser/tokenizer.ts | 94 ++++++++++++++++ 10 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 docs/test-configuration.md create mode 100644 features/suite_options.feature create mode 100644 lib/tag-parser.test.ts create mode 100644 lib/tag-parser/errors.ts create mode 100644 lib/tag-parser/index.ts create mode 100644 lib/tag-parser/parser.ts create mode 100644 lib/tag-parser/tokenizer.ts diff --git a/docs/configuration.md b/docs/configuration.md index 70d3fbb8..337d01b6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -51,3 +51,7 @@ Every configuration option has a similar key which can be use to override it, sh | `json.output` | `jsonOutput` | `cucumber-report.json` | | `filterSpecs` | `filterSpecs` | `true`, `false` | | `omitFiltered` | `omitFiltered` | `true`, `false` | + +## Test configuration + +Some of Cypress' [configuration options](https://docs.cypress.io/guides/references/configuration) can be overridden per-test, [Test configuration](test-configuration.md). diff --git a/docs/readme.md b/docs/readme.md index 3646828e..0e3ffc82 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -8,4 +8,5 @@ * [JSON report](json-report.md) * [Localisation](localisation.md) * [Configuration](configuration.md) +* [Test configuration](test-configuration.md) * [Frequently asked questions](faq.md) diff --git a/docs/test-configuration.md b/docs/test-configuration.md new file mode 100644 index 00000000..8d250ba6 --- /dev/null +++ b/docs/test-configuration.md @@ -0,0 +1,37 @@ +# Test configuration + +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. + +```gherkin +@animationDistanceThreshold(5) +@blockHosts('http://www.foo.com','http://www.bar.com') +@defaultCommandTimeout(5) +@execTimeout(5) +@includeShadowDom(true) +@includeShadowDom(false) +@keystrokeDelay(5) +@numTestsKeptInMemory(5) +@pageLoadTimeout(5) +@redirectionLimit(5) +@requestTimeout(5) +@responseTimeout(5) +@retries(5) +@retries(runMode=5) +@retries(openMode=5) +@retries(runMode=5,openMode=10) +@retries(openMode=10,runMode=5) +@screenshotOnRunFailure(true) +@screenshotOnRunFailure(false) +@scrollBehavior('center') +@scrollBehavior('top') +@scrollBehavior('bottom') +@scrollBehavior('nearest') +@slowTestThreshold(5) +@viewportHeight(720) +@viewportWidth(1280) +@waitForAnimations(true) +@waitForAnimations(false) +Feature: a feature + Scenario: a scenario + Given a table step +``` diff --git a/features/suite_options.feature b/features/suite_options.feature new file mode 100644 index 00000000..3db51491 --- /dev/null +++ b/features/suite_options.feature @@ -0,0 +1,21 @@ +Feature: suite options + Scenario: suite specific retry + Given a file named "cypress/e2e/a.feature" with: + """ + @retries(2) + Feature: a feature + Scenario: a scenario + Given a step + """ + And a file named "cypress/support/step_definitions/steps.js" with: + """ + const { Given } = require("@badeball/cypress-cucumber-preprocessor"); + let attempt = 0; + Given("a step", () => { + if (attempt++ === 0) { + throw "some error"; + } + }); + """ + When I run cypress + Then it passes diff --git a/lib/create-tests.ts b/lib/create-tests.ts index 6c2fdfa2..01bd7fd6 100644 --- a/lib/create-tests.ts +++ b/lib/create-tests.ts @@ -25,6 +25,8 @@ import { getTags } from "./environment-helpers"; import { notNull } from "./type-guards"; +import { looksLikeOptions, tagToCypressOptions } from "./tag-parser"; + declare global { namespace globalThis { var __cypress_cucumber_preprocessor_dont_use_this: true | undefined; @@ -347,7 +349,12 @@ function createPickle( const env = { [INTERNAL_PROPERTY_NAME]: internalProperties }; - it(scenarioName, { env }, function () { + const suiteOptions = tags + .filter(looksLikeOptions) + .map(tagToCypressOptions) + .reduce(Object.assign, {}); + + it(scenarioName, { env, ...suiteOptions }, function () { const { remainingSteps, testCaseStartedId } = retrieveInternalProperties(); assignRegistry(registry); diff --git a/lib/tag-parser.test.ts b/lib/tag-parser.test.ts new file mode 100644 index 00000000..fd76e403 --- /dev/null +++ b/lib/tag-parser.test.ts @@ -0,0 +1,60 @@ +import util from "util"; + +import assert from "assert"; + +import { tagToCypressOptions } from "./tag-parser"; + +function example(tag: string, expectedOptions: any) { + it(`should return ${util.inspect(expectedOptions)} for ${tag}`, () => { + const actualOptions = tagToCypressOptions(tag); + + assert.deepStrictEqual(actualOptions, expectedOptions); + }); +} + +describe("tagToCypressOptions", () => { + example("@animationDistanceThreshold(5)", { animationDistanceThreshold: 5 }); + // example("@baseUrl('http://www.foo.com')'", { baseUrl: "http://www.foo.com" }); + example("@blockHosts('http://www.foo.com')", { + blockHosts: "http://www.foo.com", + }); + example("@blockHosts('http://www.foo.com','http://www.bar.com')", { + blockHosts: ["http://www.foo.com", "http://www.bar.com"], + }); + example("@defaultCommandTimeout(5)", { defaultCommandTimeout: 5 }); + example("@execTimeout(5)", { execTimeout: 5 }); + // example("@experimentalSessionAndOrigin(true)", { + // experimentalSessionAndOrigin: 5, + // }); + // example("@experimentalSessionAndOrigin(false)", { + // experimentalSessionAndOrigin: 5, + // }); + example("@includeShadowDom(true)", { includeShadowDom: true }); + example("@includeShadowDom(false)", { includeShadowDom: false }); + example("@keystrokeDelay(5)", { keystrokeDelay: 5 }); + example("@numTestsKeptInMemory(5)", { numTestsKeptInMemory: 5 }); + example("@pageLoadTimeout(5)", { pageLoadTimeout: 5 }); + example("@redirectionLimit(5)", { redirectionLimit: 5 }); + example("@requestTimeout(5)", { requestTimeout: 5 }); + example("@responseTimeout(5)", { responseTimeout: 5 }); + example("@retries(5)", { retries: 5 }); + example("@retries(runMode=5)", { retries: { runMode: 5 } }); + example("@retries(openMode=5)", { retries: { openMode: 5 } }); + example("@retries(runMode=5,openMode=10)", { + retries: { runMode: 5, openMode: 10 }, + }); + example("@retries(openMode=10,runMode=5)", { + retries: { runMode: 5, openMode: 10 }, + }); + example("@screenshotOnRunFailure(true)", { screenshotOnRunFailure: true }); + example("@screenshotOnRunFailure(false)", { screenshotOnRunFailure: false }); + example("@scrollBehavior('center')", { scrollBehavior: "center" }); + example("@scrollBehavior('top')", { scrollBehavior: "top" }); + example("@scrollBehavior('bottom')", { scrollBehavior: "bottom" }); + example("@scrollBehavior('nearest')", { scrollBehavior: "nearest" }); + example("@slowTestThreshold(5)", { slowTestThreshold: 5 }); + example("@viewportHeight(720)", { viewportHeight: 720 }); + example("@viewportWidth(1280)", { viewportWidth: 1280 }); + example("@waitForAnimations(true)", { waitForAnimations: true }); + example("@waitForAnimations(false)", { waitForAnimations: false }); +}); diff --git a/lib/tag-parser/errors.ts b/lib/tag-parser/errors.ts new file mode 100644 index 00000000..d9028354 --- /dev/null +++ b/lib/tag-parser/errors.ts @@ -0,0 +1,5 @@ +export class TagError extends Error {} + +export class TagTokenizerError extends TagError {} + +export class TagParserError extends TagError {} diff --git a/lib/tag-parser/index.ts b/lib/tag-parser/index.ts new file mode 100644 index 00000000..bb5b23b2 --- /dev/null +++ b/lib/tag-parser/index.ts @@ -0,0 +1,9 @@ +import Parser from "./parser"; + +export function tagToCypressOptions(tag: string): Cypress.TestConfigOverrides { + return new Parser(tag).parse(); +} + +export function looksLikeOptions(tag: string) { + return tag.includes("("); +} diff --git a/lib/tag-parser/parser.ts b/lib/tag-parser/parser.ts new file mode 100644 index 00000000..5ba48c2f --- /dev/null +++ b/lib/tag-parser/parser.ts @@ -0,0 +1,196 @@ +import { TagParserError } from "./errors"; + +import { + isAt, + isClosingParanthesis, + isComma, + isDigit, + isEqual, + isOpeningParanthesis, + isQuote, + isWordChar, + Tokenizer, +} from "./tokenizer"; + +function createUnexpectedEndOfString() { + return new TagParserError("Unexpected end-of-string"); +} + +function createUnexpectedToken( + token: TYield, + expectation: string +) { + return new Error( + `Unexpected token at ${token.position}: ${token.value} (${expectation})` + ); +} + +function expectToken(token: Token) { + if (token.done) { + throw createUnexpectedEndOfString(); + } + + return token; +} + +function parsePrimitiveToken(token: Token) { + if (token.done) { + throw createUnexpectedEndOfString(); + } + + const value = token.value.value; + + const char = value[0]; + + if (value === "false") { + return false; + } else if (value === "true") { + return true; + } + if (isDigit(char)) { + return parseInt(value); + } else if (isQuote(char)) { + return value.slice(1, -1); + } else { + throw createUnexpectedToken( + token.value, + "expected a string, a boolean or a number" + ); + } +} + +type TYield = T extends Generator ? R : never; + +type TReturn = T extends Generator ? R : never; + +interface IteratorYieldResult { + done?: false; + value: TYield; +} + +interface IteratorReturnResult { + done: true; + value: TReturn; +} + +type IteratorResult = + | IteratorYieldResult> + | IteratorReturnResult>; + +type TokenGenerator = ReturnType; + +type Token = IteratorResult; + +class BufferedGenerator { + private tokens: (IteratorYieldResult | IteratorReturnResult)[] = + []; + + private position = -1; + + constructor(generator: Generator) { + do { + this.tokens.push(generator.next()); + } while (!this.tokens[this.tokens.length - 1].done); + } + + next() { + if (this.position < this.tokens.length - 1) { + this.position++; + } + + return this.tokens[this.position]; + } + + peak(n: number = 1) { + return this.tokens[this.position + n]; + } +} + +type Primitive = string | boolean | number; + +export default class Parser { + public constructor(private content: string) {} + + parse(): Record> { + const tokens = new BufferedGenerator(new Tokenizer(this.content).tokens()); + + let next: Token = expectToken(tokens.next()); + + if (!isAt(next.value.value)) { + throw createUnexpectedToken(next.value, "expected tag to begin with '@'"); + } + + next = expectToken(tokens.next()); + + if (!isWordChar(next.value.value[0])) { + throw createUnexpectedToken( + next.value, + "expected tag to start with a property name" + ); + } + + const propertyName = next.value.value; + + next = expectToken(tokens.next()); + + if (!isOpeningParanthesis(next.value.value)) { + throw createUnexpectedToken(next.value, "expected opening paranthesis"); + } + + const isObjectMode = isEqual(expectToken(tokens.peak(2)).value.value); + const entries: [string, Primitive][] = []; + const values: Primitive[] = []; + + if (isObjectMode) { + while (true) { + const key = expectToken(tokens.next()).value.value; + + next = expectToken(tokens.next()); + + if (!isEqual(next.value.value)) { + throw createUnexpectedToken(next.value, "expected equal sign"); + } + + const value = parsePrimitiveToken(tokens.next()); + + entries.push([key, value]); + + if (!isComma(expectToken(tokens.peak()).value.value)) { + break; + } else { + tokens.next(); + } + } + } else { + while (true) { + const value = parsePrimitiveToken(tokens.next()); + + values.push(value); + + if (!isComma(expectToken(tokens.peak()).value.value)) { + break; + } else { + tokens.next(); + } + } + } + + next = expectToken(tokens.next()); + + if (next.done) { + throw createUnexpectedEndOfString(); + } else if (!isClosingParanthesis(next.value.value)) { + throw createUnexpectedToken(next.value, "expected closing paranthesis"); + } + + if (isObjectMode) { + return { + [propertyName]: Object.fromEntries(entries), + }; + } else { + return { + [propertyName]: values.length === 1 ? values[0] : values, + }; + } + } +} diff --git a/lib/tag-parser/tokenizer.ts b/lib/tag-parser/tokenizer.ts new file mode 100644 index 00000000..b4e6e29a --- /dev/null +++ b/lib/tag-parser/tokenizer.ts @@ -0,0 +1,94 @@ +import { TagTokenizerError } from "./errors"; + +export const isAt = (char: string): boolean => char === "@"; +export const isOpeningParanthesis = (char: string): boolean => char === "("; +export const isClosingParanthesis = (char: string): boolean => char === ")"; +export const isWordChar = (char: string): boolean => /[a-zA-Z]/.test(char); +export const isQuote = (char: string): boolean => char === '"' || char === "'"; +export const isDigit = (char: string): boolean => /[0-9]/.test(char); +export const isComma = (char: string): boolean => char === ","; +export const isEqual = (char: string): boolean => char === "="; + +export class Tokenizer { + public constructor(private content: string) {} + + public *tokens(): Generator< + { + value: string; + position: number; + }, + void, + unknown + > { + let position = 0; + + while (position < this.content.length) { + const curchar = this.content[position]; + + if ( + isAt(curchar) || + isOpeningParanthesis(curchar) || + isClosingParanthesis(curchar) || + isComma(curchar) || + isEqual(curchar) + ) { + yield { + value: curchar, + position, + }; + + position++; + } else if (isDigit(curchar)) { + const start = position; + + while ( + isDigit(this.content[position]) && + position < this.content.length + ) { + position++; + } + + yield { + value: this.content.slice(start, position), + position: start, + }; + } else if (isWordChar(curchar)) { + const start = position; + + while ( + isWordChar(this.content[position]) && + position < this.content.length + ) { + position++; + } + + yield { + value: this.content.slice(start, position), + position: start, + }; + } else if (isQuote(curchar)) { + const start = position++; + + while ( + !isQuote(this.content[position]) && + position < this.content.length + ) { + position++; + } + + if (position === this.content.length) { + throw new TagTokenizerError("Unexpected end-of-string"); + } else { + position++; + } + + yield { + value: this.content.slice(start, position), + position: start, + }; + } else { + throw new TagTokenizerError(`Unknown token at ${position}: ${curchar}`); + } + } + } +} From d4b9a1a4d524e9a80b4044a3fd63e95c81b193db Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Sun, 19 Jun 2022 17:08:09 +0200 Subject: [PATCH 54/62] v11.1.0 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec8dc2a..afc2a0a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v11.1.0 + +- Enable test configuration overrides, such as retrability of a single scenario, fixes [#697](https://github.com/badeball/cypress-cucumber-preprocessor/issues/697). + ## v11.0.0 Breaking changes: diff --git a/package.json b/package.json index c353855c..63cdef28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "11.0.0", + "version": "11.1.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor", From 71d08c282872ff71ccff0066a11c0b987a310a23 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 06:38:33 +0200 Subject: [PATCH 55/62] Add default export to esbuild.js --- esbuild.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esbuild.ts b/esbuild.ts index 55b1fbc8..8a7dea8f 100644 --- a/esbuild.ts +++ b/esbuild.ts @@ -25,3 +25,5 @@ export function createEsbuildPlugin( }, }; } + +export default createEsbuildPlugin; From 8d467b330105234e6426676a44d2ef99e78d55a1 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 06:41:05 +0200 Subject: [PATCH 56/62] Add support for *.mjs step definitions --- lib/preprocessor-configuration.ts | 12 ++++++------ lib/step-definitions.test.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/preprocessor-configuration.ts b/lib/preprocessor-configuration.ts index 12b2f045..dd1b6822 100644 --- a/lib/preprocessor-configuration.ts +++ b/lib/preprocessor-configuration.ts @@ -286,15 +286,15 @@ export interface IEnvironmentOverrides { } export const DEFAULT_PRE_10_STEP_DEFINITIONS = [ - "[integration-directory]/[filepath]/**/*.{js,ts}", - "[integration-directory]/[filepath].{js,ts}", - "cypress/support/step_definitions/**/*.{js,ts}", + "[integration-directory]/[filepath]/**/*.{js,mjs,ts}", + "[integration-directory]/[filepath].{js,mjs,ts}", + "cypress/support/step_definitions/**/*.{js,mjs,ts}", ]; export const DEFAULT_POST_10_STEP_DEFINITIONS = [ - "[filepath]/**/*.{js,ts}", - "[filepath].{js,ts}", - "cypress/support/step_definitions/**/*.{js,ts}", + "[filepath]/**/*.{js,mjs,ts}", + "[filepath].{js,mjs,ts}", + "cypress/support/step_definitions/**/*.{js,mjs,ts}", ]; export class PreprocessorConfiguration implements IPreprocessorConfiguration { diff --git a/lib/step-definitions.test.ts b/lib/step-definitions.test.ts index ba2edf72..1bddf1f4 100644 --- a/lib/step-definitions.test.ts +++ b/lib/step-definitions.test.ts @@ -121,9 +121,9 @@ describe("getStepDefinitionPatternsPre10()", () => { }, {}, [ - "/foo/bar/cypress/integration/baz/**/*.{js,ts}", - "/foo/bar/cypress/integration/baz.{js,ts}", - "/foo/bar/cypress/support/step_definitions/**/*.{js,ts}", + "/foo/bar/cypress/integration/baz/**/*.{js,mjs,ts}", + "/foo/bar/cypress/integration/baz.{js,mjs,ts}", + "/foo/bar/cypress/support/step_definitions/**/*.{js,mjs,ts}", ] ); @@ -199,9 +199,9 @@ describe("getStepDefinitionPatternsPost10()", () => { }, {}, [ - "/foo/bar/cypress/e2e/baz/**/*.{js,ts}", - "/foo/bar/cypress/e2e/baz.{js,ts}", - "/foo/bar/cypress/support/step_definitions/**/*.{js,ts}", + "/foo/bar/cypress/e2e/baz/**/*.{js,mjs,ts}", + "/foo/bar/cypress/e2e/baz.{js,mjs,ts}", + "/foo/bar/cypress/support/step_definitions/**/*.{js,mjs,ts}", ] ); From 24b7b420b5f4087e73e77496add4abf866791e0c Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 06:04:24 +0200 Subject: [PATCH 57/62] Rename examples .. to make space for more, similar examples. --- .github/workflows/examples.yml | 24 +++++++++---------- examples/{browserify => browserify-ts}/.npmrc | 0 .../cypress.config.ts | 0 .../cypress/e2e/duckduckgo.feature | 0 .../cypress/e2e/duckduckgo.ts | 0 .../package.json | 0 examples/{esbuild => esbuild-ts}/.npmrc | 0 .../{esbuild => esbuild-ts}/cypress.config.ts | 0 .../cypress/e2e/duckduckgo.feature | 0 .../cypress/e2e/duckduckgo.ts | 0 examples/{esbuild => esbuild-ts}/package.json | 0 examples/{webpack => webpack-ts}/.npmrc | 0 .../{webpack => webpack-ts}/cypress.config.ts | 0 .../cypress/e2e/duckduckgo.feature | 0 .../cypress/e2e/duckduckgo.ts | 0 examples/{webpack => webpack-ts}/package.json | 0 .../{webpack => webpack-ts}/tsconfig.json | 0 17 files changed, 12 insertions(+), 12 deletions(-) rename examples/{browserify => browserify-ts}/.npmrc (100%) rename examples/{browserify => browserify-ts}/cypress.config.ts (100%) rename examples/{browserify => browserify-ts}/cypress/e2e/duckduckgo.feature (100%) rename examples/{browserify => browserify-ts}/cypress/e2e/duckduckgo.ts (100%) rename examples/{browserify => browserify-ts}/package.json (100%) rename examples/{esbuild => esbuild-ts}/.npmrc (100%) rename examples/{esbuild => esbuild-ts}/cypress.config.ts (100%) rename examples/{esbuild => esbuild-ts}/cypress/e2e/duckduckgo.feature (100%) rename examples/{esbuild => esbuild-ts}/cypress/e2e/duckduckgo.ts (100%) rename examples/{esbuild => esbuild-ts}/package.json (100%) rename examples/{webpack => webpack-ts}/.npmrc (100%) rename examples/{webpack => webpack-ts}/cypress.config.ts (100%) rename examples/{webpack => webpack-ts}/cypress/e2e/duckduckgo.feature (100%) rename examples/{webpack => webpack-ts}/cypress/e2e/duckduckgo.ts (100%) rename examples/{webpack => webpack-ts}/package.json (100%) rename examples/{webpack => webpack-ts}/tsconfig.json (100%) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index ec65b4b5..40444a96 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -10,7 +10,7 @@ defaults: shell: bash jobs: - browserify: + browserify-ts: runs-on: ubuntu-20.04 container: image: cypress/base:17.3.0 @@ -31,16 +31,16 @@ jobs: - name: Make checkout sparse run: | shopt -s extglob - rm -rf examples/!(browserify) + rm -rf examples/!(browserify-ts) rm -rf !(examples) - name: Install NPM modules - working-directory: examples/browserify + working-directory: examples/browserify-ts run: npm install - name: Run Cypress - working-directory: examples/browserify + working-directory: examples/browserify-ts run: npx cypress run - webpack: + webpack-ts: runs-on: ubuntu-20.04 container: image: cypress/base:17.3.0 @@ -61,16 +61,16 @@ jobs: - name: Make checkout sparse run: | shopt -s extglob - rm -rf examples/!(webpack) + rm -rf examples/!(webpack-ts) rm -rf !(examples) - name: Install NPM modules - working-directory: examples/webpack + working-directory: examples/webpack-ts run: npm install - name: Run Cypress - working-directory: examples/webpack + working-directory: examples/webpack-ts run: npx cypress run - esbuild: + esbuild-ts: runs-on: ubuntu-20.04 container: image: cypress/base:17.3.0 @@ -91,11 +91,11 @@ jobs: - name: Make checkout sparse run: | shopt -s extglob - rm -rf examples/!(esbuild) + rm -rf examples/!(esbuild-ts) rm -rf !(examples) - name: Install NPM modules - working-directory: examples/esbuild + working-directory: examples/esbuild-ts run: npm install - name: Run Cypress - working-directory: examples/esbuild + working-directory: examples/esbuild-ts run: npx cypress run diff --git a/examples/browserify/.npmrc b/examples/browserify-ts/.npmrc similarity index 100% rename from examples/browserify/.npmrc rename to examples/browserify-ts/.npmrc diff --git a/examples/browserify/cypress.config.ts b/examples/browserify-ts/cypress.config.ts similarity index 100% rename from examples/browserify/cypress.config.ts rename to examples/browserify-ts/cypress.config.ts diff --git a/examples/browserify/cypress/e2e/duckduckgo.feature b/examples/browserify-ts/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/browserify/cypress/e2e/duckduckgo.feature rename to examples/browserify-ts/cypress/e2e/duckduckgo.feature diff --git a/examples/browserify/cypress/e2e/duckduckgo.ts b/examples/browserify-ts/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/browserify/cypress/e2e/duckduckgo.ts rename to examples/browserify-ts/cypress/e2e/duckduckgo.ts diff --git a/examples/browserify/package.json b/examples/browserify-ts/package.json similarity index 100% rename from examples/browserify/package.json rename to examples/browserify-ts/package.json diff --git a/examples/esbuild/.npmrc b/examples/esbuild-ts/.npmrc similarity index 100% rename from examples/esbuild/.npmrc rename to examples/esbuild-ts/.npmrc diff --git a/examples/esbuild/cypress.config.ts b/examples/esbuild-ts/cypress.config.ts similarity index 100% rename from examples/esbuild/cypress.config.ts rename to examples/esbuild-ts/cypress.config.ts diff --git a/examples/esbuild/cypress/e2e/duckduckgo.feature b/examples/esbuild-ts/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/esbuild/cypress/e2e/duckduckgo.feature rename to examples/esbuild-ts/cypress/e2e/duckduckgo.feature diff --git a/examples/esbuild/cypress/e2e/duckduckgo.ts b/examples/esbuild-ts/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/esbuild/cypress/e2e/duckduckgo.ts rename to examples/esbuild-ts/cypress/e2e/duckduckgo.ts diff --git a/examples/esbuild/package.json b/examples/esbuild-ts/package.json similarity index 100% rename from examples/esbuild/package.json rename to examples/esbuild-ts/package.json diff --git a/examples/webpack/.npmrc b/examples/webpack-ts/.npmrc similarity index 100% rename from examples/webpack/.npmrc rename to examples/webpack-ts/.npmrc diff --git a/examples/webpack/cypress.config.ts b/examples/webpack-ts/cypress.config.ts similarity index 100% rename from examples/webpack/cypress.config.ts rename to examples/webpack-ts/cypress.config.ts diff --git a/examples/webpack/cypress/e2e/duckduckgo.feature b/examples/webpack-ts/cypress/e2e/duckduckgo.feature similarity index 100% rename from examples/webpack/cypress/e2e/duckduckgo.feature rename to examples/webpack-ts/cypress/e2e/duckduckgo.feature diff --git a/examples/webpack/cypress/e2e/duckduckgo.ts b/examples/webpack-ts/cypress/e2e/duckduckgo.ts similarity index 100% rename from examples/webpack/cypress/e2e/duckduckgo.ts rename to examples/webpack-ts/cypress/e2e/duckduckgo.ts diff --git a/examples/webpack/package.json b/examples/webpack-ts/package.json similarity index 100% rename from examples/webpack/package.json rename to examples/webpack-ts/package.json diff --git a/examples/webpack/tsconfig.json b/examples/webpack-ts/tsconfig.json similarity index 100% rename from examples/webpack/tsconfig.json rename to examples/webpack-ts/tsconfig.json From 17eb8341761e02ad01db8a129e4b94b74597d48f Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 06:58:53 +0200 Subject: [PATCH 58/62] Simplify ts examples --- examples/browserify-ts/cypress.config.ts | 8 +++----- examples/esbuild-ts/cypress.config.ts | 4 ++-- examples/webpack-ts/cypress.config.ts | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/browserify-ts/cypress.config.ts b/examples/browserify-ts/cypress.config.ts index 0870872c..deb1a7b4 100644 --- a/examples/browserify-ts/cypress.config.ts +++ b/examples/browserify-ts/cypress.config.ts @@ -1,9 +1,8 @@ import { defineConfig } from "cypress"; -import * as browserify from "@cypress/browserify-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -import { preprocessor } from "@badeball/cypress-cucumber-preprocessor/browserify"; +import browserify from "@badeball/cypress-cucumber-preprocessor/browserify"; -export async function setupNodeEvents( +async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions ): Promise { @@ -11,8 +10,7 @@ export async function setupNodeEvents( on( "file:preprocessor", - preprocessor(config, { - ...browserify.defaultOptions, + browserify(config, { typescript: require.resolve("typescript"), }) ); diff --git a/examples/esbuild-ts/cypress.config.ts b/examples/esbuild-ts/cypress.config.ts index d0745e44..7da25494 100644 --- a/examples/esbuild-ts/cypress.config.ts +++ b/examples/esbuild-ts/cypress.config.ts @@ -1,9 +1,9 @@ import { defineConfig } from "cypress"; import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild"; +import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild"; -export async function setupNodeEvents( +async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions ): Promise { diff --git a/examples/webpack-ts/cypress.config.ts b/examples/webpack-ts/cypress.config.ts index 9690b132..2298a9a6 100644 --- a/examples/webpack-ts/cypress.config.ts +++ b/examples/webpack-ts/cypress.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "cypress"; import * as webpack from "@cypress/webpack-preprocessor"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -export async function setupNodeEvents( +async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions ): Promise { From 1a9d09742158aee7aad0540c21733e27d8338c3b Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 06:05:02 +0200 Subject: [PATCH 59/62] Add examples in mjs --- .github/workflows/examples.yml | 90 +++++++++++++++++++ examples/browserify-esm/.npmrc | 1 + examples/browserify-esm/cypress.config.mjs | 20 +++++ .../cypress/e2e/duckduckgo.feature | 4 + .../browserify-esm/cypress/e2e/duckduckgo.mjs | 15 ++++ examples/browserify-esm/package.json | 7 ++ examples/esbuild-esm/.npmrc | 1 + examples/esbuild-esm/cypress.config.mjs | 26 ++++++ .../cypress/e2e/duckduckgo.feature | 4 + .../esbuild-esm/cypress/e2e/duckduckgo.mjs | 15 ++++ examples/esbuild-esm/package.json | 7 ++ examples/readme.md | 4 + examples/webpack-esm/.npmrc | 1 + examples/webpack-esm/cypress.config.mjs | 51 +++++++++++ .../cypress/e2e/duckduckgo.feature | 4 + .../webpack-esm/cypress/e2e/duckduckgo.mjs | 15 ++++ examples/webpack-esm/package.json | 7 ++ 17 files changed, 272 insertions(+) create mode 100644 examples/browserify-esm/.npmrc create mode 100644 examples/browserify-esm/cypress.config.mjs create mode 100644 examples/browserify-esm/cypress/e2e/duckduckgo.feature create mode 100644 examples/browserify-esm/cypress/e2e/duckduckgo.mjs create mode 100644 examples/browserify-esm/package.json create mode 100644 examples/esbuild-esm/.npmrc create mode 100644 examples/esbuild-esm/cypress.config.mjs create mode 100644 examples/esbuild-esm/cypress/e2e/duckduckgo.feature create mode 100644 examples/esbuild-esm/cypress/e2e/duckduckgo.mjs create mode 100644 examples/esbuild-esm/package.json create mode 100644 examples/readme.md create mode 100644 examples/webpack-esm/.npmrc create mode 100644 examples/webpack-esm/cypress.config.mjs create mode 100644 examples/webpack-esm/cypress/e2e/duckduckgo.feature create mode 100644 examples/webpack-esm/cypress/e2e/duckduckgo.mjs create mode 100644 examples/webpack-esm/package.json diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 40444a96..51aae2b9 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -10,6 +10,36 @@ defaults: shell: bash jobs: + browserify-esm: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(browserify-esm) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/browserify-esm + run: npm install + - name: Run Cypress + working-directory: examples/browserify-esm + run: npx cypress run + browserify-ts: runs-on: ubuntu-20.04 container: @@ -40,6 +70,36 @@ jobs: working-directory: examples/browserify-ts run: npx cypress run + webpack-esm: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(webpack-esm) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/webpack-esm + run: npm install + - name: Run Cypress + working-directory: examples/webpack-esm + run: npx cypress run + webpack-ts: runs-on: ubuntu-20.04 container: @@ -70,6 +130,36 @@ jobs: working-directory: examples/webpack-ts run: npx cypress run + esbuild-esm: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(esbuild-esm) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/esbuild-esm + run: npm install + - name: Run Cypress + working-directory: examples/esbuild-esm + run: npx cypress run + esbuild-ts: runs-on: ubuntu-20.04 container: diff --git a/examples/browserify-esm/.npmrc b/examples/browserify-esm/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/browserify-esm/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/browserify-esm/cypress.config.mjs b/examples/browserify-esm/cypress.config.mjs new file mode 100644 index 00000000..73a66e88 --- /dev/null +++ b/examples/browserify-esm/cypress.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from "cypress"; +import preprocessor from "@badeball/cypress-cucumber-preprocessor"; +import browserify from "@badeball/cypress-cucumber-preprocessor/browserify.js"; + +export async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on("file:preprocessor", browserify.default(config)); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/browserify-esm/cypress/e2e/duckduckgo.feature b/examples/browserify-esm/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/browserify-esm/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/browserify-esm/cypress/e2e/duckduckgo.mjs b/examples/browserify-esm/cypress/e2e/duckduckgo.mjs new file mode 100644 index 00000000..3caf3d92 --- /dev/null +++ b/examples/browserify-esm/cypress/e2e/duckduckgo.mjs @@ -0,0 +1,15 @@ +import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/browserify-esm/package.json b/examples/browserify-esm/package.json new file mode 100644 index 00000000..1caa0c5a --- /dev/null +++ b/examples/browserify-esm/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@cypress/browserify-preprocessor": "latest", + "cypress": "latest" + } +} diff --git a/examples/esbuild-esm/.npmrc b/examples/esbuild-esm/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/esbuild-esm/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/esbuild-esm/cypress.config.mjs b/examples/esbuild-esm/cypress.config.mjs new file mode 100644 index 00000000..60a24f5b --- /dev/null +++ b/examples/esbuild-esm/cypress.config.mjs @@ -0,0 +1,26 @@ +import { defineConfig } from "cypress"; +import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; +import preprocessor from "@badeball/cypress-cucumber-preprocessor"; +import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild.js"; + +export async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + createBundler({ + plugins: [createEsbuildPlugin.default(config)], + }) + ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/esbuild-esm/cypress/e2e/duckduckgo.feature b/examples/esbuild-esm/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/esbuild-esm/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/esbuild-esm/cypress/e2e/duckduckgo.mjs b/examples/esbuild-esm/cypress/e2e/duckduckgo.mjs new file mode 100644 index 00000000..3caf3d92 --- /dev/null +++ b/examples/esbuild-esm/cypress/e2e/duckduckgo.mjs @@ -0,0 +1,15 @@ +import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/esbuild-esm/package.json b/examples/esbuild-esm/package.json new file mode 100644 index 00000000..49c5c7f9 --- /dev/null +++ b/examples/esbuild-esm/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@bahmutov/cypress-esbuild-preprocessor": "latest", + "cypress": "latest" + } +} diff --git a/examples/readme.md b/examples/readme.md new file mode 100644 index 00000000..ff78f91e --- /dev/null +++ b/examples/readme.md @@ -0,0 +1,4 @@ +# Examples + +The examples illustrates using each bundler in each language flavor. + diff --git a/examples/webpack-esm/.npmrc b/examples/webpack-esm/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/webpack-esm/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/webpack-esm/cypress.config.mjs b/examples/webpack-esm/cypress.config.mjs new file mode 100644 index 00000000..b0611cf1 --- /dev/null +++ b/examples/webpack-esm/cypress.config.mjs @@ -0,0 +1,51 @@ +import { defineConfig } from "cypress"; +import webpack from "@cypress/webpack-preprocessor"; +import preprocessor from "@badeball/cypress-cucumber-preprocessor"; + +export async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + webpack({ + webpackOptions: { + resolve: { + extensions: [".ts", ".js"], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: [/node_modules/], + use: [ + { + loader: "ts-loader", + }, + ], + }, + { + test: /\.feature$/, + use: [ + { + loader: "@badeball/cypress-cucumber-preprocessor/webpack", + options: config, + }, + ], + }, + ], + }, + }, + }) + ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +export default defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/webpack-esm/cypress/e2e/duckduckgo.feature b/examples/webpack-esm/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/webpack-esm/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/webpack-esm/cypress/e2e/duckduckgo.mjs b/examples/webpack-esm/cypress/e2e/duckduckgo.mjs new file mode 100644 index 00000000..3caf3d92 --- /dev/null +++ b/examples/webpack-esm/cypress/e2e/duckduckgo.mjs @@ -0,0 +1,15 @@ +import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/webpack-esm/package.json b/examples/webpack-esm/package.json new file mode 100644 index 00000000..de1e6464 --- /dev/null +++ b/examples/webpack-esm/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@cypress/webpack-preprocessor": "latest", + "cypress": "latest" + } +} From db0811c20680c6e1ea15c674885d8d0ce4a1b9b2 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 07:05:04 +0200 Subject: [PATCH 60/62] Add examples in cjs --- .github/workflows/examples.yml | 90 +++++++++++++++++++ .gitignore | 1 + examples/browserify-cjs/.npmrc | 1 + examples/browserify-cjs/cypress.config.js | 20 +++++ .../cypress/e2e/duckduckgo.feature | 4 + .../browserify-cjs/cypress/e2e/duckduckgo.js | 15 ++++ examples/browserify-cjs/package.json | 7 ++ examples/esbuild-cjs/.npmrc | 1 + examples/esbuild-cjs/cypress.config.js | 26 ++++++ .../cypress/e2e/duckduckgo.feature | 4 + .../esbuild-cjs/cypress/e2e/duckduckgo.js | 15 ++++ examples/esbuild-cjs/package.json | 7 ++ examples/webpack-cjs/.npmrc | 1 + examples/webpack-cjs/cypress.config.js | 51 +++++++++++ .../cypress/e2e/duckduckgo.feature | 4 + .../webpack-cjs/cypress/e2e/duckduckgo.js | 15 ++++ examples/webpack-cjs/package.json | 7 ++ 17 files changed, 269 insertions(+) create mode 100644 examples/browserify-cjs/.npmrc create mode 100644 examples/browserify-cjs/cypress.config.js create mode 100644 examples/browserify-cjs/cypress/e2e/duckduckgo.feature create mode 100644 examples/browserify-cjs/cypress/e2e/duckduckgo.js create mode 100644 examples/browserify-cjs/package.json create mode 100644 examples/esbuild-cjs/.npmrc create mode 100644 examples/esbuild-cjs/cypress.config.js create mode 100644 examples/esbuild-cjs/cypress/e2e/duckduckgo.feature create mode 100644 examples/esbuild-cjs/cypress/e2e/duckduckgo.js create mode 100644 examples/esbuild-cjs/package.json create mode 100644 examples/webpack-cjs/.npmrc create mode 100644 examples/webpack-cjs/cypress.config.js create mode 100644 examples/webpack-cjs/cypress/e2e/duckduckgo.feature create mode 100644 examples/webpack-cjs/cypress/e2e/duckduckgo.js create mode 100644 examples/webpack-cjs/package.json diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 51aae2b9..e972bb2f 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -10,6 +10,36 @@ defaults: shell: bash jobs: + browserify-cjs: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(browserify-cjs) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/browserify-cjs + run: npm install + - name: Run Cypress + working-directory: examples/browserify-cjs + run: npx cypress run + browserify-esm: runs-on: ubuntu-20.04 container: @@ -70,6 +100,36 @@ jobs: working-directory: examples/browserify-ts run: npx cypress run + webpack-cjs: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(webpack-cjs) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/webpack-cjs + run: npm install + - name: Run Cypress + working-directory: examples/webpack-cjs + run: npx cypress run + webpack-esm: runs-on: ubuntu-20.04 container: @@ -130,6 +190,36 @@ jobs: working-directory: examples/webpack-ts run: npx cypress run + esbuild-cjs: + runs-on: ubuntu-20.04 + container: + image: cypress/base:17.3.0 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Cache NPM modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: npm + - name: Cache Cypress binaries + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: cypress + # In lack of native support, https://github.com/actions/checkout/issues/172. + - name: Make checkout sparse + run: | + shopt -s extglob + rm -rf examples/!(esbuild-cjs) + rm -rf !(examples) + - name: Install NPM modules + working-directory: examples/esbuild-cjs + run: npm install + - name: Run Cypress + working-directory: examples/esbuild-cjs + run: npx cypress run + esbuild-esm: runs-on: ubuntu-20.04 container: diff --git a/.gitignore b/.gitignore index 3829b696..9f6ba042 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/*.js **/*.d.ts +!examples/**/* !features/**/* !cucumber.d.ts !cucumber.js diff --git a/examples/browserify-cjs/.npmrc b/examples/browserify-cjs/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/browserify-cjs/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/browserify-cjs/cypress.config.js b/examples/browserify-cjs/cypress.config.js new file mode 100644 index 00000000..cc6c1741 --- /dev/null +++ b/examples/browserify-cjs/cypress.config.js @@ -0,0 +1,20 @@ +const { defineConfig } = require("cypress"); +const preprocessor = require("@badeball/cypress-cucumber-preprocessor"); +const browserify = require("@badeball/cypress-cucumber-preprocessor/browserify"); + +async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on("file:preprocessor", browserify.default(config)); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +module.exports = defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/browserify-cjs/cypress/e2e/duckduckgo.feature b/examples/browserify-cjs/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/browserify-cjs/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/browserify-cjs/cypress/e2e/duckduckgo.js b/examples/browserify-cjs/cypress/e2e/duckduckgo.js new file mode 100644 index 00000000..eef8e9f9 --- /dev/null +++ b/examples/browserify-cjs/cypress/e2e/duckduckgo.js @@ -0,0 +1,15 @@ +const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/browserify-cjs/package.json b/examples/browserify-cjs/package.json new file mode 100644 index 00000000..1caa0c5a --- /dev/null +++ b/examples/browserify-cjs/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@cypress/browserify-preprocessor": "latest", + "cypress": "latest" + } +} diff --git a/examples/esbuild-cjs/.npmrc b/examples/esbuild-cjs/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/esbuild-cjs/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/esbuild-cjs/cypress.config.js b/examples/esbuild-cjs/cypress.config.js new file mode 100644 index 00000000..7753d590 --- /dev/null +++ b/examples/esbuild-cjs/cypress.config.js @@ -0,0 +1,26 @@ +const { defineConfig } = require("cypress"); +const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); +const preprocessor = require("@badeball/cypress-cucumber-preprocessor"); +const createEsbuildPlugin = require("@badeball/cypress-cucumber-preprocessor/esbuild"); + +async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + createBundler({ + plugins: [createEsbuildPlugin.default(config)], + }) + ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +module.exports = defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/esbuild-cjs/cypress/e2e/duckduckgo.feature b/examples/esbuild-cjs/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/esbuild-cjs/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/esbuild-cjs/cypress/e2e/duckduckgo.js b/examples/esbuild-cjs/cypress/e2e/duckduckgo.js new file mode 100644 index 00000000..eef8e9f9 --- /dev/null +++ b/examples/esbuild-cjs/cypress/e2e/duckduckgo.js @@ -0,0 +1,15 @@ +const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/esbuild-cjs/package.json b/examples/esbuild-cjs/package.json new file mode 100644 index 00000000..49c5c7f9 --- /dev/null +++ b/examples/esbuild-cjs/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@bahmutov/cypress-esbuild-preprocessor": "latest", + "cypress": "latest" + } +} diff --git a/examples/webpack-cjs/.npmrc b/examples/webpack-cjs/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/examples/webpack-cjs/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/examples/webpack-cjs/cypress.config.js b/examples/webpack-cjs/cypress.config.js new file mode 100644 index 00000000..772c92e5 --- /dev/null +++ b/examples/webpack-cjs/cypress.config.js @@ -0,0 +1,51 @@ +const { defineConfig } = require("cypress"); +const webpack = require("@cypress/webpack-preprocessor"); +const preprocessor = require("@badeball/cypress-cucumber-preprocessor"); + +async function setupNodeEvents(on, config) { + await preprocessor.addCucumberPreprocessorPlugin(on, config); + + on( + "file:preprocessor", + webpack({ + webpackOptions: { + resolve: { + extensions: [".ts", ".js"], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: [/node_modules/], + use: [ + { + loader: "ts-loader", + }, + ], + }, + { + test: /\.feature$/, + use: [ + { + loader: "@badeball/cypress-cucumber-preprocessor/webpack", + options: config, + }, + ], + }, + ], + }, + }, + }) + ); + + // Make sure to return the config object as it might have been modified by the plugin. + return config; +} + +module.exports = defineConfig({ + e2e: { + specPattern: "**/*.feature", + supportFile: false, + setupNodeEvents, + }, +}); diff --git a/examples/webpack-cjs/cypress/e2e/duckduckgo.feature b/examples/webpack-cjs/cypress/e2e/duckduckgo.feature new file mode 100644 index 00000000..858c6bb6 --- /dev/null +++ b/examples/webpack-cjs/cypress/e2e/duckduckgo.feature @@ -0,0 +1,4 @@ +Feature: duckduckgo.com + Scenario: visting the frontpage + When I visit duckduckgo.com + Then I should see a search bar diff --git a/examples/webpack-cjs/cypress/e2e/duckduckgo.js b/examples/webpack-cjs/cypress/e2e/duckduckgo.js new file mode 100644 index 00000000..eef8e9f9 --- /dev/null +++ b/examples/webpack-cjs/cypress/e2e/duckduckgo.js @@ -0,0 +1,15 @@ +const { When, Then } = require("@badeball/cypress-cucumber-preprocessor"); + +When("I visit duckduckgo.com", () => { + cy.visit("https://duckduckgo.com/"); +}); + +Then("I should see a search bar", () => { + cy.get("input").should( + "have.attr", + "placeholder", + "Search the web without being tracked" + ); + + assert.deepEqual({}, {}); +}); diff --git a/examples/webpack-cjs/package.json b/examples/webpack-cjs/package.json new file mode 100644 index 00000000..de1e6464 --- /dev/null +++ b/examples/webpack-cjs/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@badeball/cypress-cucumber-preprocessor": "latest", + "@cypress/webpack-preprocessor": "latest", + "cypress": "latest" + } +} From 081404a90c052daa97e68853e0dd99b973f7e2d7 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 07:27:40 +0200 Subject: [PATCH 61/62] Add a readme linking to each example --- examples/readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/readme.md b/examples/readme.md index ff78f91e..29c7960c 100644 --- a/examples/readme.md +++ b/examples/readme.md @@ -2,3 +2,8 @@ The examples illustrates using each bundler in each language flavor. +| | CJS | ESM | TS | +|------------|------------------------|------------------------|-----------------------| +| Browserify | [Link](browserify-cjs) | [Link](browserify-esm) | [Link](browserify-ts) | +| Esbuild | [Link](esbuild-cjs) | [Link](esbuild-esm) | [Link](esbuild-ts) | +| Webpack | [Link](webpack-cjs) | [Link](webpack-esm) | [Link](webpack-ts) | From 7a993bea17bfc7f593328fd569890256a212fb66 Mon Sep 17 00:00:00 2001 From: Jonas Amundsen Date: Thu, 23 Jun 2022 07:38:01 +0200 Subject: [PATCH 62/62] v11.2.0 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc2a0a0..2e263ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## v11.2.0 + +- Enable `*.mjs` file extension by default, when looking for step definitions. + +- Add a default export to `@badeball/cypress-cucumber-preprocessor/esbuild`. + +- Add examples for CJS and ESM. + ## v11.1.0 - Enable test configuration overrides, such as retrability of a single scenario, fixes [#697](https://github.com/badeball/cypress-cucumber-preprocessor/issues/697). diff --git a/package.json b/package.json index 63cdef28..c2d02082 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@badeball/cypress-cucumber-preprocessor", - "version": "11.1.0", + "version": "11.2.0", "author": "Jonas Amundsen", "license": "MIT", "homepage": "https://github.com/badeball/cypress-cucumber-preprocessor",