Skip to content
Permalink
Browse files
fix(cucumber): Corrected how steps are reported for scenarios that us…
…e before/after hooks

See cucumber/cucumber-js#1195
  • Loading branch information
jan-molak committed Apr 23, 2019
1 parent 9599fe6 commit 65633098a81636d2fa6ef769231ca5a143bd12bb
@@ -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`)))
@@ -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');
});

Some generated files are not rendered by default. Learn more.

@@ -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'
*/
@@ -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> | 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);
});
}
});

0 comments on commit 6563309

Please sign in to comment.