Permalink
Browse files

feat(core): support for ES6-style task definitions

affects: @serenity-js/cucumber-2, serenity-js

- Instead of using the @step annotation it's enough for a task to define a toString() method, which will be used to determine its description #22
- RecordedTask and RecordedActivity are now replaced by RecordedActivity, which can also define the location where it was invoked to support #18
- A more functional-style DSL provided to make prototyping faster, for example, to create a task it's enough to write:  task_where('{0} proceeds to checkout') #21

ISSUES CLOSED: #21, #22
  • Loading branch information...
jan-molak committed Apr 26, 2017
1 parent 3afb8fc commit fff470a50e4ff78707ca388e06bc308ecfa1712e
@@ -42,7 +42,7 @@
},
"peerDependencies": {
"cucumber": "2.0.0-rc.9",
"serenity-js": ">= 1.2.5"
"serenity-js": ">= 1.4"
},
"devDependencies": {
"@types/cucumber": "2.0.0",
@@ -5,7 +5,6 @@ import {
Outcome,
RecordedActivity,
RecordedScene,
RecordedTask,
SceneFinished,
SceneStarts,
Tag,
@@ -17,7 +16,7 @@ import childProcess = require('child_process');
export class Spawned {
messages: any[] = [];
result: Promise<number>;
result: PromiseLike<number>;
constructor(pathToScript: string, args: string[], options: ForkOptions) {
@@ -39,8 +38,8 @@ export class Spawned {
function deserialised(event: any): DomainEvent<any> {
const tagsFrom = (tags: Tag[]) => tags.map(_ => new Tag(_.type, _.values)),
scene = ({ name, category, path, tags, id }: RecordedScene): RecordedScene => new RecordedScene(name, category, path, tagsFrom(tags), id),
activity = ({ name, id }: RecordedActivity): RecordedActivity => new RecordedTask(name, id),
scene = ({ name, category, location, tags, id }: RecordedScene): RecordedScene => new RecordedScene(name, category, location, tagsFrom(tags), id),
activity = ({ name, location, id }: RecordedActivity): RecordedActivity => new RecordedActivity(name, location, id),
outcome = <T>(type: (T) => T, { subject, result, error }: Outcome<T>) => new Outcome(type(subject), result, error);
switch (event.type) {
@@ -1,5 +1,5 @@
import { serenity } from 'serenity-js';
import { ActivityFinished, ActivityStarts, Outcome, RecordedScene, RecordedTask, Result, SceneFinished, SceneStarts, Tag } from 'serenity-js/lib/serenity/domain';
import { ActivityFinished, ActivityStarts, Outcome, RecordedActivity, RecordedScene, Result, SceneFinished, SceneStarts, Tag } from 'serenity-js/lib/serenity/domain';
import { DataTable, DocString, FailureException, Scenario, ScenarioResult, Step, StepArgument, StepResult } from './model';
const CucumberStep = require('cucumber/lib/models/step').default; // tslint:disable-line:no-var-requires
@@ -82,14 +82,14 @@ class CucumberScene extends RecordedScene {
super(
scenario.name,
scenario.feature.name,
scenario.uri,
{ path: scenario.uri, line: scenario.line },
scenario.tags.map(tag => Tag.from(tag.name)),
`${ scenario.feature.name }:${ scenario.line }:${ scenario.name }`,
);
}
}
class CucumberTask extends RecordedTask {
class CucumberTask extends RecordedActivity {
static from = (step: Step) => new CucumberTask(step);
private static fullNameOf = (step: Step): string => [
@@ -48,7 +48,7 @@
"@types/mkdirp": "0.3.29",
"@types/node": "6.0.63",
"@types/selenium-webdriver": "2.53.39",
"@types/stack-trace": "0.0.28",
"@types/stacktrace-js": "0.0.30",
"co": "4.6.0",
"glob": "7.1.1",
"graceful-fs": "4.1.11",
@@ -58,6 +58,7 @@
"moment": "2.17.1",
"selenium-webdriver": "3.0.1",
"stack-trace": "0.0.9",
"stacktrace-js": "1.3.1",
"ts-md5": "1.2.0",
"util-arity": "1.1.0"
},
@@ -7,8 +7,8 @@ import {
ActivityStarts,
DomainEvent,
Outcome,
RecordedActivity,
RecordedScene,
RecordedTask,
Result,
SceneFinished,
SceneStarts,
@@ -35,22 +35,22 @@ describe('serenity-protractor', () => {
scene_1 = new RecordedScene(
scenario_1,
feature,
featureFile,
{ path: featureFile },
),
scene_1_duration = 10,
scene_2 = new RecordedScene(
scenario_2,
feature,
featureFile,
{ path: featureFile },
),
scene_2_duration = 7,
logsIn = new RecordedTask('Logs in'),
logsIn = new RecordedActivity('Logs in'),
logsIn_duration = 10,
entersUsername = new RecordedTask('Enters username'),
entersUsername = new RecordedActivity('Enters username'),
entersUsername_duration = 6,
entersPassword = new RecordedTask('Enters password'),
entersPassword = new RecordedActivity('Enters password'),
entersPassword_duration = 4,
selectsAProduct = new RecordedTask('Selects a product');
selectsAProduct = new RecordedActivity('Selects a product');
const examplesOfSuccess = [ Result.SUCCESS, Result.PENDING, Result.IGNORED, Result.SKIPPED ],
examplesOfFailure = [ Result.FAILURE, Result.COMPROMISED, Result.ERROR ];
@@ -18,8 +18,8 @@ import {
Photo,
PhotoAttempted,
PhotoReceipt,
RecordedActivity,
RecordedScene,
RecordedTask,
Result,
SceneFinished,
SceneStarts,
@@ -42,7 +42,7 @@ describe('Photographer', () => {
fileSystem: any;
const
activity = new RecordedTask('Adds an item to the basket'),
activity = new RecordedActivity('Adds an item to the basket'),
photoName = 'photo.png',
photoPath = 'target/serenity/site/' + photoName,
now = 1469028588000;
@@ -248,7 +248,7 @@ describe('Photographer', () => {
it('is not interested in events other that the Start and Finish of an Activity', () => {
const scene = new RecordedScene('A user adds a product to their basket', 'Checkout', 'checkout.feature');
const scene = new RecordedScene('A user adds a product to their basket', 'Checkout');
thePhotographer.notifyOf(new SceneStarts(scene, now));
thePhotographer.notifyOf(new SceneFinished(new Outcome(scene, Result.SUCCESS), now));
@@ -1,6 +1,6 @@
import expect = require('../../../expect');
import { RecordedTask, Tag } from '../../../../src/serenity/domain/model';
import { RecordedActivity, Tag } from '../../../../src/serenity/domain/model';
describe ('Serenity Domain Model', () => {
@@ -34,28 +34,35 @@ describe ('Serenity Domain Model', () => {
});
});
describe ('Activity', () => {
describe ('RecordedActivity', () => {
it ('is comparable', () => {
const
a1 = new RecordedTask('Pays with a credit card'),
a2 = new RecordedTask('Pays with a credit card');
a1 = new RecordedActivity('Pays with a credit card'),
a2 = new RecordedActivity('Pays with a credit card');
expect(a1.equals(a2)).to.be.true;
expect(a1).to.deep.equal(a2);
});
it ('can be represented as string', () => {
const a = new RecordedTask('Pays with a credit card');
const a = new RecordedActivity('Pays with a credit card');
expect(a.toString()).to.equal('Pays with a credit card');
});
it ('can have a custom identifier', () => {
const a = new RecordedTask('Pays with a credit card', 'pays-with-a-credit-card');
it ('knows where it was invoked', () => {
const a = new RecordedActivity('Pays with a credit card', { path: '/some/path/to/script.ts', column: 10, line: 5 });
expect(a.toString()).to.equal('Pays with a credit card (id: pays-with-a-credit-card)');
expect(a).recorded.calledAt({ path: '/some/path/to/script.ts', column: 10, line: 5 });
});
it ('can have a custom id', () => {
const some_location = { path: '', column: 0, line: 0 };
const a = new RecordedActivity('Pays with a credit card', some_location, 'some-custom-id');
expect(a.id).to.equal('some-custom-id');
});
});
});
@@ -0,0 +1,111 @@
import sinon = require('sinon');
import expect = require('../../expect');
import { Actor, PerformsTasks, Task, task_where } from '../../../src/serenity/screenplay';
import { step } from '../../../src/serenity/recording';
import { Journal, StageManager } from '../../../src/serenity/stage/stage_manager';
describe ('When recording', () => {
let alice: Actor,
stage_manager: StageManager;
beforeEach(() => {
stage_manager = new StageManager(new Journal());
alice = new Actor('Alice', stage_manager);
});
describe ('Actor', () => {
describe('performing activities that follow', () => {
describe('Serenity BDD-style implementation', () => {
class Follow implements Task {
static the = (personOfInterest: string) => new Follow(personOfInterest);
@step('{0} follows the #personOfInterest')
performAs(actor: PerformsTasks): PromiseLike<void> {
return Promise.resolve();
}
constructor(private personOfInterest: string) {
}
}
it('notifies the Stage Manager when the activity starts and finishes', () => alice.attemptsTo(Follow.the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries).to.have.lengthOf(2);
expect(entries[ 0 ].value).to.deep.equal(entries[ 1 ].value.subject);
}));
it('notifies the Stage Manager of Activity\'s invocation location', () => alice.attemptsTo(Follow.the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries[ 0 ].value).to.be.recorded.as('Alice follows the white rabbit').calledAt({
line: 43,
column: 97,
path: '/recording.spec.ts',
});
}));
});
describe('ES6-style implementation', () => {
class Follow implements Task {
static the = (personOfInterest: string) => new Follow(personOfInterest);
performAs(actor: PerformsTasks): PromiseLike<void> {
return Promise.resolve();
}
toString = () => `{0} follows the ${ this.personOfInterest }`;
constructor(private personOfInterest: string) {
}
}
it('notifies the Stage Manager when the activity starts and finishes', () => alice.attemptsTo(Follow.the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries).to.have.lengthOf(2);
expect(entries[ 0 ].value).to.deep.equal(entries[ 1 ].value.subject);
}));
it('notifies the Stage Manager of Activity\'s invocation location', () => alice.attemptsTo(Follow.the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries[ 0 ].value).to.be.recorded.as('Alice follows the white rabbit').calledAt({
line: 77,
column: 97,
path: '/recording.spec.ts',
});
}));
});
describe('minimalist implementation', () => {
const follow_the = (person_of_interest: string) => task_where(`{0} follows the ${person_of_interest}`);
it('notifies the Stage Manager when the activity starts and finishes', () => alice.attemptsTo(follow_the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries).to.have.lengthOf(2);
expect(entries[ 0 ].value).to.deep.equal(entries[ 1 ].value.subject);
}));
it('notifies the Stage Manager of Activity\'s invocation location', () => alice.attemptsTo(follow_the('white rabbit')).then(() => {
const entries = stage_manager.readTheJournal();
expect(entries[ 0 ].value).to.be.recorded.as('Alice follows the white rabbit').calledAt({
line: 99,
column: 97,
path: '/recording.spec.ts',
});
}));
});
});
});
});
Oops, something went wrong.

0 comments on commit fff470a

Please sign in to comment.