Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The onActionDone method should take TestRunErrorFormattableAdapter instance instead of Error (closes #4867) #4895

Merged
merged 4 commits into from Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 5 additions & 9 deletions src/reporter/index.js
Expand Up @@ -2,7 +2,6 @@ import { find, sortBy, union } from 'lodash';
import { writable as isWritableStream } from 'is-stream';
import ReporterPluginHost from './plugin-host';
import formatCommand from './command/format-command';
import TestCafeErrorList from '../errors/error-list';

export default class Reporter {
constructor (plugin, task, outStream, name) {
Expand Down Expand Up @@ -135,14 +134,11 @@ export default class Reporter {
reportItem.pendingTestRunDonePromise.resolve();
}

_prepareReportTestActionEventArgs ({ command, result, testRun, errors }) {
_prepareReportTestActionEventArgs ({ command, result, testRun, err }) {
const args = {};

if (errors) {
errors = errors instanceof TestCafeErrorList ? errors.items : [errors];

args.errors = errors;
}
if (err)
args.err = err;

return Object.assign(args, {
testRunId: testRun.id,
Expand Down Expand Up @@ -218,9 +214,9 @@ export default class Reporter {
}
});

task.on('test-action-done', async ({ apiActionName, command, result, testRun, errors }) => {
task.on('test-action-done', async ({ apiActionName, command, result, testRun, err }) => {
if (this.plugin.reportTestActionDone) {
const args = this._prepareReportTestActionEventArgs({ command, result, testRun, errors });
const args = this._prepareReportTestActionEventArgs({ command, result, testRun, err });

await this.plugin.reportTestActionDone(apiActionName, args);
}
Expand Down
47 changes: 32 additions & 15 deletions src/test-run/index.js
Expand Up @@ -42,6 +42,7 @@ import {
} from './commands/utils';

import { TEST_RUN_ERRORS } from '../errors/types';
import processTestFnError from '../errors/process-test-fn-error';

const lazyRequire = require('import-lazy')(require);
const SessionController = lazyRequire('./session-controller');
Expand Down Expand Up @@ -294,16 +295,15 @@ export default class TestRun extends AsyncEventEmitter {
await fn(this);
}
catch (err) {
let screenshotPath = null;
await this._makeScreenshotOnFail();

const { screenshots } = this.opts;
this.addError(err);

if (screenshots && screenshots.takeOnFails)
screenshotPath = await this.executeCommand(new browserManipulationCommands.TakeScreenshotOnFailCommand());

this.addError(err, screenshotPath);
return false;
}
finally {
this.errScreenshotPath = null;
}

return !this._addPendingPageErrorIfAny();
}
Expand Down Expand Up @@ -378,19 +378,19 @@ export default class TestRun extends AsyncEventEmitter {
return false;
}

_createErrorAdapter (err, screenshotPath) {
_createErrorAdapter (err) {
return new TestRunErrorFormattableAdapter(err, {
userAgent: this.browserConnection.userAgent,
screenshotPath: screenshotPath || '',
screenshotPath: this.errScreenshotPath || '',
testRunPhase: this.phase
});
}

addError (err, screenshotPath) {
addError (err) {
const errList = err instanceof TestCafeErrorList ? err.items : [err];

errList.forEach(item => {
const adapter = this._createErrorAdapter(item, screenshotPath);
const adapter = this._createErrorAdapter(item);

this.errs.push(adapter);
});
Expand Down Expand Up @@ -617,8 +617,9 @@ export default class TestRun extends AsyncEventEmitter {
}

async executeAction (actionName, command, callsite) {
let error = null;
let result = null;
let errorAdapter = null;
let error = null;
let result = null;

await this.emitActionStart(actionName, command);

Expand All @@ -627,9 +628,18 @@ export default class TestRun extends AsyncEventEmitter {
}
catch (err) {
error = err;

// NOTE: check if error is TestCafeErrorList is specific for the `useRole` action
// if error is TestCafeErrorList we do not need to create an adapter,
// since error is already was processed in role initializer
if (!(err instanceof TestCafeErrorList)) {
await this._makeScreenshotOnFail();

errorAdapter = this._createErrorAdapter(processTestFnError(err));
}
}

await this.emitActionDone(actionName, command, result, error);
await this.emitActionDone(actionName, command, result, errorAdapter);

if (error)
throw error;
Expand Down Expand Up @@ -709,6 +719,13 @@ export default class TestRun extends AsyncEventEmitter {
return Promise.reject(err);
}

async _makeScreenshotOnFail () {
const { screenshots } = this.opts;

if (!this.errScreenshotPath && screenshots && screenshots.takeOnFails)
this.errScreenshotPath = await this.executeCommand(new browserManipulationCommands.TakeScreenshotOnFailCommand());
}

_decorateWithFlag (fn, flagName, value) {
return async () => {
this[flagName] = value;
Expand Down Expand Up @@ -844,9 +861,9 @@ export default class TestRun extends AsyncEventEmitter {
await this.emit('action-start', { command, apiActionName });
}

async emitActionDone (apiActionName, command, result, errors) {
async emitActionDone (apiActionName, command, result, err) {
if (!this.preventEmitActionEvents)
await this.emit('action-done', { command, apiActionName, result, errors });
await this.emit('action-done', { command, apiActionName, result, err });
}
}

Expand Down
6 changes: 3 additions & 3 deletions test/functional/fixtures/reporter/reporter.js
Expand Up @@ -48,7 +48,7 @@ function generateReporter (log, options = {}) {
log.push(item);
},

async reportTestActionDone (name, { command, test, fixture, errors }) {
async reportTestActionDone (name, { command, test, fixture, err }) {
if (!emitOnDone)
return;

Expand All @@ -57,8 +57,8 @@ function generateReporter (log, options = {}) {

const item = { name, action: 'done', command };

if (errors && errors.length)
item.errors = errors.map(err => err.code);
if (err)
item.err = err.code;

if (includeTestInfo) {
if (test.id) {
Expand Down
42 changes: 38 additions & 4 deletions test/functional/fixtures/reporter/test.js
Expand Up @@ -308,7 +308,7 @@ describe('Reporter', () => {
options: new ClickOptions(),
selector: 'Selector(\'#non-existing-target\')'
},
errors: ['E24']
err: 'E24'
}
]);
});
Expand Down Expand Up @@ -541,7 +541,7 @@ describe('Reporter', () => {
selector: 'Selector(\'#non-existing-element\')',
type: 'click'
},
errors: ['E24']
err: 'E24'
},
{
name: 'useRole',
Expand All @@ -553,8 +553,7 @@ describe('Reporter', () => {
phase: 'initialized'
},
type: 'useRole'
},
errors: ['E24']
}
}
]);
});
Expand Down Expand Up @@ -675,5 +674,40 @@ describe('Reporter', () => {
});
});

describe('Screenshot errors', () => {
it('Screenshot on action error', () => {
let testDoneErrors = null;
const actionDoneErrors = [];

function screenshotReporter () {
return {
async reportTestActionDone (name, { err }) {
actionDoneErrors.push(err);
},
async reportTaskStart () {
},
async reportFixtureStart () {
},
async reportTestDone (name, testRunInfo) {
testDoneErrors = testRunInfo.errs;
},
async reportTaskDone () {
}
};
}

return runTests('testcafe-fixtures/index-test.js', 'Screenshot on action error', {
only: 'chrome',
reporter: screenshotReporter,
screenshotsOnFails: true
})
.then(() => {
expect(actionDoneErrors[0]).is.undefined;
expect(actionDoneErrors[1].code).eql('E24');
expect(testDoneErrors.map(err => err.code)).eql(['E24', 'E8']);
expect(testDoneErrors[0].screenshotPath).is.not.empty;
expect(testDoneErrors[0].screenshotPath).eql(testDoneErrors[1].screenshotPath);
});
});
});
});
Expand Up @@ -63,3 +63,9 @@ test('Client Function', async () => {
test('Eval', async t => {
await t.eval(() => document.getElementById('#target'));
});

test('Screenshot on action error', async t => {
t.hover('body');

await t.click('#unexisting-element');
});
26 changes: 15 additions & 11 deletions test/server/test-controller-events-test.js
@@ -1,11 +1,12 @@
const expect = require('chai').expect;
const AsyncEventEmitter = require('../../lib/utils/async-event-emitter');
const TestRun = require('../../lib/test-run');
const TestController = require('../../lib/api/test-controller');
const Task = require('../../lib/runner/task');
const BrowserJob = require('../../lib/runner/browser-job');
const Reporter = require('../../lib/reporter');
const { Role } = require('../../lib/api/exportable-lib');
const expect = require('chai').expect;
const AsyncEventEmitter = require('../../lib/utils/async-event-emitter');
const TestRun = require('../../lib/test-run');
const TestController = require('../../lib/api/test-controller');
const Task = require('../../lib/runner/task');
const BrowserJob = require('../../lib/runner/browser-job');
const Reporter = require('../../lib/reporter');
const { Role } = require('../../lib/api/exportable-lib');
const TestRunErrorFormattableAdapter = require('../../lib/errors/test-run/formattable-adapter');

class TestRunMock extends TestRun {
constructor () {
Expand Down Expand Up @@ -175,10 +176,12 @@ describe('TestController action events', () => {

it('Error action', () => {
let actionResult = null;
let errorAdapter = null;

initializeReporter({
async reportTestActionDone (name, { command, errors }) {
actionResult = { name, command: command.type, err: errors[0].message };
async reportTestActionDone (name, { command, err }) {
errorAdapter = err;
actionResult = { name, command: command.type, err: err.errMsg };
}
});

Expand All @@ -191,8 +194,9 @@ describe('TestController action events', () => {
throw new Error();
})
.catch(err => {
expect(errorAdapter).instanceOf(TestRunErrorFormattableAdapter);
expect(err.message).eql('test error');
expect(actionResult).eql({ name: 'click', command: 'click', err: 'test error' });
expect(actionResult).eql({ name: 'click', command: 'click', err: 'Error: test error' });
});
});
});