Skip to content

Commit e7a9540

Browse files
committed
A place to start
0 parents  commit e7a9540

18 files changed

+297
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# IntelliJ
2+
.idea
3+
*.iml
4+
5+
# Node
6+
node_modules
7+
*.log
8+
9+
# Build artifacts
10+
target

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6

cross-app-demo.gif

588 KB
Loading

package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "tutorial-cross-app-testing",
3+
"version": "1.0.0",
4+
"description": "Cross-application testing with Protractor and Serenity/JS",
5+
"main": "index.js",
6+
"scripts": {
7+
"clean": "rimraf target",
8+
"lint": "tslint --config tslint.json --project tsconfig.json --format stylish",
9+
"pretest": "serenity update",
10+
"pree2e": "npm run lint && npm run webdriver:update -- --standalone --versions.standalone=2.53.1 --versions.chrome=2.29",
11+
"e2e": "protractor ./protractor.conf.js",
12+
"e2e-single": "protractor ./protractor.conf.js --mochaOpts.grep",
13+
"report": "serenity run",
14+
"test": "failsafe e2e report",
15+
"webdriver-manager": "webdriver-manager",
16+
"webdriver:update": "npm run webdriver-manager update"
17+
},
18+
"repository": {
19+
"type": "git",
20+
"url": "git+https://github.com/serenity-js/tutorial-cross-app-testing.git"
21+
},
22+
"author": "Jan Molak <jan.molak@smartcodeltd.co.uk>",
23+
"license": "Apache-2.0",
24+
"bugs": {
25+
"url": "https://github.com/serenity-js/tutorial-cross-app-testing/issues"
26+
},
27+
"homepage": "https://github.com/serenity-js/tutorial-cross-app-testing#readme",
28+
"devDependencies": {
29+
"@types/chai": "3.5.0",
30+
"@types/chai-as-promised": "0.0.30",
31+
"@types/mocha": "2.2.40",
32+
"@types/node": "7.0.12",
33+
"chai": "3.5.0",
34+
"chai-as-promised": "6.0.0",
35+
"mocha": "3.2.0",
36+
"npm-failsafe": "0.2.1",
37+
"protractor": "5.1.1",
38+
"rimraf": "2.6.1",
39+
"serenity-cli": "0.2.4",
40+
"serenity-js": "1.3.0",
41+
"ts-node": "3.0.2",
42+
"tslint": "5.1.0",
43+
"tslint-microsoft-contrib": "4.0.1",
44+
"typescript": "2.2.2"
45+
},
46+
"engines": {
47+
"node": ">= 6.9.x"
48+
}
49+
}

protractor.conf.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
exports.config = {
2+
3+
baseUrl: 'https://google.com',
4+
5+
allScriptsTimeout: 110000,
6+
7+
framework: 'custom',
8+
frameworkPath: require.resolve('serenity-js'),
9+
10+
mochaOpts: {
11+
ui: 'bdd', // use the describe/it syntax (default: 'bdd').
12+
compiler: 'ts:ts-node/register', // interpret step definitions as TypeScript,
13+
reporter: 'spec'
14+
},
15+
16+
specs: ['spec/**/*.spec.ts'],
17+
18+
capabilities: {
19+
browserName: 'chrome'
20+
},
21+
22+
restartBrowserBetweenTests: true,
23+
};

