Skip to content

Commit

Permalink
Merge branch 'suite-level-test-configuration-mallison'
Browse files Browse the repository at this point in the history
This fixes #1158 [1].

[1] #1158
  • Loading branch information
badeball committed Mar 1, 2024
2 parents d56a503 + 3e3b311 commit 58003f0
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.

- Add support for skipped / pending scenario hooks, fixes [#1159](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1159).

- Add support for suite-level test configuration, fixes [#1158](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1158).

## v20.0.1

- Handle more corner cases related to reload-behavior, fixes [#1142](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1142).
Expand Down
162 changes: 162 additions & 0 deletions features/suite_only_options.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
@cypress>=12
Feature: suite only options
Scenario: Configuring testIsolation on a Feature
Given additional Cypress configuration
"""
{
"e2e": {
"testIsolation": true
}
}
"""
And a file named "cypress/e2e/a.feature" with:
"""
@testIsolation(false)
Feature: a feature
Scenario: a scenario
Given a step
Scenario: another scenario
Then another step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given, Then } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", () => {
cy.get("body").invoke('html', 'Hello world')
});
Given("another step", () => {
cy.contains("Hello world").should("exist");
});
"""
When I run cypress
Then it passes

Scenario: Configuring testIsolation on a Rule
Given additional Cypress configuration
"""
{
"e2e": {
"testIsolation": true
}
}
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature
@testIsolation(false)
Rule: a rule
Scenario: a scenario
Given a step
Scenario: another scenario
Then another step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given, Then } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", () => {
cy.get("body").invoke('html', 'Hello world')
});
Given("another step", () => {
cy.contains("Hello world").should("exist");
});
"""
When I run cypress
Then it passes

Scenario: Configuring testIsolation on a Scenario fails
Given additional Cypress configuration
"""
{
"e2e": {
"testIsolation": true
}
}
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature
@testIsolation(false)
Scenario: a scenario
Given a step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given, Then } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", () => {
cy.get("body").invoke('html', 'Hello world')
});
"""
When I run cypress
Then it fails
And the output should contain
"""
Tag @testIsolation(false) can only be used on a Feature or a Rule
"""

Scenario: Configuring testIsolation on a Scenario Outline fails
Given additional Cypress configuration
"""
{
"e2e": {
"testIsolation": true
}
}
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature
@testIsolation(false)
Scenario Outline: a scenario
Given a step
Examples:
| foo |
| bar |
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given, Then } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", () => {
cy.get("body").invoke('html', 'Hello world')
});
"""
When I run cypress
Then it fails
And the output should contain
"""
Tag @testIsolation(false) can only be used on a Feature or a Rule
"""

Scenario: Configuring testIsolation on Examples fails
Given additional Cypress configuration
"""
{
"e2e": {
"testIsolation": true
}
}
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature
Scenario Outline: a scenario
Given a step
@testIsolation(false)
Examples:
| foo |
| bar |
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given, Then } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", () => {
cy.get("body").invoke('html', 'Hello world')
});
"""
When I run cypress
Then it fails
And the output should contain
"""
Tag @testIsolation(false) can only be used on a Feature or a Rule
"""
13 changes: 11 additions & 2 deletions features/support/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from "path";
import { promises as fs } from "fs";
import assert from "assert";
import { version as cypressVersion } from "cypress/package.json";
import { promises as fs } from "fs";
import path from "path";

export async function writeFile(filePath: string, fileContent: string) {
await fs.mkdir(path.dirname(filePath), { recursive: true });
Expand Down Expand Up @@ -106,3 +107,11 @@ export function stringToNdJson(content: string) {
export function ndJsonToString(ndjson: any) {
return ndjson.map((o: any) => JSON.stringify(o)).join("\n") + "\n";
}

export function isPost12() {
return parseInt(cypressVersion.split(".")[0], 10) >= 12;
}

export function isPre12() {
return !isPost12();
}
10 changes: 8 additions & 2 deletions features/support/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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 path from "path";
import { isPre12, writeFile } from "./helpers";

const projectPath = path.join(__dirname, "..", "..");

Expand Down Expand Up @@ -95,6 +95,12 @@ Before({ tags: "not @no-default-plugin" }, async function () {
);
});

Before({ tags: "@cypress>=12" }, async function () {
if (isPre12()) {
return "skipped";
}
});

After(function () {
if (
this.lastRun != null &&
Expand Down
80 changes: 74 additions & 6 deletions lib/browser-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,20 @@ import {
HOOK_FAILURE_EXPR,
INTERNAL_SPEC_PROPERTIES,
INTERNAL_SUITE_PROPERTIES,
TEST_ISOLATION_CONFIGURATION_OPTION,
} from "./constants";

import {
ITaskSpecEnvelopes,
ITaskTestCaseStarted,
ITaskTestCaseFinished,
ITaskTestStepStarted,
ITaskTestCaseStarted,
ITaskTestStepFinished,
ITaskTestStepStarted,
TASK_SPEC_ENVELOPES,
TASK_TEST_CASE_STARTED,
TASK_TEST_CASE_FINISHED,
TASK_TEST_STEP_STARTED,
TASK_TEST_CASE_STARTED,
TASK_TEST_STEP_FINISHED,
TASK_TEST_STEP_STARTED,
} from "./cypress-task-definitions";

import { notNull } from "./helpers/type-guards";
Expand Down Expand Up @@ -286,7 +287,17 @@ function createStepDescription({
}

function createFeature(context: CompositionContext, feature: messages.Feature) {
describe(feature.name || "<unamed feature>", () => {
const suiteOptions = collectTagNames(feature.tags)
.filter(looksLikeOptions)
.map(tagToCypressOptions)
.filter((tag) => {
return Object.keys(tag).every(
(key) => key === TEST_ISOLATION_CONFIGURATION_OPTION
);
})
.reduce(Object.assign, {});

describe(feature.name || "<unamed feature>", suiteOptions, () => {
before(function () {
beforeHandler.call(this, context);
});
Expand Down Expand Up @@ -346,7 +357,17 @@ function createRule(context: CompositionContext, rule: messages.Rule) {
}
}

describe(rule.name || "<unamed rule>", () => {
const suiteOptions = collectTagNames(rule.tags)
.filter(looksLikeOptions)
.map(tagToCypressOptions)
.filter((tag) => {
return Object.keys(tag).every(
(key) => key === TEST_ISOLATION_CONFIGURATION_OPTION
);
})
.reduce(Object.assign, {});

describe(rule.name || "<unamed rule>", suiteOptions, () => {
if (rule.children) {
for (const child of rule.children) {
if (child.scenario) {
Expand Down Expand Up @@ -420,9 +441,56 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
[INTERNAL_SPEC_PROPERTIES]: internalProperties,
};

const scenario = assertAndReturn(
context.astIdsMap.get(
assertAndReturn(
pickle.astNodeIds?.[0],
"Expected to find at least one astNodeId"
)
),
`Expected to find scenario associated with id = ${pickle.astNodeIds?.[0]}`
);

if ("tags" in scenario && "id" in scenario) {
const tagsDefinedOnThisScenarioTagNameAstIdMap = scenario.tags.reduce(
(acc, tag) => {
acc[tag.name] = tag.id;
return acc;
},
{} as Record<string, string>
);

if ("examples" in scenario) {
for (const example of scenario.examples) {
example.tags.forEach((tag) => {
tagsDefinedOnThisScenarioTagNameAstIdMap[tag.name] = tag.id;
});
}
}

for (const tag of pickle.tags) {
if (
looksLikeOptions(tag.name) &&
tagsDefinedOnThisScenarioTagNameAstIdMap[tag.name] === tag.astNodeId &&
Object.keys(tagToCypressOptions(tag.name)).every(
(key) => key === TEST_ISOLATION_CONFIGURATION_OPTION
)
) {
throw new Error(
`Tag ${tag.name} can only be used on a Feature or a Rule`
);
}
}
}

const suiteOptions = tags
.filter(looksLikeOptions)
.map(tagToCypressOptions)
.filter((tag) =>
Object.keys(tag).every(
(key) => key !== TEST_ISOLATION_CONFIGURATION_OPTION
)
)
.reduce(Object.assign, {});

if (suiteOptions.env) {
Expand Down
2 changes: 2 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export const INTERNAL_SUITE_PROPERTIES = INTERNAL_PROPERTY_NAME + "_suite";

export const HOOK_FAILURE_EXPR =
/Because this error occurred during a `[^`]+` hook we are skipping all of the remaining tests\./;

export const TEST_ISOLATION_CONFIGURATION_OPTION = "testIsolation";

0 comments on commit 58003f0

Please sign in to comment.