439 lines
64 KiB
JavaScript
439 lines
64 KiB
JavaScript
|
"use strict";
|
||
|
var __rest = (this && this.__rest) || function (s, e) {
|
||
|
var t = {};
|
||
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||
|
t[p] = s[p];
|
||
|
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||
|
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||
|
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||
|
t[p[i]] = s[p[i]];
|
||
|
}
|
||
|
return t;
|
||
|
};
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const lodash_1 = require("lodash");
|
||
|
const is_stream_1 = require("is-stream");
|
||
|
const plugin_host_1 = __importDefault(require("./plugin-host"));
|
||
|
const plugin_methods_1 = __importDefault(require("./plugin-methods"));
|
||
|
const format_command_1 = __importDefault(require("./command/format-command"));
|
||
|
const runtime_1 = require("../errors/runtime");
|
||
|
const reporter_1 = require("../utils/reporter");
|
||
|
const resolve_path_relatively_cwd_1 = __importDefault(require("../utils/resolve-path-relatively-cwd"));
|
||
|
const make_dir_1 = __importDefault(require("make-dir"));
|
||
|
const path_1 = __importDefault(require("path"));
|
||
|
const fs_1 = __importDefault(require("fs"));
|
||
|
const debug_1 = __importDefault(require("debug"));
|
||
|
const debugLog = (0, debug_1.default)('testcafe:reporter');
|
||
|
class Reporter {
|
||
|
constructor(plugin, messageBus, outStream, name) {
|
||
|
this.plugin = new plugin_host_1.default(plugin, outStream, name);
|
||
|
this.messageBus = messageBus;
|
||
|
this.disposed = false;
|
||
|
this.taskInfo = null;
|
||
|
this.outStream = outStream;
|
||
|
this._assignMessageBusEventHandlers();
|
||
|
}
|
||
|
static _isSpecialStream(stream) {
|
||
|
return stream.isTTY || stream === process.stdout || stream === process.stderr;
|
||
|
}
|
||
|
static _createPendingPromise() {
|
||
|
let resolver = null;
|
||
|
const promise = new Promise(resolve => {
|
||
|
resolver = resolve;
|
||
|
});
|
||
|
promise.resolve = resolver;
|
||
|
return promise;
|
||
|
}
|
||
|
async init() {
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.init,
|
||
|
initialObject: null,
|
||
|
args: [{}],
|
||
|
});
|
||
|
}
|
||
|
async dispatchToPlugin({ method, initialObject, args = [] }) {
|
||
|
try {
|
||
|
// @ts-ignore
|
||
|
await this.plugin[method](...args);
|
||
|
}
|
||
|
catch (originalError) {
|
||
|
const uncaughtError = new runtime_1.ReporterPluginError({
|
||
|
name: this.plugin.name,
|
||
|
method,
|
||
|
originalError,
|
||
|
});
|
||
|
debugLog('Plugin error: %O', uncaughtError);
|
||
|
debugLog('Plugin error: initialObject: %O', initialObject);
|
||
|
if (initialObject)
|
||
|
await initialObject.emit('error', uncaughtError);
|
||
|
else
|
||
|
throw uncaughtError;
|
||
|
}
|
||
|
}
|
||
|
_assignMessageBusEventHandlers() {
|
||
|
const messageBus = this.messageBus;
|
||
|
messageBus.on('warning-add', async (e) => await this._onWarningAddHandler(e));
|
||
|
messageBus.once('start', async (task) => await this._onceTaskStartHandler(task));
|
||
|
messageBus.on('test-run-start', async (testRun) => await this._onTaskTestRunStartHandler(testRun));
|
||
|
messageBus.on('test-run-done', async (testRun) => await this._onTaskTestRunDoneHandler(testRun));
|
||
|
messageBus.on('test-action-start', async (e) => await this._onTaskTestActionStart(e));
|
||
|
messageBus.on('test-action-done', async (e) => await this._onTaskTestActionDone(e));
|
||
|
messageBus.once('done', async () => await this._onceTaskDoneHandler());
|
||
|
}
|
||
|
async dispose() {
|
||
|
var _a;
|
||
|
if (this.disposed)
|
||
|
return Promise.resolve();
|
||
|
this.disposed = true;
|
||
|
if (!(0, lodash_1.isFunction)((_a = this === null || this === void 0 ? void 0 : this.outStream) === null || _a === void 0 ? void 0 : _a.once)
|
||
|
|| Reporter._isSpecialStream(this.outStream)
|
||
|
|| !(0, is_stream_1.writable)(this.outStream))
|
||
|
return Promise.resolve();
|
||
|
const streamFinishedPromise = new Promise(resolve => {
|
||
|
this.outStream.once('finish', resolve);
|
||
|
this.outStream.once('error', resolve);
|
||
|
});
|
||
|
this.outStream.end();
|
||
|
return streamFinishedPromise;
|
||
|
}
|
||
|
static async _ensureOutStream(outStream) {
|
||
|
if (typeof outStream !== 'string')
|
||
|
return outStream;
|
||
|
const fullReporterOutputPath = (0, resolve_path_relatively_cwd_1.default)(outStream);
|
||
|
await (0, make_dir_1.default)(path_1.default.dirname(fullReporterOutputPath));
|
||
|
return fs_1.default.createWriteStream(fullReporterOutputPath);
|
||
|
}
|
||
|
static _addDefaultReporter(reporters) {
|
||
|
reporters.push({
|
||
|
name: 'spec',
|
||
|
output: process.stdout,
|
||
|
});
|
||
|
}
|
||
|
static async getReporterPlugins(reporters = []) {
|
||
|
if (!reporters.length)
|
||
|
Reporter._addDefaultReporter(reporters);
|
||
|
return Promise.all(reporters.map(async ({ name, output, options }) => {
|
||
|
const pluginFactory = (0, reporter_1.getPluginFactory)(name);
|
||
|
const processedName = (0, reporter_1.processReporterName)(name);
|
||
|
const outStream = output ? await Reporter._ensureOutStream(output) : void 0;
|
||
|
return {
|
||
|
plugin: pluginFactory(options),
|
||
|
name: processedName,
|
||
|
outStream,
|
||
|
};
|
||
|
}));
|
||
|
}
|
||
|
async _onWarningAddHandler({ message, testRun, actionId }) {
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportWarnings,
|
||
|
initialObject: this.messageBus,
|
||
|
args: [
|
||
|
{
|
||
|
message,
|
||
|
testRunId: testRun === null || testRun === void 0 ? void 0 : testRun.id,
|
||
|
actionId,
|
||
|
},
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
//Task
|
||
|
static _createTestItem(test, runsPerTest) {
|
||
|
return {
|
||
|
fixture: test.fixture,
|
||
|
test: test,
|
||
|
testRunIds: [],
|
||
|
screenshotPath: null,
|
||
|
screenshots: [],
|
||
|
videos: [],
|
||
|
quarantine: null,
|
||
|
errs: [],
|
||
|
warnings: [],
|
||
|
unstable: false,
|
||
|
startTime: null,
|
||
|
testRunInfo: null,
|
||
|
pendingRuns: runsPerTest,
|
||
|
pendingStarts: runsPerTest,
|
||
|
pendingTestRunDonePromise: Reporter._createPendingPromise(),
|
||
|
pendingTestRunStartPromise: Reporter._createPendingPromise(),
|
||
|
browsers: [],
|
||
|
};
|
||
|
}
|
||
|
static _createTestQueue(task) {
|
||
|
const runsPerTest = task.browserConnectionGroups.length;
|
||
|
return task.tests.map(test => Reporter._createTestItem(test, runsPerTest));
|
||
|
}
|
||
|
static _createTestRunInfo(reportItem) {
|
||
|
return {
|
||
|
errs: (0, lodash_1.sortBy)(reportItem.errs, ['userAgent', 'code']),
|
||
|
warnings: reportItem.warnings,
|
||
|
durationMs: +new Date() - reportItem.startTime,
|
||
|
unstable: reportItem.unstable,
|
||
|
screenshotPath: reportItem.screenshotPath,
|
||
|
screenshots: reportItem.screenshots,
|
||
|
videos: reportItem.videos,
|
||
|
quarantine: reportItem.quarantine,
|
||
|
skipped: reportItem.test.skip,
|
||
|
browsers: reportItem.browsers,
|
||
|
testId: reportItem.test.id,
|
||
|
fixture: {
|
||
|
id: reportItem.fixture.id,
|
||
|
name: reportItem.fixture.name,
|
||
|
path: reportItem.fixture.path,
|
||
|
meta: reportItem.fixture.meta,
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
_getTestItemForTestRun(taskInfo, testRun) {
|
||
|
return (0, lodash_1.find)(taskInfo.testQueue, i => i.test === testRun.test);
|
||
|
}
|
||
|
async _shiftTestQueue() {
|
||
|
if (!this.taskInfo)
|
||
|
return;
|
||
|
let currentFixture = null;
|
||
|
let nextReportItem = null;
|
||
|
let testItem = null;
|
||
|
const testQueue = this.taskInfo.testQueue;
|
||
|
while (testQueue.length && testQueue[0].testRunInfo) {
|
||
|
testItem = testQueue.shift();
|
||
|
currentFixture = testItem.fixture;
|
||
|
// NOTE: here we assume that tests are sorted by fixture.
|
||
|
// Therefore, if the next report item has a different
|
||
|
// fixture, we can report this fixture start.
|
||
|
nextReportItem = testQueue[0];
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTestDone,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
testItem.test.name,
|
||
|
testItem.testRunInfo,
|
||
|
testItem.test.meta,
|
||
|
],
|
||
|
});
|
||
|
if (!nextReportItem || nextReportItem.fixture === currentFixture)
|
||
|
continue;
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportFixtureStart,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
nextReportItem.fixture.name,
|
||
|
nextReportItem.fixture.path,
|
||
|
nextReportItem.fixture.meta,
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
async _resolveTestItem(taskInfo, testItem, testRun) {
|
||
|
if (!taskInfo.task)
|
||
|
return;
|
||
|
if (taskInfo.task.screenshots.hasCapturedFor(testRun.test)) {
|
||
|
testItem.screenshotPath = taskInfo.task.screenshots.getPathFor(testRun.test);
|
||
|
testItem.screenshots = taskInfo.task.screenshots.getScreenshotsInfo(testRun.test);
|
||
|
}
|
||
|
if (taskInfo.task.videos)
|
||
|
testItem.videos = taskInfo.task.videos.getTestVideos(testItem.test.id);
|
||
|
if (testRun.quarantine) {
|
||
|
const testItemQuarantine = testRun.quarantine.attempts.reduce((result, { errors }, index) => {
|
||
|
const passed = !errors.length;
|
||
|
const quarantineAttempt = index + 1;
|
||
|
result[quarantineAttempt] = { passed };
|
||
|
return result;
|
||
|
}, {});
|
||
|
Object.assign(testItem.quarantine, testItemQuarantine);
|
||
|
}
|
||
|
if (!testItem.testRunInfo) {
|
||
|
testItem.testRunInfo = Reporter._createTestRunInfo(testItem);
|
||
|
if (testItem.test.skip)
|
||
|
taskInfo.skipped++;
|
||
|
else if (testItem.errs.length)
|
||
|
taskInfo.failed++;
|
||
|
else
|
||
|
taskInfo.passed++;
|
||
|
}
|
||
|
await this._shiftTestQueue();
|
||
|
testItem.pendingTestRunDonePromise.resolve();
|
||
|
}
|
||
|
_prepareReportTestActionEventArgs({ command, duration, result, testRun, err }) {
|
||
|
const args = {};
|
||
|
if (err)
|
||
|
args.err = err;
|
||
|
if (typeof duration === 'number')
|
||
|
args.duration = duration;
|
||
|
const testFixture = testRun.test.fixture;
|
||
|
return Object.assign(args, {
|
||
|
testRunId: testRun.id,
|
||
|
test: {
|
||
|
id: testRun.test.id,
|
||
|
name: testRun.test.name,
|
||
|
phase: testRun.phase,
|
||
|
},
|
||
|
fixture: {
|
||
|
name: testFixture.name,
|
||
|
id: testFixture.id,
|
||
|
},
|
||
|
command: (0, format_command_1.default)(command, result),
|
||
|
browser: testRun.browser,
|
||
|
});
|
||
|
}
|
||
|
async _onceTaskStartHandler(task) {
|
||
|
this.taskInfo = {
|
||
|
task: task,
|
||
|
passed: 0,
|
||
|
failed: 0,
|
||
|
skipped: 0,
|
||
|
testCount: task.tests.filter(test => !test.skip).length,
|
||
|
testQueue: Reporter._createTestQueue(task),
|
||
|
stopOnFirstFail: task.opts.stopOnFirstFail,
|
||
|
pendingTaskDonePromise: Reporter._createPendingPromise(),
|
||
|
};
|
||
|
const startTime = task.startTime;
|
||
|
const browserConnectionsInfo = []
|
||
|
.concat(...task.browserConnectionGroups)
|
||
|
.map(connection => connection.connectionInfo);
|
||
|
const first = this.taskInfo.testQueue[0];
|
||
|
const taskProperties = {
|
||
|
configuration: task.opts,
|
||
|
dashboardUrl: task.opts.dashboardUrl,
|
||
|
};
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTaskStart,
|
||
|
initialObject: task,
|
||
|
args: [
|
||
|
startTime,
|
||
|
browserConnectionsInfo,
|
||
|
this.taskInfo.testCount,
|
||
|
task.testStructure,
|
||
|
taskProperties,
|
||
|
],
|
||
|
});
|
||
|
if (first) {
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportFixtureStart,
|
||
|
initialObject: task,
|
||
|
args: [
|
||
|
first.fixture.name,
|
||
|
first.fixture.path,
|
||
|
first.fixture.meta,
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
async _onTaskTestRunStartHandler(testRun) {
|
||
|
if (!this.taskInfo)
|
||
|
return void 0;
|
||
|
const testItem = this._getTestItemForTestRun(this.taskInfo, testRun);
|
||
|
testItem.testRunIds.push(testRun.id);
|
||
|
if (!testItem.startTime)
|
||
|
testItem.startTime = +new Date();
|
||
|
testItem.pendingStarts--;
|
||
|
if (!testItem.pendingStarts) {
|
||
|
// @ts-ignore
|
||
|
if (this.plugin.reportTestStart) {
|
||
|
const testStartInfo = {
|
||
|
testRunIds: testItem.testRunIds,
|
||
|
testId: testItem.test.id,
|
||
|
startTime: new Date(testItem.startTime),
|
||
|
skipped: testItem.test.skip,
|
||
|
};
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTestStart,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
testItem.test.name,
|
||
|
testItem.test.meta,
|
||
|
testStartInfo,
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
testItem.pendingTestRunStartPromise.resolve();
|
||
|
}
|
||
|
return testItem.pendingTestRunStartPromise;
|
||
|
}
|
||
|
async _onTaskTestRunDoneHandler(testRun) {
|
||
|
if (!this.taskInfo)
|
||
|
return;
|
||
|
const reportItem = this._getTestItemForTestRun(this.taskInfo, testRun);
|
||
|
const isTestRunStoppedTaskExecution = !!testRun.errs.length && this.taskInfo.stopOnFirstFail;
|
||
|
const browser = Object.assign({ testRunId: testRun.id }, testRun.browser);
|
||
|
reportItem.browsers.push(browser);
|
||
|
reportItem.pendingRuns = isTestRunStoppedTaskExecution ? 0 : reportItem.pendingRuns - 1;
|
||
|
reportItem.unstable = reportItem.unstable || testRun.unstable;
|
||
|
reportItem.errs = reportItem.errs.concat(testRun.errs);
|
||
|
reportItem.warnings = testRun.warningLog ? (0, lodash_1.union)(reportItem.warnings, testRun.warningLog.messages) : [];
|
||
|
if (testRun.quarantine) {
|
||
|
reportItem.quarantine = reportItem.quarantine || {};
|
||
|
const reportItemQuarantine = testRun.quarantine.attempts.reduce((result, { errors, testRunId }) => {
|
||
|
const passed = !errors.length;
|
||
|
result[testRunId] = { passed, errors };
|
||
|
browser.quarantineAttemptsTestRunIds = browser.quarantineAttemptsTestRunIds || [];
|
||
|
browser.quarantineAttemptsTestRunIds.push(testRunId);
|
||
|
return result;
|
||
|
}, {});
|
||
|
Object.assign(reportItem.quarantine, reportItemQuarantine);
|
||
|
}
|
||
|
if (!reportItem.pendingRuns)
|
||
|
await this._resolveTestItem(this.taskInfo, reportItem, testRun);
|
||
|
await reportItem.pendingTestRunDonePromise;
|
||
|
}
|
||
|
async _onTaskTestActionStart(_a) {
|
||
|
var { apiActionName } = _a, restArgs = __rest(_a, ["apiActionName"]);
|
||
|
if (!this.taskInfo)
|
||
|
return;
|
||
|
// @ts-ignore
|
||
|
if (this.plugin.reportTestActionStart) {
|
||
|
restArgs = this._prepareReportTestActionEventArgs(restArgs);
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTestActionStart,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
apiActionName,
|
||
|
restArgs,
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
async _onTaskTestActionDone(_a) {
|
||
|
var { apiActionName } = _a, restArgs = __rest(_a, ["apiActionName"]);
|
||
|
if (!this.taskInfo)
|
||
|
return;
|
||
|
// @ts-ignore
|
||
|
if (this.plugin.reportTestActionDone) {
|
||
|
restArgs = this._prepareReportTestActionEventArgs(restArgs);
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTestActionDone,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
apiActionName,
|
||
|
restArgs,
|
||
|
],
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
async _onceTaskDoneHandler() {
|
||
|
var _a;
|
||
|
if (!this.taskInfo)
|
||
|
return;
|
||
|
const endTime = new Date();
|
||
|
const result = {
|
||
|
passedCount: this.taskInfo.passed,
|
||
|
failedCount: this.taskInfo.failed,
|
||
|
skippedCount: this.taskInfo.skipped,
|
||
|
};
|
||
|
await this.dispatchToPlugin({
|
||
|
method: plugin_methods_1.default.reportTaskDone,
|
||
|
initialObject: this.taskInfo.task,
|
||
|
args: [
|
||
|
endTime,
|
||
|
this.taskInfo.passed,
|
||
|
(_a = this.taskInfo.task) === null || _a === void 0 ? void 0 : _a.warningLog.messages,
|
||
|
result,
|
||
|
],
|
||
|
});
|
||
|
this.taskInfo.pendingTaskDonePromise.resolve();
|
||
|
}
|
||
|
}
|
||
|
exports.default = Reporter;
|
||
|
module.exports = exports.default;
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVwb3J0ZXIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG1DQUtnQjtBQUVoQix5Q0FBeUQ7QUFDekQsZ0VBQStDO0FBQy9DLHNFQUFvRDtBQUNwRCw4RUFBcUQ7QUFDckQsK0NBQXdEO0FBZ0J4RCxnREFBMEU7QUFDMUUsdUdBQTRFO0FBQzVFLHdEQUErQjtBQUMvQixnREFBd0I7QUFDeEIsNENBQW9CO0FBSXBCLGtEQUEwQjtBQTBGMUIsTUFBTSxRQUFRLEdBQUcsSUFBQSxlQUFLLEVBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUU1QyxNQUFxQixRQUFRO0lBT3pCLFlBQW9CLE1BQXNCLEVBQUUsVUFBc0IsRUFBRSxTQUFtQixFQUFFLElBQVk7UUFDakcsSUFBSSxDQUFDLE1BQU0sR0FBTyxJQUFJLHFCQUFrQixDQUFDLE1BQU0sRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDbEUsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7UUFFN0IsSUFBSSxDQUFDLFFBQVEsR0FBSSxLQUFLLENBQUM7UUFDdkIsSUFBSSxDQUFDLFFBQVEsR0FBSSxJQUFJLENBQUM7UUFDdEIsSUFBSSxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUM7UUFFM0IsSUFBSSxDQUFDLDhCQUE4QixFQUFFLENBQUM7SUFDMUMsQ0FBQztJQUVPLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBRSxNQUFnQjtRQUM3QyxPQUFRLE1BQXNCLENBQUMsS0FBSyxJQUFJLE1BQU0sS0FBSyxPQUFPLENBQUMsTUFBTSxJQUFJLE1BQU0sS0FBSyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQ25HLENBQUM7SUFFTyxNQUFNLENBQUMscUJBQXFCO1FBQ2hDLElBQUksUUFBUSxHQUFHLElBQUksQ0FBQztRQUVwQixNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxPQUFP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
|