Skip to content
Permalink
Browse files
feat(cucumber): support for Cucumber 2.x
affects: @serenity-js/core, @serenity-js/cucumber, @integration/cucumber-2, @integration/cucumber,
@integration/testing-tools, @serenity-js-examples/cucumber

@serenity-js/cucumber module supports both Cucumber 1.x and 2.x

ISSUES CLOSED: Closes #28
  • Loading branch information
jan-molak committed Aug 28, 2018
1 parent 9542f38 commit d8b8ff45029ea83a0c6786c5117425a230e27156
Showing with 1,973 additions and 394 deletions.
  1. +1 −1 examples/cucumber/package.json
  2. +7 −0 integration/cucumber-2/.gitignore
  3. +8 −0 integration/cucumber-2/features/data_table.feature
  4. +11 −0 integration/cucumber-2/features/descriptions.feature
  5. +11 −0 integration/cucumber-2/features/doc_strings.feature
  6. +9 −0 integration/cucumber-2/features/example_capability/example.feature
  7. +10 −0 integration/cucumber-2/features/example_theme/example_capability/example.feature
  8. +6 −0 integration/cucumber-2/features/failing_scenario.feature
  9. +6 −0 integration/cucumber-2/features/passing_scenario.feature
  10. +20 −0 integration/cucumber-2/features/pending_scenarios.feature
  11. +13 −0 integration/cucumber-2/features/scenario_outlines.feature
  12. +2 −0 integration/cucumber-2/features/step_definitions/README.md
  13. +12 −0 integration/cucumber-2/features/step_definitions/ambiguous.steps.ts
  14. +30 −0 integration/cucumber-2/features/step_definitions/callback.steps.ts
  15. +29 −0 integration/cucumber-2/features/step_definitions/promise.steps.ts
  16. +23 −0 integration/cucumber-2/features/step_definitions/synchronous.steps.ts
  17. +8 −0 integration/cucumber-2/features/support/after_hook.ts
  18. +8 −0 integration/cucumber-2/features/support/before_hook.ts
  19. +15 −0 integration/cucumber-2/features/support/configure_serenity.ts
  20. +8 −0 integration/cucumber-2/features/support/wip_hook.ts
  21. +22 −0 integration/cucumber-2/features/tags.feature
  22. +6 −0 integration/cucumber-2/features/timed_out_scenario.feature
  23. +41 −0 integration/cucumber-2/package.json
  24. +58 −0 integration/cucumber-2/spec/ambiguous_steps.spec.ts
  25. +35 −0 integration/cucumber-2/spec/capabilities.spec.ts
  26. +38 −0 integration/cucumber-2/spec/data_table.spec.ts
  27. +45 −0 integration/cucumber-2/spec/descriptions.spec.ts
  28. +39 −0 integration/cucumber-2/spec/doc_string.spec.ts
  29. +46 −0 integration/cucumber-2/spec/failing_scenario.spec.ts
  30. +39 −0 integration/cucumber-2/spec/passing_scenario.spec.ts
  31. +109 −0 integration/cucumber-2/spec/pending_scenarios.spec.ts
  32. +86 −0 integration/cucumber-2/spec/scenario_outlines.spec.ts
  33. +69 −0 integration/cucumber-2/spec/tags.spec.ts
  34. +36 −0 integration/cucumber-2/spec/themes.spec.ts
  35. +51 −0 integration/cucumber-2/spec/timed_out_scenario.spec.ts
  36. +19 −0 integration/cucumber-2/src/cucumber.ts
  37. +1 −0 integration/cucumber-2/src/index.ts
  38. +10 −0 integration/cucumber-2/tsconfig-lint.json
  39. +13 −0 integration/cucumber-2/tsconfig.json
  40. +1 −1 integration/cucumber/features/pending_scenarios.feature
  41. +22 −0 integration/cucumber/features/tags.feature
  42. +2 −4 integration/cucumber/package.json
  43. +2 −2 integration/cucumber/spec/ambiguous_steps.spec.ts
  44. +2 −2 integration/cucumber/spec/capabilities.spec.ts
  45. +2 −2 integration/cucumber/spec/data_table.spec.ts
  46. +2 −2 integration/cucumber/spec/descriptions.spec.ts
  47. +2 −2 integration/cucumber/spec/doc_string.spec.ts
  48. +2 −2 integration/cucumber/spec/failing_scenario.spec.ts
  49. +2 −2 integration/cucumber/spec/passing_scenario.spec.ts
  50. +2 −2 integration/cucumber/spec/pending_scenarios.spec.ts
  51. +2 −2 integration/cucumber/spec/scenario_outlines.spec.ts
  52. +69 −0 integration/cucumber/spec/tags.spec.ts
  53. +2 −2 integration/cucumber/spec/themes.spec.ts
  54. +2 −2 integration/cucumber/spec/timed_out_scenario.spec.ts
  55. +0 −1 integration/cucumber/src/index.ts
  56. +3 −1 integration/{cucumber → testing-tools}/src/Pick.ts
  57. +1 −0 integration/testing-tools/src/index.ts
  58. +226 −0 package-lock.json
  59. +2 −0 package.json
  60. +1 −1 packages/core/src/model/ScenarioParameters.ts
  61. +1 −1 packages/cucumber/package.json
  62. +1 −2 packages/cucumber/spec/gherkin/feature-files/FeatureFileLoader.spec.ts
  63. +8 −0 packages/cucumber/spec/gherkin/feature-files/FeatureFileMap.spec.ts
  64. +127 −40 packages/cucumber/spec/gherkin/feature-files/FeatureFileMapper.spec.ts
  65. +8 −0 packages/cucumber/src/adapters/Dependencies.ts
  66. +217 −0 packages/cucumber/src/adapters/cucumber-0.ts
  67. +1 −0 packages/cucumber/src/adapters/cucumber-1.ts
  68. +11 −0 packages/cucumber/src/adapters/cucumber-2.ts
  69. +7 −0 packages/cucumber/src/adapters/cucumber-3.ts
  70. +7 −0 packages/cucumber/src/adapters/cucumber-4.ts
  71. +43 −0 packages/cucumber/src/adapters/index.ts
  72. +7 −0 packages/cucumber/src/gherkin/constructables.ts
  73. +1 −1 packages/cucumber/src/gherkin/feature-files/FeatureFileLoader.ts
  74. +15 −7 packages/cucumber/src/gherkin/feature-files/FeatureFileMap.ts
  75. +66 −20 packages/cucumber/src/gherkin/feature-files/FeatureFileMapper.ts
  76. +1 −0 packages/cucumber/src/gherkin/index.ts
  77. +6 −4 packages/cucumber/src/gherkin/model/Background.ts
  78. +6 −4 packages/cucumber/src/gherkin/model/Feature.ts
  79. +12 −0 packages/cucumber/src/gherkin/model/FeatureFileNode.ts
  80. +8 −5 packages/cucumber/src/gherkin/model/Scenario.ts
  81. +6 −4 packages/cucumber/src/gherkin/model/ScenarioOutline.ts
  82. +3 −2 packages/cucumber/src/gherkin/model/Step.ts
  83. +2 −0 packages/cucumber/src/gherkin/nodes/ScenarioDefinition.ts
  84. +0 −1 packages/cucumber/src/index.ts
  85. +0 −1 packages/cucumber/src/listener/index.ts
  86. +0 −63 packages/cucumber/src/listener/listener.ts
  87. +55 −191 packages/cucumber/src/notifier/Notifier.ts
  88. +1 −13 packages/cucumber/src/notifier/index.ts
  89. +4 −4 packages/cucumber/src/register.ts
