Skip to content
Permalink
Browse files
fix(serenity-bdd): support for out-of-order events in SerenityBDDRepo…
…rter

In cases where Cucumber interrupts execution of an already started Serenity/JS activity (i.e. when a
Cucumber step times out before actor finishes what they're doing), some internal domain events could
arrive during the subsequnt scenario, confusing SerenityBDDReporter and making it throw a bit of a
cryptic error along the lines of "Cannot read property 'children' of undefined". This new
implementation of the reporter resolves this issue by aggregating scenario-specific events before
they're analysed, making sure they're ordered correctly, and generating the report only when all the
events are available.

Closes #518
  • Loading branch information
jan-molak committed Oct 24, 2020
1 parent 1ccdc99 commit 77db83e722602b1f39aba7cde113bfb5724842b5
Show file tree
Hide file tree
Showing 95 changed files with 1,344 additions and 1,027 deletions.
@@ -4,7 +4,7 @@ const path = require('path');
module.exports = {
source: 'src',
includes: ['\\.ts$', '\\.js$'],
// excludes: ['^.nyc', '^lib', '^node_modules', '^spec'],
excludes: ['.*serenity-bdd-reporter/processors.*'],
destination: './target/site',
plugins: [
{ name: 'esdoc-lint-plugin', option: {enable: true} },
@@ -38,7 +38,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A passing scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name('Given a step that passes')))
.next(ActivityFinished, event => {
@@ -42,7 +42,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('An assertion failure scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).to.equal(new Name('Cucumber')))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises a scenario failing due to an assertion error')))
.next(ActivityStarts, event => expect(event.details.name).to.equal(new Name('Given a step that fails with assertion error')))
.next(ActivityFinished, event => {
@@ -41,7 +41,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A failing scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).to.equal(new Name('Cucumber')))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises a failing scenario')))
.next(ActivityStarts, event => expect(event.details.name).to.equal(new Name('Given a step that fails with generic error')))
.next(ActivityFinished, event => expect(event.outcome).to.be.instanceOf(ExecutionFailedWithError))
@@ -41,7 +41,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A passing scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name('Given a step that passes')))
.next(ActivityFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
@@ -54,7 +54,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A scenario with steps marked as pending')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name(`Given a step that's marked as pending`)))
.next(ActivityFinished, event => expect(event.outcome.constructor).to.equal(ImplementationPending))
@@ -98,7 +98,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A scenario with steps that have not been implemented yet')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name(`Given a step that hasn't been implemented yet`)))
.next(ActivityFinished, event => expect(event.outcome.constructor).to.equal(ImplementationPending))
@@ -134,7 +134,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A scenario which tag marks it as pending')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name(`Given step number one that passes`)))
.next(ActivityFinished, event => expect(event.outcome).to.equal(new ExecutionSkipped()))
@@ -167,7 +167,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A scenario which tag marks it as pending')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).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.details.name).to.equal(new Name(`Given step number one that passes`)))
.next(ActivityFinished, event => expect(event.outcome).to.equal(new ExecutionSkipped()))
@@ -48,16 +48,16 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneSequenceDetected, event => {
expect(event.value.name).to.equal(expectedScenarioName);
expect(event.value.category).to.equal(expectedScenarioCategory);
expect(event.value.location.line).to.equal(outlineLine);
expect(event.details.name).to.equal(expectedScenarioName);
expect(event.details.category).to.equal(expectedScenarioCategory);
expect(event.details.location.line).to.equal(outlineLine);
})
.next(SceneParametersDetected, event => {
expect(event.scenario.name).to.equal(expectedScenarioName);
expect(event.scenario.category).to.equal(expectedScenarioCategory);
expect(event.value.name).to.equal(expectedExamplesName);
expect(event.value.description).to.equal(expectedExamplesDescription);
expect(event.value.values).to.deep.equal({ result: 'passes' });
expect(event.details.name).to.equal(expectedScenarioName);
expect(event.details.category).to.equal(expectedScenarioCategory);
expect(event.parameters.name).to.equal(expectedExamplesName);
expect(event.parameters.description).to.equal(expectedExamplesDescription);
expect(event.parameters.values).to.deep.equal({ result: 'passes' });
})
.next(SceneStarts, event => {
expect(event.details.name).to.equal(expectedScenarioName);
@@ -70,16 +70,16 @@ describe('@serenity-js/cucumber', function () {
expect(event.details.location.line).to.equal(firstScenarioLine);
})
.next(SceneSequenceDetected, event => {
expect(event.value.name).to.equal(expectedScenarioName);
expect(event.value.category).to.equal(expectedScenarioCategory);
expect(event.value.location.line).to.equal(outlineLine);
expect(event.details.name).to.equal(expectedScenarioName);
expect(event.details.category).to.equal(expectedScenarioCategory);
expect(event.details.location.line).to.equal(outlineLine);
})
.next(SceneParametersDetected, event => {
expect(event.scenario.name).to.equal(expectedScenarioName);
expect(event.scenario.category).to.equal(expectedScenarioCategory);
expect(event.value.name).to.equal(expectedExamplesName);
expect(event.value.description).to.equal(expectedExamplesDescription);
expect(event.value.values).to.deep.equal({ result: 'fails with generic error' });
expect(event.details.name).to.equal(expectedScenarioName);
expect(event.details.category).to.equal(expectedScenarioCategory);
expect(event.parameters.name).to.equal(expectedExamplesName);
expect(event.parameters.description).to.equal(expectedExamplesDescription);
expect(event.parameters.values).to.deep.equal({ result: 'fails with generic error' });
})
.next(SceneStarts, event => {
expect(event.details.name).to.equal(expectedScenarioName);
@@ -41,7 +41,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A screenplay scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).to.equal(new Name('Cucumber')))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises Screenplay activities')))
// before step
.next(ActivityStarts, event => expect(event.details.name).to.equal(new Name('Lara makes an arrow')))
@@ -41,7 +41,7 @@ describe('@serenity-js/cucumber', function () {

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name('A timed out scenario')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Cucumber')))
.next(TestRunnerDetected, event => expect(event.name).to.equal(new Name('Cucumber')))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('Serenity/JS recognises a timed out scenario')))
.next(ActivityStarts, event => expect(event.details.name).to.equal(new Name('Given a step that times out')))
.next(ActivityFinished, event => {
@@ -28,10 +28,10 @@ describe('@serenity-js/jasmine', function () {
expect(res.exitCode).to.equal(1);

PickEvent.from(res.events)
.next(TestSuiteStarts, event => expect(event.value.name).to.equal(new Name(`a suite`)))
.next(TestSuiteStarts, event => expect(event.details.name).to.equal(new Name(`a suite`)))
.next(SceneStarts, event => expect(event.details.name).to.equal(new Name(`a spec`)))
.next(SceneTagged, event => expect(event.tag).to.equal(new FeatureTag('a suite')))
.next(TestRunnerDetected, event => expect(event.value).to.equal(new Name('Jasmine')))
.next(TestRunnerDetected, event => expect(event.name).to.equal(new Name('Jasmine')))
.next(SceneFinished, event => {
const outcome = event.outcome as ProblemIndication;
expect(outcome).to.be.instanceOf(ExecutionFailedWithError);
@@ -40,7 +40,7 @@ describe('@serenity-js/jasmine', function () {
expect(outcome.error.message).to.equal('Failed: spec'); // there's no message when the spec body is missing
})
.next(TestSuiteFinished, event => {
expect(event.value.name).to.equal(new Name(`a suite`));
expect(event.details.name).to.equal(new Name(`a suite`));
expect(event.outcome).to.be.instanceof(ExecutionFailedWithError);
expect((event.outcome as ExecutionFailedWithError).error.message).to.equal('Failed: suite beforeAll');
})
@@ -17,16 +17,16 @@ describe('@serenity-js/jasmine', function () {

PickEvent.from(res.events)
.next(TestSuiteStarts, event => {
expect(event.value.name).to.equal(new Name('Jasmine'));
expect(event.value.location.path.value).to.match(/location.spec.js$/);
expect(event.value.location.line).to.equal(1);
expect(event.details.name).to.equal(new Name('Jasmine'));
expect(event.details.location.path.value).to.match(/location.spec.js$/);
expect(event.details.location.line).to.equal(1);
// expect(event.value.location.column).to.equal(1);
})
.next(TestSuiteStarts, event => {
expect(event.value.name).to.equal(new Name('Detecting file system location'));
expect(event.value.location.path.value).to.match(/location.spec.js$/);
expect(event.value.location.line).to.equal(3);
expect(event.value.location.column).to.equal(5);
expect(event.details.name).to.equal(new Name('Detecting file system location'));
expect(event.details.location.path.value).to.match(/location.spec.js$/);
expect(event.details.location.line).to.equal(3);
expect(event.details.location.column).to.equal(5);
})
.next(SceneStarts, event => {
expect(event.details.name).to.equal(new Name('Detecting file system location works for both the suites and the individual specs'));
@@ -41,15 +41,15 @@ describe('@serenity-js/jasmine', function () {
expect(event.details.location.column).to.equal(9);
})
.next(TestSuiteFinished, event => {
expect(event.value.name).to.equal(new Name('Detecting file system location'));
expect(event.value.location.path.value).to.match(/location.spec.js$/);
expect(event.value.location.line).to.equal(3);
expect(event.value.location.column).to.equal(5);
expect(event.details.name).to.equal(new Name('Detecting file system location'));
expect(event.details.location.path.value).to.match(/location.spec.js$/);
expect(event.details.location.line).to.equal(3);
expect(event.details.location.column).to.equal(5);
})
.next(TestSuiteFinished, event => {
expect(event.value.name).to.equal(new Name('Jasmine'));
expect(event.value.location.path.value).to.match(/location.spec.js$/);
expect(event.value.location.line).to.equal(1);
expect(event.details.name).to.equal(new Name('Jasmine'));
expect(event.details.location.path.value).to.match(/location.spec.js$/);
expect(event.details.location.line).to.equal(1);
// expect(event.value.location.column).to.equal(1);
})
;

0 comments on commit 77db83e

Please sign in to comment.