Skip to content

Commit

Permalink
New default reporters created:
Browse files Browse the repository at this point in the history
 - spinner - Simple progress indicator updating with each action performed
 - console - Logs progress information to the console (scenario start, scenario end, scenario found an error)
 - file - Generates files with failing scenarios, which can be replayed with qape <files...>

Added new events for reporters:
 - action:start - Emitted before an action is executed, recieves instance and action
 - action:error - Emitted when an action execution error occured, recieves instance, action and error
 - action:end - Emitted after an action is executed, recieves instance, action and results

Added new config option afterActionWaitTime (default: 500ms). This option causes a browser to wait for specified amount of time after each action, this gives a webpage time to evaluate all scripts and possibly cause an error.

Browser userAgent will no longer display whether it is in headless mode. This caused an issue with some websites not reporting errors in headless mode.

Updated dependencies.
  • Loading branch information
Filipoliko committed Dec 11, 2018
1 parent ee80ef9 commit 7f03ddd
Show file tree
Hide file tree
Showing 18 changed files with 702 additions and 113 deletions.
27 changes: 18 additions & 9 deletions docs/Reporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
You can define reporters via config.reporters.

```
var LocalCool = require('/path/to/LocalCool');
var ExampleReporter = require('/path/to/ExampleReporter');
export default {
reporters: [
// For reporter published in npm registry as 'qape-reporter-cool'
'cool',
// LocalCool is Class extending EventEmitter
LocalCool
// For reporter published in npm registry as 'qape-reporter-example'
'example',
// ExampleReporter is Class extending EventEmitter
ExampleReporter
]
}
```

## Usage
This is how you can create your own custom reporter. See [default reporter](../src/reporter/DefaultReporter.js) for extended example.
This is how you can create your own custom reporter. See [console reporter](../src/reporter/ConsoleReporter.js), [file reporter](../src/reporter/FileReporter.js), or [spinner reporter](../src/reporter/SpinnerReporter.js) for extended examples.