@@ -42,6 +42,6 @@
"cucumber": "1.3.3",
"npm-failsafe": "0.4.1",
"serenity-cli": "0.11.0",
"ts-node": "7.0.0"
"ts-node": "7.0.1"
}
}
@@ -0,0 +1,7 @@
# Node
node_modules
*.log

# Build artifacts
.nyc_output
lib
@@ -0,0 +1,8 @@
Feature: Serenity/JS recognises a scenario with Data Tables

Scenario: Scenario with a Data Table step

Given a step that receives a table:
| Developer | Website |
| Jan Molak | janmolak.com |

@@ -0,0 +1,11 @@
Feature: Serenity/JS recognises all the important elements of a scenario

In order to accurately report the scenario
Serenity/JS should recognise all of its important parts

Scenario: First scenario

A scenario where all the steps pass
Is reported as passing

Given a step that passes
@@ -0,0 +1,11 @@
Feature: Serenity/JS recognises a scenario with DocStrings

Scenario: Scenario with a DocString step

Given a step that receives a doc string:
"""
Dear customer,
Please click this link to reset your password.
"""

@@ -0,0 +1,9 @@
Feature: Serenity/JS recognises capabilities

This feature file is nested in a 1-level deep directory structure.
The name of the folder becomes the name of the Capability.

Scenario: A passing scenario

Given a step that passes

