Skip to content
Permalink
Browse files
feat(protractor): browser tags include browser version and platform name
Closes #132
  • Loading branch information
jan-molak committed Feb 7, 2020
1 parent 6d3a385 commit bc4a038484f75e90e44c5399c43213b472e71f38
@@ -3,80 +3,84 @@ import { browser, by, element, protractor } from 'protractor';
describe('Todo List App', function () {

// Test script style
describe('test script', () => {

it('allows for the list to show active items only', async function () {
await browser.get('http://todomvc.com/examples/angularjs/#/');

await element(by.css('.new-todo')).sendKeys('Play guitar');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);

await element(by.css('.new-todo')).sendKeys('Read a book');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);

await element(by.css('.new-todo')).sendKeys('Write some code');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);

await element(
by.xpath(`//li[*[@class='view' and contains(.,'Write some code')]]//input[contains(@class,'toggle')]`),
)
.click();

await element(by.linkText(`Active`))
.click();

await element.all(by.css('.todo-list li')).getText().then(items => {
expect(items).toEqual([
'Play guitar',
'Read a book',
]);
});
});
it('allows for the list to show active items only', async function () {
await browser.get('http://todomvc.com/examples/angularjs/#/');

// Page Object(s) style
await element(by.css('.new-todo')).sendKeys('Play guitar');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);

const TodoListApp = {
open: async () => {
await browser.get('http://todomvc.com/examples/angularjs/#/');
},
await element(by.css('.new-todo')).sendKeys('Read a book');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);

recordItemCalled: async (itemName: string) => {
await element(by.css('.new-todo')).sendKeys(itemName);
await element(by.css('.new-todo')).sendKeys('Write some code');
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);
},

completeItemCalled: async (itemName: string) => {
await element(
by.xpath(`//li[*[@class='view' and contains(.,'${ itemName }')]]//input[contains(@class,'toggle')]`),
)
by.xpath(`//li[*[@class='view' and contains(.,'Write some code')]]//input[contains(@class,'toggle')]`),
)
.click();
},

filterToShowActiveItemsOnly: async () => {
await element(by.linkText(`Active`))
.click();
},

recordedItems: async () => {
return element.all(by.css('.todo-list li')).getText();
},
};

it('allows for the list to show active items only', async function () {
await TodoListApp.open();

await TodoListApp.recordItemCalled('Play guitar');
await TodoListApp.recordItemCalled('Read a book');
await TodoListApp.recordItemCalled('Write some code');

await TodoListApp.completeItemCalled('Write some code');

await TodoListApp.filterToShowActiveItemsOnly();
await element.all(by.css('.todo-list li')).getText().then(items => {
expect(items).toEqual([
'Play guitar',
'Read a book',
]);
});
});
});

