Skip to content
Permalink
Browse files
feat(protractor): ExecuteScript interactions and cleanup of the packa…
…ge structure
  • Loading branch information
jan-molak committed Mar 2, 2019
1 parent 2c36962 commit 753d5110707ee8dd2126c9070a2a96c1a918c31d
Showing with 770 additions and 244 deletions.
  1. +14 −13 packages/core/spec/io/formatted.spec.ts
  2. +8 −4 packages/core/src/io/formatted.ts
  3. +158 −0 packages/protractor/spec/screenplay/interactions/execute-script/ExecuteAsynchronousScript.spec.ts
  4. +57 −0 packages/protractor/spec/screenplay/interactions/execute-script/ExecuteScriptFromUrl.spec.ts
  5. +142 −0 packages/protractor/spec/screenplay/interactions/execute-script/ExecuteSynchronousScript.spec.ts
  6. +1 −0 packages/protractor/spec/screenplay/interactions/execute-script/resources/execute-script-sample.js
  7. +9 −0 ...ages/protractor/spec/screenplay/interactions/execute-script/resources/execute-script-sandbox.html
  8. +95 −1 packages/protractor/src/screenplay/abilities/BrowseTheWeb.ts
  9. +24 −0 packages/protractor/src/screenplay/interactions/execute-script/ExecuteAsynchronousScript.ts
  10. +25 −0 packages/protractor/src/screenplay/interactions/execute-script/ExecuteScript.ts
  11. +48 −0 packages/protractor/src/screenplay/interactions/execute-script/ExecuteScriptFromUrl.ts
  12. +59 −0 packages/protractor/src/screenplay/interactions/execute-script/ExecuteScriptWithArguments.ts
  13. +24 −0 packages/protractor/src/screenplay/interactions/execute-script/ExecuteSynchronousScript.ts
  14. +3 −0 packages/protractor/src/screenplay/interactions/execute-script/index.ts
  15. +1 −0 packages/protractor/src/screenplay/interactions/index.ts
  16. +1 −1 packages/protractor/src/screenplay/promiseOf.ts
  17. +2 −2 packages/protractor/src/screenplay/questions/CSSClasses.ts
  18. +1 −6 packages/protractor/src/screenplay/questions/Pick.ts
  19. +10 −0 packages/protractor/src/screenplay/questions/RelativeQuestion.ts
  20. +0 −152 packages/protractor/src/screenplay/questions/Target.ts
  21. +0 −61 packages/protractor/src/screenplay/questions/Text.ts
  22. +3 −2 packages/protractor/src/screenplay/questions/Value.ts
  23. +2 −2 packages/protractor/src/screenplay/questions/index.ts
  24. +23 −0 packages/protractor/src/screenplay/questions/text/Text.ts
  25. +31 −0 packages/protractor/src/screenplay/questions/text/TextOfMultipleElements.ts
  26. +28 −0 packages/protractor/src/screenplay/questions/text/TextOfSingleElement.ts
  27. +1 −0 packages/protractor/src/screenplay/questions/text/index.ts