@@ -0,0 +1,10 @@
Feature: Serenity/JS recognises capabilities and themes

This feature file is nested in a 2-level deep directory structure.
The name of the folder in which the feature file lives becomes the name of the Capability.
The name of the folder in which the capability folder lives becomes the name of the Theme.

Scenario: A passing scenario

Given a step that passes

@@ -0,0 +1,6 @@
Feature: Serenity/JS recognises a failing scenario

Scenario: A failing scenario

Given a step that fails

@@ -0,0 +1,6 @@
Feature: Serenity/JS recognises a passing scenario

Scenario: A passing scenario

Given a step that passes

@@ -0,0 +1,20 @@
Feature: Serenity/JS recognises pending scenarios

Scenario: A scenario with steps marked as pending

Given a step that's marked as pending

Scenario: A scenario with steps that have not been implemented yet

Given a step that hasn't been implemented yet


@wip
Scenario: A scenario which tag marks it as pending

The @wip tag is interpreted as pending thanks to support/wip_hook.ts
Every step in this scenario will be reported as pending.

Given step number one that passes
And step number two that is marked as pending
And step number three that fails
@@ -0,0 +1,13 @@
Feature: Serenity/JS recognises scenario outlines

Scenario Outline: Sample outline

Given a step that <result>

Examples: Example results

Description of the examples

