Skip to content
Permalink
Browse files
fix(jasmine): corrected synchronisation of async events
This change ensures that any async events, such as storing the screenshots or restarting the
browser, complete before the scenario is marked as finished

re #405
  • Loading branch information
jan-molak committed Jan 22, 2020
1 parent c350e22 commit 38fd1c7ad5fc8396a8c2a4e9a68286cac7f033f7
@@ -1,8 +1,7 @@
import 'mocha';

import { expect, ifExitCodeIsOtherThan, logOutput, PickEvent } from '@integration/testing-tools';
import { InteractionStarts, SceneFinished, SceneStarts, SceneTagged, TestRunnerDetected } from '@serenity-js/core/lib/events';
import { ExecutionSuccessful, FeatureTag, Name } from '@serenity-js/core/lib/model';
import { AsyncOperationAttempted, AsyncOperationCompleted, InteractionStarts, SceneFinished, SceneFinishes, SceneStarts, TestRunFinished } from '@serenity-js/core/lib/events';
import { Description, ExecutionSuccessful, Name } from '@serenity-js/core/lib/model';
import 'mocha';
import { protractor } from '../src/protractor';

describe('@serenity-js/jasmine', function () {
@@ -13,7 +12,7 @@ describe('@serenity-js/jasmine', function () {
* - https://github.com/jan-molak/serenity-js/issues/56
*/

this.timeout(10000);
this.timeout(30000);

it('supports restarting the browser between test scenarios', () =>
protractor(
@@ -27,20 +26,31 @@ describe('@serenity-js/jasmine', function () {
expect(res.exitCode).to.equal(0);

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://version/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://version/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://accessibility/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://accessibility/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://chrome-urls/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://chrome-urls/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

.next(TestRunFinished, event => expect(event.timestamp).to.not.be.undefined)
;
}));

@@ -49,26 +59,37 @@ describe('@serenity-js/jasmine', function () {
'./examples/protractor.conf.js',
'--specs=examples/multiple_passing_scenarios.spec.js',
)
.then(ifExitCodeIsOtherThan(0, logOutput))
.then(res => {
.then(ifExitCodeIsOtherThan(0, logOutput))
.then(res => {

expect(res.exitCode).to.equal(0);
expect(res.exitCode).to.equal(0);

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://version/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

PickEvent.from(res.events)
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the first time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://version/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://accessibility/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the second time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://accessibility/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://chrome-urls/'`)))
.next(SceneFinishes, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(AsyncOperationAttempted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] Invoking ProtractorRunner.afterEach...')))
.next(AsyncOperationCompleted, event => expect(event.taskDescription).to.equal(new Description('[ProtractorReporter] ProtractorRunner.afterEach succeeded')))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))

.next(SceneStarts, event => expect(event.value.name).to.equal(new Name('A scenario passes the third time')))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine disables synchronisation with Angular`)))
.next(InteractionStarts, event => expect(event.value.name).to.equal(new Name(`Jasmine navigates to 'chrome://chrome-urls/'`)))
.next(SceneFinished, event => expect(event.outcome).to.equal(new ExecutionSuccessful()))
;
}));
.next(TestRunFinished, event => expect(event.timestamp).to.not.be.undefined)
;
}));
});
@@ -9,7 +9,7 @@ export class Serenity {
constructor(clock: Clock = new Clock()) {
this.stage = new Stage(
new Extras(),
new StageManager(Duration.ofSeconds(2), clock), // todo: cue timeout should be configurable
new StageManager(Duration.ofSeconds(5), clock), // todo: cue timeout should be configurable
);
}

@@ -0,0 +1,21 @@
import { ensure, isDefined, JSONObject } from 'tiny-types';

import { ScenarioDetails, Timestamp } from '../model';
import { DomainEvent } from './DomainEvent';

export class SceneFinishes extends DomainEvent {
static fromJSON(o: JSONObject) {
return new SceneFinishes(
ScenarioDetails.fromJSON(o.value as JSONObject),
Timestamp.fromJSON(o.timestamp as string),
);
}

constructor(
public readonly value: ScenarioDetails,
timestamp?: Timestamp,
) {
super(timestamp);
ensure('value', value, isDefined());
}
}
@@ -15,6 +15,7 @@ export * from './SceneBackgroundDetected';
export * from './SceneDescriptionDetected';
export * from './SceneStarts';
export * from './SceneFinished';
export * from './SceneFinishes';
export * from './SceneTagged';
export * from './SceneSequenceDetected';
export * from './SceneTemplateDetected';
@@ -267,7 +267,7 @@ describe('SerenityReporterForJasmine', () => {

describe('starts and', () => {

beforeEach(() => {
beforeEach(async () => {
reporter.suiteStarted({
id: 'suite1',
description: 'Jasmine',
@@ -312,7 +312,7 @@ describe('SerenityReporterForJasmine', () => {
},
});

reporter.specDone({
await reporter.specDone({
id: 'spec0',
description: 'passes',
fullName: 'Jasmine A scenario passes',
@@ -420,8 +420,8 @@ describe('SerenityReporterForJasmine', () => {
/**
* @test {SerenityReporterForJasmine#specDone}
*/
it('has been excluded', () => {
reporter.specDone({
it('has been excluded', async () => {
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -441,8 +441,8 @@ describe('SerenityReporterForJasmine', () => {
/**
* @test {SerenityReporterForJasmine#specDone}
*/
it('is marked as pending', () => {
reporter.specDone({
it('is marked as pending', async () => {
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -466,8 +466,8 @@ describe('SerenityReporterForJasmine', () => {
/**
* @test {SerenityReporterForJasmine#specDone}
*/
it('has failed with an error', () => {
reporter.specDone({
it('has failed with an error', async () => {
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -498,8 +498,8 @@ describe('SerenityReporterForJasmine', () => {
/**
* @test {SerenityReporterForJasmine#specDone}
*/
it('has failed with no stack trace', () => {
reporter.specDone({
it('has failed with no stack trace', async () => {
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -530,8 +530,8 @@ describe('SerenityReporterForJasmine', () => {
/**
* @test {SerenityReporterForJasmine#specDone}
*/
it('has failed with an assertion error', () => {
reporter.specDone({
it('has failed with an assertion error', async () => {
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -564,10 +564,10 @@ describe('SerenityReporterForJasmine', () => {
});

/** @test {SerenityReporterForJasmine#specDone} */
it('has failed with multiple errors', () => {
it('has failed with multiple errors', async () => {
// The failure with multiple errors could only happen when someone has
// a bare-bones protractor/jasmine setup and uses Serenity/JS just for the reporting
reporter.specDone({
await reporter.specDone({
id: 'spec0',
description: 'scenario',
fullName: 'scenario',
@@ -633,7 +633,7 @@ describe('SerenityReporterForJasmine', () => {

describe('the `it` block is not nested within a `describe` block and', () => {

beforeEach(() => {
beforeEach(async () => {
reporter.specStarted({
id: 'spec0',
description: 'A scenario passes',
@@ -650,7 +650,7 @@ describe('SerenityReporterForJasmine', () => {
},
});

reporter.specDone({
await reporter.specDone({
id: 'spec0',
description: 'A scenario passes',
fullName: 'A scenario passes',
@@ -74,15 +74,4 @@ describe('@serenity-js/jasmine', () => {
});
});
});

/**
* @test {bootstrap}
* @test {monkeyPatched}
*/
it('registers a beforeEach and afterAll hooks to ensure Serenity/JS is synchronised with Jasmine', () => {
serenityReporterForJasmine(jasmine);

expect(jasmine.getEnv().beforeEach).to.have.been.calledOnce; // tslint:disable-line:no-unused-expression
expect(jasmine.getEnv().afterAll).to.have.been.calledOnce; // tslint:disable-line:no-unused-expression
});
});
@@ -2,6 +2,7 @@ import { AssertionError, ImplementationPendingError, Serenity } from '@serenity-
import {
DomainEvent,
SceneFinished,
SceneFinishes,
SceneStarts,
SceneTagged,
TaskFinished,
@@ -67,7 +68,7 @@ export class SerenityReporterForJasmine {
);
}

specDone(result: SpecResult) {
specDone(result: SpecResult): Promise<void> {

/**
* Serenity doesn't allow for more than one failure per activity, but Jasmine does.
@@ -84,19 +85,26 @@ export class SerenityReporterForJasmine {
});
}

this.emit(new SceneFinished(
this.emit(new SceneFinishes(
this.scenarioDetailsOf(result),
this.outcomeFrom(result),
this.serenity.currentTime(),
));

return this.serenity.waitForNextCue()
.then(() => {
this.emit(new SceneFinished(
this.scenarioDetailsOf(result),
this.outcomeFrom(result),
this.serenity.currentTime(),
));
});
}

/**
* @param {JasmineDoneInfo} suiteInfo
*/
jasmineDone(suiteInfo: JasmineDoneInfo) {
this.emit(new TestRunFinished(this.serenity.currentTime()));

return this.serenity.waitForNextCue();
}

/**
@@ -25,8 +25,5 @@ export function bootstrap(jasmine = (global as any).jasmine) {
jasmine.Suite = monkeyPatched(jasmine.Suite);
jasmine.Spec = monkeyPatched(jasmine.Spec);

jasmine.getEnv().beforeEach(() => serenity.waitForNextCue());
jasmine.getEnv().afterAll(() => serenity.waitForNextCue());

return new SerenityReporterForJasmine(serenity);
}

0 comments on commit 38fd1c7

Please sign in to comment.