readme.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Cross-Application Testing with Protractor and Serenity/JS
2+
3+
This project demonstrates how a Serenity/JS test scenario can interact with both Angular and non-Angular apps
4+
and acts as supporting material for
5+
"[Cross-application testing with Serenity/JS](https://janmolak.com/cross-application-testing-with-serenity-js-4103a272b75b)"
6+
7+
To run it:
8+
```
9+
npm install
10+
npm test
11+
```
12+
13+
Please make sure that you're using the latest [long-term support](https://nodejs.org/en/download/) of Node.js
14+
15+
----
16+
17+
Have fun!
18+
19+
[Jan Molak](https://janmolak.com)

spec/cross-app.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { protractor } from 'protractor';
2+
import { BrowseTheWeb } from 'serenity-js/lib/serenity-protractor';
3+
import { Actor } from 'serenity-js/lib/serenity/screenplay';
4+
5+
import { Ensure, Multiply } from '../src/calculator';
6+
import { Google, SelectResult } from '../src/google';
7+
8+
import { equals } from '../src/assertions';
9+
10+
describe('Cross-application testing', function() {
11+
12+
this.timeout(60 * 1000);
13+
14+
describe('Combining Protractor and Serenity/JS', () => {
15+
const Steph = Actor.named('Steph').whoCan(BrowseTheWeb.using(protractor.browser));
16+
17+
it('allows you to easily navigate between non-Angular and Angular apps', () => Steph.attemptsTo(
18+
Google.the('juliemr calculator'),
19+
SelectResult.of('Super Calculator'),
20+
Multiply.number(6).by(7),
21+
Ensure.result(equals('42')),
22+
));
23+
});
24+
});

src/assertions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import chai = require('chai');
2+
import chaiAsPromised = require('chai-as-promised');
3+
4+
chai.use(chaiAsPromised);
5+
6+
export const equals = expected => actual => chai.expect(actual).to.eventually.equal(expected);

src/calculator/ensure.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Text } from 'serenity-js/lib/serenity-protractor';
2+
import { step } from 'serenity-js/lib/serenity/recording';
3+
import { PerformsTasks, See, Task } from 'serenity-js/lib/serenity/screenplay';
4+
import { Assertion } from 'serenity-js/lib/serenity/screenplay/expectations';
5+
6+
import { Calculator } from './ui/calculator';
7+
8+
export class Ensure implements Task {
9+
static result = (assertion: Assertion<string>): Task => new Ensure(assertion);
10+
11+
@step('{0} checks the result')
12+
performAs(actor: PerformsTasks): PromiseLike<void> {
13+
return actor.attemptsTo(
14+
See.if(Text.of(Calculator.Result), this.assertion),
15+
);
16+
}
17+
18+
constructor(private assertion: Assertion<string>) {
19+
}
20+
}

src/calculator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ensure';
2+
export * from './multiply';

src/calculator/multiply.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Click, Enter, Select, UseAngular } from 'serenity-js/lib/serenity-protractor';
2+
import { step } from 'serenity-js/lib/serenity/recording';
3+
import { PerformsTasks, Task } from 'serenity-js/lib/serenity/screenplay';
4+
5+
import { Calculator } from './ui/calculator';
6+
7+
export class Multiply implements Task {
8+
static number = (multiplier: number) => ({
9+
by: (multiplicand: number) => new Multiply(multiplier, multiplicand),
10+
})
11+
12+
@step('{0} multiplies #multiplier by #multiplicand')
13+
performAs(actor: PerformsTasks): PromiseLike<void> {
14+
return actor.attemptsTo(
15+
UseAngular.enableSynchronisation(),
16+
Enter.theValue(this.multiplier).into(Calculator.Left_Operand),
17+
Select.theValue('*').from(Calculator.Operator),
18+
Enter.theValue(this.multiplicand).into(Calculator.Right_Operand),
19+
Click.on(Calculator.Go),
20+
);
21+
}
22+
23+
constructor(private multiplier: number, private multiplicand: number) {
24+
}
25+
}

src/calculator/ui/calculator.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { by } from 'protractor';
2+
import { Target } from 'serenity-js/lib/serenity-protractor';
3+
4+
export class Calculator {
5+
static Left_Operand = Target.the('Left operand').located(by.model('first'));
6+
static Operator = Target.the('Operator').located(by.model('operator'));
7+
static Right_Operand = Target.the('Right operand').located(by.model('second'));
8+
static Go = Target.the('Go').located(by.id('gobutton'));
9+
static Result = Target.the('Result').located(by.css('h2'));
10+
}

