Skip to content

Commit f568877

Browse files
Revert "remove support for generators (#1725)"
This reverts commit a2dcce6.
1 parent 06be59a commit f568877

19 files changed

+400
-73
lines changed

.mocharc.yml

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ colors: true
22
file:
33
- test/test_helper.ts
44
full-trace: true
5-
forbid-only: true
6-
forbid-pending: true
75
recursive: true
86
reporter: dot
97
require: 'ts-node/register'

CHANGELOG.md

+2-5
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
99
----
1010
## [Unreleased] (In Git)
1111

12-
See the [migration guide](./docs/migration.md) for details of how to migrate from 7.x.x to 8.x.x
13-
1412
### Breaking changes
1513

1614
* Drop support for Node.js 10 and 15, add support for Node.js 16
17-
* Remove deprecated `--retryTagFilter` option (the correct option is `--retry-tag-filter`)
18-
* Remove `setDefinitionFunctionWrapper` and step definition option `wrapperOptions`
15+
* Remove deprecated `--retryTagFilter` option (the correct option is `--retry-tag-filter`)
1916

2017
### Added
2118

@@ -36,7 +33,7 @@ See the [migration guide](./docs/migration.md) for details of how to migrate fro
3633
### Fixed
3734

3835
* Prevent duplicate scenario execution where the same feature is targeted in multiple line expressions ([#1706](https://github.com/cucumber/cucumber-js/issues/1706))
39-
* Fixed reports banner to point to [new docs](https://cucumber.io/docs/cucumber/environment-variables/) about environment variables
36+
* Fixed reports banner to point to [new docs](https://cucumber.io/docs/cucumber/environment-variables/) about environment variables
4037
* Re-add color functions for use with custom formatters [1582](https://github.com/cucumber/cucumber-js/issues/1582)
4138
* IParameterTypeDefinition regexp fix [1702](https://github.com/cucumber/cucumber-js/issues/1702)
4239

dependency-lint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ignoreErrors:
2020
- '@typescript-eslint/eslint-plugin' # peer dependency of standard-with-typescript
2121
- '@typescript-eslint/parser' # peer dependency of @typescript-eslint/eslint-plugin
2222
- '@types/*' # type definitions
23+
- bluebird # features/generator_step_definitions.feature
2324
- coffeescript # features/compiler.feature
2425
- eslint-config-prettier # .eslintrc.yml - extends - prettier
2526
- eslint-config-standard-with-typescript # .eslintrc.yml - extends - standard-with-typescript

docs/migration.md

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1-
# Migrating from 7.x.x to 8.x.x
2-
3-
## Removal of setDefinitionFunctionWrapper
4-
5-
If this was used to wrap generator functions, please transition to using async / await.
6-
If this was used to wrap step definitions, please use `BeforeStep` / `AfterStep` hooks instead.
7-
If you had other use cases, please create an issue.
8-
9-
# Migrating from 6.x.x to 7.x.x
1+
# Migrating to cucumber-js 7.x.x
102

113
## Package Name
124

135
cucumber-js is now published at `@cucumber/cucumber` instead of `cucumber`. To upgrade, you'll need to remove the old package and add the new one:
14-
6+
157
```shell
168
$ npm rm cucumber
179
$ npm install --save-dev @cucumber/cucumber
18-
```
19-
10+
```
11+
2012
You'll need to update any `import`/`require` statements in your support code to use the new package name.
2113

2214
(The executable is still `cucumber-js` though.)

docs/support_files/api_reference.md

+32
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Aliases: `Given`, `When`, `Then`.
112112
* `pattern`: A regex or string pattern to match against a gherkin step.
113113
* `options`: An object with the following keys:
114114
- `timeout`: A step-specific timeout, to override the default timeout.
115+
- `wrapperOptions`: Step-specific options that are passed to the definition function wrapper.
115116
* `fn`: A function, which should be defined as follows:
116117
- Should have one argument for each capture in the regular expression.
117118
- May have an additional argument if the gherkin step has a docstring or data table.
@@ -131,6 +132,37 @@ Set the default timeout for asynchronous steps. Defaults to `5000` milliseconds.
131132

132133
---
133134

135+
#### `setDefinitionFunctionWrapper(wrapper)`
136+
137+
Set a function used to wrap step / hook definitions.
138+
139+
The `wrapper` function is expected to take 2 arguments:
140+
141+
- `fn` is the original function defined for the step - needs to be called in order for the step to be run.
142+
- `options` is the step specific `wrapperOptions` and may be undefined.
143+
144+
A common use case is attaching a screenshot on step failure - this would typically look something like (for a promise-based setup):
145+
146+
```javascript
147+
setDefinitionFunctionWrapper(function(fn, options) {
148+
return function(...args) {
149+
// call original function with correct `this` and arguments
150+
// ensure return value of function is returned
151+
return fn.apply(this, args)
152+
.catch(error => {
153+
// call a method on world
154+
this.doScreenshot();
155+
// rethrow error to avoid swallowing failure
156+
throw error;
157+
});
158+
}
159+
})
160+
```
161+
162+
When used, the result is wrapped again to ensure it has the same length of the original step / hook definition.
163+
164+
---
165+
134166
#### `setWorldConstructor(constructor)`
135167

136168
Set a custom world constructor, to override the default world constructor:

docs/support_files/step_definitions.md

+30
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,36 @@ When(/^I view my profile$/, function () {
6969
});
7070
```
7171

72+
73+
## Definition function wrapper
74+
75+
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.
76+
77+
```javascript
78+
// features/step_definitions/file_steps.js
79+
const { Then } = require('@cucumber/cucumber');
80+
const assert = require('assert');
81+
const mzFs = require('mz/fs');
82+
83+
Then(/^the file named (.*) is empty$/, function *(fileName) {
84+
contents = yield mzFs.readFile(fileName, 'utf8');
85+
assert.equal(contents, '');
86+
});
87+
88+
// features/support/setup.js
89+
const { setDefinitionFunctionWrapper } = require('@cucumber/cucumber');
90+
const isGenerator = require('is-generator');
91+
const Promise = require('bluebird');
92+
93+
setDefinitionFunctionWrapper(function (fn) {
94+
if (isGenerator.fn(fn)) {
95+
return Promise.coroutine(fn);
96+
} else {
97+
return fn;
98+
}
99+
});
100+
```
101+
72102
## Pending steps
73103

74104
Each interface has its own way of marking a step as pending
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Feature: Generator Step Definitions
2+
In order to use new JavaScript features
3+
As a developer
4+
I want Cucumber to provide the possibility to use ES6 features
5+
6+
Background:
7+
Given a file named "features/a.feature" with:
8+
"""
9+
Feature: Step is a generator
10+
Scenario: Step generator run successfully
11+
When I call a step which is a generator with return value "ok"
12+
Then I can see the yielded "ok" value in the context
13+
"""
14+
And a file named "features/step_definitions/cucumber_steps.js" with:
15+
"""
16+
const assert = require('assert')
17+
const {setWorldConstructor, Then, When} = require('@cucumber/cucumber')
18+
19+
setWorldConstructor(function () {
20+
this.context = ""
21+
})
22+
23+
When(/^I call a step which is a generator with return value "([^"]*)"$/, function *(return_value) {
24+
this.context = yield Promise.resolve(return_value);
25+
})
26+
27+
Then(/^I can see the yielded "([^"]*)" value in the context$/, function(return_value) {
28+
assert.equal(this.context, return_value)
29+
})
30+
"""
31+
32+
@spawn
33+
Scenario: without generator function runner
34+
When I run cucumber-js
35+
Then it fails
36+
And the error output contains the text:
37+
"""
38+
The following hook/step definitions use generator functions:
39+
40+
features/step_definitions/cucumber_steps.js:8
41+
42+
Use 'this.setDefinitionFunctionWrapper(fn)' to wrap them in a function that returns a promise
43+
"""
44+
45+
Scenario: with generator function wrapper
46+
Given a file named "features/support/setup.js" with:
47+
"""
48+
const isGenerator = require('is-generator')
49+
const {coroutine} = require('bluebird')
50+
const {setDefinitionFunctionWrapper} = require('@cucumber/cucumber')
51+
52+
setDefinitionFunctionWrapper(function (fn) {
53+
if (isGenerator.fn(fn)) {
54+
return coroutine(fn)
55+
} else {
56+
return fn
57+
}
58+
})
59+
"""
60+
When I run cucumber-js
61+
Then it passes
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Feature: Step Wrapper with Options
2+
In order to be able to write more complex step definition wrappers
3+
As a developer
4+
I want Cucumber to provide the "options" object to the wrapping function
5+
6+
@spawn
7+
Scenario: options passed to the step definitions wrapper
8+
Given a file named "features/a.feature" with:
9+
"""
10+
Feature: Step with an option
11+
Scenario: Steps
12+
When I run a step with options
13+
"""
14+
And a file named "features/step_definitions/cucumber_steps.js" with:
15+
"""
16+
const {When} = require('@cucumber/cucumber')
17+
18+
When(/^I run a step with options$/, {wrapperOptions: {retry: 2}}, function () {})
19+
"""
20+
And a file named "features/support/setup.js" with:
21+
"""
22+
const {setDefinitionFunctionWrapper} = require('@cucumber/cucumber')
23+
24+
setDefinitionFunctionWrapper(function (fn, options = {}) {
25+
if (options.retry) {
26+
console.log("Max retries: ", options.retry);
27+
}
28+
return fn;
29+
})
30+
"""
31+
When I run cucumber-js
32+
Then the output contains the text:
33+
"""
34+
Max retries: 2
35+
"""

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
"@cucumber/messages": "^16.0.1",
177177
"@cucumber/tag-expressions": "^3.0.1",
178178
"assertion-error-formatter": "^3.0.0",
179+
"bluebird": "^3.7.2",
179180
"capital-case": "^1.0.4",
180181
"cli-table3": "^0.6.0",
181182
"colors": "^1.4.0",
@@ -186,6 +187,7 @@
186187
"figures": "^3.2.0",
187188
"glob": "^7.1.6",
188189
"indent-string": "^4.0.0",
190+
"is-generator": "^1.0.3",
189191
"is-stream": "^2.0.0",
190192
"knuth-shuffle-seeded": "^1.0.6",
191193
"mz": "^2.7.0",
@@ -196,13 +198,15 @@
196198
"stacktrace-js": "^2.0.2",
197199
"string-argv": "^0.3.1",
198200
"tmp": "^0.2.1",
201+
"util-arity": "^1.1.0",
199202
"verror": "^1.10.0"
200203
},
201204
"devDependencies": {
202205
"@cucumber/compatibility-kit": "7.0.0",
203206
"@cucumber/message-streams": "2.1.0",
204207
"@cucumber/query": "10.1.0",
205208
"@sinonjs/fake-timers": "7.1.2",
209+
"@types/bluebird": "3.5.35",
206210
"@types/chai": "4.2.21",
207211
"@types/dirty-chai": "2.0.2",
208212
"@types/express": "4.17.13",

src/cli/helpers_spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ describe('helpers', () => {
135135
stepDefinitions: [
136136
new StepDefinition({
137137
code: noopFunction,
138+
unwrappedCode: noopFunction,
138139
id: '0',
139140
line: 9,
140141
options: {},
@@ -172,6 +173,7 @@ describe('helpers', () => {
172173
stepDefinitions: [
173174
new StepDefinition({
174175
code: noopFunction,
176+
unwrappedCode: noopFunction,
175177
id: '0',
176178
line: 9,
177179
options: {},
@@ -209,6 +211,7 @@ describe('helpers', () => {
209211
beforeTestCaseHookDefinitions: [
210212
new TestCaseHookDefinition({
211213
code: noopFunction,
214+
unwrappedCode: noopFunction,
212215
id: '0',
213216
line: 3,
214217
options: {
@@ -220,13 +223,15 @@ describe('helpers', () => {
220223
afterTestCaseHookDefinitions: [
221224
new TestCaseHookDefinition({
222225
code: noopFunction,
226+
unwrappedCode: noopFunction,
223227
id: '1',
224228
line: 7,
225229
options: {},
226230
uri: 'features/support/hooks.js',
227231
}),
228232
new TestCaseHookDefinition({
229233
code: noopFunction,
234+
unwrappedCode: noopFunction,
230235
id: '2',
231236
line: 11,
232237
options: {},
@@ -280,6 +285,7 @@ describe('helpers', () => {
280285
beforeTestRunHookDefinitions: [
281286
new TestRunHookDefinition({
282287
code: noopFunction,
288+
unwrappedCode: noopFunction,
283289
id: '0',
284290
line: 3,
285291
options: {},
@@ -289,13 +295,15 @@ describe('helpers', () => {
289295
afterTestRunHookDefinitions: [
290296
new TestRunHookDefinition({
291297
code: noopFunction,
298+
unwrappedCode: noopFunction,
292299
id: '1',
293300
line: 7,
294301
options: {},
295302
uri: 'features/support/run-hooks.js',
296303
}),
297304
new TestRunHookDefinition({
298305
code: noopFunction,
306+
unwrappedCode: noopFunction,
299307
id: '2',
300308
line: 11,
301309
options: {},

src/formatter/helpers/usage_helpers/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function buildEmptyMapping(
3434
const mapping: Record<string, IUsage> = {}
3535
stepDefinitions.forEach((stepDefinition) => {
3636
mapping[stepDefinition.id] = {
37-
code: stepDefinition.code.toString(),
37+
code: stepDefinition.unwrappedCode.toString(),
3838
line: stepDefinition.line,
3939
pattern: stepDefinition.expression.source,
4040
patternType: stepDefinition.expression.constructor.name,

0 commit comments

Comments
 (0)