Skip to content

Commit

Permalink
feat(cucumber): support for Cucumber 10
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-molak committed Oct 17, 2023
1 parent bc4b29d commit adb3ee5
Show file tree
Hide file tree
Showing 23 changed files with 27,509 additions and 9,588 deletions.
15 changes: 15 additions & 0 deletions integration/cucumber-10/.c8rc.json
@@ -0,0 +1,15 @@
{
"extends": "../../.c8rc.json",
"reportDir": "./target/coverage",
"allowExternal": true,
"include": [
"**/packages/core/lib/**/*.js",
"**/packages/cucumber/lib/**/*.js"
],
"exclude": [
"**/target/**",
"**/spec/**",
"**/*.d.ts",
"**/*.spec.ts"
]
}
6 changes: 6 additions & 0 deletions integration/cucumber-10/.gitignore
@@ -0,0 +1,6 @@
# Node
node_modules
*.log

# Build artifacts
lib
3 changes: 3 additions & 0 deletions integration/cucumber-10/.mocha-reporters.json
@@ -0,0 +1,3 @@
{
"dot": "-"
}
14 changes: 14 additions & 0 deletions integration/cucumber-10/.mocharc.yml
@@ -0,0 +1,14 @@
check-leaks: false
color: true
diff: true
full-trace: true
reporter: mocha-multi
reporter-options: 'mocha-multi=.mocha-reporters.json'
require:
- 'ts-node/register'
- 'src/register-cucumber.ts'
spec: './node_modules/@integration/cucumber-specs/spec/**/*.spec.ts'
timeout: 30000
v8-stack-trace-limit: 100
node-option:
- trace-warnings
44 changes: 44 additions & 0 deletions integration/cucumber-10/package.json
@@ -0,0 +1,44 @@
{
"name": "@integration/cucumber-10",
"version": "3.0.0",
"description": "Specs verifying integration between Serenity/JS and Cucumber.js",
"author": {
"name": "Jan Molak",
"email": "jan.molak@smartcodeltd.co.uk",
"url": "https://janmolak.com"
},
"homepage": "https://serenity-js.org",
"license": "Apache-2.0",
"private": true,
"config": {
"access": "private"
},
"scripts": {
"clean": "rimraf target",
"test": "c8 mocha --parallel --config .mocharc.yml"
},
"repository": {
"type": "git",
"url": "https://github.com/serenity-js/serenity-js.git"
},
"bugs": {
"url": "https://github.com/serenity-js/serenity-js/issues"
},
"engines": {
"node": "^16.13 || ^18.12 || ^20"
},
"devDependencies": {
"@cucumber/cucumber": "^10.0.0",
"@integration/testing-tools": "3.0.0",
"@integration/cucumber-specs": "3.0.0",
"@serenity-js/core": "^3.0.0",
"@serenity-js/cucumber": "^3.0.0",
"@types/mocha": "^10.0.2",
"@types/chai": "^4.3.6",
"c8": "8.0.1",
"mocha": "^10.2.0",
"mocha-multi": "^1.1.7",
"ts-node": "^10.9.1",
"typescript": "5.1.6"
}
}
17 changes: 17 additions & 0 deletions integration/cucumber-10/src/register-cucumber.ts
@@ -0,0 +1,17 @@
import { registerRunner } from '@integration/cucumber-specs';
import * as path from 'path'; // eslint-disable-line unicorn/import-style

const pathToCucumberExecutable = path.resolve(
require.resolve('@cucumber/cucumber/package.json'),
'..',
'bin',
'cucumber-js',
);

const cwd = path.resolve(__dirname, '../');

registerRunner(pathToCucumberExecutable, cwd, [
'--require-module', 'ts-node/register',
'--format', '@serenity-js/cucumber',
'--require', require.resolve('./support/configure_serenity'),
]);
@@ -0,0 +1,9 @@
import { Given } from '@cucumber/cucumber';

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