await TodoListApp.recordedItems().then(items => {
expect(items).toEqual([
'Play guitar',
'Read a book',
]);
// Page Object(s) style
describe('page objects', () => {

const TodoListApp = {
open: async () => {
await browser.get('http://todomvc.com/examples/angularjs/#/');
},

recordItemCalled: async (itemName: string) => {
await element(by.css('.new-todo')).sendKeys(itemName);
await element(by.css('.new-todo')).sendKeys(protractor.Key.ENTER);
},

completeItemCalled: async (itemName: string) => {
await element(
by.xpath(`//li[*[@class='view' and contains(.,'${ itemName }')]]//input[contains(@class,'toggle')]`),
)
.click();
},

filterToShowActiveItemsOnly: async () => {
await element(by.linkText(`Active`))
.click();
},

recordedItems: async () => {
return element.all(by.css('.todo-list li')).getText();
},
};

it('allows for the list to show active items only', async function () {
await TodoListApp.open();

await TodoListApp.recordItemCalled('Play guitar');
await TodoListApp.recordItemCalled('Read a book');
await TodoListApp.recordItemCalled('Write some code');

await TodoListApp.completeItemCalled('Write some code');

await TodoListApp.filterToShowActiveItemsOnly();

await TodoListApp.recordedItems().then(items => {
expect(items).toEqual([
'Play guitar',
'Read a book',
]);
});
});
});

@@ -34,7 +34,7 @@ describe('Tag', () => {

given([
new ArbitraryTag('wip'),
new BrowserTag('chrome'),
new BrowserTag('chrome', '80.0.3987.87'),
new CapabilityTag('checkout'),
new ContextTag('mac osx'),
new FeatureTag('testability'),
@@ -1,3 +1,4 @@
import { JSONObject } from 'tiny-types';
import { Tag } from './Tag';

/**
@@ -6,7 +7,23 @@ import { Tag } from './Tag';
export class BrowserTag extends Tag {
static readonly Type = 'browser';

constructor(browser: string) {
super(browser, BrowserTag.Type);
static fromJSON(o: JSONObject) {
return new BrowserTag(o.browserName as string, o.browserVersion as string);
}

constructor(
public readonly browserName: string,
public readonly browserVersion: string,
) {
super([ browserName, browserVersion ].join(' '), BrowserTag.Type);
}

toJSON() {
return {
name: this.name,
type: BrowserTag.Type,
browserName: this.browserName,
browserVersion: this.browserVersion,
};
}
}
@@ -10,6 +10,7 @@ import { Tag } from './Tag';
* it will appear in text form in the test results lists, so it is better to keep context names relatively short.
*
* @access public
* @deprecated
*/
export class ContextTag extends Tag {
static readonly Type = 'context';
@@ -0,0 +1,12 @@
import { Tag } from './Tag';

/**
* @access public
*/
export class PlatformTag extends Tag {
static readonly Type = 'platform';

constructor(platform: string) {
super(platform, PlatformTag.Type);
}
}
@@ -10,6 +10,10 @@ export abstract class Tag extends TinyType {

const found = Object.keys(TagTypes).find(t => TagTypes[t].Type === type) || TagTypes.ArbitraryTag.name;

if (TagTypes[found].hasOwnProperty('fromJSON')) {
return TagTypes[found].fromJSON(o);
}

return new TagTypes[found](o.name);
}

@@ -5,6 +5,7 @@ export * from './ContextTag';
export * from './FeatureTag';
export * from './IssueTag';
export * from './ManualTag';
export * from './PlatformTag';
export * from './ThemeTag';
export * from './Tag';
export * from './Tags';
@@ -92,7 +92,7 @@ describe('ProtractorFrameworkAdapter', () => {
failedCount: 0,
specResults: [{
description: sample('passing.spec.ts').description,
duration: 1000,
duration: 2000,
assertions: [{
passed: true,
}],
@@ -105,7 +105,7 @@ describe('ProtractorFrameworkAdapter', () => {
failedCount: 1,
specResults: [{
description: sample('failing.spec.ts').description,
duration: 1000,
duration: 2000,
assertions: [{
passed: false,
errorMsg: (sample('failing.spec.ts').outcome as ProblemIndication).error.message,
@@ -120,21 +120,21 @@ describe('ProtractorFrameworkAdapter', () => {
failedCount: 1,
specResults: [{
description: sample('passing.spec.ts').description,
duration: 1000,
duration: 2000,
assertions: [{
passed: true,
}],
}, {
description: sample('failing.spec.ts').description,
duration: 1000,
duration: 2000,
assertions: [{
passed: false,
errorMsg: (sample('failing.spec.ts').outcome as ProblemIndication).error.message,
stackTrace: (sample('failing.spec.ts').outcome as ProblemIndication).error.stack,
}],
}, {
description: sample('passing.spec.ts').description,
duration: 1000,
duration: 2000,
assertions: [{
passed: true,
}],
@@ -1,8 +1,11 @@
import { ArtifactArchiver, Duration, Serenity } from '@serenity-js/core';
import { ArtifactArchiver, Serenity } from '@serenity-js/core';

import deepmerge = require('deepmerge');
const isPlainObject = require('is-plain-object'); // tslint:disable-line:no-var-requires fails when using default import

import { Runner } from 'protractor';
import { BrowserDetector } from './browser-detector';

import { Config } from './Config';
import { ProtractorReport, ProtractorReporter } from './reporter';
import { TestRunnerDetector } from './TestRunnerDetector';
@@ -37,7 +40,11 @@ export class ProtractorFrameworkAdapter {
this.serenity.configure({
cueTimeout: config.serenity.cueTimeout,
actors: config.serenity.actors,
crew: [...config.serenity.crew, reporter],
crew: [
new BrowserDetector(),
...config.serenity.crew,
reporter,
],
});

return Promise.resolve()
@@ -0,0 +1,61 @@
import { Stage } from '@serenity-js/core';
import { AsyncOperationAttempted, AsyncOperationCompleted, DomainEvent, SceneStarts, SceneTagged } from '@serenity-js/core/lib/events';
import { BrowserTag, CorrelationId, Description, PlatformTag } from '@serenity-js/core/lib/model';
import { StageCrewMember } from '@serenity-js/core/lib/stage';
import { protractor } from 'protractor';

/**
* @private
*
* @see https://github.com/serenity-js/serenity-js/issues/455
* @see https://github.com/serenity-bdd/serenity-core/pull/1860/files
* @see https://github.com/serenity-js/serenity-js/issues/132
*/
export class BrowserDetector implements StageCrewMember {

constructor(
private readonly stage: Stage = null,
) {
}

assignedTo(stage: Stage): StageCrewMember {
return new BrowserDetector(stage);
}

notifyOf(event: DomainEvent): void {
if (event instanceof SceneStarts) {
const id = CorrelationId.create();

this.stage.announce(new AsyncOperationAttempted(
new Description(`[${ this.constructor.name }] Detecting web browser details...`),
id,
this.stage.currentTime(),
));

protractor.browser.getCapabilities().then(capabilities => {
const
platform = capabilities.get('platform'),
browserName = capabilities.get('browserName'),
browserVersion = capabilities.get('version');

this.stage.announce(new SceneTagged(
event.value,
new BrowserTag(browserName, browserVersion),
this.stage.currentTime(),
));

this.stage.announce(new SceneTagged(
event.value,
new PlatformTag(platform),
this.stage.currentTime(),
));

this.stage.announce(new AsyncOperationCompleted(
new Description(`[${ this.constructor.name }] Detected web browser details`),
id,
this.stage.currentTime(),
));
});
}
}
}
@@ -0,0 +1 @@
export * from './BrowserDetector';
@@ -262,7 +262,7 @@ describe('SerenityBDDReporter', () => {
*/
it('indicates the web browser where the test was executed', () => {
given(reporter).isNotifiedOfFollowingEvents(
new SceneTagged(defaultCardScenario, new BrowserTag('chrome')),
new SceneTagged(defaultCardScenario, new BrowserTag('chrome', '80.0.3987.87')),
new SceneFinished(defaultCardScenario, new ExecutionSuccessful()),
new TestRunFinishes(),
);
@@ -272,7 +272,9 @@ describe('SerenityBDDReporter', () => {
expect(report.context).to.equal('chrome');

expect(report.tags).to.deep.include.members([{
name: 'chrome',
browserName: 'chrome',
browserVersion: '80.0.3987.87',
name: 'chrome 80.0.3987.87',
type: 'browser',
}]);
});
@@ -315,7 +317,7 @@ describe('SerenityBDDReporter', () => {
*/
it('ensures that the user-specified context takes precedence over browser context', () => {
given(reporter).isNotifiedOfFollowingEvents(
new SceneTagged(defaultCardScenario, new BrowserTag('safari')),
new SceneTagged(defaultCardScenario, new BrowserTag('safari', '13.0.5')),
new SceneTagged(defaultCardScenario, new ContextTag('iphone')),
new SceneFinished(defaultCardScenario, new ExecutionSuccessful()),
new TestRunFinishes(),
@@ -326,8 +328,10 @@ describe('SerenityBDDReporter', () => {
expect(report.context).to.equal('iphone');

expect(report.tags).to.deep.include.members([{
name: 'safari',
name: 'safari 13.0.5',
type: 'browser',
browserName: 'safari',
browserVersion: '13.0.5',
}, {
name: 'iphone',
type: 'context',

0 comments on commit bc4a038

Please sign in to comment.