@@ -17,19 +17,20 @@ describe ('`formatted` tag function', () => {
class SomeAttribute {}

given(
{ description: 'no parameters', actual: formatted `Hello World!`, expected: 'Hello World!' },
{ description: 'an undefined parameter', actual: formatted `param: ${ undefined }`, expected: 'param: undefined' },
{ description: 'a number parameter', actual: formatted `Answer: ${ 42 }`, expected: 'Answer: 42' },
{ description: 'a string parameter', actual: formatted `Hello ${ 'World' }!`, expected: "Hello 'World'!" },
{ description: 'an object parameter', actual: formatted `${ { twitter: '@JanMolak'} }`, expected: "{ twitter: '@JanMolak' }" },
{ description: 'an array parameter', actual: formatted `${ [1, 2, '3'] }`, expected: "[ 1, 2, '3' ]" },
{ description: 'an object array parameter', actual: formatted `${ [{ name: 'Jan'}] }`, expected: "[ { name: 'Jan' } ]" },
{ description: 'a Date parameter', actual: formatted `${ new Date('Jan 27, 2019') }`, expected: '2019-01-27T00:00:00.000Z' },
{ description: 'a promised parameter', actual: formatted `${ p('something') }`, expected: 'a promised value' },
{ description: 'a question', actual: formatted `${ q('value') }`, expected: 'the meaning of life' },
{ description: 'an inspectable object', actual: formatted `${ i('result') }`, expected: 'result' },
{ description: 'an "toStringable" object', actual: formatted `${ ts('result') }`, expected: 'result' },
{ description: 'a function parameter', actual: formatted `${ SomeAttribute }`, expected: 'SomeAttribute property' },
{ description: 'no parameters', actual: formatted `Hello World!`, expected: 'Hello World!' },
{ description: 'an undefined parameter', actual: formatted `param: ${ undefined }`, expected: 'param: undefined' },
{ description: 'a number parameter', actual: formatted `Answer: ${ 42 }`, expected: 'Answer: 42' },
{ description: 'a string parameter', actual: formatted `Hello ${ 'World' }!`, expected: "Hello 'World'!" },
{ description: 'an object parameter', actual: formatted `${ { twitter: '@JanMolak'} }`, expected: "{ twitter: '@JanMolak' }" },
{ description: 'an array parameter', actual: formatted `${ [1, 2, '3'] }`, expected: "[ 1, 2, '3' ]" },
{ description: 'an array of params', actual: formatted `${ [ Promise.resolve(1), q('2') ] }`, expected: '[ a promised value, the meaning of life ]' },
{ description: 'an object array parameter', actual: formatted `${ [{ name: 'Jan'}] }`, expected: "[ { name: 'Jan' } ]" },
{ description: 'a Date parameter', actual: formatted `${ new Date('Jan 27, 2019') }`, expected: '2019-01-27T00:00:00.000Z' },
{ description: 'a promised parameter', actual: formatted `${ p('something') }`, expected: 'a promised value' },
{ description: 'a question', actual: formatted `${ q('value') }`, expected: 'the meaning of life' },
{ description: 'an inspectable object', actual: formatted `${ i('result') }`, expected: 'result' },
{ description: 'an "toStringable" object', actual: formatted `${ ts('result') }`, expected: 'result' },
{ description: 'a function parameter', actual: formatted `${ SomeAttribute }`, expected: 'SomeAttribute property' },
).
it('produces a human-readable description when given a template with', ({ actual, expected }) => {
expect(actual).to.equal(expected);
@@ -32,6 +32,10 @@ function descriptionOf(value: KnowableUnknown<any>): string {
return inspect(value);
}

if (Array.isArray(value)) {
return `[ ${ value.map(item => descriptionOf(item)).join(', ') } ]`;
}

if (isAPromise(value)) {
return `a promised value`;
}
@@ -44,6 +48,10 @@ function descriptionOf(value: KnowableUnknown<any>): string {
return value.toISOString();
}

if (hasItsOwnToString(value)) {
return value.toString();
}

if (isInspectable(value)) {
return value.inspect();
}
@@ -52,10 +60,6 @@ function descriptionOf(value: KnowableUnknown<any>): string {
return `${ value.name } property`;
}

if (hasItsOwnToString(value)) {
return value.toString();
}

return inspect(value, { breakLength: Infinity, compact: true, sorted: false }).replace(/\r?\n/g, '');
}

@@ -0,0 +1,158 @@
import { expect } from '@integration/testing-tools';
import { Ensure, equals } from '@serenity-js/assertions';
import { Actor, Question } from '@serenity-js/core';
import { ActivityFinished, ActivityStarts, ArtifactGenerated } from '@serenity-js/core/lib/events';
import { Name, TextData } from '@serenity-js/core/lib/model';
import { Clock, StageManager } from '@serenity-js/core/lib/stage';

import { by, protractor } from 'protractor';
import * as sinon from 'sinon';
import { BrowseTheWeb, ExecuteScript, Navigate, Target, Value } from '../../../../src';
import { pageFromTemplate } from '../../../fixtures';

/** @test {ExecuteAsynchronousScript} */
describe('ExecuteAsynchronousScript', function() {
this.timeout(30000);

const Joe = Actor.named('Joe').whoCan(
BrowseTheWeb.using(protractor.browser),
);

const page = pageFromTemplate(`
<html>
<body>
<form>
<input type="text" id="name" />
</form>
</body>
</html>
`);

class Sandbox {
static Input = Target.the('input field').located(by.id('name'));
}

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
it('allows the actor to execute an asynchronous script', () => Joe.attemptsTo(
Navigate.to(page),

ExecuteScript.async(`
var callback = arguments[arguments.length - 1];
setTimeout(function() {
document.getElementById('name').value = 'Joe';
callback();
}, 100);
`),

Ensure.that(Value.of(Sandbox.Input), equals(Joe.name)),
));

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
it('allows the actor to execute an asynchronous script with a static argument', () => Joe.attemptsTo(
Navigate.to(page),

ExecuteScript.async(`
var name = arguments[0];
var callback = arguments[arguments.length - 1];
setTimeout(function() {
document.getElementById('name').value = name;
callback();
}, 100);
`).withArguments(Joe.name),

Ensure.that(Value.of(Sandbox.Input), equals(Joe.name)),
));

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
it('allows the actor to execute an asynchronous script with a promised argument', () => Joe.attemptsTo(
Navigate.to(page),

ExecuteScript.async(`
var name = arguments[0];
var callback = arguments[arguments.length - 1];
setTimeout(function() {
document.getElementById('name').value = name;
callback();
}, 100);
`).withArguments(Promise.resolve(Joe.name)),

Ensure.that(Value.of(Sandbox.Input), equals(Joe.name)),
));

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
it('allows the actor to execute an asynchronous script with a Target argument', () => Joe.attemptsTo(
Navigate.to(page),

ExecuteScript.async(`
var name = arguments[0];
var field = arguments[1];
var callback = arguments[arguments.length - 1];
setTimeout(function() {
field.value = name;
callback();
}, 100);
`).withArguments(Joe.name, Sandbox.Input),

Ensure.that(Value.of(Sandbox.Input), equals(Joe.name)),
));

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
/** @test {ExecuteAsynchronousScript#toString} */
it(`provides a sensible description of the interaction being performed when invoked without arguments`, () => {
expect(ExecuteScript.async(`
arguments[arguments.length - 1]();
`).toString()).to.equal(`#actor executes an asynchronous script`);
});

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript#toString} */
it(`provides a sensible description of the interaction being performed when invoked with arguments`, () => {
const arg3 = Question.about('arg number 3', actor => void 0);

expect(ExecuteScript.async(`arguments[arguments.length - 1]();`)
.withArguments(Promise.resolve('arg1'), 'arg2', arg3).toString(),
).to.equal(`#actor executes an asynchronous script with arguments: [ a promised value, 'arg2', arg number 3 ]`);
});

/** @test {ExecuteScript.async} */
/** @test {ExecuteAsynchronousScript} */
it('emits the events so that the details of the script being executed can be reported', () => {
const
frozenClock = new Clock(() => new Date('1970-01-01')),

stageManager = sinon.createStubInstance(StageManager),
actor = new Actor('Ashwin', stageManager as any, frozenClock)
.whoCan(BrowseTheWeb.using(protractor.browser));

return actor.attemptsTo(
ExecuteScript.async(`arguments[arguments.length - 1]();`),
).then(() => {
const events = stageManager.notifyOf.getCalls().map(call => call.lastArg);

expect(events).to.have.lengthOf(3);
expect(events[ 0 ]).to.be.instanceOf(ActivityStarts);
expect(events[ 1 ]).to.be.instanceOf(ArtifactGenerated);
expect(events[ 2 ]).to.be.instanceOf(ActivityFinished);

expect((events[ 1 ] as ArtifactGenerated).equals(
new ArtifactGenerated(
new Name(`Script source`),
TextData.fromJSON({
contentType: 'text/javascript;charset=UTF-8',
data: 'arguments[arguments.length - 1]();',
}),
frozenClock.now(),
),
)).to.equal(true, JSON.stringify(events[ 1 ].toJSON()));
});
});
});
@@ -0,0 +1,57 @@
import { expect } from '@integration/testing-tools';
import { Ensure, equals } from '@serenity-js/assertions';
import { Actor, LogicError } from '@serenity-js/core';

import { by, protractor } from 'protractor';
import { BrowseTheWeb, ExecuteScript, Navigate, Target, Text } from '../../../../src';

/** @test {ExecuteScriptFromUrl} */
describe('ExecuteScriptFromUrl', function() {
this.timeout(30000);

const Joe = Actor.named('Joe').whoCan(
BrowseTheWeb.using(protractor.browser),
);

const
pathToScript = `file://${ require.resolve('./resources/execute-script-sample.js') }`,
pathToPage = `file://${ require.resolve('./resources/execute-script-sandbox.html') }`;

class Sandbox {
static Result = Target.the('sandbox result').located(by.id('result'));
}

/** @test {ExecuteScript.from} */
/** @test {ExecuteScriptFromUrl} */
it('allows the actor to execute a script stored at a specific location', () => Joe.attemptsTo(
Navigate.to(pathToPage),

ExecuteScript.from(pathToScript),

Ensure.that(Text.of(Sandbox.Result), equals('Script loaded successfully')),
));

/** @test {ExecuteScript.from} */
/** @test {ExecuteScriptFromUrl} */
it('complains if the script could not be loaded', () => expect(Joe.attemptsTo(
Navigate.to(pathToPage),

ExecuteScript.from(pathToScript + '.invalid'),
)).to.be.rejectedWith(LogicError, `Couldn't load script from ${ pathToScript }.invalid`));

/** @test {ExecuteScript.from} */
/** @test {ExecuteScriptFromUrl} */
it('complains if the script has already been loaded', () => expect(Joe.attemptsTo(
Navigate.to(pathToPage),

ExecuteScript.from(pathToScript),
ExecuteScript.from(pathToScript),
)).to.be.rejectedWith(LogicError, `Script from ${ pathToScript } has already been loaded`));

/** @test {ExecuteScript.from} */
/** @test {ExecuteScriptFromUrl#toString} */
it(`provides a sensible description of the interaction being performed`, () => {
expect(ExecuteScript.from(pathToScript).toString())
.to.equal(`#actor executes a script from ${ pathToScript }`);
});
});

0 comments on commit 753d511

Please sign in to comment.