Given(/^.*step .* passes$/, function () {
return void 0;
});
36 changes: 36 additions & 0 deletions integration/cucumber-10/src/step_definitions/common.steps.ts
@@ -0,0 +1,36 @@
import { DataTable, Given } from '@cucumber/cucumber';
import { AssertionError } from '@serenity-js/core';
import * as assert from 'assert';

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

Given(/^.*step .* fails with a generic error$/, function () {
throw new Error(`Something's wrong`);
});

Given(/^.*step .* fails with an assertion error$/, function () {
throw new AssertionError(`Expected false to equal true`);
});

Given(/^.*step .* fails with a non-Serenity assertion error$/, function () {
assert.strictEqual(false, true);
});

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

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

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

Given(/^.*step that times out$/, { timeout: 100 }, () =>
new Promise((resolve, reject) => {
setTimeout(resolve, 250);
}));
50 changes: 50 additions & 0 deletions integration/cucumber-10/src/step_definitions/hooks.steps.ts
@@ -0,0 +1,50 @@
import { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, defineParameterType, Given } from '@cucumber/cucumber';
import { Actor, actorCalled, actorInTheSpotlight, Interaction } from '@serenity-js/core';

const Perform = {
in: (scope: string) => Interaction.where(`#actor performs in ${ scope }`, () => void 0)
}

defineParameterType({
regexp: /[A-Z][a-z]+/,
transformer(name: string) {
return actorCalled(name);
},
name: 'actor',
});

defineParameterType({
regexp: /he|she|they|his|her|their/,
transformer() {
return actorInTheSpotlight();
},
name: 'pronoun',
});

BeforeAll(() =>
actorCalled('Helen').attemptsTo(Perform.in('BeforeAll'))
);

Before(() =>
actorCalled('Helen').attemptsTo(Perform.in('Before'))
);

BeforeStep(() =>
actorCalled('Helen').attemptsTo(Perform.in('BeforeStep'))
);

Given('{actor} fulfills a task', (actor: Actor) =>
actor.attemptsTo(Perform.in('Given'))
);

AfterStep(() =>
actorCalled('Helen').attemptsTo(Perform.in('AfterStep'))
);

After(() =>
actorCalled('Helen').attemptsTo(Perform.in('After'))
);

AfterAll(() =>
actorCalled('Helen').attemptsTo(Perform.in('AfterAll'))
);
34 changes: 34 additions & 0 deletions integration/cucumber-10/src/step_definitions/named_hooks.steps.ts
@@ -0,0 +1,34 @@
import { After, Before, defineParameterType, Given } from '@cucumber/cucumber';
import { Actor, actorCalled, actorInTheSpotlight, Interaction } from '@serenity-js/core';

const Perform = {
in: (scope: string) => Interaction.where(`#actor performs in ${ scope }`, () => void 0)
}

defineParameterType({
regexp: /[A-Z][a-z]+/,
transformer(name: string) {
return actorCalled(name);
},
name: 'actor',
});

defineParameterType({
regexp: /he|she|they|his|her|their/,
transformer() {
return actorInTheSpotlight();
},
name: 'pronoun',
});

Before({ name: 'Perform some setup in named Before hook' }, () =>
actorCalled('Helen').attemptsTo(Perform.in('named Before hook'))
);

Given('{actor} fulfills a task', (actor: Actor) =>
actor.attemptsTo(Perform.in('Given'))
);

After({ name: 'Perform some teardown in named After hook' }, () =>
actorCalled('Helen').attemptsTo(Perform.in('named After hook'))
);
11 changes: 11 additions & 0 deletions integration/cucumber-10/src/step_definitions/retry.steps.ts
@@ -0,0 +1,11 @@
import { Given } from '@cucumber/cucumber';

let retriesNeeded = 2;

