Skip to content

Commit

Permalink
Implement test run warnings (#671 part2) (#679)
Browse files Browse the repository at this point in the history
* Implement warnings infrastructure. "Screenshots dir not specified" warning

* "Screenshots creation error" warning

* "Screenshots and resizing doesn't supported on Linux" warning

* Bump version
  • Loading branch information
inikulin committed Jul 21, 2016
1 parent fa9b1a1 commit 69c7c4c
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 141 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ lib
site
.sass-cache
.publish
___test-screenshots___
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "testcafe",
"license": "MIT",
"version": "0.0.25-alpha",
"version": "0.1.0-alpha",
"main": "lib/index",
"bin": {
"testcafe": "./bin/testcafe.js"
Expand Down
17 changes: 6 additions & 11 deletions src/errors/runtime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ import { renderers } from 'callsite-record';
import MESSAGE from './message';
import createStackFilter from '../create-stack-filter';
import getCallsite from '../get-callsite';


// Utils
function getText (template, ...args) {
return args.reduce((msg, arg) => msg.replace(/{.+?}/, arg), template);
}
import renderTemplate from '../../utils/render-template';

// Errors
export class GeneralError extends Error {
constructor () {
super(getText.apply(null, arguments));
super(renderTemplate.apply(null, arguments));
Error.captureStackTrace(this, GeneralError);

// HACK: workaround for the `instanceof` problem
Expand All @@ -23,19 +18,19 @@ export class GeneralError extends Error {

export class TestCompilationError extends Error {
constructor (originalError) {
super(getText(MESSAGE.cannotPrepareTestsDueToError, originalError.toString()));
super(renderTemplate(MESSAGE.cannotPrepareTestsDueToError, originalError.toString()));

// NOTE: stack includes message as well.
this.stack = getText(MESSAGE.cannotPrepareTestsDueToError, originalError.stack);
this.stack = renderTemplate(MESSAGE.cannotPrepareTestsDueToError, originalError.stack);
this.constructor = TestCompilationError;
}
}

export class APIError extends Error {
constructor (methodName, template, ...args) {
var rawMessage = getText(template, ...args);
var rawMessage = renderTemplate(template, ...args);

super(getText(MESSAGE.cannotPrepareTestsDueToError, rawMessage));
super(renderTemplate(MESSAGE.cannotPrepareTestsDueToError, rawMessage));

// NOTE: `rawMessage` is used in error substitution if it occurs in test run.
this.rawMessage = rawMessage;
Expand Down
2 changes: 1 addition & 1 deletion src/legacy/test-run/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export default class LegacyTestRun extends Session {

this.isFileDownloading = false;

// TODO remove it then we move shared data to session storage
this.errs = [];
this.warnings = [];
this.nativeDialogsInfo = null;
this.nativeDialogsInfoTimeStamp = 0;
this.stepsSharedData = {};
Expand Down
24 changes: 18 additions & 6 deletions src/reporter/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { find, sortBy } from 'lodash';
import { find, sortBy, uniqBy } from 'lodash';
import ReporterPluginHost from './plugin-host';

export default class Reporter {
Expand Down Expand Up @@ -26,11 +26,22 @@ export default class Reporter {
screenshotPath: null,
pendingRuns: runsPerTest,
errs: [],
warnings: [],
unstable: false,
startTime: null
};
}

static _createTestRunInfo (reportItem) {
return {
errs: sortBy(reportItem.errs, ['userAgent', 'type']),
warnings: reportItem.warnings,
durationMs: new Date() - reportItem.startTime,
unstable: reportItem.unstable,
screenshotPath: reportItem.screenshotPath
};
}

_getReportItemForTestRun (testRun) {
return find(this.reportQueue, i => i.test === testRun.test);
}
Expand All @@ -41,14 +52,11 @@ export default class Reporter {
// previous one frees all browser connections.
// Therefore, tests always get completed sequentially.
var reportItem = this.reportQueue.shift();
var durationMs = new Date() - reportItem.startTime;

if (!reportItem.errs.length)
this.passed++;

reportItem.errs = sortBy(reportItem.errs, ['userAgent', 'type']);

this.plugin.reportTestDone(reportItem.test.name, reportItem.errs, durationMs, reportItem.unstable, reportItem.screenshotPath);
this.plugin.reportTestDone(reportItem.test.name, Reporter._createTestRunInfo(reportItem));

// NOTE: here we assume that tests are sorted by fixture.
// Therefore, if the next report item has a different
Expand Down Expand Up @@ -80,8 +88,12 @@ export default class Reporter {
var reportItem = this._getReportItemForTestRun(testRun);

reportItem.pendingRuns--;
reportItem.errs = reportItem.errs.concat(testRun.errs);
reportItem.unstable = reportItem.unstable || testRun.unstable;
reportItem.errs = reportItem.errs.concat(testRun.errs);

// NOTE: avoid duplicate warnings
reportItem.warnings = reportItem.warnings.concat(testRun.warnings);
reportItem.warnings = uniqBy(reportItem.warnings, 'message');

if (!reportItem.pendingRuns) {
if (task.screenshots.hasCapturedFor(testRun.test))
Expand Down
14 changes: 8 additions & 6 deletions src/runner/screenshots/capturer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ export default class Capturer {
async _takeScreenshot (windowId, filePath) {
await ensureDir(dirname(filePath));
await takeScreenshot(windowId, filePath);

return filePath;
}

async captureAction (windowId, { stepName, customPath }) {
this.testEntry.screenshotCapturingCalled = true;

if (!this.enabled)
return null;

Expand All @@ -38,7 +34,11 @@ export default class Capturer {
if (customPath)
this.testEntry.path = this.baseScreenshotsPath;

return await this._takeScreenshot(windowId, filePath);
await this._takeScreenshot(windowId, filePath);

this.testEntry.hasScreenshots = true;

return filePath;
}

async captureError (windowId, { stepName, screenshotRequired }) {
Expand All @@ -47,7 +47,9 @@ export default class Capturer {

var filePath = joinPath(this.testScreenshotsPath, 'errors', Capturer._getFileName(stepName));

return await this._takeScreenshot(windowId, filePath);
await this._takeScreenshot(windowId, filePath);

return filePath;
}
}

12 changes: 4 additions & 8 deletions src/runner/screenshots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { join as joinPath } from 'path';
import shortId from 'shortid';
import { find } from 'lodash';
import Capturer from './capturer';
import SCREENSHOTS_WARNING_MESSAGES from './warning-messages';

export default class Screenshots {
constructor (path) {
Expand All @@ -21,9 +20,9 @@ export default class Screenshots {

_addTestEntry (test) {
var testEntry = {
test: test,
path: this.screenshotsPath ? joinPath(this.screenshotsPath, shortId.generate()) : '',
screenshotCapturingCalled: false
test: test,
path: this.screenshotsPath ? joinPath(this.screenshotsPath, shortId.generate()) : '',
hasScreenshots: false
};

this.testEntries.push(testEntry);
Expand All @@ -36,13 +35,10 @@ export default class Screenshots {
}

hasCapturedFor (test) {
return this._getTestEntry(test).screenshotCapturingCalled;
return this._getTestEntry(test).hasScreenshots;
}

getPathFor (test) {
if (this._getTestEntry(test).screenshotCapturingCalled && !this.enabled)
return SCREENSHOTS_WARNING_MESSAGES.screenshotDirNotSet;

return this._getTestEntry(test).path;
}

Expand Down
4 changes: 0 additions & 4 deletions src/runner/screenshots/warning-messages.js

This file was deleted.

70 changes: 36 additions & 34 deletions src/test-run/browser-manipulation-queue.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { EventEmitter } from 'events';
import shortId from 'shortid';
import OS from 'os-family';
import { resize as resizeWindow, getViewportSize } from 'testcafe-browser-natives';
import SCREENSHOTS_WARNING_MESSAGES from '../runner/screenshots/warning-messages';
import { isServiceCommand } from './commands/utils';
import COMMAND_TYPE from './commands/type';
import Warning from '../warnings';
import WARNING_MESSAGE from '../warnings/message';


export default class BrowserManipulationQueue {
export default class BrowserManipulationQueue extends EventEmitter {
constructor (windowId, screenshotCapturer) {
super();

this.commands = [];
this.windowId = windowId;
this.screenshotCapturer = screenshotCapturer;
Expand All @@ -24,52 +28,42 @@ export default class BrowserManipulationQueue {
return await resizeWindow(this.windowId, currentWidth, currentHeight, width, height);
}

async _takeScreenshot (customPath) {
try {
return await this.screenshotCapturer.captureAction(this.windowId, {
stepName: shortId.generate(),
customPath: customPath
});
}
catch (e) {
// NOTE: swallow the error silently if we can't take screenshots for some
// reason (e.g. we don't have permissions to write a screenshot file).
async _takeScreenshot (capture) {
if (!this.screenshotCapturer.enabled) {
this.emit('warning', new Warning(WARNING_MESSAGE.screenshotsPathNotSpecified));
return null;
}
}

async _takeScreenshotOnFail () {
if (!this.screenshotCapturer.enabled)
return SCREENSHOTS_WARNING_MESSAGES.screenshotDirNotSet;

try {
return await this.screenshotCapturer.captureError(this.windowId, {
stepName: shortId.generate(),
screenshotRequired: true
});
return await capture();
}
catch (e) {
return SCREENSHOTS_WARNING_MESSAGES.cannotCreate;
catch (err) {
this.emit('warning', new Warning(WARNING_MESSAGE.screenshotError, err.message));
return null;
}
}

push (command) {
this.commands.push(command);
}

removeAllNonServiceManipulations () {
this.commands = this.commands.filter(command => isServiceCommand(command));
}

async executePendingManipulation (driverMsg) {
// TODO: remove once https://github.com/DevExpress/testcafe-browser-natives/issues/12 implemented
if (OS.linux) {
this.emit('warning', new Warning(WARNING_MESSAGE.browserManipulationsNotSupportedOnLinux));
return null;
}

var command = this.commands.shift();

switch (command.type) {
case COMMAND_TYPE.takeScreenshot:
return await this._takeScreenshot(command.path);
return await this._takeScreenshot(() => this.screenshotCapturer.captureAction(this.windowId, {
stepName: shortId.generate(),
customPath: command.path
}));

case COMMAND_TYPE.takeScreenshotOnFail:
return await this._takeScreenshotOnFail();
return await this._takeScreenshot(() => this.screenshotCapturer.captureError(this.windowId, {
stepName: shortId.generate(),
screenshotRequired: true
}));

case COMMAND_TYPE.resizeWindow:
return await this._resizeWindow(driverMsg.currentWidth, driverMsg.currentHeight, command.width, command.height);
Expand All @@ -80,4 +74,12 @@ export default class BrowserManipulationQueue {

return null;
}

push (command) {
this.commands.push(command);
}

removeAllNonServiceManipulations () {
this.commands = this.commands.filter(command => isServiceCommand(command));
}
}
27 changes: 19 additions & 8 deletions src/test-run/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path';
import { some } from 'lodash';
import { readSync as read } from 'read-file-relative';
import Promise from 'pinkie';
import Mustache from 'mustache';
Expand Down Expand Up @@ -37,10 +38,9 @@ export default class TestRun extends Session {

super(uploadsRoot);

this.opts = opts;
this.test = test;
this.browserConnection = browserConnection;
this.browserManipulationQueue = new BrowserManipulationQueue(this.id, screenshotCapturer);
this.opts = opts;
this.test = test;
this.browserConnection = browserConnection;

this.running = false;
this.state = STATE.initial;
Expand All @@ -51,17 +51,23 @@ export default class TestRun extends Session {
this.pendingRequest = null;
this.pendingPageError = null;

this.errs = [];
this.warnings = [];

this.lastDriverStatusId = null;
this.lastDriverStatusResponse = null;

this.browserManipulationQueue = new BrowserManipulationQueue(this.id, screenshotCapturer);

this.browserManipulationQueue.on('warning', warning => this._addWarning(warning));

this.debugLog = new TestRunDebugLog(this.browserConnection.userAgent);

this.injectable.scripts.push('/testcafe-core.js');
this.injectable.scripts.push('/testcafe-ui.js');
this.injectable.scripts.push('/testcafe-runner.js');
this.injectable.scripts.push('/testcafe-driver.js');
this.injectable.styles.push('/testcafe-ui-styles.css');

this.errs = [];
this.lastDriverStatusId = null;
this.lastDriverStatusResponse = null;
}


Expand Down Expand Up @@ -166,6 +172,11 @@ export default class TestRun extends Session {
this.errs.push(adapter);
}

_addWarning (warning) {
// NOTE: avoid duplicates
if (!some(this.warnings, { message: warning.message }))
this.warnings.push(warning);
}

// Task queue
_enqueueCommand (command, callsite) {
Expand Down
3 changes: 3 additions & 0 deletions src/utils/render-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function renderTemplate (template, ...args) {
return args.reduce((msg, arg) => msg.replace(/{.+?}/, arg), template);
}
7 changes: 7 additions & 0 deletions src/warnings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import renderTemplate from '../utils/render-template';

export default class Warning {
constructor () {
this.message = renderTemplate.apply(null, arguments);
}
}
5 changes: 5 additions & 0 deletions src/warnings/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
screenshotsPathNotSpecified: 'Cannot take screenshots because the screenshot directory is not specified. To specify it, use the "-s" or "--screenshots" command line option or the "screenshots" method of the test runner in case you are using API.',
screenshotError: 'Was unable to take a screenshot due to error.\n\n{errMessage}',
browserManipulationsNotSupportedOnLinux: 'The screenshot and window resize functionality are not yet supported on Linux. Subscribe to the following issue to keep track: https://github.com/DevExpress/testcafe-browser-natives/issues/12'
};

0 comments on commit 69c7c4c

Please sign in to comment.