diff --git a/docs/support_files/hooks.md b/docs/support_files/hooks.md index 843d1eb9a..bcdff3abe 100644 --- a/docs/support_files/hooks.md +++ b/docs/support_files/hooks.md @@ -65,9 +65,25 @@ defineSupportCode(function({After, Before}) { See more documentation on [tag expressions](https://docs.cucumber.io/tag-expressions/) +## Skipping in a Before Hook + +If you need to imperatively skip a test using a `Before` hook, this can be done using any of the constructs defined in [skipped steps](./step_definitions.md) + +This includes using: a synchronous return, an asynchronous callback, or an asynchronous promise + +```javascript +defineSupportCode(({After, Before}) => { + // Synchronous + Before(function() { + // perform some runtime check to decide whether to skip the proceeding scenario + return 'skipped' + }); +}); +``` + ## BeforeAll / AfterAll -If you have some setup / teardown that needs to be done before or after all scenarios, use `BeforeAll` / `AfterAll`. Like hooks and steps, these can be synchronous, accept a callback, or return a promise. +If you have some setup / teardown that needs to be done before or after all scenarios, use `BeforeAll` / `AfterAll`. Like hooks and steps, these can be synchronous, accept a callback, or return a promise. Unlike `Before` / `After` these methods will not have a world instance as `this`. This is becauce each scenario gets its own world instance and these hooks run before / after **all** scenarios. diff --git a/docs/support_files/step_definitions.md b/docs/support_files/step_definitions.md index 8febd3a80..dddceb58a 100644 --- a/docs/support_files/step_definitions.md +++ b/docs/support_files/step_definitions.md @@ -85,3 +85,14 @@ Each interface has its own way of marking a step as pending * synchronous - return `'pending'` * asynchronous callback - execute the callback with `null, 'pending'` * asynchronous promise - promise resolves to `'pending'` + +## Skipped steps + +Marking a step as skipped will also mark the proceeding steps of the same scenario as skipped. + +This can be used to mark a scenario as skipped based on a runtime condition. + +Each interface has its own way of marking a step as skipped +* synchronous - return `'skipped'` +* asynchronous callback - execute the callback with `null, 'skipped'` +* asynchronous promise - promise resolves to `'skipped'` diff --git a/features/skipped_steps.feature b/features/skipped_steps.feature new file mode 100644 index 000000000..45f8e2bee --- /dev/null +++ b/features/skipped_steps.feature @@ -0,0 +1,90 @@ +Feature: Skipped steps + + Using this feature, a scenario can be imperatively 'skipped'. + + For example, skipping in a `Given` step will mark the following steps of the same scenario as skipped. + + There are three methods of skipping. One for synchronous steps, one for an asynchronous callback, and one for an asynchronous promise. + + Background: + Given a file named "features/skipped.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a skipped step + """ + + Scenario: Synchronous skipped step + Given a file named "features/step_definitions/skipped_steps.js" with: + """ + import {defineSupportCode} from 'cucumber' + + defineSupportCode(({Given}) => { + Given(/^a skipped step$/, function() { + return 'skipped' + }) + }) + """ + When I run cucumber.js + Then it passes + And the step "a skipped step" has status "skipped" + + + Scenario: Callback skipped step + Given a file named "features/step_definitions/skipped_steps.js" with: + """ + import {defineSupportCode} from 'cucumber' + + defineSupportCode(({Given}) => { + Given(/^a skipped step$/, function(callback) { + callback(null, 'skipped') + }) + }) + """ + When I run cucumber.js + Then it passes + And the step "a skipped step" has status "skipped" + + Scenario: Promise skipped step + Given a file named "features/step_definitions/skipped_steps.js" with: + """ + import {defineSupportCode} from 'cucumber' + + defineSupportCode(({Given}) => { + Given(/^a skipped step$/, function(){ + return { + then: function(onResolve, onReject) { + setTimeout(function() { + onResolve('skipped') + }) + } + } + }) + }) + """ + When I run cucumber.js + Then it passes + And the step "a skipped step" has status "skipped" + + Scenario: Hook skipped scenario steps + Given a file named "features/support/hooks.js" with: + """ + import {defineSupportCode} from 'cucumber' + + defineSupportCode(({After, Before}) => { + Before(function() {return 'skipped'}) + }) + """ + And a file named "features/step_definitions/skipped_steps.js" with: + """ + import {defineSupportCode} from 'cucumber' + + defineSupportCode(({Given}) => { + Given(/^a skipped step$/, function() { + var a = 1; + }) + }) + """ + When I run cucumber.js + Then it passes + And the step "a skipped step" has status "skipped" diff --git a/src/runtime/step_runner.js b/src/runtime/step_runner.js index 3ef838c0a..9e26c11ba 100644 --- a/src/runtime/step_runner.js +++ b/src/runtime/step_runner.js @@ -50,7 +50,9 @@ async function run({ const testStepResult = { duration: endTiming() } - if (result === 'pending') { + if (result === 'skipped') { + testStepResult.status = Status.SKIPPED + } else if (result === 'pending') { testStepResult.status = Status.PENDING } else if (error) { testStepResult.exception = error