diff --git a/integration/cucumber/spec/pending_scenarios.spec.ts b/integration/cucumber/spec/pending_scenarios.spec.ts index 22f6ea019a0..913c5bd385e 100644 --- a/integration/cucumber/spec/pending_scenarios.spec.ts +++ b/integration/cucumber/spec/pending_scenarios.spec.ts @@ -170,7 +170,7 @@ describe('@serenity-js/cucumber', function () { .next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber'))) .next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises pending scenarios'))) .next(ActivityStarts, event => expect(event.value.name).to.equal(new Name(`Given step number one that passes`))) - .next(ActivityFinished, event => expect(event.outcome.constructor).to.equal(ImplementationPending)) + .next(ActivityFinished, event => expect(event.outcome).to.equal(new ExecutionSkipped())) .next(ActivityStarts, event => expect(event.value.name).to.equal(new Name(`And step number two that is marked as pending`))) .next(ActivityFinished, event => expect(event.outcome).to.equal(new ExecutionSkipped())) .next(ActivityStarts, event => expect(event.value.name).to.equal(new Name(`And step number three that fails with generic error`))) diff --git a/integration/cucumber/spec/screenplay_scenario.spec.ts b/integration/cucumber/spec/screenplay_scenario.spec.ts index 3b56eace109..b3a6ac15f8e 100644 --- a/integration/cucumber/spec/screenplay_scenario.spec.ts +++ b/integration/cucumber/spec/screenplay_scenario.spec.ts @@ -26,17 +26,13 @@ describe('@serenity-js/cucumber', function () { .withStepDefsIn('screenplay') .toRun('features/screenplay_scenario.feature'), - // fixme: a bug in Event Protocol causes Cucumber 3-5 to incorrectly report test-step-start and test-step-finished events - // fixme: see https://github.com/cucumber/cucumber-js/issues/1195 - // ...cucumberVersions(3, 4, 5) - // .thatRequires('lib/support/configure_serenity.js') - // .withStepDefsIn('tasty-cucumber') - // .withArgs( - // // '--format', 'node_modules/@serenity-js/cucumber/register.js', - // '--format', 'event-protocol', - // ) - // // .toRun('features/screenplay_scenario.feature'), - // .toRun('features/tasty-cucumber.feature'), + ...cucumberVersions(3, 4, 5) + .thatRequires('lib/support/configure_serenity.js') + .withStepDefsIn('screenplay') + .withArgs( + '--format', 'node_modules/@serenity-js/cucumber/register.js', + ) + .toRun('features/screenplay_scenario.feature'), ]). it('recognises Screenplay activities in any part of the Cucumber scenario', (runner: CucumberRunner) => runner.run(). then(ifExitCodeIsOtherThan(0, logOutput)). @@ -68,7 +64,4 @@ describe('@serenity-js/cucumber', function () { .next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful())) ; })); - - // todo: I added a skipped test so that I remember to add the tests above back in when the Cucumber bug is fixed - it('recognises Screenplay activities in any part of the Cucumber scenario when the Event Protocol is being used'); }); diff --git a/package-lock.json b/package-lock.json index ffa2446f2ab..4d7f99214e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8617,7 +8617,7 @@ "neo-async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "integrity": "sha1-udFeTXHGdikIZUtRg+04t1M0CDU=", "dev": true }, "nice-try": { @@ -16173,7 +16173,7 @@ "uglify-js": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.4.tgz", - "integrity": "sha512-GpKo28q/7Bm5BcX9vOu4S46FwisbPbAmkkqPnGIpKvKTM96I85N6XHQV+k4I6FA2wxgLhcsSyHoNhzucwCflvA==", + "integrity": "sha1-SmTVf1kOIKiYugV/g43N+2epObk=", "dev": true, "optional": true, "requires": { @@ -16184,7 +16184,7 @@ "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "integrity": "sha1-1YuytcHuj4ew00ACfp6U4iLFpCI=", "dev": true, "optional": true }, diff --git a/packages/core/src/io/ModuleLoader.ts b/packages/core/src/io/ModuleLoader.ts index 882f10c8bef..7ce94b6332c 100644 --- a/packages/core/src/io/ModuleLoader.ts +++ b/packages/core/src/io/ModuleLoader.ts @@ -9,9 +9,6 @@ export class ModuleLoader { /** * @package * - * @param fromDir - * Directory where NPM should start the module lookup. For example: process.cwd() - * * @param moduleId * NPM module id, for example 'cucumber' or '@serenity-js/core' */ diff --git a/packages/cucumber/spec/listeners/CucumberEventProtocolAdapter.spec.ts b/packages/cucumber/spec/listeners/CucumberEventProtocolAdapter.spec.ts new file mode 100644 index 00000000000..0d7c5d233d5 --- /dev/null +++ b/packages/cucumber/spec/listeners/CucumberEventProtocolAdapter.spec.ts @@ -0,0 +1,267 @@ +import { EventRecorder, expect, PickEvent } from '@integration/testing-tools'; +import { Serenity } from '@serenity-js/core'; +import { SceneFinished, SceneStarts, SceneTagged, TaskFinished, TaskStarts, TestRunnerDetected } from '@serenity-js/core/lib/events'; +import { FileSystemLocation, Path, Version } from '@serenity-js/core/lib/io'; +import { Category, ExecutionFailedWithError, ExecutionSkipped, ExecutionSuccessful, FeatureTag, ImplementationPending, Name, ScenarioDetails } from '@serenity-js/core/lib/model'; + +import { EventEmitter } from 'events'; + +import * as sinon from 'sinon'; +import { JSONObject } from 'tiny-types'; +import { AmbiguousStepDefinitionError } from '../../src/errors'; +import { listenerForCucumber } from '../../src/listeners'; + +describe('CucumberEventProtocolAdapter', () => { + + type CucumberHook = () => Promise | void; + + const fakeCucumber = { + After: (hook: CucumberHook) => Promise.resolve(hook()), + AfterAll: (hook: CucumberHook) => Promise.resolve(hook()), + }; + + let recorder: EventRecorder, + serenity: Serenity, + log: typeof console.log, + eventBroadcaster: EventEmitter, + adapter: any; + + beforeEach(() => { + + log = sinon.spy(); + serenity = new Serenity(); + recorder = new EventRecorder(); + eventBroadcaster = new EventEmitter(); + + serenity.setTheStage( + recorder, + ); + + const listener = listenerForCucumber(new Version('5.0.0'), fakeCucumber, serenity); + + adapter = new listener({ eventBroadcaster, log }); + }); + + it('correctly recognises Cucumber Event Protocol events', () => { + + emitAllFrom(require('./samples/scenario-with-hooks.json')); + + const expectedScenarioDetails = new ScenarioDetails( + new Name('Hooks'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/tasty-cucumber.feature'), + 3, + 3, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails)) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I have a tasty cucumber in my belly'))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Given I have a tasty cucumber in my belly')); + expect(e.outcome).to.equal(new ExecutionSuccessful()); + }) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name(`Then I'm very happy`))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name(`Then I'm very happy`)); + expect(e.outcome).to.equal(new ExecutionSuccessful()); + }) + .next(SceneFinished, e => { + expect(e.value).to.equal(expectedScenarioDetails); + expect(e.outcome).to.equal(new ExecutionSuccessful()); + }) + ; + }); + + it('correctly recognises undefined steps', () => { + + emitAllFrom(require('./samples/scenario-with-undefined-steps.json')); + + const expectedScenarioDetails = new ScenarioDetails( + new Name('Undefined steps'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/undefined-steps.feature'), + 3, + 3, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails)) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I have an undefined step'))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Given I have an undefined step')); + expect(e.outcome).to.be.instanceOf(ImplementationPending); + }) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name(`Then I should implement it`))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Then I should implement it')); + expect(e.outcome).to.be.instanceOf(ImplementationPending); + }) + .next(SceneFinished, e => { + expect(e.value).to.equal(expectedScenarioDetails); + expect(e.outcome).to.be.instanceOf(ImplementationPending); + }) + ; + }); + + it('correctly recognises pending steps', () => { + + emitAllFrom(require('./samples/scenario-with-pending-steps.json')); + + const expectedScenarioDetails = new ScenarioDetails( + new Name('Pending steps'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/pending-steps.feature'), + 3, + 3, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails)) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I have a pending step'))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Given I have a pending step')); + expect(e.outcome).to.be.instanceOf(ImplementationPending); + }) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name(`Then I should implement it`))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Then I should implement it')); + expect(e.outcome).to.be.instanceOf(ExecutionSkipped); + }) + .next(SceneFinished, e => { + expect(e.value).to.equal(expectedScenarioDetails); + expect(e.outcome).to.be.instanceOf(ImplementationPending); + }) + ; + }); + + it('correctly recognises ambiguous steps', () => { + + emitAllFrom(require('./samples/scenario-with-ambiguous-steps.json')); + + const expectedScenarioDetails = new ScenarioDetails( + new Name('Ambiguous steps'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/ambiguous-steps.feature'), + 3, + 3, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails)) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I have an ambiguous step definition'))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Given I have an ambiguous step definition')); + expect(e.outcome).to.be.instanceOf(ExecutionFailedWithError); + expect((e.outcome as ExecutionFailedWithError).error).to.be.instanceOf(AmbiguousStepDefinitionError); + }) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name(`Then I should correct it`))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Then I should correct it')); + expect(e.outcome).to.be.instanceOf(ExecutionSkipped); + }) + .next(SceneFinished, e => { + expect(e.value).to.equal(expectedScenarioDetails); + expect(e.outcome).to.be.instanceOf(ExecutionFailedWithError); + expect((e.outcome as ExecutionFailedWithError).error).to.be.instanceOf(AmbiguousStepDefinitionError); + }) + ; + }); + + it('correctly recognises errors thrown in steps', () => { + + emitAllFrom(require('./samples/scenario-with-errors.json')); + + const expectedScenarioDetails = new ScenarioDetails( + new Name('Errors in steps'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/errors-in-steps.feature'), + 3, + 3, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails)) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I have a step that throws an error'))) + .next(TaskFinished, e => { + expect(e.value.name).to.equal(new Name('Given I have a step that throws an error')); + expect(e.outcome).to.be.instanceOf(ExecutionFailedWithError); + expect((e.outcome as ExecutionFailedWithError).error).to.be.instanceOf(Error); + expect((e.outcome as ExecutionFailedWithError).error.message).to.equal(`We're sorry, something happened`); + }) + .next(SceneFinished, e => { + expect(e.value).to.equal(expectedScenarioDetails); + expect(e.outcome).to.be.instanceOf(ExecutionFailedWithError); + expect((e.outcome as ExecutionFailedWithError).error).to.be.instanceOf(Error); + expect((e.outcome as ExecutionFailedWithError).error.message).to.equal(`We're sorry, something happened`); + }) + ; + }); + + it('correctly recognises scenario outlines', () => { + + emitAllFrom(require('./samples/scenario-outline.json')); + + const expectedScenarioDetails = (line: number) => new ScenarioDetails( + new Name('The things I like'), + new Category('Event Protocol'), + new FileSystemLocation( + new Path('features/outlines.feature'), + line, + 7, + ), + ); + + PickEvent.from(recorder.events) + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails(10))) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I like programming'))) + .next(TaskFinished, e => expect(e.value.name).to.equal(new Name('Given I like programming'))) + .next(SceneFinished, e => expect(e.value).to.equal(expectedScenarioDetails(10))) + + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails(11))) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I like to play guitar'))) + .next(TaskFinished, e => expect(e.value.name).to.equal(new Name('Given I like to play guitar'))) + .next(SceneFinished, e => expect(e.value).to.equal(expectedScenarioDetails(11))) + + .next(SceneStarts, e => expect(e.value).to.equal(expectedScenarioDetails(12))) + .next(TestRunnerDetected, e => expect(e.value).to.equal(new Name('Cucumber'))) + .next(SceneTagged, e => expect(e.tag).to.equal(new FeatureTag('Event Protocol'))) + .next(TaskStarts, e => expect(e.value.name).to.equal(new Name('Given I like martial arts'))) + .next(TaskFinished, e => expect(e.value.name).to.equal(new Name('Given I like martial arts'))) + .next(SceneFinished, e => expect(e.value).to.equal(expectedScenarioDetails(12))) + ; + }); + + function emitAllFrom(events: JSONObject[]): void { + events.forEach(event => { + // I can't use the convenient { type, ...body } construct because ESDoc/Babylon doesn't understand it; falling back to es5: + const emitted = Object.assign({}, event); // tslint:disable-line:prefer-object-spread + delete emitted.type; + eventBroadcaster.emit(event.type as string, emitted); + }); + } +}); diff --git a/packages/cucumber/spec/listeners/samples/scenario-outline.json b/packages/cucumber/spec/listeners/samples/scenario-outline.json new file mode 100644 index 00000000000..7b0e402c282 --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-outline.json @@ -0,0 +1,27 @@ +[ + {"type":"source","uri":"features/outlines.feature","data":"Feature: Event Protocol\n\n Scenario Outline: The things I like\n\n Given I like \n\n Examples: Some things I like\n\n | something |\n | programming |\n | to play guitar |\n | martial arts |\n\n\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/outlines.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"ScenarioOutline","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario Outline","name":"The things I like","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I like "}],"examples":[{"type":"Examples","tags":[],"location":{"line":7,"column":5},"keyword":"Examples","name":"Some things I like","tableHeader":{"type":"TableRow","location":{"line":9,"column":7},"cells":[{"type":"TableCell","location":{"line":9,"column":9},"value":"something"}]},"tableBody":[{"type":"TableRow","location":{"line":10,"column":7},"cells":[{"type":"TableCell","location":{"line":10,"column":9},"value":"programming"}]},{"type":"TableRow","location":{"line":11,"column":7},"cells":[{"type":"TableCell","location":{"line":11,"column":9},"value":"to play guitar"}]},{"type":"TableRow","location":{"line":12,"column":7},"cells":[{"type":"TableCell","location":{"line":12,"column":9},"value":"martial arts"}]}]}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/outlines.feature","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like programming","arguments":[],"locations":[{"line":10,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":10,"column":7},{"line":3,"column":3}]}}, + {"type":"pickle-accepted","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like programming","arguments":[],"locations":[{"line":10,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":10,"column":7},{"line":3,"column":3}]},"uri":"features/outlines.feature"}, + {"type":"pickle","uri":"features/outlines.feature","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like to play guitar","arguments":[],"locations":[{"line":11,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":11,"column":7},{"line":3,"column":3}]}}, + {"type":"pickle-accepted","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like to play guitar","arguments":[],"locations":[{"line":11,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":11,"column":7},{"line":3,"column":3}]},"uri":"features/outlines.feature"}, + {"type":"pickle","uri":"features/outlines.feature","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like martial arts","arguments":[],"locations":[{"line":12,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":12,"column":7},{"line":3,"column":3}]}}, + {"type":"pickle-accepted","pickle":{"name":"The things I like","language":"en","steps":[{"text":"I like martial arts","arguments":[],"locations":[{"line":12,"column":7},{"line":5,"column":11}]}],"tags":[],"locations":[{"line":12,"column":7},{"line":3,"column":3}]},"uri":"features/outlines.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/outlines.feature","line":5},"actionLocation":{"uri":"step_definitions/outlines.steps.ts","line":3}}],"sourceLocation":{"uri":"features/outlines.feature","line":10}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/outlines.feature","line":10}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":10}}}, + {"type":"test-step-finished","index":0,"result":{"duration":5,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":10}}}, + {"type":"test-case-finished","result":{"duration":5,"status":"passed"},"sourceLocation":{"uri":"features/outlines.feature","line":10}}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/outlines.feature","line":5},"actionLocation":{"uri":"step_definitions/outlines.steps.ts","line":3}}],"sourceLocation":{"uri":"features/outlines.feature","line":11}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/outlines.feature","line":11}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":11}}}, + {"type":"test-step-finished","index":0,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":11}}}, + {"type":"test-case-finished","result":{"duration":0,"status":"passed"},"sourceLocation":{"uri":"features/outlines.feature","line":11}}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/outlines.feature","line":5},"actionLocation":{"uri":"step_definitions/outlines.steps.ts","line":3}}],"sourceLocation":{"uri":"features/outlines.feature","line":12}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/outlines.feature","line":12}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":12}}}, + {"type":"test-step-finished","index":0,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/outlines.feature","line":12}}}, + {"type":"test-case-finished","result":{"duration":0,"status":"passed"},"sourceLocation":{"uri":"features/outlines.feature","line":12}}, + {"type":"test-run-finished","result":{"duration":5,"success":true}} +] diff --git a/packages/cucumber/spec/listeners/samples/scenario-with-ambiguous-steps.json b/packages/cucumber/spec/listeners/samples/scenario-with-ambiguous-steps.json new file mode 100644 index 00000000000..f27e560fd8f --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-with-ambiguous-steps.json @@ -0,0 +1,15 @@ +[ + {"type":"source","uri":"features/ambiguous-steps.feature","data":"Feature: Event Protocol\n\n Scenario: Ambiguous steps\n\n Given I have an ambiguous step definition\n Then I should correct it\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/ambiguous-steps.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"Scenario","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"Ambiguous steps","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I have an ambiguous step definition"},{"type":"Step","location":{"line":6,"column":5},"keyword":"Then ","text":"I should correct it"}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/ambiguous-steps.feature","pickle":{"tags":[],"name":"Ambiguous steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have an ambiguous step definition","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should correct it","arguments":[],"locations":[{"line":6,"column":10}]}]}}, + {"type":"pickle-accepted","pickle":{"tags":[],"name":"Ambiguous steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have an ambiguous step definition","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should correct it","arguments":[],"locations":[{"line":6,"column":10}]}]},"uri":"features/ambiguous-steps.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":5}},{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":6},"actionLocation":{"uri":"step_definitions/ambiguous.steps.ts","line":11}}],"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}}, + {"type":"test-step-finished","index":0,"result":{"exception":"Multiple step definitions match:\n /^I have an ambiguous step definition$/ - step_definitions/ambiguous.steps.ts:3\n /^I have an ambiguous step definition$/ - step_definitions/ambiguous.steps.ts:7","status":"ambiguous"},"testCase":{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}}, + {"type":"test-step-started","index":1,"testCase":{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}}, + {"type":"test-step-finished","index":1,"result":{"status":"skipped"},"testCase":{"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}}, + {"type":"test-case-finished","result":{"duration":0,"status":"ambiguous","exception":"Multiple step definitions match:\n /^I have an ambiguous step definition$/ - step_definitions/ambiguous.steps.ts:3\n /^I have an ambiguous step definition$/ - step_definitions/ambiguous.steps.ts:7"},"sourceLocation":{"uri":"features/ambiguous-steps.feature","line":3}}, + {"type":"test-run-finished","result":{"duration":0,"success":false}} +] diff --git a/packages/cucumber/spec/listeners/samples/scenario-with-errors.json b/packages/cucumber/spec/listeners/samples/scenario-with-errors.json new file mode 100644 index 00000000000..29e65e4ad92 --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-with-errors.json @@ -0,0 +1,13 @@ +[ + {"type":"source","uri":"features/errors-in-steps.feature","data":"Feature: Event Protocol\n\n Scenario: Errors in steps\n\n Given I have a step that throws an error\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/errors-in-steps.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"Scenario","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"Errors in steps","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I have a step that throws an error"}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/errors-in-steps.feature","pickle":{"tags":[],"name":"Errors in steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a step that throws an error","arguments":[],"locations":[{"line":5,"column":11}]}]}}, + {"type":"pickle-accepted","pickle":{"tags":[],"name":"Errors in steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a step that throws an error","arguments":[],"locations":[{"line":5,"column":11}]}]},"uri":"features/errors-in-steps.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/errors-in-steps.feature","line":5},"actionLocation":{"uri":"step_definitions/errors.steps.ts","line":3}}],"sourceLocation":{"uri":"features/errors-in-steps.feature","line":3}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/errors-in-steps.feature","line":3}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/errors-in-steps.feature","line":3}}}, + {"type":"test-step-finished","index":0,"result":{"duration":0,"exception":"Error: We're sorry, something happened\n at World. (step_definitions/errors.steps.ts:4:11)","status":"failed"},"testCase":{"sourceLocation":{"uri":"features/errors-in-steps.feature","line":3}}}, + {"type":"test-case-finished","result":{"duration":0,"status":"failed","exception":"Error: We're sorry, something happened\n at World. (step_definitions/errors.steps.ts:4:11)"},"sourceLocation":{"uri":"features/errors-in-steps.feature","line":3}}, + {"type":"test-run-finished","result":{"duration":0,"success":false}} +] diff --git a/packages/cucumber/spec/listeners/samples/scenario-with-hooks.json b/packages/cucumber/spec/listeners/samples/scenario-with-hooks.json new file mode 100644 index 00000000000..767bad2d5ff --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-with-hooks.json @@ -0,0 +1,19 @@ +[ + {"type":"source","uri":"features/tasty-cucumber.feature","data":"Feature: Event Protocol\n\n Scenario: Hooks\n\n Given I have a tasty cucumber in my belly\n Then I'm very happy\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/tasty-cucumber.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"Scenario","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"Hooks","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I have a tasty cucumber in my belly"},{"type":"Step","location":{"line":6,"column":5},"keyword":"Then ","text":"I'm very happy"}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/tasty-cucumber.feature","pickle":{"tags":[],"name":"Hooks","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a tasty cucumber in my belly","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I'm very happy","arguments":[],"locations":[{"line":6,"column":10}]}]}}, + {"type":"pickle-accepted","pickle":{"tags":[],"name":"Hooks","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a tasty cucumber in my belly","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I'm very happy","arguments":[],"locations":[{"line":6,"column":10}]}]},"uri":"features/tasty-cucumber.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"actionLocation":{"uri":"step_definitions/tasty-cucumber.steps.ts","line":3}},{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":5},"actionLocation":{"uri":"step_definitions/tasty-cucumber.steps.ts","line":7}},{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":6},"actionLocation":{"uri":"step_definitions/tasty-cucumber.steps.ts","line":11}},{"actionLocation":{"uri":"step_definitions/tasty-cucumber.steps.ts","line":15}}],"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-finished","index":0,"result":{"duration":2,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-started","index":1,"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-finished","index":1,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-started","index":2,"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-finished","index":2,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-started","index":3,"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-step-finished","index":3,"result":{"duration":0,"status":"passed"},"testCase":{"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}}, + {"type":"test-case-finished","result":{"duration":2,"status":"passed"},"sourceLocation":{"uri":"features/tasty-cucumber.feature","line":3}}, + {"type":"test-run-finished","result":{"duration":2,"success":true}} +] diff --git a/packages/cucumber/spec/listeners/samples/scenario-with-pending-steps.json b/packages/cucumber/spec/listeners/samples/scenario-with-pending-steps.json new file mode 100644 index 00000000000..6f0bbe80765 --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-with-pending-steps.json @@ -0,0 +1,15 @@ +[ + {"type":"source","uri":"features/pending-steps.feature","data":"Feature: Event Protocol\n\n Scenario: Pending steps\n\n Given I have a pending step\n Then I should implement it\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/pending-steps.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"Scenario","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"Pending steps","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I have a pending step"},{"type":"Step","location":{"line":6,"column":5},"keyword":"Then ","text":"I should implement it"}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/pending-steps.feature","pickle":{"tags":[],"name":"Pending steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a pending step","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should implement it","arguments":[],"locations":[{"line":6,"column":10}]}]}}, + {"type":"pickle-accepted","pickle":{"tags":[],"name":"Pending steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have a pending step","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should implement it","arguments":[],"locations":[{"line":6,"column":10}]}]},"uri":"features/pending-steps.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/pending-steps.feature","line":5},"actionLocation":{"uri":"step_definitions/pending.steps.ts","line":3}},{"sourceLocation":{"uri":"features/pending-steps.feature","line":6},"actionLocation":{"uri":"step_definitions/pending.steps.ts","line":7}}],"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/pending-steps.feature","line":3}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}}, + {"type":"test-step-finished","index":0,"result":{"duration":4,"status":"pending"},"testCase":{"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}}, + {"type":"test-step-started","index":1,"testCase":{"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}}, + {"type":"test-step-finished","index":1,"result":{"status":"skipped"},"testCase":{"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}}, + {"type":"test-case-finished","result":{"duration":4,"status":"pending"},"sourceLocation":{"uri":"features/pending-steps.feature","line":3}}, + {"type":"test-run-finished","result":{"duration":4,"success":false}} +] diff --git a/packages/cucumber/spec/listeners/samples/scenario-with-undefined-steps.json b/packages/cucumber/spec/listeners/samples/scenario-with-undefined-steps.json new file mode 100644 index 00000000000..b129d98be4a --- /dev/null +++ b/packages/cucumber/spec/listeners/samples/scenario-with-undefined-steps.json @@ -0,0 +1,15 @@ +[ + {"type":"source","uri":"features/undefined-steps.feature","data":"Feature: Event Protocol\n\n Scenario: Undefined steps\n\n Given I have an undefined step\n Then I should implement it\n","media":{"encoding":"utf-8","type":"text/x.cucumber.gherkin+plain"}}, + {"type":"gherkin-document","uri":"features/undefined-steps.feature","document":{"type":"GherkinDocument","feature":{"type":"Feature","tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Event Protocol","children":[{"type":"Scenario","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"Undefined steps","steps":[{"type":"Step","location":{"line":5,"column":5},"keyword":"Given ","text":"I have an undefined step"},{"type":"Step","location":{"line":6,"column":5},"keyword":"Then ","text":"I should implement it"}]}]},"comments":[]}}, + {"type":"pickle","uri":"features/undefined-steps.feature","pickle":{"tags":[],"name":"Undefined steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have an undefined step","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should implement it","arguments":[],"locations":[{"line":6,"column":10}]}]}}, + {"type":"pickle-accepted","pickle":{"tags":[],"name":"Undefined steps","language":"en","locations":[{"line":3,"column":3}],"steps":[{"text":"I have an undefined step","arguments":[],"locations":[{"line":5,"column":11}]},{"text":"I should implement it","arguments":[],"locations":[{"line":6,"column":10}]}]},"uri":"features/undefined-steps.feature"}, + {"type":"test-run-started"}, + {"type":"test-case-prepared","steps":[{"sourceLocation":{"uri":"features/undefined-steps.feature","line":5}},{"sourceLocation":{"uri":"features/undefined-steps.feature","line":6}}],"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}, + {"type":"test-case-started","sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}, + {"type":"test-step-started","index":0,"testCase":{"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}}, + {"type":"test-step-finished","index":0,"result":{"status":"undefined"},"testCase":{"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}}, + {"type":"test-step-started","index":1,"testCase":{"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}}, + {"type":"test-step-finished","index":1,"result":{"status":"undefined"},"testCase":{"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}}, + {"type":"test-case-finished","result":{"duration":0,"status":"undefined"},"sourceLocation":{"uri":"features/undefined-steps.feature","line":3}}, + {"type":"test-run-finished","result":{"duration":0,"success":false}} +] diff --git a/packages/cucumber/src/gherkin/model/Hook.ts b/packages/cucumber/src/gherkin/model/Hook.ts new file mode 100644 index 00000000000..728381be5b3 --- /dev/null +++ b/packages/cucumber/src/gherkin/model/Hook.ts @@ -0,0 +1,4 @@ +import { FeatureFileNode } from './FeatureFileNode'; + +export class Hook extends FeatureFileNode { +} diff --git a/packages/cucumber/src/gherkin/model/Scenario.ts b/packages/cucumber/src/gherkin/model/Scenario.ts index 8bd3d4a4de7..9838b184506 100644 --- a/packages/cucumber/src/gherkin/model/Scenario.ts +++ b/packages/cucumber/src/gherkin/model/Scenario.ts @@ -2,6 +2,7 @@ import { FileSystemLocation } from '@serenity-js/core/lib/io'; import { Description, Name, Tag } from '@serenity-js/core/lib/model'; import { FeatureFileNode } from './FeatureFileNode'; +import { Hook } from './Hook'; import { Step } from './Step'; export class Scenario extends FeatureFileNode { @@ -9,7 +10,7 @@ export class Scenario extends FeatureFileNode { location: FileSystemLocation, name: Name, public readonly description: Description, - public readonly steps: Step[], + public readonly steps: Array, public readonly tags: Tag[] = [], public readonly outline?: FileSystemLocation, ) { diff --git a/packages/cucumber/src/gherkin/model/index.ts b/packages/cucumber/src/gherkin/model/index.ts index bc61d7d34a3..6e068e4f2e0 100644 --- a/packages/cucumber/src/gherkin/model/index.ts +++ b/packages/cucumber/src/gherkin/model/index.ts @@ -1,5 +1,6 @@ export * from './Background'; export * from './Feature'; +export * from './Hook'; export * from './Scenario'; export * from './ScenarioOutline'; export * from './Step'; diff --git a/packages/cucumber/src/listeners/CucumberEventProtocolAdapter.ts b/packages/cucumber/src/listeners/CucumberEventProtocolAdapter.ts index 2e85dd8f040..677d8b8ccfe 100644 --- a/packages/cucumber/src/listeners/CucumberEventProtocolAdapter.ts +++ b/packages/cucumber/src/listeners/CucumberEventProtocolAdapter.ts @@ -1,23 +1,33 @@ import { AssertionError, ImplementationPendingError, TestCompromisedError, UnknownError } from '@serenity-js/core/lib/errors'; -import { ErrorSerialiser, Path } from '@serenity-js/core/lib/io'; +import { ErrorSerialiser, FileSystemLocation, Path } from '@serenity-js/core/lib/io'; import { ExecutionCompromised, ExecutionFailedWithAssertionError, ExecutionFailedWithError, ExecutionSkipped, ExecutionSuccessful, - ImplementationPending, + ImplementationPending, Name, Outcome, } from '@serenity-js/core/lib/model'; import { AmbiguousStepDefinitionError } from '../errors'; -import { Feature, ScenarioOutline } from '../gherkin'; +import { Feature, Hook, ScenarioOutline, Step } from '../gherkin'; import { Scenario } from '../gherkin/model'; import { CucumberFormatterOptions } from './CucumberFormatterOptions'; import { Dependencies } from './Dependencies'; import { ensure, isDefined } from 'tiny-types'; +interface Location { + uri: string; + line: number; +} + +interface StepLocations { + actionLocation?: Location; + sourceLocation?: Location; +} + export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Dependencies) { return class CucumberEventProtocolAdapter { @@ -35,6 +45,55 @@ export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Depend cache.set(path, mapper.map(document, path)); }); + eventBroadcaster.on('test-case-prepared', ({ steps, sourceLocation }: { + steps: StepLocations[], + sourceLocation: Location, + }) => { + ensure('test-case-prepared :: steps', steps, isDefined()); + ensure('test-case-prepared :: sourceLocation', sourceLocation, isDefined()); + + const + path = new Path(sourceLocation.uri), + map = cache.get(path), + scenario = map.get(Scenario).onLine(sourceLocation.line); + + if (!! scenario.outline) { + const outline = map.get(ScenarioOutline).onLine(scenario.outline.line); + + map.set(new ScenarioOutline( + outline.location, + outline.name, + outline.description, + interleaveStepsAndHooks(outline.steps, steps), + outline.parameters, + )).onLine(scenario.outline.line); + } else { + map.set(new Scenario( + scenario.location, + scenario.name, + scenario.description, + interleaveStepsAndHooks(scenario.steps, steps), + scenario.tags, + scenario.outline, + )).onLine(sourceLocation.line); + } + }); + + function interleaveStepsAndHooks(steps: Step[], stepsLocations: StepLocations[]): Array { + const + isAHook = (stepLocations: StepLocations) => + stepLocations.actionLocation && ! stepLocations.sourceLocation, + matching = (location: StepLocations) => + (step: Step) => + step.location.path.value === location.sourceLocation.uri && step.location.line === location.sourceLocation.line; + + return stepsLocations.map(location => + isAHook(location) + ? new Hook(new FileSystemLocation(new Path(location.actionLocation.uri), location.actionLocation.line), new Name('Setup')) + : steps.find(matching(location)), + ); + } + eventBroadcaster.on('test-case-started', ({ sourceLocation }) => { ensure('test-case-started :: sourceLocation', sourceLocation, isDefined()); @@ -60,7 +119,7 @@ export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Depend scenario = map.get(Scenario).onLine(testCase.sourceLocation.line), step = scenario.steps[index]; - if (!! step) { + if (step instanceof Step) { // ignore hooks notifier.stepStarts(step); } }); @@ -76,7 +135,7 @@ export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Depend scenario = map.get(Scenario).onLine(testCase.sourceLocation.line), step = scenario.steps[index]; - if (!! step) { + if (step instanceof Step) { // ignore hooks notifier.stepFinished(step, this.outcomeFrom(result)); } }); @@ -122,24 +181,15 @@ export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Depend // tslint:enable:switch-default } - errorFrom(exception: Error | string) { - if (exception instanceof Error) { - return exception; + errorFrom(exception: Error | string): Error { + switch (true) { + case exception instanceof Error: + return exception as Error; + case typeof exception === 'string' && exception.startsWith('Multiple step definitions match'): + return new AmbiguousStepDefinitionError(exception as string); + default: + return ErrorSerialiser.deserialiseFromStackTrace(exception as string); } - - if (typeof exception === 'string') { - switch (true) { - case exception.startsWith('Multiple step definitions match'): - return new AmbiguousStepDefinitionError(exception); - default: - return ErrorSerialiser.deserialiseFromStackTrace(exception); - } - } - - const message = `Cucumber has reported the following error, which Serenity/JS didn't recognise: "${ exception }"`; - this.log(message, exception); - - return new UnknownError(message); } }; } diff --git a/packages/cucumber/src/listeners/index.ts b/packages/cucumber/src/listeners/index.ts index ba83b67b0ec..c8a257a03a5 100644 --- a/packages/cucumber/src/listeners/index.ts +++ b/packages/cucumber/src/listeners/index.ts @@ -1,4 +1,4 @@ -import { ConfigurationError, serenity } from '@serenity-js/core'; +import { ConfigurationError, Serenity } from '@serenity-js/core'; import { Path, Version } from '@serenity-js/core/lib/io'; import Gherkin = require('gherkin'); @@ -6,7 +6,7 @@ import Gherkin = require('gherkin'); import { Cache, FeatureFileLoader, FeatureFileMap, FeatureFileMapper, FeatureFileParser } from '../gherkin'; import { Notifier } from '../notifier'; -export function listenerForCucumber(version: Version, cucumber: any) { +export function listenerForCucumber(version: Version, cucumber: any, serenity: Serenity) { try { const diff --git a/packages/cucumber/src/register.ts b/packages/cucumber/src/register.ts index fdbfc712433..1852431a1eb 100644 --- a/packages/cucumber/src/register.ts +++ b/packages/cucumber/src/register.ts @@ -1,3 +1,4 @@ +import { serenity } from '@serenity-js/core'; import { ModuleLoader } from '@serenity-js/core/lib/io'; import { listenerForCucumber } from './listeners'; @@ -6,4 +7,4 @@ const loader = new ModuleLoader(process.cwd()); const version = loader.versionOf('cucumber'); const cucumber = loader.require('cucumber'); -export = listenerForCucumber(version, cucumber); +export = listenerForCucumber(version, cucumber, serenity);