diff --git a/CHANGELOG.md b/CHANGELOG.md index db3e987f7..ede556f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,11 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO ---- ## [Unreleased] (In Git) -See the [migration guide](./docs/migration.md) for details of how to migrate from 7.x.x to 8.x.x - ### Breaking changes * Drop support for Node.js 10 and 15, add support for Node.js 16 * Remove deprecated `--retryTagFilter` option (the correct option is `--retry-tag-filter`) -* Remove `setDefinitionFunctionWrapper` and step definition option `wrapperOptions` +* Remove validation that step definition functions are not generators * Remove `--predictable-ids` option (was only used for internal testing) ### Added diff --git a/dependency-lint.yml b/dependency-lint.yml index c1fe0f844..a4a45798d 100644 --- a/dependency-lint.yml +++ b/dependency-lint.yml @@ -20,6 +20,7 @@ ignoreErrors: - '@typescript-eslint/eslint-plugin' # peer dependency of standard-with-typescript - '@typescript-eslint/parser' # peer dependency of @typescript-eslint/eslint-plugin - '@types/*' # type definitions + - bluebird # features/generator_step_definitions.feature - coffeescript # features/compiler.feature - eslint-config-prettier # .eslintrc.yml - extends - prettier - eslint-config-standard-with-typescript # .eslintrc.yml - extends - standard-with-typescript diff --git a/docs/migration.md b/docs/migration.md index 726cbb01d..e45c9e569 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,12 +1,31 @@ -# Migrating from 7.x.x to 8.x.x +# Migrating to cucumber-js 8.x.x -## Removal of setDefinitionFunctionWrapper +## Generator step definitions -If this was used to wrap generator functions, please transition to using async / await. -If this was used to wrap step definitions, please use `BeforeStep` / `AfterStep` hooks instead. -If you had other use cases, please create an issue. +Generator functions used in step definitions (`function*` with the `yield` keyword) +are not natively supported anymore with cucumber-js. -# Migrating from 6.x.x to 7.x.x +You may consider using `async`/`await` rather than generators. + +You can still use generators as before but you need to add your own dependencies +to `bluebird` and `is-generator`. Cucumber-js will no display explicit error message +anymore in case you use a generator without wrapping it properly. + +```javascript +const isGenerator = require('is-generator') +const {coroutine} = require('bluebird') +const {setDefinitionFunctionWrapper} = require('@cucumber/cucumber') + +setDefinitionFunctionWrapper(function (fn) { + if (isGenerator.fn(fn)) { + return coroutine(fn) + } else { + return fn + } +}) +``` + +# Migrating to cucumber-js 7.x.x ## Package Name diff --git a/docs/support_files/api_reference.md b/docs/support_files/api_reference.md index 0e9ee94cd..c3fa28d45 100644 --- a/docs/support_files/api_reference.md +++ b/docs/support_files/api_reference.md @@ -113,6 +113,7 @@ Aliases: `Given`, `When`, `Then`. * `pattern`: A regex or string pattern to match against a gherkin step. * `options`: An object with the following keys: - `timeout`: A step-specific timeout, to override the default timeout. + - `wrapperOptions`: Step-specific options that are passed to the definition function wrapper. * `fn`: A function, which should be defined as follows: - Should have one argument for each capture in the regular expression. - May have an additional argument if the gherkin step has a docstring or data table. @@ -132,6 +133,37 @@ Set the default timeout for asynchronous steps. Defaults to `5000` milliseconds. --- +#### `setDefinitionFunctionWrapper(wrapper)` + +_Note: the usage of `setDefinitionFunctionWrapper` is discouraged in favor of [BeforeStep](#beforestepoptions-fn) and [AfterStep](#afterstepoptions-fn) hooks._ + +Set a function used to wrap step / hook definitions. + +The `wrapper` function is expected to take 2 arguments: + +- `fn` is the original function defined for the step - needs to be called in order for the step to be run. +- `options` is the step specific `wrapperOptions` and may be undefined. + +Example: + +```javascript +setDefinitionFunctionWrapper(function(fn, options) { + return function(...args) { + // call original function with correct `this` and arguments + // ensure return value of function is returned + return fn.apply(this, args) + .catch(error => { + // rethrow error to avoid swallowing failure + throw error; + }); + } +}) +``` + +When used, the result is wrapped again to ensure it has the same length of the original step / hook definition. + +--- + #### `setWorldConstructor(constructor)` Set a custom world constructor, to override the default world constructor: diff --git a/docs/support_files/step_definitions.md b/docs/support_files/step_definitions.md index fba2aed2e..b62e2da76 100644 --- a/docs/support_files/step_definitions.md +++ b/docs/support_files/step_definitions.md @@ -69,6 +69,36 @@ When(/^I view my profile$/, function () { }); ``` + +## Definition function wrapper + +If you would like to wrap step or hook definitions in with some additional logic you can use `setDefinitionFunctionWrapper(fn)`. The definitions will be wrapped after they have all been loaded but before the tests begin to run. One example usage is wrapping generator functions to return promises. Cucumber will do an additional stage of wrapping to ensure the function retains its original length. + +```javascript +// features/step_definitions/file_steps.js +const { Then } = require('@cucumber/cucumber'); +const assert = require('assert'); +const mzFs = require('mz/fs'); + +Then(/^the file named (.*) is empty$/, function *(fileName) { + contents = yield mzFs.readFile(fileName, 'utf8'); + assert.equal(contents, ''); +}); + +// features/support/setup.js +const { setDefinitionFunctionWrapper } = require('@cucumber/cucumber'); +const isGenerator = require('is-generator'); +const Promise = require('bluebird'); + +setDefinitionFunctionWrapper(function (fn) { + if (isGenerator.fn(fn)) { + return Promise.coroutine(fn); + } else { + return fn; + } +}); +``` + ## Pending steps Each interface has its own way of marking a step as pending diff --git a/features/step_wrapper_with_options.feature b/features/step_wrapper_with_options.feature new file mode 100644 index 000000000..63f7428f8 --- /dev/null +++ b/features/step_wrapper_with_options.feature @@ -0,0 +1,35 @@ +Feature: Step Wrapper with Options + In order to be able to write more complex step definition wrappers + As a developer + I want Cucumber to provide the "options" object to the wrapping function + + @spawn + Scenario: options passed to the step definitions wrapper + Given a file named "features/a.feature" with: + """ + Feature: Step with an option + Scenario: Steps + When I run a step with options + """ + And a file named "features/step_definitions/cucumber_steps.js" with: + """ + const {When} = require('@cucumber/cucumber') + + When(/^I run a step with options$/, {wrapperOptions: {retry: 2}}, function () {}) + """ + And a file named "features/support/setup.js" with: + """ + const {setDefinitionFunctionWrapper} = require('@cucumber/cucumber') + + setDefinitionFunctionWrapper(function (fn, options = {}) { + if (options.retry) { + console.log("Max retries: ", options.retry); + } + return fn; + }) + """ + When I run cucumber-js + Then the output contains the text: + """ + Max retries: 2 + """ diff --git a/package-lock.json b/package-lock.json index 1c0fb5151..54968d582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "stacktrace-js": "^2.0.2", "string-argv": "^0.3.1", "tmp": "^0.2.1", + "util-arity": "^1.1.0", "verror": "^1.10.0" }, "bin": { @@ -1443,9 +1444,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } @@ -6935,6 +6936,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-arity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", + "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -8304,9 +8310,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -12497,6 +12503,11 @@ "punycode": "^2.1.0" } }, + "util-arity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", + "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 0d9ed67fa..e99a6be3b 100644 --- a/package.json +++ b/package.json @@ -204,6 +204,7 @@ "stacktrace-js": "^2.0.2", "string-argv": "^0.3.1", "tmp": "^0.2.1", + "util-arity": "^1.1.0", "verror": "^1.10.0" }, "devDependencies": { diff --git a/src/cli/helpers_spec.ts b/src/cli/helpers_spec.ts index 711f54f01..4f4e0e640 100644 --- a/src/cli/helpers_spec.ts +++ b/src/cli/helpers_spec.ts @@ -135,6 +135,7 @@ describe('helpers', () => { stepDefinitions: [ new StepDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '0', line: 9, options: {}, @@ -172,6 +173,7 @@ describe('helpers', () => { stepDefinitions: [ new StepDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '0', line: 9, options: {}, @@ -209,6 +211,7 @@ describe('helpers', () => { beforeTestCaseHookDefinitions: [ new TestCaseHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '0', line: 3, options: { @@ -220,6 +223,7 @@ describe('helpers', () => { afterTestCaseHookDefinitions: [ new TestCaseHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '1', line: 7, options: {}, @@ -227,6 +231,7 @@ describe('helpers', () => { }), new TestCaseHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '2', line: 11, options: {}, @@ -280,6 +285,7 @@ describe('helpers', () => { beforeTestRunHookDefinitions: [ new TestRunHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '0', line: 3, options: {}, @@ -289,6 +295,7 @@ describe('helpers', () => { afterTestRunHookDefinitions: [ new TestRunHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '1', line: 7, options: {}, @@ -296,6 +303,7 @@ describe('helpers', () => { }), new TestRunHookDefinition({ code: noopFunction, + unwrappedCode: noopFunction, id: '2', line: 11, options: {}, diff --git a/src/formatter/helpers/usage_helpers/index.ts b/src/formatter/helpers/usage_helpers/index.ts index a711c83b7..cfd327948 100644 --- a/src/formatter/helpers/usage_helpers/index.ts +++ b/src/formatter/helpers/usage_helpers/index.ts @@ -34,7 +34,7 @@ function buildEmptyMapping( const mapping: Record = {} stepDefinitions.forEach((stepDefinition) => { mapping[stepDefinition.id] = { - code: stepDefinition.code.toString(), + code: stepDefinition.unwrappedCode.toString(), line: stepDefinition.line, pattern: stepDefinition.expression.source, patternType: stepDefinition.expression.constructor.name, diff --git a/src/formatter/helpers/usage_helpers/index_spec.ts b/src/formatter/helpers/usage_helpers/index_spec.ts index d0d096956..081224a2a 100644 --- a/src/formatter/helpers/usage_helpers/index_spec.ts +++ b/src/formatter/helpers/usage_helpers/index_spec.ts @@ -7,28 +7,65 @@ import { buildSupportCodeLibrary } from '../../../../test/runtime_helpers' describe('Usage Helpers', () => { describe('getUsage', () => { describe('with step definitions', () => { - it('includes stringified code', async () => { - // Arrange - const code = function (): string { - return 'original code' - } - const supportCodeLibrary = buildSupportCodeLibrary(({ Given }) => { - Given('a step', code) - }) - const { eventDataCollector } = await getEnvelopesAndEventDataCollector({ - supportCodeLibrary, - }) + describe('without function definition wrapper', () => { + it('includes stringified code', async () => { + // Arrange + const code = function (): string { + return 'original code' + } + const supportCodeLibrary = buildSupportCodeLibrary(({ Given }) => { + Given('a step', code) + }) + const { eventDataCollector } = + await getEnvelopesAndEventDataCollector({ supportCodeLibrary }) - // Act - const output = getUsage({ - cwd: '/project', - eventDataCollector, - stepDefinitions: supportCodeLibrary.stepDefinitions, + // Act + const output = getUsage({ + cwd: '/project', + eventDataCollector, + stepDefinitions: supportCodeLibrary.stepDefinitions, + }) + + // Assert + expect(output).to.have.lengthOf(1) + expect(output[0].code).to.eql(code.toString()) }) + }) - // Assert - expect(output).to.have.lengthOf(1) - expect(output[0].code).to.eql(code.toString()) + describe('with function definition wrapper', () => { + it('includes unwrapped version of stringified code', async () => { + // Arrange + const code = function (): string { + return 'original code' + } + const supportCodeLibrary = buildSupportCodeLibrary( + ({ Given, setDefinitionFunctionWrapper }) => { + Given('a step', code) + setDefinitionFunctionWrapper( + (fn: Function) => + function (fn: Function) { + if (fn.length === 1) { + return fn + } + return fn + } + ) + } + ) + const { eventDataCollector } = + await getEnvelopesAndEventDataCollector({ supportCodeLibrary }) + + // Act + const output = getUsage({ + cwd: '/project', + eventDataCollector, + stepDefinitions: supportCodeLibrary.stepDefinitions, + }) + + // Assert + expect(output).to.have.lengthOf(1) + expect(output[0].code).to.eql(code.toString()) + }) }) }) }) diff --git a/src/index.ts b/src/index.ts index 839bf6f86..3272703ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,6 +38,7 @@ export const defineParameterType = methods.defineParameterType export const defineStep = methods.defineStep export const Given = methods.Given export const setDefaultTimeout = methods.setDefaultTimeout +export const setDefinitionFunctionWrapper = methods.setDefinitionFunctionWrapper export const setWorldConstructor = methods.setWorldConstructor export const Then = methods.Then export const When = methods.When diff --git a/src/models/definition.ts b/src/models/definition.ts index 437bd7cc6..171e89648 100644 --- a/src/models/definition.ts +++ b/src/models/definition.ts @@ -16,6 +16,7 @@ export interface IGetInvocationDataResponse { export interface IDefinitionOptions { timeout?: number + wrapperOptions?: any } export interface IHookDefinitionOptions extends IDefinitionOptions { @@ -27,6 +28,7 @@ export interface IDefinitionParameters { id: string line: number options: T + unwrappedCode?: Function uri: string } @@ -41,6 +43,7 @@ export interface IDefinition { readonly id: string readonly line: number readonly options: IDefinitionOptions + readonly unwrappedCode: Function readonly uri: string getInvocationParameters: ( @@ -53,6 +56,7 @@ export default abstract class Definition { public readonly id: string public readonly line: number public readonly options: IDefinitionOptions + public readonly unwrappedCode: Function public readonly uri: string constructor({ @@ -60,12 +64,14 @@ export default abstract class Definition { id, line, options, + unwrappedCode, uri, }: IDefinitionParameters) { this.code = code this.id = id this.line = line this.options = options + this.unwrappedCode = unwrappedCode this.uri = uri } diff --git a/src/support_code_library_builder/index.ts b/src/support_code_library_builder/index.ts index 4a7f04d55..b03eb6332 100644 --- a/src/support_code_library_builder/index.ts +++ b/src/support_code_library_builder/index.ts @@ -7,6 +7,8 @@ import TestRunHookDefinition from '../models/test_run_hook_definition' import StepDefinition from '../models/step_definition' import { formatLocation } from '../formatter/helpers' import validateArguments from './validate_arguments' +import arity from 'util-arity' + import { CucumberExpression, ParameterTypeRegistry, @@ -70,6 +72,7 @@ export class SupportCodeLibraryBuilder { private beforeTestStepHookDefinitionConfigs: ITestStepHookDefinitionConfig[] private cwd: string private defaultTimeout: number + private definitionFunctionWrapper: any private newId: IdGenerator.NewId private parameterTypeRegistry: ParameterTypeRegistry private stepDefinitionConfigs: IStepDefinitionConfig[] @@ -102,6 +105,9 @@ export class SupportCodeLibraryBuilder { setDefaultTimeout: (milliseconds) => { this.defaultTimeout = milliseconds }, + setDefinitionFunctionWrapper: (fn) => { + this.definitionFunctionWrapper = fn + }, setWorldConstructor: (fn) => { this.World = fn }, @@ -236,16 +242,39 @@ export class SupportCodeLibraryBuilder { } } + wrapCode({ + code, + wrapperOptions, + }: { + code: Function + wrapperOptions: any + }): Function { + if (doesHaveValue(this.definitionFunctionWrapper)) { + const codeLength = code.length + const wrappedCode = this.definitionFunctionWrapper(code, wrapperOptions) + if (wrappedCode !== code) { + return arity(codeLength, wrappedCode) + } + return wrappedCode + } + return code + } + buildTestCaseHookDefinitions( configs: ITestCaseHookDefinitionConfig[], canonicalIds?: string[] ): TestCaseHookDefinition[] { return configs.map(({ code, line, options, uri }, index) => { - return new TestCaseHookDefinition({ + const wrappedCode = this.wrapCode({ code, + wrapperOptions: options.wrapperOptions, + }) + return new TestCaseHookDefinition({ + code: wrappedCode, id: canonicalIds ? canonicalIds[index] : this.newId(), line, options, + unwrappedCode: code, uri, }) }) @@ -255,11 +284,16 @@ export class SupportCodeLibraryBuilder { configs: ITestStepHookDefinitionConfig[] ): TestStepHookDefinition[] { return configs.map(({ code, line, options, uri }) => { - return new TestStepHookDefinition({ + const wrappedCode = this.wrapCode({ code, + wrapperOptions: options.wrapperOptions, + }) + return new TestStepHookDefinition({ + code: wrappedCode, id: this.newId(), line, options, + unwrappedCode: code, uri, }) }) @@ -269,11 +303,16 @@ export class SupportCodeLibraryBuilder { configs: ITestRunHookDefinitionConfig[] ): TestRunHookDefinition[] { return configs.map(({ code, line, options, uri }) => { - return new TestRunHookDefinition({ + const wrappedCode = this.wrapCode({ code, + wrapperOptions: options.wrapperOptions, + }) + return new TestRunHookDefinition({ + code: wrappedCode, id: this.newId(), line, options, + unwrappedCode: code, uri, }) }) @@ -311,14 +350,19 @@ export class SupportCodeLibraryBuilder { ) } + const wrappedCode = this.wrapCode({ + code, + wrapperOptions: options.wrapperOptions, + }) stepDefinitions.push( new StepDefinition({ - code, + code: wrappedCode, expression, id: canonicalIds ? canonicalIds[index] : this.newId(), line, options, pattern, + unwrappedCode: code, uri, }) ) @@ -369,6 +413,7 @@ export class SupportCodeLibraryBuilder { this.beforeTestCaseHookDefinitionConfigs = [] this.beforeTestRunHookDefinitionConfigs = [] this.beforeTestStepHookDefinitionConfigs = [] + this.definitionFunctionWrapper = null this.defaultTimeout = 5000 this.parameterTypeRegistry = new ParameterTypeRegistry() this.stepDefinitionConfigs = [] diff --git a/src/support_code_library_builder/index_spec.ts b/src/support_code_library_builder/index_spec.ts index f0f806fa1..6485741a5 100644 --- a/src/support_code_library_builder/index_spec.ts +++ b/src/support_code_library_builder/index_spec.ts @@ -38,40 +38,66 @@ describe('supportCodeLibraryBuilder', () => { }) describe('step', () => { - it('adds a step definition and makes original code available', function () { - // Arrange - const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function - supportCodeLibraryBuilder.reset('path/to/project', uuid()) - supportCodeLibraryBuilder.methods.defineStep('I do a thing', step) + describe('without definition function wrapper', () => { + it('adds a step definition and makes original code available', function () { + // Arrange + const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function + supportCodeLibraryBuilder.reset('path/to/project', uuid()) + supportCodeLibraryBuilder.methods.defineStep('I do a thing', step) - // Act - const options = supportCodeLibraryBuilder.finalize() + // Act + const options = supportCodeLibraryBuilder.finalize() - // Assert - expect(options.stepDefinitions).to.have.lengthOf(1) - const stepDefinition = options.stepDefinitions[0] - expect(stepDefinition.code).to.eql(step) - }) + // Assert + expect(options.stepDefinitions).to.have.lengthOf(1) + const stepDefinition = options.stepDefinitions[0] + expect(stepDefinition.code).to.eql(step) + expect(stepDefinition.unwrappedCode).to.eql(step) + }) - it('uses the canonical ids provided in order', function () { - // Arrange - const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function - supportCodeLibraryBuilder.reset('path/to/project', uuid()) - supportCodeLibraryBuilder.methods.defineStep('I do a thing', step) - supportCodeLibraryBuilder.methods.defineStep('I do another thing', step) + it('uses the canonical ids provided in order', function () { + // Arrange + const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function + supportCodeLibraryBuilder.reset('path/to/project', uuid()) + supportCodeLibraryBuilder.methods.defineStep('I do a thing', step) + supportCodeLibraryBuilder.methods.defineStep('I do another thing', step) - // Act - const options = supportCodeLibraryBuilder.finalize({ - stepDefinitionIds: ['one', 'two'], - beforeTestCaseHookDefinitionIds: [], - afterTestCaseHookDefinitionIds: [], + // Act + const options = supportCodeLibraryBuilder.finalize({ + stepDefinitionIds: ['one', 'two'], + beforeTestCaseHookDefinitionIds: [], + afterTestCaseHookDefinitionIds: [], + }) + + // Assert + expect(options.stepDefinitions).to.have.lengthOf(2) + expect( + options.stepDefinitions.map((stepDefinition) => stepDefinition.id) + ).to.deep.eq(['one', 'two']) }) + }) - // Assert - expect(options.stepDefinitions).to.have.lengthOf(2) - expect( - options.stepDefinitions.map((stepDefinition) => stepDefinition.id) - ).to.deep.eq(['one', 'two']) + describe('with definition function wrapper', () => { + it('adds a step definition and makes original code available', function () { + // Arrange + const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function + supportCodeLibraryBuilder.reset('path/to/project', uuid()) + supportCodeLibraryBuilder.methods.defineStep('I do a thing', step) + supportCodeLibraryBuilder.methods.setDefinitionFunctionWrapper( + function (fn: Function) { + return fn() + } + ) + + // Act + const options = supportCodeLibraryBuilder.finalize() + + // Assert + expect(options.stepDefinitions).to.have.lengthOf(1) + const stepDefinition = options.stepDefinitions[0] + expect(stepDefinition.code).not.to.eql(step) + expect(stepDefinition.unwrappedCode).to.eql(step) + }) }) }) diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index 4738353e6..9f24f0ccb 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -41,6 +41,7 @@ export type TestStepFunction = ( export interface IDefineStepOptions { timeout?: number + wrapperOptions?: any } export interface IDefineTestCaseHookOptions { @@ -77,6 +78,7 @@ export interface IDefineSupportCodeMethods { code: TestStepFunction ) => void) setDefaultTimeout: (milliseconds: number) => void + setDefinitionFunctionWrapper: (fn: Function) => void setWorldConstructor: (fn: any) => void After: ((code: TestCaseHookFunction) => void) & ((