Skip to content

Commit

Permalink
Enable screenshot functionality (closes DevExpress#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderMoskovkin committed Oct 29, 2015
1 parent 5bf5090 commit 8afefd4
Show file tree
Hide file tree
Showing 21 changed files with 204 additions and 83 deletions.
4 changes: 2 additions & 2 deletions src/client/runner/api/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -698,11 +698,11 @@ export function upload (what, path) {
);
}

export function screenshot () {
export function screenshot (filePath) {
stepIterator.asyncAction(function (iteratorCallback) {
stepIterator.takeScreenshot(function () {
iteratorCallback();
}, false);
}, false, filePath);
});
}

Expand Down
4 changes: 3 additions & 1 deletion src/client/runner/iframe-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ IFrameRunner.prototype._onGetStepsSharedData = function (e) {
IFrameRunner.prototype._onTakeScreenshot = function (e) {
var msg = {
cmd: RunnerBase.IFRAME_TAKE_SCREENSHOT_REQUEST_CMD,
isFailedStep: e.isFailedStep
isFailedStep: e.isFailedStep,
stepName: e.stepName,
filePath: e.filePath
};

messageSandbox.sendServiceMsg(msg, window.top);
Expand Down
2 changes: 2 additions & 0 deletions src/client/runner/runner-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ RunnerBase.prototype._initIFrameBehavior = function () {
case RunnerBase.IFRAME_TAKE_SCREENSHOT_REQUEST_CMD:
runner._onTakeScreenshot({
isFailedStep: message.isFailedStep,
stepName: message.stepName,
filePath: message.filePath,
callback: function () {
msg = {
cmd: RunnerBase.IFRAME_TAKE_SCREENSHOT_RESPONSE_CMD
Expand Down
42 changes: 14 additions & 28 deletions src/client/runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ Runner.prototype._onError = function (err) {

this._onTakeScreenshot({
isFailedStep: true,
//TODO:
//withoutStepName: !(ERRORS.hasErrorStepName(err) && ERRORS.hasErrorStepName(err)),
stepName: this.stepIterator.getCurrentStep(),
callback: function () {
runner.stopped = true;
transport.fail(err, Runner.checkStatus);
Expand Down Expand Up @@ -138,39 +137,26 @@ Runner.prototype._onGetStepsSharedData = function (e) {
};

Runner.prototype._onTakeScreenshot = function (e) {
var savedTitle = document.title,
windowMark = '[tc-' + Date.now() + ']',
browserName = null,
callback = e && e.callback ? e.callback : function () {
var savedTitle = document.title,
pageUrl = window.location.toString(),
callback = e && e.callback ? e.callback : function () {
},
runner = this;

runner.eventEmitter.emit(RunnerBase.SCREENSHOT_CREATING_STARTED_EVENT, {});
runner = this;

if (!SETTINGS.get().ENABLE_SCREENSHOTS)
return callback();

if (browserUtils.isMSEdge)
browserName = 'MSEDGE';
else if (browserUtils.isSafari)
browserName = 'SAFARI';
else if (browserUtils.isOpera || browserUtils.isOperaWithWebKit)
browserName = 'OPERA';
else if (browserUtils.isWebKit)
browserName = 'CHROME';
else if (browserUtils.isMozilla)
browserName = 'FIREFOX';
else if (browserUtils.isIE)
browserName = 'IE';
runner.eventEmitter.emit(RunnerBase.SCREENSHOT_CREATING_STARTED_EVENT, {});

var msg = {
cmd: 'CMD_TAKE_SCREENSHOT', //TODO: fix
windowMark: windowMark,
browserName: browserName,
isFailedStep: e.isFailedStep,
withoutStepName: e.withoutStepName,
url: window.location.toString()
cmd: COMMAND.takeScreenshot,
pageUrl: pageUrl,
isFailedStep: e.isFailedStep,
stepName: e.stepName,
filePath: e.filePath
};

var assignedTitle = savedTitle + windowMark,
var assignedTitle = `[ ${pageUrl} ]`,
checkTitleIntervalId = window.setInterval(function () {
if (document.title !== assignedTitle) {
savedTitle = document.title;
Expand Down
8 changes: 7 additions & 1 deletion src/client/runner/step-iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,9 @@ StepIterator.prototype.runLast = function () {
};

StepIterator.prototype.getCurrentStep = function () {
if (!this.initialized)
return '';

return this.state.stepNames ? this.state.stepNames[this.state.step - 1] : SETTINGS.get().CURRENT_TEST_STEP_NAME;
};

Expand All @@ -513,6 +516,7 @@ StepIterator.prototype.onActionRun = function () {
this.eventEmitter.emit(StepIterator.ACTION_RUN_EVENT, {});
};


//Global __waitFor()
StepIterator.prototype.setGlobalWaitFor = function (event, timeout) {
this.globalWaitForEvent = event;
Expand Down Expand Up @@ -545,9 +549,11 @@ StepIterator.prototype.__waitFor = function (callback) {
});
};

StepIterator.prototype.takeScreenshot = function (callback, isFailedStep) {
StepIterator.prototype.takeScreenshot = function (callback, isFailedStep, filePath) {
this.eventEmitter.emit(StepIterator.TAKE_SCREENSHOT_EVENT, {
isFailedStep: isFailedStep,
stepName: this.getCurrentStep(),
filePath: filePath || '',
callback: callback
});
};
Expand Down
1 change: 1 addition & 0 deletions src/client/test-run/index.js.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
testCafeCore.SETTINGS.set({
CURRENT_TEST_STEP_NAME: nextStep ? stepNames[nextStep - 1] : 'Test initialization',
BROWSER_STATUS_URL: '{{{browserStatusUrl}}}',
ENABLE_SCREENSHOTS: {{{enableScreenshots}}},
TAKE_SCREENSHOT_ON_FAILS: {{{takeScreenshotOnFails}}},
SKIP_JS_ERRORS: {{{skipJsErrors}}},
ENABLE_SOURCE_INDEX: true,
Expand Down
15 changes: 9 additions & 6 deletions src/reporters/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,16 @@ export default class BaseReporter {
// This happens because the next test can't be completed until the
// previous one frees all browser connections.
// Therefore, tests always get completed sequentially.
var reportItem = this.reportQueue.shift();
var durationMs = new Date() - reportItem.startTime;
var reportItem = this.reportQueue.shift();
var durationMs = new Date() - reportItem.startTime;
var screenshotPath = reportItem.screenshotCreated ? reportItem.screenshotPath : null;

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

BaseReporter._errorSorter(reportItem.errs);

this._reportTestDone(reportItem.testName, reportItem.errs, durationMs, reportItem.unstable);
this._reportTestDone(reportItem.testName, reportItem.errs, durationMs, reportItem.unstable, screenshotPath);

// NOTE: here we assume that tests are sorted by fixture.
// Therefore, if the next report item has a different
Expand Down Expand Up @@ -120,8 +121,10 @@ export default class BaseReporter {
testRun.errs.forEach(err => err.userAgent = userAgent);

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

if (!reportItem.pendingRuns)
this._shiftReportQueue();
Expand Down Expand Up @@ -182,7 +185,7 @@ export default class BaseReporter {
throw new Error('Not implemented');
}

_reportTestDone (name, errs, durationMs, unstable) {
_reportTestDone (name, errs, durationMs, unstable, screenshotPath) {
throw new Error('Not implemented');
}

Expand Down
4 changes: 2 additions & 2 deletions src/reporters/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export default class JSONReporter extends BaseReporter {
this.report.fixtures.push(this.currentFixture);
}

_reportTestDone (name, errs, durationMs, unstable) {
_reportTestDone (name, errs, durationMs, unstable, screenshotPath) {
errs = errs.map(err => this._formatError(err));

this.currentFixture.tests.push({ name, errs, durationMs, unstable });
this.currentFixture.tests.push({ name, errs, durationMs, unstable, screenshotPath });
}

_reportTaskDone (passed, total, endTime) {
Expand Down
5 changes: 4 additions & 1 deletion src/reporters/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default class ListReporter extends SpecReporter {
this.currentFixtureName = name;
}

_reportTestDone (name, errs, durationMs, unstable) {
_reportTestDone (name, errs, durationMs, unstable, screenshotPath) {
var hasErr = !!errs.length;
var nameStyle = hasErr ? this.style.red : this.style.gray;
var symbol = hasErr ? this.style.red(this.symbols.err) : this.style.green(this.symbols.ok);
Expand All @@ -25,6 +25,9 @@ export default class ListReporter extends SpecReporter {
if (unstable)
title += this.style.yellow(' (unstable)');

if (screenshotPath)
title += ` (screenshots: ${screenshotPath})`;

this._write(title);

if (hasErr) {
Expand Down
5 changes: 4 additions & 1 deletion src/reporters/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default class SpecReporter extends BaseReporter {
._newline();
}

_reportTestDone (name, errs, durationMs, unstable) {
_reportTestDone (name, errs, durationMs, unstable, screenshotPath) {
var hasErr = !!errs.length;
var nameStyle = hasErr ? this.style.red : this.style.gray;
var symbol = hasErr ? this.style.red(this.symbols.err) : this.style.green(this.symbols.ok);
Expand All @@ -47,6 +47,9 @@ export default class SpecReporter extends BaseReporter {
if (unstable)
title += this.style.yellow(' (unstable)');

if (screenshotPath)
title += ` (screenshots: ${screenshotPath})`;

this._write(title);

if (hasErr) {
Expand Down
5 changes: 4 additions & 1 deletion src/reporters/xunit.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ export default class XUnitReporter extends BaseReporter {
this.currentFixtureName = escapeHtml(name);
}

_reportTestDone (name, errs, durationMs, unstable) {
_reportTestDone (name, errs, durationMs, unstable, screenshotPath) {
var hasErr = !!errs.length;

if (unstable)
name += ' (unstable)';

if (screenshotPath)
name += ` (screenshots: ${screenshotPath})`;

name = escapeHtml(name);

var openTag = `<testcase classname="${this.currentFixtureName}" name="${name}" time="${durationMs / 1000}"`;
Expand Down
4 changes: 2 additions & 2 deletions src/runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ export default class Runner {
}

screenshots (path, takeOnFails = false) {
this.opts.takeScreenshotOnFails = takeOnFails;
this.bootstrapper.screenshotPath = path;
this.opts.takeScreenshotOnFails = takeOnFails;
this.opts.screenshotPath = path;

return this;
}
Expand Down
30 changes: 28 additions & 2 deletions src/runner/test-run/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import path from 'path';
import { readSync as read } from 'read-file-relative';
import Mustache from 'mustache';
import { Session } from 'testcafe-hammerhead';
import { screenshot as takeScreenshot } from 'testcafe-browser-natives';
import COMMAND from './command';
import ERROR_TYPE from '../../reporters/errors/type';
import { exists, mkdir } from '../../utils/fs';


// Const
Expand Down Expand Up @@ -34,6 +36,17 @@ export default class TestRun extends Session {
this.nativeDialogsInfoTimeStamp = 0;
this.stepsSharedData = {};

this.enableScreenshots = !!this.opts.screenshotPath;
this.screenshotCreated = false;

if (this.enableScreenshots) {
if (!this.test.screenshotPath)
this.test.screenshotPath = path.join(this.opts.screenshotPath, Date.now().toString());

this.screenshotPath = path.join(this.test.screenshotPath,
this.browserConnection.userAgent.toString().replace('/', '@'));
}

this.injectable.scripts.push('/testcafe-core.js');
this.injectable.scripts.push('/testcafe-ui.js');
this.injectable.scripts.push('/testcafe-runner.js');
Expand Down Expand Up @@ -65,6 +78,7 @@ export default class TestRun extends Session {
testError: this.testError ? JSON.stringify(this.testError) : 'null',
browserHeartbeatUrl: this.browserConnection.heartbeatUrl,
browserStatusUrl: this.browserConnection.statusUrl,
enableScreenshots: this.enableScreenshots,
takeScreenshotOnFails: this.opts.takeScreenshotOnFails,
skipJsErrors: this.opts.skipJsErrors,
nativeDialogsInfo: JSON.stringify(this.nativeDialogsInfo),
Expand Down Expand Up @@ -171,6 +185,18 @@ ServiceMessages[COMMAND.nativeDialogsInfoSet] = function (msg) {
}
};

ServiceMessages[COMMAND.takeScreenshot] = function () {
//TODO:
ServiceMessages[COMMAND.takeScreenshot] = async function (msg) {
if (!await exists(this.screenshotPath))
await mkdir(this.screenshotPath);

var pageUrl = msg.pageUrl;
var stepName = msg.stepName;
var isFailedStep = msg.isFailedStep;

var filePath = msg.filePath ?
path.join(path.dirname(this.test.fixture.path), msg.filePath) :
`${this.screenshotPath}\\${isFailedStep ? 'errors\\' : ''}${stepName || 'Page Load'}.png`;

return await takeScreenshot(pageUrl, filePath)
.then(() => this.screenshotCreated = true);
};
34 changes: 34 additions & 0 deletions src/utils/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import fs from 'fs';
import path from 'path';
import promisify from 'es6-promisify';


var stat = promisify(fs.stat);
var mkdirPromisified = promisify(fs.mkdir);


export async function exists (dirPath) {
try {
await stat(dirPath);
}
catch (err) {
return false;
}

return true;
}

export async function mkdir (dirPath) {
var dirs = dirPath.split(path.sep);
var i = 0;
var curPath = '';

while (dirs[i]) {
curPath = curPath ? path.join(curPath, dirs[i]) : dirs[i];

if (!await exists(curPath))
await mkdirPromisified(curPath);

i++;
}
}
Loading

0 comments on commit 8afefd4

Please sign in to comment.