Skip to content
Permalink
Browse files
feat(protractor): ProtractorFrameworkAdapter for Cucumber
And other small fixes here and there
  • Loading branch information
jan-molak committed Apr 17, 2019
1 parent 0da74d1 commit 7474dbb07c1de7cd9ab860f86bdf64b007e9dac6
Showing 159 changed files with 1,896 additions and 439 deletions.
@@ -7,7 +7,7 @@ import { requestHandler } from '@serenity-js-examples/calculator-app';
export class Actors implements DressingRoom {
prepare(actor: Actor): Actor {
return actor.whoCan(
ManageALocalServer.running(requestHandler),
ManageALocalServer.runningAHttpListener(requestHandler),
CallAnApi.at('http://localhost'),
);
}
@@ -42,12 +42,12 @@
"@serenity-js/cucumber": "2.0.1-alpha.47",
"@serenity-js/local-server": "2.0.1-alpha.47",
"@serenity-js/rest": "2.0.1-alpha.47",
"@types/cucumber": "4.0.4",
"@types/express": "4.16.0",
"cucumber": "5.1.0",
"express": "4.16.4",
"npm-failsafe": "0.4.1",
"@types/cucumber": "^4.0.5",
"@types/express": "^4.16.1",
"cucumber": "^5.1.0",
"express": "^4.16.4",
"npm-failsafe": "^0.4.1",
"serenity-cli": "^0.11.3",
"ts-node": "7.0.1"
"ts-node": "^8.1.0"
}
}
@@ -0,0 +1,7 @@
# Node
node_modules
*.log

# Build artifacts
.nyc_output
lib
@@ -0,0 +1,6 @@
Feature: Interactions

Scenario: Actor interacts with a webiste

When Umbra navigates to the test website
Then she should see the title of "Test Website"
@@ -0,0 +1,25 @@
import { Ensure, equals } from '@serenity-js/assertions';
import { WithStage } from '@serenity-js/cucumber';
import { LocalServer, StartLocalServer, StopLocalServer } from '@serenity-js/local-server';
import { Navigate, UseAngular, Website } from '@serenity-js/protractor';
import { After, Then, When } from 'cucumber';

When(/^(.*) navigates to the test website$/, function(this: WithStage, actorName: string) {
return this.stage.actor(actorName).attemptsTo(
StartLocalServer.onRandomPort(),
UseAngular.disableSynchronisation(),
Navigate.to(LocalServer.url()),
);
});

Then(/(?:he|she|they) should see the title of "(.*)"/, function(this: WithStage, expectedTitle: string) {
return this.stage.theActorInTheSpotlight().attemptsTo(
Ensure.that(Website.title(), equals(expectedTitle)),
);
});

After(function () {
return this.stage.theActorCalled('Umbra').attemptsTo(
StopLocalServer.ifRunning(),
);
});
@@ -0,0 +1,15 @@
import express = require('express');