| result |
| passes |
| fails |
@@ -0,0 +1,2 @@
# Notes
- [Cucumber.js step definitions](https://github.com/cucumber/cucumber-js/blob/2.x/docs/support_files/step_definitions.md)
@@ -0,0 +1,12 @@
import { defineSupportCode } from 'cucumber';

defineSupportCode(({ Given }) => {

Given(/^.*step (?:.*) passes$/, function() {
return void 0;
});

Given(/^.*step (?:.*) passes$/, function() {
return void 0;
});
});
@@ -0,0 +1,30 @@
import { defineSupportCode, TableDefinition } from 'cucumber';

type Callback = (error?: Error, pending?: string) => void;

defineSupportCode(({ Given }) => {

Given(/^.*step (?:.*) passes$/, function(done: Callback) {
done();
});

Given(/^.*step (?:.*) fails$/, function(done: Callback) {
done(new Error(`Something's wrong`));
});

Given(/^.*step (?:.*) marked as pending/, function(done: Callback) {
done(void 0, 'pending');
});

Given(/^.*step (?:.*) receives a table:$/, function(data: TableDefinition, done) {
done();
});

Given(/^.*step (?:.*) receives a doc string:$/, function(docstring: string, done) {
done();
});

Given(/^.*step that times out$/, { timeout: 100 }, function(done: Callback) {
setTimeout(done, 1000);
});
});
@@ -0,0 +1,29 @@
import { defineSupportCode, TableDefinition } from 'cucumber';

defineSupportCode(({ Given }) => {
Given(/^.*step (?:.*) passes$/, function() {
return Promise.resolve();
});

Given(/^.*step (?:.*) fails$/, function() {
return Promise.reject(new Error(`Something's wrong`));
});

Given(/^.*step (?:.*) marked as pending/, function() {
return Promise.resolve('pending');
});

Given(/^.*step (?:.*) receives a table:$/, function(data: TableDefinition) {
return Promise.resolve();
});

Given(/^.*step (?:.*) receives a doc string:$/, function(docstring: string) {
return Promise.resolve();
});

Given(/^.*step that times out$/, { timeout: 100 }, function() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});
@@ -0,0 +1,23 @@
import { defineSupportCode, TableDefinition } from 'cucumber';

defineSupportCode(({ Given }) => {
Given(/^.*step (?:.*) passes$/, function() {
return void 0;
});

Given(/^.*step (?:.*) fails$/, function() {
throw new Error(`Something's wrong`);
});

Given(/^.*step (?:.*) marked as pending/, function() {
return 'pending';
});

Given(/^.*step (?:.*) receives a table:$/, function(data: TableDefinition) {
return void 0;
});

Given(/^.*step (?:.*) receives a doc string:$/, function(docstring: string) {
return void 0;
});
});
@@ -0,0 +1,8 @@
import { defineSupportCode } from 'cucumber';

defineSupportCode(({ After }) => {

After(function() {
// no-op
});
});
@@ -0,0 +1,8 @@
import { defineSupportCode } from 'cucumber';

defineSupportCode(({ Before }) => {

Before(function() {
// no-op
});
});
@@ -0,0 +1,15 @@
import { ChildProcessReporter } from '@integration/testing-tools';
import { serenity } from '@serenity-js/core';
import { DebugReporter } from '@serenity-js/core/lib/stage';
import { defineSupportCode } from 'cucumber';

defineSupportCode(({ setDefaultTimeout }) => {
setDefaultTimeout(5000);

console.log('>>> configure_serenity :: loading serenity from', require.resolve('@serenity-js/core'));

serenity.stageManager.register(
new ChildProcessReporter(),
new DebugReporter(),
);
});
@@ -0,0 +1,8 @@
import { defineSupportCode } from 'cucumber';

defineSupportCode(({ Before }) => {

Before({ tags: '@wip' }, function() {
return 'pending';
});
});
@@ -0,0 +1,22 @@
@feature-tag
Feature: Serenity/JS recognises tags at multiple levels

@scenario-tag
Scenario: A tagged scenario

Given a step that passes

@scenario-outline-tag
Scenario Outline: More tagged scenarios

Given a step that <result>

@example-set-1
Examples:
| result |
| passes |

@example-set-2
Examples:
| result |
| passes |
@@ -0,0 +1,6 @@
Feature: Serenity/JS recognises a timed out scenario

Scenario: A timed out scenario

Given a step that times out

@@ -0,0 +1,41 @@
{
"name": "@integration/cucumber-2",
"version": "0.0.0",
"description": "Cucumber JS 2.x integration tests",
"author": {
"name" : "Jan Molak",
"email" : "jan.molak@smartcodeltd.co.uk",
"url" : "https://janmolak.com"
},
"homepage": "http://serenity-js.org",
"license": "Apache-2.0",
"private": true,
"config": {
"access": "private"
},
"scripts": {
"clean": "rimraf lib",
"lint": "tslint --project tsconfig-lint.json --config ../../tslint.json --format stylish",
"test": "mocha-parallel-tests --opts ../../mocha.opts 'spec/**/*.spec.*'"
},
"repository": {
"type": "git",
"url": "https://github.com/jan-molak/serenity-js.git"
},
"bugs": {
"url": "https://github.com/jan-molak/serenity-js/issues"
},
"engines": {
"node": ">= 6.9.x",
"npm": ">= 3"
},
"dependencies": {
"@integration/testing-tools": "*",
"@serenity-js/core": "*",
"@serenity-js/cucumber": "*"
},
"devDependencies": {
"@types/cucumber": "2.1.0",
"cucumber": "2.3.1"
}
}
@@ -0,0 +1,58 @@
import { expect, ifExitCodeIsOtherThan, logOutput, Pick } from '@integration/testing-tools';
import {
ActivityFinished,
ActivityStarts,
SceneFinished,
SceneStarts,
SceneTagged,
TestRunnerDetected,
} from '@serenity-js/core/lib/events';
import { ExecutionFailedWithError, FeatureTag, Name } from '@serenity-js/core/lib/model';

import 'mocha';

import { cucumber } from '../src';

describe('@serenity-js/cucumber', function() {

this.timeout(5000);

it('recognises scenarios with ambiguous steps', () =>
cucumber(
'--require', 'features/support/configure_serenity.ts',
'--require', `features/step_definitions/ambiguous.steps.ts`,
'--require', 'node_modules/@serenity-js/cucumber/register.js',
'features/passing_scenario.feature',
).
then(ifExitCodeIsOtherThan(1, logOutput)).
then(res => {
expect(res.exitCode).to.equal(1);

Pick.from(res.events)
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A passing scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises a passing scenario')))
.next(ActivityStarts, event => expect(event.value.name).to.equal(new Name('Given a step that passes')))
.next(ActivityFinished, event => {
expect(event.outcome).to.be.instanceOf(ExecutionFailedWithError);

const err = (event.outcome as ExecutionFailedWithError).error;

const lines = err.message.split('\n');

expect(lines[0]).to.equal('Each step should have one matching step definition, yet there are several:');
expect(lines[1]).to.contain('/^.*step (?:.*) passes$/');
expect(lines[1]).to.contain('ambiguous.steps.ts');
expect(lines[2]).to.contain('/^.*step (?:.*) passes$/');
expect(lines[2]).to.contain('ambiguous.steps.ts');
})
.next(SceneFinished, event => {
expect(event.outcome).to.be.instanceOf(ExecutionFailedWithError);

const err = (event.outcome as ExecutionFailedWithError).error;

expect(err.message).to.equal('Ambiguous step definition detected');
})
;
}));
});

0 comments on commit d8b8ff4

Please sign in to comment.