Skip to content

Commit fbff6b0

Browse files
implement event protocol (#879)
1 parent 66b61e5 commit fbff6b0

File tree

108 files changed

+3510
-4514
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+3510
-4514
lines changed

CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
### [master (unreleased)](https://github.com/cucumber/cucumber-js/compare/v2.3.1...master)
22

33
#### BREAKING CHANGES
4-
* `pretty` formatter has been removed. All errors are now reported in a `pretty` format instead. Use the `progress-bar` or `progress` formats instead.
4+
* `pretty` formatter has been removed. All errors are now reported in a `pretty` format instead. The `progress` formatter is now the default.
5+
* Major changes to custom formatters API due to rewrite in support of event protocol. Please see the updated documentation.
6+
* Remove `registerHandler` and `registerListener`. Use `BeforeAll` / `AfterAll` for setup code. Use the event protocol formatter if used for reporting. Please open an issue if you have another use case.
57

68
#### New Features
79

810
* Add `--i18n-languages` and `--i18n-keywords <ISO 639-1>` CLI options
9-
* Add `BeforeAll` / `AfterAll` hooks to replace uses of `registerHandler`
11+
* Add `BeforeAll` / `AfterAll` hooks for suite level setup / teardown
12+
* Add event protocol formatter
1013

1114
#### Bug Fixes
1215

CONTRIBUTING.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,12 @@ The browser example is only updated when releasing a new version.
4040
4141
├── models # data structures
4242
43-
├── runtime # run features / scenarios / steps, trigger events
43+
├── runtime # run test cases, emits the event protocol
4444
4545
└── support_code_library # load hooks / step definitions
4646
```
4747

48-
The runtime triggers events similar to an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
49-
but waits for the listener to finish (the same style used in hook and step definitions).
48+
The runtime emits events with an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
5049

5150
### Coding style
5251

docs/cli.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ The executable is also aliased as `cucumber-js` and `cucumberjs`.
1212
The latter is causing the operating system to invoke JScript instead of Node.js,
1313
because of the file extension.
1414

15-
**Note on global installs:** Cucumber does not work when installed globally because cucumber
16-
needs to be required in your support files and globally installed modules cannot be required.
15+
**Note on global installs:** Cucumber does not work when installed globally because cucumber
16+
needs to be required in your support files and globally installed modules cannot be required.
1717

1818
## Running specific features
1919

@@ -47,12 +47,12 @@ This option may be used multiple times in order to output different formats to d
4747
If multiple formats are specified with the same output, only the last is used.
4848

4949
Built-in formatters
50+
* event-protocol - prints the event protocl. See [current docs](https://docs.cucumber.io/event-protocol/) and the [proposed updates](https://github.com/cucumber/cucumber/pull/172) which were implemented.
5051
* json - prints the feature as JSON
51-
* pretty - prints the feature as is (default)
52-
* progress - prints one character per scenario
52+
* progress - prints one character per scenario (default)
5353
* progress-bar - prints a progress bar and outputs errors/warnings along the way
5454
* rerun - prints the paths of any non passing scenarios ([example](/features/rerun_formatter.feature))
55-
* suggested use: add the rerun formatter to your default profile and the output file to your `.gitignore`.
55+
* suggested use: add the rerun formatter to your default profile and the output file to your `.gitignore`.
5656
* After a failed run, remove any arguments used for selecting feature files and add the rerun file in order to rerun just failed scenarios. The rerun file must start with `@` sign in order for cucumber to parse it as a rerun file instead of a feature file.
5757
* Use with `--fail-fast` to rerun the failure and the remaining features.
5858
* snippets - prints just the code snippets for undefined steps

docs/custom_formatters.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# Custom Formatters
22

3-
Custom formatters should be a javascript class. The constructor will be an options object which is the options defined with `--format-options` and the following:
3+
Custom formatters should be a javascript class. The constructor will be an options object which is the options defined with `--format-options` and the following:
44

55
* `colorFns`: a series of helper functions for outputting colors. See [here](/src/formatter/get_color_fns.js). Respects `colorsEnabled` option
66
* `cwd`: the current working directory
7+
* `eventBroadcaster`: an event emitter that emits the event protocol (which is still being defined). See [current docs](https://docs.cucumber.io/event-protocol/) and the [proposed updates](https://github.com/cucumber/cucumber/pull/172) which were implemented.
8+
* `eventDataCollector`: an instance of [EventDataCollector](/src/formatter/helpers/event_data_collector.js) which handles the complexity of grouping the data for related events
79
* `log`: function which will write the passed string to the the designated stream (stdout or the to file the formatter output is being redirected to).
8-
* `snippetBuilder`: an object with a `build` method that should be called with a [step](/src/models/step.js) to get the snippet for an undefined step
10+
* `snippetBuilder`: an object with a `build` method that should be called with `{keywordType, pickleStep}`. The `pickleStep` can be retrieved with the `eventDataCollector` while the `keywordType` is complex to compute (see the [SnippetsFormatter](/src/formatter/snippets_formatter.js) for an example).
911
* `stream`: the underlying stream the formatter is writing to. `log` is a shortcut for writing to it.
1012

11-
Custom formatters should then define instance methods in order to output something during a particular event. For example it should define a `handleBeforeFeatures` function in order to output something as cucumber is starting.
13+
The constructor of custom formatters should add listeners to the `eventBroadcaster`.
1214

1315
See a couple examples [here](/features/custom_formatter.feature) and the built in formatters [here](/src/formatter)
1416

docs/support_files/api_reference.md

-11
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,6 @@ Alias of `defineStep`.
112112

113113
---
114114

115-
#### `registerHandler(event[, options], fn)`
116-
117-
* `event`: One of the supported event names [listed here](./event_handlers.md).
118-
* `options`: An object with the following keys:
119-
- `timeout`: A step-specific timeout, to override the default timeout.
120-
* `fn`: A function, defined as follows:
121-
- The first argument is the object as defined [here](./event_handlers.md).
122-
- When using the asynchronous callback interface, have one final argument for the callback function.
123-
124-
---
125-
126115
#### `setDefaultTimeout(milliseconds)`
127116

128117
Set the default timeout for asynchronous steps. Defaults to `5000` milliseconds.

docs/support_files/event_handlers.md

-51
This file was deleted.

features/attachments.feature

+9-9
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Feature: Attachments
2929
"""
3030
When I run cucumber.js
3131
Then the "Before" hook has the attachment
32-
| DATA | MIME TYPE |
33-
| iVBORw== | image/png |
32+
| DATA | MEDIA ENCODING | MEDIA TYPE |
33+
| iVBORw== | base64 | image/png |
3434

3535
Scenario: Attach a stream (callback)
3636
Given a file named "features/support/hooks.js" with:
@@ -50,8 +50,8 @@ Feature: Attachments
5050
"""
5151
When I run cucumber.js
5252
Then the "Before" hook has the attachment
53-
| DATA | MIME TYPE |
54-
| iVBORw== | image/png |
53+
| DATA | MEDIA ENCODING | MEDIA TYPE |
54+
| iVBORw== | base64 | image/png |
5555

5656
Scenario: Attach a stream (promise)
5757
Given a file named "features/support/hooks.js" with:
@@ -72,8 +72,8 @@ Feature: Attachments
7272
"""
7373
When I run cucumber.js
7474
Then the "Before" hook has the attachment
75-
| DATA | MIME TYPE |
76-
| iVBORw== | image/png |
75+
| DATA | MEDIA ENCODING | MEDIA TYPE |
76+
| iVBORw== | base64 | image/png |
7777

7878
Scenario: Attach from a before hook
7979
Given a file named "features/support/hooks.js" with:
@@ -88,7 +88,7 @@ Feature: Attachments
8888
"""
8989
When I run cucumber.js
9090
Then the "Before" hook has the attachment
91-
| DATA | MIME TYPE |
91+
| DATA | MEDIA TYPE |
9292
| text | text/plain |
9393

9494
Scenario: Attach from an after hook
@@ -104,7 +104,7 @@ Feature: Attachments
104104
"""
105105
When I run cucumber.js
106106
Then the "After" hook has the attachment
107-
| DATA | MIME TYPE |
107+
| DATA | MEDIA TYPE |
108108
| text | text/plain |
109109

110110
Scenario: Attach from a step definition
@@ -120,5 +120,5 @@ Feature: Attachments
120120
"""
121121
When I run cucumber.js
122122
Then the step "a step" has the attachment
123-
| DATA | MIME TYPE |
123+
| DATA | MEDIA TYPE |
124124
| text | text/plain |

features/custom_formatter.feature

+41-14
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,35 @@ Feature: custom formatter
1414
import {Formatter} from 'cucumber'
1515
1616
class SimpleFormatter extends Formatter {
17-
handleBeforeScenario(scenario) {
18-
this.log(scenario.feature.name + ' / ' + scenario.name + '\n');
17+
constructor(options) {
18+
super(options)
19+
options.eventBroadcaster
20+
.on('test-case-started', ::this.logTestCaseName)
21+
.on('test-step-finished', ::this.logTestStep)
22+
.on('test-case-finished', ::this.logSeparator)
23+
.on('test-run-finished', ::this.logTestRunResult)
1924
}
2025
21-
handleStepResult(stepResult) {
22-
const {status, step} = stepResult;
23-
this.log(' ' + step.keyword + step.name + ' - ' + status + '\n');
26+
logTestCaseName({sourceLocation}) {
27+
const {gherkinDocument, pickle} = this.eventDataCollector.getTestCaseData(sourceLocation)
28+
this.log(gherkinDocument.feature.name + ' / ' + pickle.name + '\n');
2429
}
2530
26-
handleAfterScenario() {
31+
logTestStep({testCase, index, result}) {
32+
const {gherkinKeyword, pickleStep, testStep} = this.eventDataCollector.getTestStepData({testCase, index})
33+
if (pickleStep) {
34+
this.log(' ' + gherkinKeyword + pickleStep.text + ' - ' + result.status + '\n');
35+
} else {
36+
this.log(' Hook - ' + result.status + '\n');
37+
}
38+
}
39+
40+
logSeparator() {
2741
this.log('\n');
2842
}
2943
30-
handleFeaturesResult(featuresResult) {
31-
this.log(featuresResult.success ? 'SUCCESS' : 'FAILURE');
44+
logTestRunResult({result}) {
45+
this.log(result.success ? 'SUCCESS' : 'FAILURE');
3246
}
3347
}
3448
@@ -56,16 +70,29 @@ Feature: custom formatter
5670
import {SummaryFormatter} from 'cucumber'
5771
5872
class SimpleFormatter extends SummaryFormatter {
59-
handleBeforeScenario(scenario) {
60-
this.log(scenario.feature.name + ' / ' + scenario.name + '\n');
73+
constructor(options) {
74+
super(options)
75+
options.eventBroadcaster
76+
.on('test-case-started', ::this.logTestCaseName)
77+
.on('test-step-finished', ::this.logTestStep)
78+
.on('test-case-finished', ::this.logSeparator)
79+
}
80+
81+
logTestCaseName({sourceLocation}) {
82+
const {gherkinDocument, pickle} = this.eventDataCollector.getTestCaseData(sourceLocation)
83+
this.log(gherkinDocument.feature.name + ' / ' + pickle.name + '\n');
6184
}
6285
63-
handleStepResult(stepResult) {
64-
const {status, step} = stepResult;
65-
this.log(' ' + step.keyword + step.name + ' - ' + status + '\n');
86+
logTestStep({testCase, index, result}) {
87+
const {gherkinKeyword, pickleStep, testStep} = this.eventDataCollector.getTestStepData({testCase, index})
88+
if (pickleStep) {
89+
this.log(' ' + gherkinKeyword + pickleStep.text + ' - ' + result.status + '\n');
90+
} else {
91+
this.log(' Hook - ' + result.status + '\n');
92+
}
6693
}
6794
68-
handleAfterScenario() {
95+
logSeparator() {
6996
this.log('\n');
7097
}
7198
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Feature: Event Protocol Formatter
2+
3+
Scenario: gherkin error
4+
Given a file named "features/a.feature" with:
5+
"""
6+
Feature: a feature
7+
Scenario: a scenario
8+
Given a step
9+
Examples:
10+
| a | b |
11+
"""
12+
When I run cucumber.js with `--tags @a -f event-protocol`
13+
Then the output matches the fixture "event_protocol_formatter/gherkin-error.ndjson"
14+
And it fails
15+
16+
Scenario: rejected pickle
17+
Given a file named "features/a.feature" with:
18+
"""
19+
Feature: a feature
20+
Scenario: a scenario
21+
Given a step
22+
"""
23+
When I run cucumber.js with `--tags @a -f event-protocol`
24+
Then the output matches the fixture "event_protocol_formatter/rejected-pickle.ndjson"
25+
26+
Scenario: passed
27+
Given a file named "features/a.feature" with:
28+
"""
29+
Feature: a feature
30+
Scenario: a scenario
31+
Given a step
32+
"""
33+
Given a file named "features/step_definitions/steps.js" with:
34+
"""
35+
import {defineSupportCode} from 'cucumber'
36+
37+
defineSupportCode(({Given}) => {
38+
Given(/^a step$/, function() {})
39+
})
40+
"""
41+
When I run cucumber.js with `-f event-protocol`
42+
Then the output matches the fixture "event_protocol_formatter/passed.ndjson"
43+
44+
Scenario: failed
45+
Given a file named "features/a.feature" with:
46+
"""
47+
Feature: a feature
48+
Scenario: a scenario
49+
Given a step
50+
"""
51+
Given a file named "features/step_definitions/steps.js" with:
52+
"""
53+
import {defineSupportCode} from 'cucumber'
54+
55+
defineSupportCode(({Given}) => {
56+
Given(/^a step$/, function(callback) { callback(new Error('my error')) })
57+
})
58+
"""
59+
When I run cucumber.js with `-f event-protocol`
60+
Then the output matches the fixture "event_protocol_formatter/failed.ndjson"
61+
And it fails

features/fail_fast.feature

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ Feature: Fail fast
2222
})
2323
"""
2424
When I run cucumber.js with `--fail-fast`
25-
Then it runs the scenario "Failing"
25+
Then the step "a passing step" has status "skipped"
2626
And it fails
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{"type":"source","uri":"features/a.feature","data":"Feature: a feature\n Scenario: a scenario\n Given a step","media":{"encoding":"utf-8","type":"text/vnd.cucumber.gherkin+plain"}}
2+
{"type":"gherkin-document","uri":"features/a.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","children":[{"type":"Scenario","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","steps":[{"type":"Step","location":{"line":3,"column":5},"keyword":"Given ","text":"a step"}]}]},"comments":[]}}
3+
{"type":"pickle","uri":"features/a.feature","pickle":{"tags":[],"name":"a scenario","language":"en","locations":[{"line":2,"column":3}],"steps":[{"text":"a step","arguments":[],"locations":[{"line":3,"column":11}]}]}}
4+
{"type":"pickle-accepted","pickle":{"tags":[],"name":"a scenario","language":"en","locations":[{"line":2,"column":3}],"steps":[{"text":"a step","arguments":[],"locations":[{"line":3,"column":11}]}]},"uri":"features/a.feature"}
5+
{"type":"test-run-started"}
6+
{"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/a.feature","line":3},"actionLocation":{"uri":"features/step_definitions/steps.js","line":4}}],"sourceLocation":{"uri":"features/a.feature","line":2}}
7+
{"type":"test-case-started","sourceLocation":{"uri":"features/a.feature","line":2}}
8+
{"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/a.feature","line":2}}}
9+
{"type":"test-step-finished","index":0,"result":{"duration":0,"exception":"Error: my error\n at World.<anonymous> (features/step_definitions/steps.js:4:51)","status":"failed"},"testCase":{"sourceLocation":{"uri":"features/a.feature","line":2}}}
10+
{"type":"test-case-finished","result":{"duration":0,"status":"failed"},"sourceLocation":{"uri":"features/a.feature","line":2}}
11+
{"type":"test-run-finished","result":{"duration":0,"success":false}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"type":"source","uri":"features/a.feature","data":"Feature: a feature\n Scenario: a scenario\n Given a step\n Examples:\n | a | b |","media":{"encoding":"utf-8","type":"text/vnd.cucumber.gherkin+plain"}}
2+
{"type":"attachment","source":{"uri":"features/a.feature","start":{"line":4,"column":5}},"data":"(4:5): expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #ScenarioOutlineLine, #Comment, #Empty, got 'Examples:'","media":{"encoding":"utf-8","type":"text/vnd.cucumber.stacktrace+plain"}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{"type":"source","uri":"features/a.feature","data":"Feature: a feature\n Scenario: a scenario\n Given a step","media":{"encoding":"utf-8","type":"text/vnd.cucumber.gherkin+plain"}}
2+
{"type":"gherkin-document","uri":"features/a.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"a feature","children":[{"type":"Scenario","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"a scenario","steps":[{"type":"Step","location":{"line":3,"column":5},"keyword":"Given ","text":"a step"}]}]},"comments":[]}}
3+
{"type":"pickle","uri":"features/a.feature","pickle":{"tags":[],"name":"a scenario","language":"en","locations":[{"line":2,"column":3}],"steps":[{"text":"a step","arguments":[],"locations":[{"line":3,"column":11}]}]}}
4+
{"type":"pickle-accepted","pickle":{"tags":[],"name":"a scenario","language":"en","locations":[{"line":2,"column":3}],"steps":[{"text":"a step","arguments":[],"locations":[{"line":3,"column":11}]}]},"uri":"features/a.feature"}
5+
{"type":"test-run-started"}
6+
{"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/a.feature","line":3},"actionLocation":{"uri":"features/step_definitions/steps.js","line":4}}],"sourceLocation":{"uri":"features/a.feature","line":2}}
7+
{"type":"test-case-started","sourceLocation":{"uri":"features/a.feature","line":2}}
8+
{"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/a.feature","line":2}}}
9+
{"type":"test-step-finished","index":0,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/a.feature","line":2}}}
10+
{"type":"test-case-finished","result":{"duration":0,"status":"passed"},"sourceLocation":{"uri":"features/a.feature","line":2}}
11+
{"type":"test-run-finished","result":{"duration":0,"success":true}}

0 commit comments

Comments
 (0)