export const app = express()
.get('/', (req: express.Request, res: express.Response) => {

res.send(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Website</title>
</head>
<body />
</html>
`);
});
@@ -0,0 +1,21 @@
import { serenity } from '@serenity-js/core';
import { FileSystem, Path } from '@serenity-js/core/lib/io';
import { ArtifactArchiver, ConsoleReporter, DebugReporter, SerenityBDDReporter } from '@serenity-js/core/lib/stage';
import { WithStage } from '@serenity-js/cucumber';

import { setDefaultTimeout, setWorldConstructor } from 'cucumber';
import { Actors } from './screenplay';

// todo: move to config and make the artifact archiver config more intuitive
serenity.setTheStage(
new ArtifactArchiver(new FileSystem(new Path('./target/site/serenity'))),
new SerenityBDDReporter(),
// new DebugReporter(),
new ConsoleReporter(),
);

setDefaultTimeout(1000);

setWorldConstructor(function(this: WithStage, { parameters }) {
this.stage = serenity.callToStageFor(new Actors());
});
@@ -0,0 +1,14 @@
import { Actor, DressingRoom } from '@serenity-js/core';
import { ManageALocalServer } from '@serenity-js/local-server';
import { BrowseTheWeb } from '@serenity-js/protractor';
import { protractor } from 'protractor';
import { app } from '../app';

export class Actors implements DressingRoom {
prepare(actor: Actor): Actor {
return actor.whoCan(
BrowseTheWeb.using(protractor.browser),
ManageALocalServer.runningAHttpListener(app),
);
}
}
@@ -0,0 +1 @@
export * from './Actors';
@@ -0,0 +1,54 @@
{
"name": "@serenity-js-examples/protractor-cucumber",
"version": "2.0.1-alpha.47",
"description": "Example implementation of a test suite using Protractor and Cucumber to exercise a Web interface",
"author": {
"name": "Jan Molak",
"email": "jan.molak@smartcodeltd.co.uk",
"url": "https://janmolak.com"
},
"homepage": "http://serenity-js.org",
"license": "Apache-2.0",
"private": true,
"config": {
"access": "private"
},
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {
"clean": "rimraf target",
"lint": "tslint --project tsconfig-lint.json --config ../../tslint.json --format stylish",
"test:update-serenity": "serenity update --artifact net.serenity-bdd:serenity-cli:jar:all:2.1.8 --repository https://jcenter.bintray.com/ --ignoreSSL",
"test:acceptance": "protractor ./protractor.conf.js",
"test:report": "serenity run --artifact net.serenity-bdd:serenity-cli:jar:all:2.1.8",
"test": "failsafe clean test:update-serenity test:acceptance test:report",
"verify": "npm test"
},
"repository": {
"type": "git",
"url": "https://github.com/jan-molak/serenity-js.git"
},
"bugs": {
"url": "https://github.com/jan-molak/serenity-js/issues"
},
"engines": {
"node": ">= 6.9.x",
"npm": ">= 3"
},
"devDependencies": {
"@serenity-js/assertions": "2.0.1-alpha.47",
"@serenity-js/core": "2.0.1-alpha.47",
"@serenity-js/cucumber": "2.0.1-alpha.47",
"@serenity-js/local-server": "2.0.1-alpha.47",
"@serenity-js/protractor": "2.0.1-alpha.47",
"@types/cucumber": "^4.0.5",
"@types/express": "^4.16.1",
"chromedriver": "^2.46.0",
"cucumber": "^5.1.0",
"express": "^4.16.4",
"npm-failsafe": "0.4.1",
"protractor": "^5.4.2",
"serenity-cli": "^0.11.3",
"ts-node": "^8.1.0"
}
}
@@ -0,0 +1,36 @@
exports.config = {
chromeDriver: require('chromedriver/lib/chromedriver').path,
SELENIUM_PROMISE_MANAGER: false,

directConnect: true,

allScriptsTimeout: 11000,

specs: [ 'features/*.feature', ],

framework: 'custom',
frameworkPath: require.resolve('@serenity-js/protractor/adapter'),

cucumberOpts: {
require: [
'features/step_definitions/**/*.ts',
'features/support/configure_serenity.ts',
],
'require-module': ['ts-node/register'],
tags: [],
},

capabilities: {
browserName: 'chrome',

chromeOptions: {
args: [
'--disable-infobars',
'--no-sandbox',
'--disable-gpu',
'--window-size=1024x768',
'--headless',
],
},
},
};
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig",
"include": [
"src/**/*.ts",
"spec/**/*.ts"
],
"exclude": [
"node_modules"
]
}
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es5",
"lib": [ "es5", "es6" ],
"module": "commonjs",
"sourceMap": true,
"declaration": true
},

"exclude": [
"node_modules"
]
}
@@ -1,9 +1,9 @@
export = function() {
this.Given(/^.*step (?:.*) passes$/, function() {
export = function () {
this.Given(/^.*step (?:.*) passes$/, function () {
return void 0;
});

this.Given(/^.*step (?:.*) passes$/, function() {
this.Given(/^.*step (?:.*) passes$/, function () {
return void 0;
});
};
@@ -3,32 +3,32 @@ import { TableDefinition } from 'cucumber';

type Callback = (error?: Error, pending?: string) => void;

export = function() {
this.Given(/^.*step (?:.*) passes$/, function(done: Callback) {
export = function () {
this.Given(/^.*step (?:.*) passes$/, function (done: Callback) {
done();
});

this.Given(/^.*step (?:.*) fails with generic error$/, function(done: Callback) {
this.Given(/^.*step (?:.*) fails with generic error$/, function (done: Callback) {
done(new Error(`Something's wrong`));
});

this.Given(/^.*step (?:.*) fails with assertion error$/, function(done: Callback) {
this.Given(/^.*step (?:.*) fails with assertion error$/, function (done: Callback) {
done(new AssertionError(`Expected false to equal true`, false, true));
});

this.Given(/^.*step (?:.*) marked as pending/, function(done: Callback) {
this.Given(/^.*step (?:.*) marked as pending/, function (done: Callback) {
done(void 0, 'pending');
});

this.Given(/^.*step (?:.*) receives a table:$/, function(data: TableDefinition, done) {
this.Given(/^.*step (?:.*) receives a table:$/, function (data: TableDefinition, done) {
done();
});

this.Given(/^.*step (?:.*) receives a doc string:$/, function(docstring: string, done) {
this.Given(/^.*step (?:.*) receives a doc string:$/, function (docstring: string, done) {
done();
});

this.Given(/^.*step that times out$/, { timeout: 100 }, function(done: Callback) {
this.Given(/^.*step that times out$/, { timeout: 100 }, function (done: Callback) {
setTimeout(done, 1000);
});
};
@@ -1,32 +1,32 @@
import { AssertionError } from '@serenity-js/core';
import { TableDefinition } from 'cucumber';

export = function() {
this.Given(/^.*step (?:.*) passes$/, function() {
export = function () {
this.Given(/^.*step (?:.*) passes$/, function () {
return Promise.resolve();
});

this.Given(/^.*step (?:.*) fails with generic error$/, function() {
this.Given(/^.*step (?:.*) fails with generic error$/, function () {
return Promise.reject(new Error(`Something's wrong`));
});

this.Given(/^.*step (?:.*) fails with assertion error$/, function() {
this.Given(/^.*step (?:.*) fails with assertion error$/, function () {
return Promise.reject(new AssertionError(`Expected false to equal true`, false, true));
});

this.Given(/^.*step (?:.*) marked as pending/, function() {
this.Given(/^.*step (?:.*) marked as pending/, function () {
return Promise.resolve('pending');
});

this.Given(/^.*step (?:.*) receives a table:$/, function(data: TableDefinition) {
this.Given(/^.*step (?:.*) receives a table:$/, function (data: TableDefinition) {
return Promise.resolve();
});

this.Given(/^.*step (?:.*) receives a doc string:$/, function(docstring: string) {
this.Given(/^.*step (?:.*) receives a doc string:$/, function (docstring: string) {
return Promise.resolve();
});

this.Given(/^.*step that times out$/, { timeout: 100 }, function() {
this.Given(/^.*step that times out$/, { timeout: 100 }, function () {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
@@ -0,0 +1,37 @@
import { Interaction } from '@serenity-js/core';
import { WithStage } from '@serenity-js/cucumber';

const
MakeAnArrow = () => Interaction.where(`#actor makes an arrow`, actor => void 0),
Nock = () => Interaction.where(`#actor fits an arrow to the bowstring`, actor => void 0),
Draw = () => Interaction.where(`#actor draws the bow`, actor => void 0),
Loose = () => Interaction.where(`#actor releases the bowstring`, actor => void 0),
RetrieveArrow = () => Interaction.where(`#actor retrieves the arrow from the target`, actor => void 0);

export = function () {
this.Before(function (this: WithStage) {
return this.stage.theActorCalled('Lara').attemptsTo(
MakeAnArrow(),
);
});

this.When(/^(.*) shoots an arrow$/, function (this: WithStage, actorName: string) {
return this.stage.theActorCalled(actorName).attemptsTo(
Nock(),
Draw(),
Loose(),
);
});

this.Then(/^she should hit a target$/, function (this: WithStage) {
return this.stage.theActorInTheSpotlight().attemptsTo(
// some assertion
);
});

this.After(function (this: WithStage) {
return this.stage.theActorCalled('Lara').attemptsTo(
RetrieveArrow(),
);
});
};

0 comments on commit 7474dbb