-
-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support testIsolation configuration tag on Feature and Rule #1160
Changes from all commits
f83f338
ffc2246
0f1b16c
a023012
cfe822c
1569aed
a6746ed
e0436a2
5cde757
99e9df3
e8893bf
3e3b311
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
@@ -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); | ||
}); | ||
|
@@ -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) { | ||
|
@@ -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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of silently filtering this out, I think it should error similarly to how Cypress does (see your example in run-mode). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a look at this but each pickle has a list of tags, following cucumber's inheritance rules. There's no reference back to the node(s) the tag is on. Here I can't tell if I could traverse the document and build a mapping of node to tag but that won't work as scenario names aren't required to be unique. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A pickle has a PS: I suspect that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tips: you can use
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I couldn't use the I could make this more efficient by building a map from tag id to parent node id in one pass. I still need to look into when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What I meant is that you can grab the pickle's 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]}`
);
console.log("Pickle tags", pickle.tags);
console.log("Scenario tags", (scenario as any).tags); For the following tests, this will output a 2-element array and a 1-element array. @foo
Feature: a feature name
@bar
Scenario: a scenario name
Given a step There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. D'oh 🤦 I'm traversing to find the scenario ast node that I can already get from the map. So, yeah, I can just check if the tag belongs to the corresponding ast node for the scenario -- if so throw an error. Otherwise carry on and just filter it out as it was defined on a parent Feature or Rule. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 5cde757 Not sure how I got myself so tied in knots with this one 😅 . |
||
Object.keys(tag).every( | ||
(key) => key !== TEST_ISOLATION_CONFIGURATION_OPTION | ||
) | ||
) | ||
.reduce(Object.assign, {}); | ||
|
||
if (suiteOptions.env) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's another instance where
describe
is invoked and that's with rules (seecreateRule
in the same file). I think that if this were to be supported, then one ought to be able to tag rules in the same way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done cfe822c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does configuration overriding work? Eg
The gherkin parser won't resolve this as tags are just strings but I guess the order of tags is being relied on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine that should work, yeah, and that the latest value takes precedence.