Skip to content

Commit

Permalink
Add support for skipped / pending scenario hooks
Browse files Browse the repository at this point in the history
This fixes #1159 [1].

[1] #1159
  • Loading branch information
badeball committed Mar 1, 2024
1 parent 86cc83e commit 8d418b9
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 97 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## Unreleased

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

## v20.0.1

- Handle more corner cases related to reload-behavior, fixes [#1142](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1142).
Expand Down
5 changes: 5 additions & 0 deletions docs/cucumber-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Hooks](#hooks)
- [Run hooks](#run-hooks)
- [Scenario hooks](#scenario-hooks)
- [Pending / skipped scenario hooks](#pending--skipped-scenario-hooks)
- [Step hooks](#step-hooks)
- [Hook ordering](#hook-ordering)
- [Named hooks](#named-hooks)
Expand Down Expand Up @@ -188,6 +189,10 @@ Before(function ({ pickle, gherkinDocument, testCaseStartedId }) {
});
```

### Pending / skipped scenario hooks

Scenario hooks can be made pending or skipped similarly to steps, as explained above, by returning `"pending"` or `"skipped"`, respectively. Both will halt the execution and Cypress will report the test as "skipped".

## Step hooks

`BeforeStep()` and `AfterStep()` are hooks invoked before and after each step, respectively. These too can be selected to conditionally run based on the tags of each scenario, as shown below.
Expand Down
39 changes: 39 additions & 0 deletions features/pending_scenario_hooks.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Feature: pending scenario hooks

Scenario: pending Before() hook
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 { Before, Given } = require("@badeball/cypress-cucumber-preprocessor")
Before(() => {
return "pending"
})
Given("a step", function() {})
"""
When I run cypress
Then it passes
And it should appear to have skipped the scenario "a scenario"

Scenario: pending After() hook
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 { After, Given } = require("@badeball/cypress-cucumber-preprocessor")
After(() => {
return "pending"
})
Given("a step", function() {})
"""
When I run cypress
Then it passes
And it should appear to have skipped the scenario "a scenario"
39 changes: 39 additions & 0 deletions features/skipped_scenario_hooks.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Feature: skipped scenario hooks

Scenario: skipped Before() hook
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 { Before, Given } = require("@badeball/cypress-cucumber-preprocessor")
Before(() => {
return "skipped"
})
Given("a step", function() {})
"""
When I run cypress
Then it passes
And it should appear to have skipped the scenario "a scenario"

Scenario: skipped After() hook
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 { After, Given } = require("@badeball/cypress-cucumber-preprocessor")
After(() => {
return "skipped"
})
Given("a step", function() {})
"""
When I run cypress
Then it passes
And it should appear to have skipped the scenario "a scenario"
192 changes: 95 additions & 97 deletions lib/browser-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,92 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
pickle,
};

const onAfterStep = (options: {
testStepId: string;
start: messages.Timestamp;
result: any;
}) => {
const { testStepId, start, result } = options;

const end = createTimestamp();

if (result === "pending" || result === "skipped") {
if (result === "pending") {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.PENDING,
duration: duration(start, end),
},
timestamp: end,
});
} else {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.SKIPPED,
duration: duration(start, end),
},
timestamp: end,
});
}

remainingSteps.shift();

for (const skippedStep of remainingSteps) {
const hookIdOrPickleStepId = assertAndReturn(
skippedStep.hook?.id ?? skippedStep.pickleStep?.id,
"Expected a step to either be a hook or a pickleStep"
);

const testStepId = getTestStepId({
context,
pickleId: pickle.id,
hookIdOrPickleStepId,
});

taskTestStepStarted(context, {
testStepId,
testCaseStartedId,
timestamp: createTimestamp(),
});

taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.SKIPPED,
duration: {
seconds: 0,
nanos: 0,
},
},
timestamp: createTimestamp(),
});
}

for (let i = 0, count = remainingSteps.length; i < count; i++) {
remainingSteps.pop();
}

cy.then(() => this.skip());
} else {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.PASSED,
duration: duration(start, end),
},
timestamp: end,
});

remainingSteps.shift();
}
};

for (const step of steps) {
if (step.hook) {
const hook = step.hook;
Expand Down Expand Up @@ -480,29 +566,17 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
testCaseStartedId,
};

runStepWithLogGroup({
return runStepWithLogGroup({
fn: () => registry.runCaseHook(this, hook, options),
keyword: hook.keyword,
text: createStepDescription(hook),
}).then((result) => {
return { start, result };
});

return cy.wrap(start, { log: false });
})
.then((start) => {
const end = createTimestamp();

taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.PASSED,
duration: duration(start, end),
},
timestamp: end,
});

remainingSteps.shift();
});
.then(({ start, result }) =>
onAfterStep({ start, result, testStepId })
);
} else if (step.pickleStep) {
const pickleStep = step.pickleStep;

Expand Down Expand Up @@ -616,85 +690,9 @@ function createPickle(context: CompositionContext, pickle: messages.Pickle) {
}
});
})
.then(({ start, result }) => {
const end = createTimestamp();

if (result === "pending" || result === "skipped") {
if (result === "pending") {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.PENDING,
duration: duration(start, end),
},
timestamp: end,
});
} else {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.SKIPPED,
duration: duration(start, end),
},
timestamp: end,
});
}

remainingSteps.shift();

for (const skippedStep of remainingSteps) {
const hookIdOrPickleStepId = assertAndReturn(
skippedStep.hook?.id ?? skippedStep.pickleStep?.id,
"Expected a step to either be a hook or a pickleStep"
);

const testStepId = getTestStepId({
context,
pickleId: pickle.id,
hookIdOrPickleStepId,
});

taskTestStepStarted(context, {
testStepId,
testCaseStartedId,
timestamp: createTimestamp(),
});

taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.SKIPPED,
duration: {
seconds: 0,
nanos: 0,
},
},
timestamp: createTimestamp(),
});
}

for (let i = 0, count = remainingSteps.length; i < count; i++) {
remainingSteps.pop();
}

cy.then(() => this.skip());
} else {
taskTestStepFinished(context, {
testStepId,
testCaseStartedId,
testStepResult: {
status: messages.TestStepResultStatus.PASSED,
duration: duration(start, end),
},
timestamp: end,
});

remainingSteps.shift();
}
});
.then(({ start, result }) =>
onAfterStep({ start, result, testStepId })
);
}
}
});
Expand Down

0 comments on commit 8d418b9

Please sign in to comment.