From 126772d66d0889d08421a30bf6e79fa9ec95e52b Mon Sep 17 00:00:00 2001 From: David Goss Date: Mon, 6 Jun 2022 21:55:14 +0100 Subject: [PATCH] support: include willBeRetried in after hook argument (#2045) --- CHANGELOG.md | 2 ++ UPGRADING.md | 4 +++ docs/support_files/api_reference.md | 2 +- src/runtime/test_case_runner.ts | 7 ++++- src/runtime/test_case_runner_spec.ts | 38 +++++++++++++++++++++++ src/support_code_library_builder/types.ts | 1 + 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8942acbc5..f2f4c36fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) on how to contribute to Cucumber. ## [Unreleased] +### Added +- Add `willBeRetried` to the parameter passed to `After` hook functions ([#2045](https://github.com/cucumber/cucumber-js/pull/2045)) ## [8.2.2] - 2022-05-27 ### Changed diff --git a/UPGRADING.md b/UPGRADING.md index 6f084607b..5469ac7f4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -29,6 +29,10 @@ setDefinitionFunctionWrapper(function (fn) { }) ``` +### Accessing `willBeRetried` from hooks + +In the argument passed to your `After` hook function, the `result` no longer has a `willBeRetried` property; this is now available at the top level of the object. + ### Using `Cli` programmatically The `Cli` class is sometimes used to run Cucumber programmatically. We've had to make a few breaking changes: diff --git a/docs/support_files/api_reference.md b/docs/support_files/api_reference.md index dee006a94..abf64228d 100644 --- a/docs/support_files/api_reference.md +++ b/docs/support_files/api_reference.md @@ -38,7 +38,7 @@ Defines a hook which is run after each scenario. * `tags`: String tag expression used to apply this hook to only specific scenarios. See [cucumber-tag-expressions](https://docs.cucumber.io/tag-expressions/) for more information. * `timeout`: A hook-specific timeout, to override the default timeout. * `fn`: A function, defined as follows: - * The first argument will be an object of the form `{pickle, gherkinDocument, result, testCaseStartedId}` + * The first argument will be an object of the form `{pickle, gherkinDocument, result, willBeRetried, testCaseStartedId}` * The pickle object comes from the [gherkin](https://github.com/cucumber/cucumber/tree/gherkin/v15.0.2/gherkin) library. See `testdata/good/*.pickles.ndjson` for examples of its structure. * When using the asynchronous callback interface, have one final argument for the callback function. diff --git a/src/runtime/test_case_runner.ts b/src/runtime/test_case_runner.ts index 9b77ce904..4a00f06d8 100644 --- a/src/runtime/test_case_runner.ts +++ b/src/runtime/test_case_runner.ts @@ -175,6 +175,7 @@ export default class TestCaseRunner { async run(): Promise { for (let attempt = 0; attempt < this.maxAttempts; attempt++) { + const moreAttemptsRemaining = attempt + 1 < this.maxAttempts this.currentTestCaseStartedId = this.newId() const testCaseStarted: messages.Envelope = { testCaseStarted: { @@ -197,6 +198,9 @@ export default class TestCaseRunner { } if (didWeRunStepsYet) { hookParameter.result = this.getWorstStepResult() + hookParameter.willBeRetried = + this.getWorstStepResult().status === + messages.TestStepResultStatus.FAILED && moreAttemptsRemaining } return await this.runHook( findHookDefinition(testStep.hookId, this.supportCodeLibrary), @@ -213,9 +217,10 @@ export default class TestCaseRunner { } }) } + const willBeRetried = this.getWorstStepResult().status === - messages.TestStepResultStatus.FAILED && attempt + 1 < this.maxAttempts + messages.TestStepResultStatus.FAILED && moreAttemptsRemaining const testCaseFinished: messages.Envelope = { testCaseFinished: { testCaseStartedId: this.currentTestCaseStartedId, diff --git a/src/runtime/test_case_runner_spec.ts b/src/runtime/test_case_runner_spec.ts index 1d5b1594d..0447ea605 100644 --- a/src/runtime/test_case_runner_spec.ts +++ b/src/runtime/test_case_runner_spec.ts @@ -359,6 +359,44 @@ describe('TestCaseRunner', () => { expect(envelopes).to.eql(expected) expect(result).to.eql(messages.TestStepResultStatus.PASSED) }) + + it('should provide the correctly willBeRetried value to the hook', async () => { + // Arrange + const hookStub = sinon.stub() + const supportCodeLibrary = buildSupportCodeLibrary( + ({ Given, After }) => { + let willPass = false + Given('a step', function () { + if (willPass) { + return + } + willPass = true + throw 'error' // eslint-disable-line @typescript-eslint/no-throw-literal + }) + After(hookStub) + } + ) + const { + gherkinDocument, + pickles: [pickle], + } = await parse({ + data: ['Feature: a', 'Scenario: b', 'Given a step'].join('\n'), + uri: 'a.feature', + }) + + // Act + await testRunner({ + gherkinDocument, + pickle, + retries: 1, + supportCodeLibrary, + }) + + // Assert + expect(hookStub).to.have.been.calledTwice() + expect(hookStub.args[0][0].willBeRetried).to.eq(true) + expect(hookStub.args[1][0].willBeRetried).to.eq(false) + }) }) describe('with a step when skipping', () => { diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index a33d2c56c..47e19d856 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -15,6 +15,7 @@ export interface ITestCaseHookParameter { gherkinDocument: messages.GherkinDocument pickle: messages.Pickle result?: messages.TestStepResult + willBeRetried?: boolean testCaseStartedId: string }