src/google/google.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { protractor } from 'protractor';
2+
import { Enter, Open, UseAngular } from 'serenity-js/lib/serenity-protractor';
3+
import { step } from 'serenity-js/lib/serenity/recording';
4+
import { PerformsTasks, Task } from 'serenity-js/lib/serenity/screenplay';
5+
6+
import { GoogleSearch } from './ui/google_search';
7+
8+
export class Google implements Task {
9+
static the = (term: string) => new Google(term);
10+
11+
@step('{0} googles the "#term"')
12+
performAs(actor: PerformsTasks): PromiseLike<void> {
13+
return actor.attemptsTo(
14+
UseAngular.disableSynchronisation(),
15+
Open.browserOn('http://google.co.uk/'),
16+
Enter.theValue(this.term).into(GoogleSearch.Query).thenHit(protractor.Key.ENTER),
17+
);
18+
}
19+
20+
constructor(private term: string) {
21+
}
22+
}

src/google/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './google';
2+
export * from './select_result';

src/google/select_result.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Click, Is, Wait } from 'serenity-js/lib/serenity-protractor';
2+
import { step } from 'serenity-js/lib/serenity/recording';
3+
import { PerformsTasks, Task } from 'serenity-js/lib/serenity/screenplay';
4+
5+
import { GoogleSearch } from './ui/google_search';
6+
7+
export class SelectResult implements Task {
8+
static of = (result: string) => new SelectResult(result);
9+
10+
@step('{0} selects "#result" from the list of results')
11+
performAs(actor: PerformsTasks): PromiseLike<void> {
12+
return actor.attemptsTo(
13+
Wait.until(GoogleSearch.Result.of(this.result), Is.clickable()),
14+
Click.on(GoogleSearch.Result.of(this.result)),
15+
);
16+
}
17+
18+
constructor(private result: string) {
19+
}
20+
}

src/google/ui/google_search.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { by } from 'protractor';
2+
import { Target } from 'serenity-js/lib/serenity-protractor';
3+
4+
export class GoogleSearch {
5+
static Query = Target.the('Query').located(by.name('q'));
6+
static Result = Target.the('Link to "{0}"').located(by.linkText('{0}'));
7+
}

tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": [ "es5", "es6" ],
5+
"module": "commonjs",
6+
"emitDecoratorMetadata": true,
7+
"experimentalDecorators": true,
8+
"sourceMap": true,
9+
"declaration": true
10+
},
11+
12+
"exclude": [
13+
"node_modules"
14+
]
15+
}

tslint.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"extends": "tslint:latest",
3+
"rulesDirectory": "node_modules/tslint-microsoft-contrib",
4+
"rules": {
5+
"quotemark": [true, "single", "avoid-escape"],
6+
"one-variable-per-declaration": false,
7+
"member-access": false,
8+
"typedef-whitespace":
9+
[
10+
true,
11+
{
12+
"call-signature": "nospace",
13+
"index-signature": "nospace",
14+
"parameter": "nospace",
15+
"property-declaration": "nospace",
16+
"variable-declaration": "nospace"
17+
},
18+
{
19+
"call-signature": "onespace",
20+
"index-signature": "onespace",
21+
"parameter": "onespace",
22+
"property-declaration": "space",
23+
"variable-declaration": "space"
24+
}
25+
],
26+
"object-literal-sort-keys": false,
27+
"no-bitwise": false,
28+
"one-line": false,
29+
"variable-name": [true, "ban-keywords"],
30+
"interface-name": [true, "never-prefix"],
31+
"max-line-length": [true, 180],
32+
"member-ordering": [true,
33+
"public-before-private",
34+
"static-before-instance",
35+
"variables-before-functions"
36+
],
37+
"only-arrow-functions": false,
38+
"arrow-parens": [true, "ban-single-arg-parens" ],
39+
"max-classes-per-file": false,
40+
"mocha-avoid-only": true
41+
}
42+
}

0 commit comments

Comments
 (0)