Skip to content
Permalink
Browse files
feat(protractor): TakeScreenshot allows the actor to capture screensh…
…ots at any point during the sce
  • Loading branch information
jan-molak committed Nov 9, 2019
1 parent d1ce1a1 commit 1d07075d2f467edd094fafdf53b149b3debd37a1
@@ -32,6 +32,7 @@
"dependencies": {
"cuid": "2.1.1",
"error-stack-parser": "2.0.2",
"filenamify": "^4.1.0",
"graceful-fs": "4.1.11",
"moment": "2.22.2",
"semver": "^6.0.0",
@@ -41,11 +42,11 @@
"devDependencies": {
"@documentation/esdoc-template": "2.0.1-alpha.85",
"@types/cuid": "1.3.0",
"@types/filenamify": "^2.0.2",
"@types/mkdirp": "0.5.2",
"@types/mocha": "^5.2.6",
"@types/sanitize-filename": "1.1.28",
"@types/semver": "^6.0.0",
"memfs": "2.15.5"
"memfs": "^2.16.1"
},
"repository": {
"type": "git",
@@ -20,8 +20,7 @@ describe ('FileSystem', () => {
}),
out = new FileSystem(processCWD, fs);

return out.store(new Path('outlet/some.json'), JSON.stringify(originalJSON)).then(absolutePath => {

return expect(out.store(new Path('outlet/some.json'), JSON.stringify(originalJSON))).to.be.fulfilled.then(absolutePath => {
expect(fs.existsSync(absolutePath.value)).to.equal(true);
expect(jsonFrom(fs.readFileSync(absolutePath.value))).to.eql(originalJSON);
});
@@ -35,7 +34,7 @@ describe ('FileSystem', () => {
out = new FileSystem(processCWD, fs),
dest = new Path('outlet/some.json');

return out.store(dest, JSON.stringify(originalJSON)).
return expect(out.store(dest, JSON.stringify(originalJSON))).to.be.fulfilled.
then(result => expect(result.equals(processCWD.resolve(dest))));
});

@@ -62,7 +61,7 @@ describe ('FileSystem', () => {
}),
out = new FileSystem(processCWD, fs);

return out.store(new Path('outlet/some.png'), imageBuffer).then(absolutePath => {
return expect(out.store(new Path('outlet/some.png'), imageBuffer)).to.be.fulfilled.then(absolutePath => {
expect(fs.existsSync(absolutePath.value)).to.equal(true);
expect(pictureAt(fs.readFileSync(absolutePath.value))).to.eql(image);
});
@@ -76,7 +75,7 @@ describe ('FileSystem', () => {
out = new FileSystem(processCWD, fs),
dest = new Path('outlet/some.png');

return out.store(dest, imageBuffer).then(absolutePath => {
return expect(out.store(dest, imageBuffer)).to.be.fulfilled.then(absolutePath => {
expect(absolutePath.equals(processCWD.join(dest))).to.equal(true);
});
});
@@ -100,7 +99,7 @@ describe ('FileSystem', () => {
}),
out = new FileSystem(processCWD, fs);

return out.remove(new Path('outlet/subdir/file-to-be-deleted.json')).then(() => {
return expect(out.remove(new Path('outlet/subdir/file-to-be-deleted.json'))).to.be.fulfilled.then(() => {

expect(fs.existsSync(processCWD.join(new Path('outlet/subdir/file-to-be-deleted.json')).value)).to.equal(false);
expect(fs.existsSync(processCWD.join(new Path('outlet/subdir/file-not-to-be-deleted.json')).value)).to.equal(true);
@@ -126,7 +125,7 @@ describe ('FileSystem', () => {
}),
out = new FileSystem(processCWD, fs);

return out.remove(new Path('outlet/subdir')).then(() => {
return expect(out.remove(new Path('outlet/subdir'))).to.be.fulfilled.then(() => {

expect(fs.existsSync(processCWD.join(new Path('outlet/subdir/file-to-be-deleted.json')).value)).to.equal(false);
expect(fs.existsSync(processCWD.join(new Path('outlet/subdir')).value)).to.equal(false);
@@ -1,17 +1,28 @@
import 'mocha';
import * as sinon from 'sinon';

import { Actor, Interaction } from '../../src/screenplay';

import { ActivityRelatedArtifactGenerated } from '../../src/events';
import { JSONData, Name } from '../../src/model';
import { Stage } from '../../src/stage';
import { Actor, Interaction } from '../../src/screenplay';
import { Stage, StageManager } from '../../src/stage';
import { Extras } from '../../src/stage/Extras';
import { expect } from '../expect';

describe('Interaction', () => {

const
stage = sinon.createStubInstance(Stage),
Ivonne = new Actor('Ivonne', stage as unknown as Stage);
let stage: Stage,
Ivonne: Actor,
stageManager: sinon.SinonStubbedInstance<StageManager>;

beforeEach(() => {
stageManager = sinon.createStubInstance(StageManager);

stage = new Stage(
new Extras(),
stageManager as unknown as StageManager,
);
Ivonne = new Actor('Ivonne', stage);
});

describe('when defining an interaction', () => {

@@ -75,20 +86,21 @@ describe('Interaction', () => {

/** @test {Interaction} */
it('can optionally emit an artifact to be attached to the report or stored', () => {
const spy = sinon.spy();
const
expectedArtifact = JSONData.fromJSON({ token: '123' }),
expectedArtifactName = new Name('Session Token');

const InteractWithTheSystem = () => Interaction.where(`#actor interacts with the system`, (actor: Actor) => {
spy(actor);

actor.collect(JSONData.fromJSON({ token: '123' }), new Name('Session Token'));
actor.collect(expectedArtifact, expectedArtifactName);
});

return expect(Ivonne.attemptsTo(
InteractWithTheSystem(),
))
.to.be.fulfilled
.then(() => {
expect(spy).to.have.been.calledWith(Ivonne);
expect(stageManager.notifyOf.args[1][0].name).to.equal(expectedArtifactName);
expect(stageManager.notifyOf.args[1][0].artifact).to.equal(expectedArtifact);
});
});
});
@@ -1,6 +1,7 @@
import * as sinon from 'sinon';
import { ImplementationPendingError } from '../../src/errors';
import { Actor, Interaction, Task } from '../../src/screenplay';
import { ActivityDetails, Name } from '../../src/model';
import { Activity, Actor, Interaction, Task } from '../../src/screenplay';
import { Stage } from '../../src/stage';
import { expect } from '../expect';

@@ -10,6 +11,9 @@ describe('Task', () => {

beforeEach(() => {
stage = sinon.createStubInstance(Stage);

// activityDetailsFor is a bit more involved than that, but this is a good approximation
stage.activityDetailsFor.callsFake((activity: Activity) => new ActivityDetails(new Name(activity.toString())));
});

const
@@ -3,8 +3,8 @@ import 'mocha';
import * as sinon from 'sinon';

import { InteractionFinished, InteractionStarts } from '../../src/events';
import { ExecutionSuccessful, Name, Timestamp } from '../../src/model';
import { Ability, Actor, See } from '../../src/screenplay';
import { ActivityDetails, ExecutionSuccessful, Name, Timestamp } from '../../src/model';
import { Ability, Activity, Actor, See } from '../../src/screenplay';
import { Stage } from '../../src/stage';
import { expect } from '../expect';
import {
@@ -29,6 +29,9 @@ describe('Actor', () => {
beforeEach(() => {
guitar = sinon.createStubInstance(AcousticGuitar);
stage = sinon.createStubInstance(Stage);

// activityDetailsFor is a bit more involved than that, but this is a good approximation
stage.activityDetailsFor.callsFake((activity: Activity) => new ActivityDetails(new Name(activity.toString())));
});

function actor(name: string) {
@@ -115,18 +118,18 @@ describe('Actor', () => {

let Bob: Actor;
const now = new Timestamp(new Date('2018-06-10T22:57:07.112Z'));
const activityName = new Name('Bob plays the chord of A');

beforeEach(() => {
stage = sinon.createStubInstance(Stage);
stage.currentTime.returns(now);
stage.activityDetailsFor.returns(new ActivityDetails(activityName));

Bob = new Actor('Bob', stage as unknown as Stage);
});

describe('announces the events that activities it performs', () => {

const activityName = new Name('Bob plays the chord of A');

/** @test {Actor} */
it('notifies when an activity begins and ends', () => Bob.whoCan(PlayAGuitar.suchAs(guitar)).attemptsTo(
PlayAChord.of(Chords.AMajor),
@@ -1,5 +1,6 @@
import * as sinon from 'sinon';
import { Actor, Note, Question, TakeNote, TakeNotes } from '../../../src/screenplay';
import { ActivityDetails, Name } from '../../../src/model';
import { Activity, Actor, Note, Question, TakeNote, TakeNotes } from '../../../src/screenplay';
import { Stage } from '../../../src/stage';
import { EnsureSame } from '../EnsureSame';

@@ -14,6 +15,8 @@ describe('TakeNote', () => {

beforeEach(() => {
stage = sinon.createStubInstance(Stage);
// activityDetailsFor is a bit more involved than that, but this is a good approximation
stage.activityDetailsFor.callsFake((activity: Activity) => new ActivityDetails(new Name(activity.toString())));

Noah = new Actor('Noah', stage as unknown as Stage).whoCan(TakeNotes.usingAnEmptyNotepad());
});
@@ -1,6 +1,7 @@
import * as sinon from 'sinon';
import { LogicError } from '../../../src/errors';
import { Ability, Actor, Log, Note, Question, TakeNotes } from '../../../src/screenplay';
import { ActivityDetails, Name } from '../../../src/model';
import { Ability, Activity, Actor, Log, Note, Question, TakeNotes } from '../../../src/screenplay';
import { Stage } from '../../../src/stage';
import { expect } from '../../expect';
import { EnsureSame } from '../EnsureSame';
@@ -15,6 +16,8 @@ describe('Note', () => {

beforeEach(() => {
stage = sinon.createStubInstance(Stage);
// activityDetailsFor is a bit more involved than that, but this is a good approximation
stage.activityDetailsFor.callsFake((activity: Activity) => new ActivityDetails(new Name(activity.toString())));

Noah = new Actor('Noah', stage as unknown as Stage)
.whoCan(TakeNotes.usingAnEmptyNotepad());
@@ -3,7 +3,7 @@ import 'mocha';
import * as sinon from 'sinon';

import { ConfigurationError, LogicError } from '../../src/errors';
import { Actor } from '../../src/screenplay';
import { Actor, Interaction } from '../../src/screenplay';
import { DressingRoom, Stage, StageManager } from '../../src/stage';
import { expect } from '../expect';

@@ -111,6 +111,40 @@ describe('Stage', () => {
});
});

describe('when correlating activities', () => {

const SomeActivity = () => Interaction.where(`#actor doesn't do much`, actor => void 0);

it('provides ActivityDetails for a given Activity', () => {
const
actors = new Extras(),
stage = new Stage(actors, stageManager as unknown as StageManager);

const details = stage.activityDetailsFor(SomeActivity(), stage.actor('Alice'));

expect(details.name.value).to.equal(`Alice doesn't do much`);
expect(details.correlationId.value).to.be.a('string'); // tslint:disable-line:no-unused-expression
});

it('allows for the recently ActivityDetails to be retrieved', () => {
const
actors = new Extras(),
stage = new Stage(actors, stageManager as unknown as StageManager);

const details = stage.activityDetailsFor(SomeActivity(), stage.actor('Alice'));

expect(stage.currentActivityDetails()).to.equal(details);
});

it('complains if ActivityDetails attempted to be retrieved before they have been generated', () => {
const
actors = new Extras(),
stage = new Stage(actors, stageManager as unknown as StageManager);

expect(() => stage.currentActivityDetails()).to.throw(LogicError, 'No activity is being performed. Did you call activityDetailsFor before invoking currentActivityDetails?');
});
});

describe('when an error occurs', () => {

it('complains when instantiated with no DressingRoom', () => {
@@ -39,8 +39,10 @@ describe('ArtifactArchiver', () => {

const
json = { key: 'value' },
jsonArtifactName = new Name('expected-json-artifact-name'),
pngArtifactName = new Name('expected-png-artifact-name');
jsonArtifactName = new Name('Scenario Name'),
expectedJsonFileName = 'scenario-name',
pngArtifactName = new Name('PNG Artifact name'),
expectedPngFileName = 'png-artifact-name';

/**
* @test {ArtifactArchiver}
@@ -82,7 +84,7 @@ describe('ArtifactArchiver', () => {

return stage.waitForNextCue().then(() => {
expect(fs.store).to.have.been.calledWith(
new Path(`scenario-report-b283bd69b0fcd75d754f678ac6685786.json`),
new Path(`scenario-${ expectedJsonFileName }.json`),
JSON.stringify(json),
);
});
@@ -100,7 +102,7 @@ describe('ArtifactArchiver', () => {

return stage.waitForNextCue().then(() => {
expect(fs.store).to.have.been.calledWith(
new Path(`photo-4fdc8acbf8f6c958b2726fc8ae435bf5.png`),
new Path(`photo-${ expectedPngFileName }.png`),
photo.base64EncodedValue,
'base64',
);
@@ -156,7 +158,7 @@ describe('ArtifactArchiver', () => {
const notifyOf = sinon.spy(stageManager, 'notifyOf');

stageManager.notifyOf(new ArtifactGenerated(
new Name('some report name'),
new Name('Some Report Name'),
TestReport.fromJSON({ key: 'value' }),
));

@@ -165,9 +167,9 @@ describe('ArtifactArchiver', () => {
const archived: ArtifactArchived = notifyOf.getCall(2).lastArg;

expect(archived).to.be.instanceOf(ArtifactArchived);
expect(archived.name).to.equal(new Name('some report name'));
expect(archived.name).to.equal(new Name('Some Report Name'));
expect(archived.type).to.equal(TestReport);
expect(archived.path).to.equal(new Path('scenario-report-b283bd69b0fcd75d754f678ac6685786.json'));
expect(archived.path).to.equal(new Path('scenario-some-report-name.json'));
});
});

@@ -23,7 +23,7 @@ export class ActivityRelatedArtifactArchived extends ArtifactArchived {
timestamp?: Timestamp,
) {
super(name, type, path, timestamp);
ensure('details', details, isDefined());
ensure('activity details', details, isDefined());
}

toJSON(): JSONObject {
@@ -20,6 +20,6 @@ export class ActivityRelatedArtifactGenerated extends ArtifactGenerated {
timestamp?: Timestamp,
) {
super(name, artifact, timestamp);
ensure('details', details, isDefined());
ensure('activity details', details, isDefined());
}
}
@@ -1,5 +1,6 @@
import { ensure, isDefined, isGreaterThan, property, TinyType } from 'tiny-types';

import filenamify = require('filenamify');
import path = require('upath');

export class Path extends TinyType {
@@ -12,7 +13,13 @@ export class Path extends TinyType {
super();
ensure(Path.name, value, isDefined(), property('length', isGreaterThan(0)));

this.value = path.normalize(value);
const normalised = path.normalize(value);

this.value = path.join(
path.dirname(normalised),
filenamify(path.basename(normalised), { replacement: '-' })
.replace(/[\s-]+/g, '-'),
);
}

join(another: Path) {
@@ -20,10 +20,7 @@ export class TrackedActivity implements Activity {
}

performAs(actor: (PerformsActivities | UsesAbilities | AnswersQuestions) & { name: string }): PromiseLike<void> {
const details = new ActivityDetails(
TrackedActivity.describer.describe(this.activity, actor),
CorrelationId.create(),
);
const details = this.stage.activityDetailsFor(this.activity, actor);

const [ activityStarts, activityFinished] = this.activity instanceof Interaction
? [ InteractionStarts, InteractionFinished ]

0 comments on commit 1d07075

Please sign in to comment.