```javascript
import EventEmitter from 'events';
Expand All @@ -30,12 +30,21 @@ export default class DefaultReporter extends EventEmitter {
// runner:start is emitted after browser instance is initialized
// eventData contains scenario and browser instance
this.on('runner:start', eventData => console.log(eventData));
// scenarios:start is emitted after specific scenario starts
// scenario:start is emitted after specific scenario starts
// eventData contains browser instance, scenario type
// For type 'defined' there is also scenario and name
// For type 'failing' there is also scenario and errors
this.on('scenario:start', eventData => console.log(eventData));
// scenarios:end is emitted after specific scenario is finished
// action:start is emitted after specific action starts
// eventData contains instance and action
this.on('action:start', eventData => console.log(eventData));
// action:error is emitted after specific action receives an execution error (i.e. by clicking unclickable element)
// eventData contains instance, action and error
this.on('action:error', eventData => console.log(eventData));
// action:end is emitted after specific action ends
// eventData contains instance, action and results
this.on('action:end', eventData => console.log(eventData));
// scenario:end is emitted after specific scenario is finished
// eventData contains browser instance, scenario type
// For type 'defined' there is also scenario, name and results
// For type 'failing' there is also scenario, errors and minified (boolean)
Expand All @@ -44,7 +53,7 @@ export default class DefaultReporter extends EventEmitter {
// runner:end is emitted after browser instance is cleared
this.on('runner:end', () => console.log('it\'s done'));
// runner:error is emitted whenever an uncaught error occurred
// eventData contains scenario, and browser instance and error
// eventData contains scenario, browser instance and error
this.on('runner:error', eventData => console.log(eventData));
}
}
Expand Down
22 changes: 18 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,23 @@
"qape": "./bin/qape.js"
},
"keywords": [
"autonomous",
"testing",
"e2e",
"monkey"
"end-2-end",
"end-to-end",
"user",
"simulator",
"monkey",
"automatic",
"robot",
"bot",
"testingbot",
"quality",
"assurance",
"qa",
"puppeteer",
"chrome"
],
"repository": {
"type": "git",
Expand All @@ -28,17 +42,17 @@
"author": "Filip Satek <[email protected]>",
"license": "MIT",
"dependencies": {
"commander": "2.17.1",
"commander": "2.19.0",
"glob-all": "3.1.0",
"lodash.isequal": "4.5.0",
"puppeteer": "1.8.0"
"puppeteer": "1.11.0"
},
"devDependencies": {
"auto-changelog": "^1.10.1",
"babel-cli": "6.26.0",
"babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
"documentation": "8.1.2",
"express": "4.16.3",
"express": "4.16.4",
"jest": "23.6.0",
"serve-static": "1.13.2",
"source-map-support": "0.5.9"
Expand Down
10 changes: 5 additions & 5 deletions src/Runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ export default class Runner {

_init() {
this._initConfig();
this._initActionsHandler();
this._initReporter();
this._initActionsHandler();
this._initScenarios();
this._initTime = new Date().getTime();
}
Expand All @@ -113,14 +113,14 @@ export default class Runner {
this._config = new Config().load(this._config);
}

_initActionsHandler() {
this._actionsHandler = new ActionsHandler(this._config).init();
}

_initReporter() {
this._reporter = new Reporter(this._config).init();
}

_initActionsHandler() {
this._actionsHandler = new ActionsHandler(this._config, this._reporter).init();
}

_initScenarios() {
this._scenarios = new Scenarios(this._config, this._actionsHandler, this._reporter).init();
}
Expand Down
22 changes: 21 additions & 1 deletion src/actions/AbstractAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ export default class AbstractAction {
throw Error('AbstractAction: Id variable must be overwritten with your identifier.');
}

constructor(config, actionsHelper) {
constructor(config, actionsHelper, reporter) {
this._config = config;

this._actionsHelper = actionsHelper;

this._reporter = reporter;

this._actionConfig = {};

this._results = {
Expand All @@ -24,20 +26,38 @@ export default class AbstractAction {

pageErrorHandler.on('page-error', errorHandler);

this._reporter.emit('action:start', {
instance,
action: this.constructor.id
});

try {
await this.action(page, browser);
} catch (e) {
this._results.executionError = e;
this._reporter.emit('action:error', {
instance,
action: this.constructor.id,
error: e
});
}

if (!page.url().startsWith(this._config.url)) {
await page.goBack();
}

await page.waitFor(this._config.afterActionWaitTime);

pageErrorHandler.removeListener('page-error', errorHandler);

this._results.afterLocation = this._getLocation(page);

this._reporter.emit('action:end', {
instance,
action: this.constructor.id,
results: this._results
});

return this._results;
}

Expand Down
6 changes: 4 additions & 2 deletions src/actions/ActionsHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const localActionsPattern = [
];

export default class ActionsHandler {
constructor(config) {
constructor(config, reporter) {
this._config = config;

this._reporter = reporter;

this._actionsHelper = null;

this._availableActions = {};
Expand All @@ -37,7 +39,7 @@ export default class ActionsHandler {
actionId = actionId || this._getRandomAction();
let Action = this._availableActions[actionId];

return new Action(this._config, this._actionsHelper);
return new Action(this._config, this._actionsHelper, this._reporter);
}

_initActionsHelper() {
Expand Down
3 changes: 2 additions & 1 deletion src/actions/__tests__/ActionsHandlerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('ActionsHandler', () => {
let actionsHandler = null;

beforeEach(() => {
actionsHandler = new ActionsHandler({});
actionsHandler = new ActionsHandler({}, {});
});

it('can be initialized', () => {
Expand All @@ -16,6 +16,7 @@ describe('ActionsHandler', () => {
actionsHandler.init();

expect(actionsHandler._config).toEqual({});
expect(actionsHandler._reporter).toEqual({});
expect(actionsHandler._actionsHelper).toEqual(null);
expect(actionsHandler._availableActions).toEqual({});
expect(actionsHandler._initActionsHelper).toHaveBeenCalledTimes(1);
Expand Down
5 changes: 1 addition & 4 deletions src/browser/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ export default class Browser {
this._page = null;

this._pageErrorHandler = null;

this._defaultPageErrorHandler = null;
}

get browser() {
Expand Down Expand Up @@ -64,9 +62,8 @@ export default class Browser {

async _initPageErrorHandler() {
this._pageErrorHandler = this._getEventEmitter();
this._defaultPageErrorHandler = error => this._pageErrorHandler.emit('page-error', error);

await this._page.exposeFunction('qapeError', (error) => this._pageErrorHandler.emit('page-error', error));
await this._page.exposeFunction('qapeError', error => this._pageErrorHandler.emit('page-error', error));
await this._page.evaluateOnNewDocument(this._config.pageErrorHandler);
}

Expand Down
3 changes: 0 additions & 3 deletions src/browser/__tests__/BrowserSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe('Browser', () => {
expect(browser._browser).toEqual(null);
expect(browser._page).toEqual(null);
expect(browser._pageErrorHandler).toEqual(null);
expect(browser._defaultPageErrorHandler).toEqual(null);
});

it('can init browser instance', async () => {
Expand Down Expand Up @@ -69,8 +68,6 @@ describe('Browser', () => {
await browser._initPageErrorHandler();

expect(browser._pageErrorHandler).toEqual(eventEmitter);
expect(browser._defaultPageErrorHandler)
.toEqual(jasmine.any(Function));
expect(page.exposeFunction)
.toHaveBeenCalledWith('qapeError', jasmine.any(Function));
expect(page.evaluateOnNewDocument)
Expand Down
12 changes: 10 additions & 2 deletions src/config/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ module.exports = {
description: 'Maximal number of actions performed in a random scenario (if error occures, the scenario is ended)',
type: 'number'
},
afterActionWaitTime: {
value: 500,
description: 'Wait time after each action, there should be some delay so the javascript at your website is executed and an error is displayed before performing another action. ',
type: 'number'
},
numberOfActionFailuresToAbortRandomScenario: {
value: 20,
description: 'Number of execution errors of actions to abort the random scenario. This prevents from infinity loops, when qape is not able to perform any action on the page and keeps retrying.',
Expand Down Expand Up @@ -64,7 +69,10 @@ module.exports = {
width: 1280,
height: 720
},
args: ['--start-maximized']
args: [
'--start-maximized',
'--user-agent=Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
]
},
description: 'Default browser settings passed to puppeteer.launch()',
type: 'Object',
Expand All @@ -91,7 +99,7 @@ module.exports = {
type: 'string'
},
reporters: {
value: ['default'],
value: ['console', 'file', 'spinner'],
description: 'Define your reporters for the QApe run. You can pass a string for reporters in npm registry, i.e. if you pass \'super\', QApe will look for reporter \'qape-reporter-super\'. You can also pass Class.',
type: 'string[]|Class[]'
},
Expand Down
24 changes: 20 additions & 4 deletions src/reporter/BaseReporter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import EventEmitter from 'events';
import DefaultReporter from './DefaultReporter';
import ConsoleReporter from './ConsoleReporter';
import FileReporter from './FileReporter';
import SpinnerReporter from './SpinnerReporter';

/**
* Base Reporter distributing events to all defined reporters
Expand Down Expand Up @@ -29,8 +31,16 @@ export default class BaseReporter extends EventEmitter {
}

this._config.reporters.forEach(reporter => {
if (reporter === 'default') {
return this._initReporterFromClass(DefaultReporter);
if (reporter === 'console') {
return this._initReporterFromClass(ConsoleReporter);
}

if (reporter === 'file') {
return this._initReporterFromClass(FileReporter);
}

if (reporter === 'spinner') {
return this._initReporterFromClass(SpinnerReporter);
}

if (typeof reporter === 'string') {
Expand All @@ -55,7 +65,13 @@ export default class BaseReporter extends EventEmitter {
*/
emit(eventName, eventData) {
this._reporters
.forEach(reporter => reporter.emit(eventName, eventData));
.forEach(reporter => {
try {
reporter.emit(eventName, eventData)
} catch (e) {
console.error(e);
}
});
}

/**
Expand Down
Loading

0 comments on commit 7f03ddd

Please sign in to comment.