Skip to content

Commit ccb9fd1

Browse files
committed
Allow step hooks to return skipped / pending
This is similar to how steps themselves can also return the above mentioned literals. This is in line with how cucumber-js behaves.
1 parent 15eb687 commit ccb9fd1

File tree

4 files changed

+241
-21
lines changed

4 files changed

+241
-21
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
66

77
- Run hooks (BeforeAll/AfterAll) may now be optionally named. This is in line with how cucumber-js behaves.
88

9+
- Allow step hooks to return skipped / pending.
10+
11+
- This is similar to how steps themselves can also return the above mentioned literals. This is in line with how cucumber-js behaves.
12+
913
## v23.2.1
1014

1115
- Determine interactive mode correctly, fixes [#1323](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1323).

features/step_hooks.feature

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
Feature: step hooks
2+
3+
Background:
4+
Given additional preprocessor configuration
5+
"""
6+
{
7+
"messages": {
8+
"enabled": true
9+
}
10+
}
11+
"""
12+
13+
Rule: skipped step hooks should affect the step result
14+
15+
Background:
16+
Given a file named "cypress/e2e/a.feature" with:
17+
"""
18+
Feature: a feature
19+
Scenario: a scenario
20+
Given a preceding step
21+
And a skipped step
22+
And a succeeding step
23+
"""
24+
And a file named "cypress/support/step_definitions/steps.js" with:
25+
"""
26+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
27+
Given("a preceding step", function () {})
28+
Given("a skipped step", function() {})
29+
Given("a succeeding step", function () {})
30+
"""
31+
32+
Scenario: returning literal 'skipped' in BeforeStep
33+
Given a file named "cypress/support/step_definitions/hooks.js" with:
34+
"""
35+
const { BeforeStep } = require("@badeball/cypress-cucumber-preprocessor");
36+
BeforeStep(function ({ pickleStep }) {
37+
if (pickleStep.text === "a skipped step") {
38+
return "skipped";
39+
}
40+
});
41+
"""
42+
When I run cypress
43+
Then it passes
44+
And there should be a messages similar to "fixtures/skipped-steps.ndjson"
45+
46+
Scenario: returning wrapped 'skipped' in BeforeStep
47+
Given a file named "cypress/support/step_definitions/hooks.js" with:
48+
"""
49+
const { BeforeStep } = require("@badeball/cypress-cucumber-preprocessor");
50+
BeforeStep(function ({ pickleStep }) {
51+
if (pickleStep.text === "a skipped step") {
52+
return cy.wrap("skipped");
53+
}
54+
});
55+
"""
56+
When I run cypress
57+
Then it passes
58+
And there should be a messages similar to "fixtures/skipped-steps.ndjson"
59+
60+
Scenario: returning literal 'skipped' in AfterStep
61+
Given a file named "cypress/support/step_definitions/hooks.js" with:
62+
"""
63+
const { AfterStep } = require("@badeball/cypress-cucumber-preprocessor");
64+
AfterStep(function ({ pickleStep }) {
65+
if (pickleStep.text === "a skipped step") {
66+
return "skipped";
67+
}
68+
});
69+
"""
70+
When I run cypress
71+
Then it passes
72+
And there should be a messages similar to "fixtures/skipped-steps.ndjson"
73+
74+
Scenario: returning wrapped 'skipped' in AfterStep
75+
Given a file named "cypress/support/step_definitions/hooks.js" with:
76+
"""
77+
const { AfterStep } = require("@badeball/cypress-cucumber-preprocessor");
78+
AfterStep(function ({ pickleStep }) {
79+
if (pickleStep.text === "a skipped step") {
80+
return cy.wrap("skipped");
81+
}
82+
});
83+
"""
84+
When I run cypress
85+
Then it passes
86+
And there should be a messages similar to "fixtures/skipped-steps.ndjson"
87+
88+
Rule: pending step hooks should affect the step result
89+
90+
Background:
91+
Given a file named "cypress/e2e/a.feature" with:
92+
"""
93+
Feature: a feature
94+
Scenario: a scenario
95+
Given a preceding step
96+
And a pending step
97+
And a succeeding step
98+
"""
99+
And a file named "cypress/support/step_definitions/steps.js" with:
100+
"""
101+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
102+
Given("a preceding step", function () {})
103+
Given("a pending step", function() {})
104+
Given("a succeeding step", function () {})
105+
"""
106+
107+
Scenario: returning literal 'pending' in BeforeStep
108+
Given a file named "cypress/support/step_definitions/hooks.js" with:
109+
"""
110+
const { BeforeStep } = require("@badeball/cypress-cucumber-preprocessor");
111+
BeforeStep(function ({ pickleStep }) {
112+
if (pickleStep.text === "a pending step") {
113+
return "pending";
114+
}
115+
});
116+
"""
117+
When I run cypress
118+
Then it passes
119+
And there should be a messages similar to "fixtures/pending-steps.ndjson"
120+
121+
Scenario: returning wrapped 'pending' in BeforeStep
122+
Given a file named "cypress/support/step_definitions/hooks.js" with:
123+
"""
124+
const { BeforeStep } = require("@badeball/cypress-cucumber-preprocessor");
125+
BeforeStep(function ({ pickleStep }) {
126+
if (pickleStep.text === "a pending step") {
127+
return cy.wrap("pending");
128+
}
129+
});
130+
"""
131+
When I run cypress
132+
Then it passes
133+
And there should be a messages similar to "fixtures/pending-steps.ndjson"
134+
135+
Scenario: returning literal 'pending' in AfterStep
136+
Given a file named "cypress/support/step_definitions/hooks.js" with:
137+
"""
138+
const { AfterStep } = require("@badeball/cypress-cucumber-preprocessor");
139+
AfterStep(function ({ pickleStep }) {
140+
if (pickleStep.text === "a pending step") {
141+
return "pending";
142+
}
143+
});
144+
"""
145+
When I run cypress
146+
Then it passes
147+
And there should be a messages similar to "fixtures/pending-steps.ndjson"
148+
149+
Scenario: returning wrapped 'pending' in AfterStep
150+
Given a file named "cypress/support/step_definitions/hooks.js" with:
151+
"""
152+
const { AfterStep } = require("@badeball/cypress-cucumber-preprocessor");
153+
AfterStep(function ({ pickleStep }) {
154+
if (pickleStep.text === "a pending step") {
155+
return cy.wrap("pending");
156+
}
157+
});
158+
"""
159+
When I run cypress
160+
Then it passes
161+
And there should be a messages similar to "fixtures/pending-steps.ndjson"

lib/browser-runtime.ts

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { runStepWithLogGroup } from "./helpers/cypress";
4444
import { getTags } from "./helpers/environment";
4545
import { createTimestamp, duration, StrictTimestamp } from "./helpers/messages";
4646
import {
47+
getWorstTestStepResult,
4748
HookType,
4849
SourceMediaType,
4950
StepDefinitionPatternType,
@@ -109,6 +110,18 @@ function getSourceReferenceFromPosition(
109110
}
110111
}
111112

113+
function convertReturnValueToTestStepResultStatus(
114+
retval: any,
115+
): TestStepResultStatus {
116+
if (retval === "skipped") {
117+
return TestStepResultStatus.SKIPPED;
118+
} else if (retval === "pending") {
119+
return TestStepResultStatus.PENDING;
120+
} else {
121+
return TestStepResultStatus.PASSED;
122+
}
123+
}
124+
112125
interface IStep {
113126
hook?: ICaseHook<Mocha.Context>;
114127
pickleStep?: messages.PickleStep;
@@ -582,14 +595,17 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
582595
const onAfterStep = (options: {
583596
testStepId: string;
584597
start: messages.Timestamp;
585-
result: any;
598+
result: TestStepResultStatus;
586599
}) => {
587600
const { testStepId, start, result } = options;
588601

589602
const end = createTimestamp();
590603

591-
if (result === "pending" || result === "skipped") {
592-
if (result === "pending") {
604+
if (
605+
result === TestStepResultStatus.PENDING ||
606+
result === TestStepResultStatus.SKIPPED
607+
) {
608+
if (result === TestStepResultStatus.PENDING) {
593609
taskTestStepFinished(context, {
594610
testStepId,
595611
testCaseStartedId,
@@ -703,9 +719,11 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
703719
: () => registry.runCaseHook(this, hook, options),
704720
keyword: hook.keyword,
705721
text: createStepDescription(hook),
706-
}).then((result) => {
707-
return { start, result };
708-
});
722+
})
723+
.then(convertReturnValueToTestStepResultStatus)
724+
.then((result) => {
725+
return { start, result };
726+
});
709727
})
710728
.then(({ start, result }) =>
711729
onAfterStep({ start, result, testStepId }),
@@ -768,23 +786,28 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
768786
testStepId,
769787
};
770788

771-
const beforeHooksChain = beforeStepHooks.reduce(
789+
const beforeHooksChain = beforeStepHooks.reduce<
790+
Cypress.Chainable<TestStepResultStatus[]>
791+
>(
772792
(chain, beforeStepHook) => {
773-
return chain.then(() =>
793+
return chain.then((results) =>
774794
runStepWithLogGroup({
775795
keyword: "BeforeStep",
776796
text: createStepDescription(beforeStepHook),
777797
fn: dryRun
778798
? noopFn
779799
: () =>
780800
registry.runStepHook(this, beforeStepHook, options),
781-
}),
801+
}).then((result) => [
802+
...results,
803+
convertReturnValueToTestStepResultStatus(result),
804+
]),
782805
);
783806
},
784-
cy.wrap({} as unknown, { log: false }),
807+
cy.wrap<TestStepResultStatus[]>([], { log: false }),
785808
);
786809

787-
return beforeHooksChain.then(() => {
810+
return beforeHooksChain.then((beforeStepHookResults) => {
788811
try {
789812
return runStepWithLogGroup({
790813
keyword: ensure(
@@ -795,11 +818,14 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
795818
text,
796819
fn: () =>
797820
registry.runStepDefinition(this, text, dryRun, argument),
798-
}).then((result) => {
799-
return afterStepHooks
800-
.reduce(
821+
})
822+
.then(convertReturnValueToTestStepResultStatus)
823+
.then((stepResult) => {
824+
const afterStepHooksChain = afterStepHooks.reduce<
825+
Cypress.Chainable<TestStepResultStatus[]>
826+
>(
801827
(chain, afterStepHook) => {
802-
return chain.then(() =>
828+
return chain.then((results) =>
803829
runStepWithLogGroup({
804830
keyword: "AfterStep",
805831
text: createStepDescription(afterStepHook),
@@ -811,15 +837,26 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
811837
afterStepHook,
812838
options,
813839
),
814-
}),
840+
}).then((result) => [
841+
...results,
842+
convertReturnValueToTestStepResultStatus(result),
843+
]),
815844
);
816845
},
817-
cy.wrap({} as unknown, { log: false }),
818-
)
819-
.then(() => {
820-
return { start, result };
846+
cy.wrap<TestStepResultStatus[]>([], { log: false }),
847+
);
848+
849+
return afterStepHooksChain.then((afterStepHookResults) => {
850+
return {
851+
start,
852+
result: getWorstTestStepResult([
853+
...beforeStepHookResults,
854+
stepResult,
855+
...afterStepHookResults,
856+
]),
857+
};
821858
});
822-
});
859+
});
823860
} catch (e) {
824861
if (e instanceof MissingDefinitionError) {
825862
throw new Error(

lib/helpers/messages-enums.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,21 @@ export enum HookType {
3838
BEFORE_TEST_STEP = "BEFORE_TEST_STEP",
3939
AFTER_TEST_STEP = "AFTER_TEST_STEP",
4040
}
41+
42+
export function getWorstTestStepResult(
43+
testStepResults: readonly TestStepResultStatus[],
44+
): TestStepResultStatus {
45+
return testStepResults.slice().sort((r1, r2) => ordinal(r2) - ordinal(r1))[0];
46+
}
47+
48+
function ordinal(status: TestStepResultStatus) {
49+
return [
50+
TestStepResultStatus.UNKNOWN,
51+
TestStepResultStatus.PASSED,
52+
TestStepResultStatus.SKIPPED,
53+
TestStepResultStatus.PENDING,
54+
TestStepResultStatus.UNDEFINED,
55+
TestStepResultStatus.AMBIGUOUS,
56+
TestStepResultStatus.FAILED,
57+
].indexOf(status);
58+
}

0 commit comments

Comments
 (0)