Given('a step that eventually passes', function () {
if (retriesNeeded > 0) {
--retriesNeeded;

throw new Error('Try again');
}
});
40 changes: 40 additions & 0 deletions integration/cucumber-10/src/step_definitions/rules.steps.ts
@@ -0,0 +1,40 @@
import { DataTable, defineParameterType, Given, Then } from '@cucumber/cucumber';
import { Actor, actorCalled, actorInTheSpotlight } from '@serenity-js/core';

defineParameterType({
regexp: /[A-Z][a-z]+/,
transformer(name: string) {
return actorCalled(name);
},
name: 'actor',
});

defineParameterType({
regexp: /he|she|they|his|her|their/,
transformer() {
return actorInTheSpotlight();
},
name: 'pronoun',
});

Given('the following Frequent Flyer members:', (table: DataTable) => {
return void 0;
});

Given('the following Frequent Flyer account balances:', (table: DataTable) => {
return void 0;
});

Given('{actor} transfers {int} points to {actor}', (sender: Actor, points: number, receiver: Actor) => {
return void 0;
});

Given('{actor} tries to transfer {int} points to {actor}', (sender: Actor, points: number, receiver: Actor) => {
return void 0;
});

Then('the transfer should not be allowed', () => void 0);

Then('the accounts should be as follows:', (table: DataTable) => {
return void 0;
});
31 changes: 31 additions & 0 deletions integration/cucumber-10/src/step_definitions/screenplay.steps.ts
@@ -0,0 +1,31 @@
import { After, Before, Then, When } from '@cucumber/cucumber';
import { Interaction, serenity } from '@serenity-js/core';

const
MakeAnArrow = () => Interaction.where(`#actor makes an arrow`, actor => void 0),
Nock = () => Interaction.where(`#actor fits an arrow to the bowstring`, actor => void 0),
Draw = () => Interaction.where(`#actor draws the bow`, actor => void 0),
Loose = () => Interaction.where(`#actor releases the bowstring`, actor => void 0),
RetrieveArrow = () => Interaction.where(`#actor retrieves the arrow from the target`, actor => void 0);

Before(() =>
serenity.theActorCalled('Lara').attemptsTo(
MakeAnArrow(),
));

When(/^(.*) shoots an arrow$/, (actorName: string) =>
serenity.theActorCalled(actorName).attemptsTo(
Nock(),
Draw(),
Loose(),
));

Then(/^she should hit a target$/, () =>
serenity.theActorInTheSpotlight().attemptsTo(
// some assertion
));

After(() =>
serenity.theActorInTheSpotlight().attemptsTo(
RetrieveArrow(),
));
6 changes: 6 additions & 0 deletions integration/cucumber-10/src/step_definitions/skip_hook.ts
@@ -0,0 +1,6 @@
/* eslint-disable unicorn/filename-case */
import { Before } from '@cucumber/cucumber';

Before({ tags: '@skip' }, function () {
return 'skipped' as any;
});
@@ -0,0 +1,5 @@
import { Before } from '@cucumber/cucumber';

Before({ tags: '@wip' }, function () {
return 'pending';
});
7 changes: 7 additions & 0 deletions integration/cucumber-10/src/support/Actors.ts
@@ -0,0 +1,7 @@
import { Actor, Cast } from '@serenity-js/core';

export class Actors extends Cast {
prepare(actor: Actor): Actor {
return actor; // no-op actors with no special abilities
}
}
15 changes: 15 additions & 0 deletions integration/cucumber-10/src/support/configure_serenity.ts
@@ -0,0 +1,15 @@
import { setDefaultTimeout } from '@cucumber/cucumber';
import { ChildProcessReporter } from '@integration/testing-tools';
import { serenity, StreamReporter } from '@serenity-js/core';

import { Actors } from './Actors';

setDefaultTimeout(10000);

serenity.configure({
actors: new Actors(),
crew: [
new ChildProcessReporter(),
new StreamReporter(),
],
});
7 changes: 7 additions & 0 deletions integration/cucumber-10/tsconfig.json
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.options.json",

"include": [
"src/**/*.ts",
]
}

0 comments on commit adb3ee5

Please sign in to comment.