From ade82e5ac3a6f4ac7c6489fc64742cf1d7ac72d7 Mon Sep 17 00:00:00 2001 From: jan-molak Date: Thu, 19 Jan 2017 00:55:22 +0000 Subject: [PATCH] feat(screenplay): `See` - a screenplay-style way of executing assertions See allows the Actor to perform an intra-flow verification and stop the scenario should the verification fail. Usage: `actor.attemptsTo(See.that(, (result) => expect(result).to.eventually.equal('foo')))` --- spec/api/screenplay/tasks/see.spec.ts | 37 +++++++++++++++++++ .../screenplay/questions/attribute.ts | 2 + .../screenplay/questions/selected_value.ts | 4 ++ .../screenplay/questions/text.ts | 4 ++ .../screenplay/questions/value.ts | 2 + .../screenplay/questions/web_element.ts | 2 + .../screenplay/questions/website.ts | 2 + src/serenity/screenplay/index.ts | 1 + src/serenity/screenplay/performables.ts | 4 +- src/serenity/screenplay/tasks/index.ts | 1 + src/serenity/screenplay/tasks/see.ts | 18 +++++++++ 11 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 spec/api/screenplay/tasks/see.spec.ts create mode 100644 src/serenity/screenplay/tasks/index.ts create mode 100644 src/serenity/screenplay/tasks/see.ts diff --git a/spec/api/screenplay/tasks/see.spec.ts b/spec/api/screenplay/tasks/see.spec.ts new file mode 100644 index 0000000000..1d42359290 --- /dev/null +++ b/spec/api/screenplay/tasks/see.spec.ts @@ -0,0 +1,37 @@ +import sinon = require('sinon'); +import expect = require('../../../expect'); +import { AssertionError } from 'chai'; + +import { Actor, Question, See, UsesAbilities } from '../../../../src/serenity/screenplay'; + +describe('Tasks', () => { + + describe('See', () => { + + class SomeResult implements Question { + answeredBy(actor: UsesAbilities): PromiseLike|string { + return Promise.resolve('some value'); + } + } + + it ('allows actor to verify a condition', () => { + let actor = Actor.named('James'); + + let promise = See. + that(new SomeResult(), r => expect(r).to.eventually.equal('some value')). + performAs(actor); + + return expect(promise).to.be.eventually.fulfilled; + }); + + it ('rejects the promise if the condition is not met', () => { + let actor = Actor.named('James'); + + let promise = See. + that(new SomeResult(), r => expect(r).to.eventually.equal('other value')). + performAs(actor); + + return expect(promise).to.be.eventually.rejectedWith(AssertionError, 'expected \'some value\' to equal \'other value\''); + }); + }); +}); diff --git a/src/serenity-protractor/screenplay/questions/attribute.ts b/src/serenity-protractor/screenplay/questions/attribute.ts index 082684a3db..cf9753a38d 100644 --- a/src/serenity-protractor/screenplay/questions/attribute.ts +++ b/src/serenity-protractor/screenplay/questions/attribute.ts @@ -24,4 +24,6 @@ class AttributeValue implements Question { constructor(private target: Target, private attribute: string) { } + + toString = () => `the value of the '${ this.attribute }' attribute of ${ this.target}`; } diff --git a/src/serenity-protractor/screenplay/questions/selected_value.ts b/src/serenity-protractor/screenplay/questions/selected_value.ts index 3a583cd92d..a973d4dde9 100644 --- a/src/serenity-protractor/screenplay/questions/selected_value.ts +++ b/src/serenity-protractor/screenplay/questions/selected_value.ts @@ -13,6 +13,8 @@ export class SelectedValue implements Question { constructor(private target: Target) { } + + toString = () => `the selected value of ${ this.target }`; } export class SelectedValues implements Question { @@ -26,4 +28,6 @@ export class SelectedValues implements Question { constructor(private target: Target) { } + + toString = () => `the selected values of ${ this.target }`; } diff --git a/src/serenity-protractor/screenplay/questions/text.ts b/src/serenity-protractor/screenplay/questions/text.ts index a05c861266..55403564ff 100644 --- a/src/serenity-protractor/screenplay/questions/text.ts +++ b/src/serenity-protractor/screenplay/questions/text.ts @@ -20,6 +20,8 @@ class TextOf implements Question { constructor(private target: Target) { } + + toString = () => `the text of ${ this.target}`; } class TextOfAll implements Question { @@ -32,4 +34,6 @@ class TextOfAll implements Question { constructor(private target: Target) { } + + toString = () => `the text of all ${ this.target}`; } diff --git a/src/serenity-protractor/screenplay/questions/value.ts b/src/serenity-protractor/screenplay/questions/value.ts index a187c935e7..a530527bfb 100644 --- a/src/serenity-protractor/screenplay/questions/value.ts +++ b/src/serenity-protractor/screenplay/questions/value.ts @@ -14,4 +14,6 @@ export class Value implements Question { constructor(private target: Target) { } + + toString = () => `the value of ${ this.target}`; } diff --git a/src/serenity-protractor/screenplay/questions/web_element.ts b/src/serenity-protractor/screenplay/questions/web_element.ts index 5b1a4038d2..af17b310ca 100644 --- a/src/serenity-protractor/screenplay/questions/web_element.ts +++ b/src/serenity-protractor/screenplay/questions/web_element.ts @@ -16,4 +16,6 @@ export class WebElement implements Question { constructor(private target: Target) { } + + toString = () => `the element representing ${ this.target }`; } diff --git a/src/serenity-protractor/screenplay/questions/website.ts b/src/serenity-protractor/screenplay/questions/website.ts index 58b6b099c3..2572789609 100644 --- a/src/serenity-protractor/screenplay/questions/website.ts +++ b/src/serenity-protractor/screenplay/questions/website.ts @@ -12,4 +12,6 @@ class WebsiteTitle implements Question { answeredBy(actor: UsesAbilities): PromiseLike { return BrowseTheWeb.as(actor).getTitle(); } + + toString = () => `the title of the current page`; } diff --git a/src/serenity/screenplay/index.ts b/src/serenity/screenplay/index.ts index 81b60499ef..09095cfe98 100644 --- a/src/serenity/screenplay/index.ts +++ b/src/serenity/screenplay/index.ts @@ -1,3 +1,4 @@ export * from './actor'; export * from './performables'; export * from './question'; +export * from './tasks'; diff --git a/src/serenity/screenplay/performables.ts b/src/serenity/screenplay/performables.ts index a7eacbbbc3..d2e94c5cf0 100644 --- a/src/serenity/screenplay/performables.ts +++ b/src/serenity/screenplay/performables.ts @@ -1,4 +1,4 @@ -import { PerformsTasks, UsesAbilities } from './actor'; +import { AnswersQuestions, PerformsTasks, UsesAbilities } from './actor'; export interface Task extends Performable { performAs(actor: PerformsTasks): PromiseLike; @@ -9,5 +9,5 @@ export interface Interaction extends Performable { } export interface Performable { - performAs(actor: PerformsTasks | UsesAbilities): PromiseLike; + performAs(actor: PerformsTasks | UsesAbilities | AnswersQuestions): PromiseLike; } diff --git a/src/serenity/screenplay/tasks/index.ts b/src/serenity/screenplay/tasks/index.ts new file mode 100644 index 0000000000..78359efb97 --- /dev/null +++ b/src/serenity/screenplay/tasks/index.ts @@ -0,0 +1 @@ +export * from './see'; diff --git a/src/serenity/screenplay/tasks/see.ts b/src/serenity/screenplay/tasks/see.ts new file mode 100644 index 0000000000..c4dd8e0329 --- /dev/null +++ b/src/serenity/screenplay/tasks/see.ts @@ -0,0 +1,18 @@ +import { AnswersQuestions, Performable, Question } from '..'; +import { step } from '../../recording/step_annotation'; + +export type AssertionFn = (subject: S) => PromiseLike; + +export class See implements Performable { + static that(subject: Question, verifier: AssertionFn) { + return new See(subject, verifier); + } + + @step('{0} looks at #question') + performAs(actor: AnswersQuestions): PromiseLike { + return this.assert(actor.toSee(this.question)); + } + + constructor(private question: Question, private assert: AssertionFn) { + } +}