518 lines
19 KiB
JavaScript
518 lines
19 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.baseFullConfig = exports.TeleTestResult = exports.TeleTestCase = exports.TeleSuite = exports.TeleReporterReceiver = void 0;
|
|
exports.computeTestCaseOutcome = computeTestCaseOutcome;
|
|
exports.parseRegexPatterns = parseRegexPatterns;
|
|
exports.serializeRegexPatterns = serializeRegexPatterns;
|
|
/**
|
|
* Copyright (c) Microsoft Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
class TeleReporterReceiver {
|
|
constructor(reporter, options = {}) {
|
|
this.isListing = false;
|
|
this._rootSuite = void 0;
|
|
this._options = void 0;
|
|
this._reporter = void 0;
|
|
this._tests = new Map();
|
|
this._rootDir = void 0;
|
|
this._config = void 0;
|
|
this._rootSuite = new TeleSuite('', 'root');
|
|
this._options = options;
|
|
this._reporter = reporter;
|
|
}
|
|
reset() {
|
|
this._rootSuite._entries = [];
|
|
this._tests.clear();
|
|
}
|
|
dispatch(message) {
|
|
const {
|
|
method,
|
|
params
|
|
} = message;
|
|
if (method === 'onConfigure') {
|
|
this._onConfigure(params.config);
|
|
return;
|
|
}
|
|
if (method === 'onProject') {
|
|
this._onProject(params.project);
|
|
return;
|
|
}
|
|
if (method === 'onBegin') {
|
|
this._onBegin();
|
|
return;
|
|
}
|
|
if (method === 'onTestBegin') {
|
|
this._onTestBegin(params.testId, params.result);
|
|
return;
|
|
}
|
|
if (method === 'onTestEnd') {
|
|
this._onTestEnd(params.test, params.result);
|
|
return;
|
|
}
|
|
if (method === 'onStepBegin') {
|
|
this._onStepBegin(params.testId, params.resultId, params.step);
|
|
return;
|
|
}
|
|
if (method === 'onStepEnd') {
|
|
this._onStepEnd(params.testId, params.resultId, params.step);
|
|
return;
|
|
}
|
|
if (method === 'onError') {
|
|
this._onError(params.error);
|
|
return;
|
|
}
|
|
if (method === 'onStdIO') {
|
|
this._onStdIO(params.type, params.testId, params.resultId, params.data, params.isBase64);
|
|
return;
|
|
}
|
|
if (method === 'onEnd') return this._onEnd(params.result);
|
|
if (method === 'onExit') return this._onExit();
|
|
}
|
|
_onConfigure(config) {
|
|
var _this$_reporter$onCon, _this$_reporter;
|
|
this._rootDir = config.rootDir;
|
|
this._config = this._parseConfig(config);
|
|
(_this$_reporter$onCon = (_this$_reporter = this._reporter).onConfigure) === null || _this$_reporter$onCon === void 0 || _this$_reporter$onCon.call(_this$_reporter, this._config);
|
|
}
|
|
_onProject(project) {
|
|
let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find(suite => suite.project().name === project.name) : undefined;
|
|
if (!projectSuite) {
|
|
projectSuite = new TeleSuite(project.name, 'project');
|
|
this._rootSuite._addSuite(projectSuite);
|
|
}
|
|
// Always update project in watch mode.
|
|
projectSuite._project = this._parseProject(project);
|
|
for (const suite of project.suites) this._mergeSuiteInto(suite, projectSuite);
|
|
}
|
|
_onBegin() {
|
|
var _this$_reporter$onBeg, _this$_reporter2;
|
|
(_this$_reporter$onBeg = (_this$_reporter2 = this._reporter).onBegin) === null || _this$_reporter$onBeg === void 0 || _this$_reporter$onBeg.call(_this$_reporter2, this._rootSuite);
|
|
}
|
|
_onTestBegin(testId, payload) {
|
|
var _this$_reporter$onTes, _this$_reporter3;
|
|
const test = this._tests.get(testId);
|
|
if (this._options.clearPreviousResultsWhenTestBegins) test.results = [];
|
|
const testResult = test._createTestResult(payload.id);
|
|
testResult.retry = payload.retry;
|
|
testResult.workerIndex = payload.workerIndex;
|
|
testResult.parallelIndex = payload.parallelIndex;
|
|
testResult.setStartTimeNumber(payload.startTime);
|
|
(_this$_reporter$onTes = (_this$_reporter3 = this._reporter).onTestBegin) === null || _this$_reporter$onTes === void 0 || _this$_reporter$onTes.call(_this$_reporter3, test, testResult);
|
|
}
|
|
_onTestEnd(testEndPayload, payload) {
|
|
var _result$errors, _this$_reporter$onTes2, _this$_reporter4;
|
|
const test = this._tests.get(testEndPayload.testId);
|
|
test.timeout = testEndPayload.timeout;
|
|
test.expectedStatus = testEndPayload.expectedStatus;
|
|
test.annotations = testEndPayload.annotations;
|
|
const result = test.results.find(r => r._id === payload.id);
|
|
result.duration = payload.duration;
|
|
result.status = payload.status;
|
|
result.errors = payload.errors;
|
|
result.error = (_result$errors = result.errors) === null || _result$errors === void 0 ? void 0 : _result$errors[0];
|
|
result.attachments = this._parseAttachments(payload.attachments);
|
|
(_this$_reporter$onTes2 = (_this$_reporter4 = this._reporter).onTestEnd) === null || _this$_reporter$onTes2 === void 0 || _this$_reporter$onTes2.call(_this$_reporter4, test, result);
|
|
// Free up the memory as won't see these step ids.
|
|
result._stepMap = new Map();
|
|
}
|
|
_onStepBegin(testId, resultId, payload) {
|
|
var _this$_reporter$onSte, _this$_reporter5;
|
|
const test = this._tests.get(testId);
|
|
const result = test.results.find(r => r._id === resultId);
|
|
const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : undefined;
|
|
const location = this._absoluteLocation(payload.location);
|
|
const step = new TeleTestStep(payload, parentStep, location, result);
|
|
if (parentStep) parentStep.steps.push(step);else result.steps.push(step);
|
|
result._stepMap.set(payload.id, step);
|
|
(_this$_reporter$onSte = (_this$_reporter5 = this._reporter).onStepBegin) === null || _this$_reporter$onSte === void 0 || _this$_reporter$onSte.call(_this$_reporter5, test, result, step);
|
|
}
|
|
_onStepEnd(testId, resultId, payload) {
|
|
var _this$_reporter$onSte2, _this$_reporter6;
|
|
const test = this._tests.get(testId);
|
|
const result = test.results.find(r => r._id === resultId);
|
|
const step = result._stepMap.get(payload.id);
|
|
step._endPayload = payload;
|
|
step.duration = payload.duration;
|
|
step.error = payload.error;
|
|
(_this$_reporter$onSte2 = (_this$_reporter6 = this._reporter).onStepEnd) === null || _this$_reporter$onSte2 === void 0 || _this$_reporter$onSte2.call(_this$_reporter6, test, result, step);
|
|
}
|
|
_onError(error) {
|
|
var _this$_reporter$onErr, _this$_reporter7;
|
|
(_this$_reporter$onErr = (_this$_reporter7 = this._reporter).onError) === null || _this$_reporter$onErr === void 0 || _this$_reporter$onErr.call(_this$_reporter7, error);
|
|
}
|
|
_onStdIO(type, testId, resultId, data, isBase64) {
|
|
const chunk = isBase64 ? globalThis.Buffer ? Buffer.from(data, 'base64') : atob(data) : data;
|
|
const test = testId ? this._tests.get(testId) : undefined;
|
|
const result = test && resultId ? test.results.find(r => r._id === resultId) : undefined;
|
|
if (type === 'stdout') {
|
|
var _this$_reporter$onStd, _this$_reporter8;
|
|
result === null || result === void 0 || result.stdout.push(chunk);
|
|
(_this$_reporter$onStd = (_this$_reporter8 = this._reporter).onStdOut) === null || _this$_reporter$onStd === void 0 || _this$_reporter$onStd.call(_this$_reporter8, chunk, test, result);
|
|
} else {
|
|
var _this$_reporter$onStd2, _this$_reporter9;
|
|
result === null || result === void 0 || result.stderr.push(chunk);
|
|
(_this$_reporter$onStd2 = (_this$_reporter9 = this._reporter).onStdErr) === null || _this$_reporter$onStd2 === void 0 || _this$_reporter$onStd2.call(_this$_reporter9, chunk, test, result);
|
|
}
|
|
}
|
|
async _onEnd(result) {
|
|
var _this$_reporter$onEnd, _this$_reporter10;
|
|
await ((_this$_reporter$onEnd = (_this$_reporter10 = this._reporter).onEnd) === null || _this$_reporter$onEnd === void 0 ? void 0 : _this$_reporter$onEnd.call(_this$_reporter10, {
|
|
status: result.status,
|
|
startTime: new Date(result.startTime),
|
|
duration: result.duration
|
|
}));
|
|
}
|
|
_onExit() {
|
|
var _this$_reporter$onExi, _this$_reporter11;
|
|
return (_this$_reporter$onExi = (_this$_reporter11 = this._reporter).onExit) === null || _this$_reporter$onExi === void 0 ? void 0 : _this$_reporter$onExi.call(_this$_reporter11);
|
|
}
|
|
_parseConfig(config) {
|
|
const result = {
|
|
...baseFullConfig,
|
|
...config
|
|
};
|
|
if (this._options.configOverrides) {
|
|
result.configFile = this._options.configOverrides.configFile;
|
|
result.reportSlowTests = this._options.configOverrides.reportSlowTests;
|
|
result.quiet = this._options.configOverrides.quiet;
|
|
result.reporter = [...this._options.configOverrides.reporter];
|
|
}
|
|
return result;
|
|
}
|
|
_parseProject(project) {
|
|
return {
|
|
metadata: project.metadata,
|
|
name: project.name,
|
|
outputDir: this._absolutePath(project.outputDir),
|
|
repeatEach: project.repeatEach,
|
|
retries: project.retries,
|
|
testDir: this._absolutePath(project.testDir),
|
|
testIgnore: parseRegexPatterns(project.testIgnore),
|
|
testMatch: parseRegexPatterns(project.testMatch),
|
|
timeout: project.timeout,
|
|
grep: parseRegexPatterns(project.grep),
|
|
grepInvert: parseRegexPatterns(project.grepInvert),
|
|
dependencies: project.dependencies,
|
|
teardown: project.teardown,
|
|
snapshotDir: this._absolutePath(project.snapshotDir),
|
|
use: project.use
|
|
};
|
|
}
|
|
_parseAttachments(attachments) {
|
|
return attachments.map(a => {
|
|
return {
|
|
...a,
|
|
body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, 'base64') : undefined
|
|
};
|
|
});
|
|
}
|
|
_mergeSuiteInto(jsonSuite, parent) {
|
|
let targetSuite = parent.suites.find(s => s.title === jsonSuite.title);
|
|
if (!targetSuite) {
|
|
targetSuite = new TeleSuite(jsonSuite.title, parent.type === 'project' ? 'file' : 'describe');
|
|
parent._addSuite(targetSuite);
|
|
}
|
|
targetSuite.location = this._absoluteLocation(jsonSuite.location);
|
|
jsonSuite.entries.forEach(e => {
|
|
if ('testId' in e) this._mergeTestInto(e, targetSuite);else this._mergeSuiteInto(e, targetSuite);
|
|
});
|
|
}
|
|
_mergeTestInto(jsonTest, parent) {
|
|
let targetTest = this._options.mergeTestCases ? parent.tests.find(s => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : undefined;
|
|
if (!targetTest) {
|
|
targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex);
|
|
parent._addTest(targetTest);
|
|
this._tests.set(targetTest.id, targetTest);
|
|
}
|
|
this._updateTest(jsonTest, targetTest);
|
|
}
|
|
_updateTest(payload, test) {
|
|
var _payload$tags, _payload$annotations;
|
|
test.id = payload.testId;
|
|
test.location = this._absoluteLocation(payload.location);
|
|
test.retries = payload.retries;
|
|
test.tags = (_payload$tags = payload.tags) !== null && _payload$tags !== void 0 ? _payload$tags : [];
|
|
test.annotations = (_payload$annotations = payload.annotations) !== null && _payload$annotations !== void 0 ? _payload$annotations : [];
|
|
return test;
|
|
}
|
|
_absoluteLocation(location) {
|
|
if (!location) return location;
|
|
return {
|
|
...location,
|
|
file: this._absolutePath(location.file)
|
|
};
|
|
}
|
|
_absolutePath(relativePath) {
|
|
if (relativePath === undefined) return;
|
|
return this._options.resolvePath ? this._options.resolvePath(this._rootDir, relativePath) : this._rootDir + '/' + relativePath;
|
|
}
|
|
}
|
|
exports.TeleReporterReceiver = TeleReporterReceiver;
|
|
class TeleSuite {
|
|
constructor(title, type) {
|
|
this.title = void 0;
|
|
this.location = void 0;
|
|
this.parent = void 0;
|
|
this._entries = [];
|
|
this._requireFile = '';
|
|
this._timeout = void 0;
|
|
this._retries = void 0;
|
|
this._project = void 0;
|
|
this._parallelMode = 'none';
|
|
this._type = void 0;
|
|
this.title = title;
|
|
this._type = type;
|
|
}
|
|
get type() {
|
|
return this._type;
|
|
}
|
|
get suites() {
|
|
return this._entries.filter(e => e.type !== 'test');
|
|
}
|
|
get tests() {
|
|
return this._entries.filter(e => e.type === 'test');
|
|
}
|
|
entries() {
|
|
return this._entries;
|
|
}
|
|
allTests() {
|
|
const result = [];
|
|
const visit = suite => {
|
|
for (const entry of suite.entries()) {
|
|
if (entry.type === 'test') result.push(entry);else visit(entry);
|
|
}
|
|
};
|
|
visit(this);
|
|
return result;
|
|
}
|
|
titlePath() {
|
|
const titlePath = this.parent ? this.parent.titlePath() : [];
|
|
// Ignore anonymous describe blocks.
|
|
if (this.title || this._type !== 'describe') titlePath.push(this.title);
|
|
return titlePath;
|
|
}
|
|
project() {
|
|
var _this$_project, _this$parent;
|
|
return (_this$_project = this._project) !== null && _this$_project !== void 0 ? _this$_project : (_this$parent = this.parent) === null || _this$parent === void 0 ? void 0 : _this$parent.project();
|
|
}
|
|
_addTest(test) {
|
|
test.parent = this;
|
|
this._entries.push(test);
|
|
}
|
|
_addSuite(suite) {
|
|
suite.parent = this;
|
|
this._entries.push(suite);
|
|
}
|
|
}
|
|
exports.TeleSuite = TeleSuite;
|
|
class TeleTestCase {
|
|
constructor(id, title, location, repeatEachIndex) {
|
|
this.title = void 0;
|
|
this.fn = () => {};
|
|
this.results = [];
|
|
this.location = void 0;
|
|
this.parent = void 0;
|
|
this.type = 'test';
|
|
this.expectedStatus = 'passed';
|
|
this.timeout = 0;
|
|
this.annotations = [];
|
|
this.retries = 0;
|
|
this.tags = [];
|
|
this.repeatEachIndex = 0;
|
|
this.id = void 0;
|
|
this.id = id;
|
|
this.title = title;
|
|
this.location = location;
|
|
this.repeatEachIndex = repeatEachIndex;
|
|
}
|
|
titlePath() {
|
|
const titlePath = this.parent ? this.parent.titlePath() : [];
|
|
titlePath.push(this.title);
|
|
return titlePath;
|
|
}
|
|
outcome() {
|
|
return computeTestCaseOutcome(this);
|
|
}
|
|
ok() {
|
|
const status = this.outcome();
|
|
return status === 'expected' || status === 'flaky' || status === 'skipped';
|
|
}
|
|
_createTestResult(id) {
|
|
const result = new TeleTestResult(this.results.length, id);
|
|
this.results.push(result);
|
|
return result;
|
|
}
|
|
}
|
|
exports.TeleTestCase = TeleTestCase;
|
|
class TeleTestStep {
|
|
constructor(payload, parentStep, location, result) {
|
|
this.title = void 0;
|
|
this.category = void 0;
|
|
this.location = void 0;
|
|
this.parent = void 0;
|
|
this.duration = -1;
|
|
this.steps = [];
|
|
this.error = void 0;
|
|
this._result = void 0;
|
|
this._endPayload = void 0;
|
|
this._startTime = 0;
|
|
this.title = payload.title;
|
|
this.category = payload.category;
|
|
this.location = location;
|
|
this.parent = parentStep;
|
|
this._startTime = payload.startTime;
|
|
this._result = result;
|
|
}
|
|
titlePath() {
|
|
var _this$parent2;
|
|
const parentPath = ((_this$parent2 = this.parent) === null || _this$parent2 === void 0 ? void 0 : _this$parent2.titlePath()) || [];
|
|
return [...parentPath, this.title];
|
|
}
|
|
get startTime() {
|
|
return new Date(this._startTime);
|
|
}
|
|
set startTime(value) {
|
|
this._startTime = +value;
|
|
}
|
|
get attachments() {
|
|
var _this$_endPayload$att, _this$_endPayload;
|
|
return (_this$_endPayload$att = (_this$_endPayload = this._endPayload) === null || _this$_endPayload === void 0 || (_this$_endPayload = _this$_endPayload.attachments) === null || _this$_endPayload === void 0 ? void 0 : _this$_endPayload.map(index => this._result.attachments[index])) !== null && _this$_endPayload$att !== void 0 ? _this$_endPayload$att : [];
|
|
}
|
|
get annotations() {
|
|
var _this$_endPayload$ann, _this$_endPayload2;
|
|
return (_this$_endPayload$ann = (_this$_endPayload2 = this._endPayload) === null || _this$_endPayload2 === void 0 ? void 0 : _this$_endPayload2.annotations) !== null && _this$_endPayload$ann !== void 0 ? _this$_endPayload$ann : [];
|
|
}
|
|
}
|
|
class TeleTestResult {
|
|
constructor(retry, id) {
|
|
this.retry = void 0;
|
|
this.parallelIndex = -1;
|
|
this.workerIndex = -1;
|
|
this.duration = -1;
|
|
this.stdout = [];
|
|
this.stderr = [];
|
|
this.attachments = [];
|
|
this.status = 'skipped';
|
|
this.steps = [];
|
|
this.errors = [];
|
|
this.error = void 0;
|
|
this._stepMap = new Map();
|
|
this._id = void 0;
|
|
this._startTime = 0;
|
|
this.retry = retry;
|
|
this._id = id;
|
|
}
|
|
setStartTimeNumber(startTime) {
|
|
this._startTime = startTime;
|
|
}
|
|
get startTime() {
|
|
return new Date(this._startTime);
|
|
}
|
|
set startTime(value) {
|
|
this._startTime = +value;
|
|
}
|
|
}
|
|
exports.TeleTestResult = TeleTestResult;
|
|
const baseFullConfig = exports.baseFullConfig = {
|
|
forbidOnly: false,
|
|
fullyParallel: false,
|
|
globalSetup: null,
|
|
globalTeardown: null,
|
|
globalTimeout: 0,
|
|
grep: /.*/,
|
|
grepInvert: null,
|
|
maxFailures: 0,
|
|
metadata: {},
|
|
preserveOutput: 'always',
|
|
projects: [],
|
|
reporter: [[process.env.CI ? 'dot' : 'list']],
|
|
reportSlowTests: {
|
|
max: 5,
|
|
threshold: 300_000 /* 5 minutes */
|
|
},
|
|
configFile: '',
|
|
rootDir: '',
|
|
quiet: false,
|
|
shard: null,
|
|
updateSnapshots: 'missing',
|
|
updateSourceMethod: 'patch',
|
|
version: '',
|
|
workers: 0,
|
|
webServer: null
|
|
};
|
|
function serializeRegexPatterns(patterns) {
|
|
if (!Array.isArray(patterns)) patterns = [patterns];
|
|
return patterns.map(s => {
|
|
if (typeof s === 'string') return {
|
|
s
|
|
};
|
|
return {
|
|
r: {
|
|
source: s.source,
|
|
flags: s.flags
|
|
}
|
|
};
|
|
});
|
|
}
|
|
function parseRegexPatterns(patterns) {
|
|
return patterns.map(p => {
|
|
if (p.s !== undefined) return p.s;
|
|
return new RegExp(p.r.source, p.r.flags);
|
|
});
|
|
}
|
|
function computeTestCaseOutcome(test) {
|
|
let skipped = 0;
|
|
let didNotRun = 0;
|
|
let expected = 0;
|
|
let interrupted = 0;
|
|
let unexpected = 0;
|
|
for (const result of test.results) {
|
|
if (result.status === 'interrupted') {
|
|
++interrupted; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
} else if (result.status === 'skipped' && test.expectedStatus === 'skipped') {
|
|
// Only tests "expected to be skipped" are skipped. These were specifically
|
|
// marked with test.skip or test.fixme.
|
|
++skipped;
|
|
} else if (result.status === 'skipped') {
|
|
// Tests that were expected to run, but were skipped are "did not run".
|
|
// This happens when:
|
|
// - testing finished early;
|
|
// - test failure prevented other tests in the serial suite to run;
|
|
// - probably more cases!
|
|
++didNotRun; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
} else if (result.status === test.expectedStatus) {
|
|
// Either passed and expected to pass, or failed and expected to fail.
|
|
++expected;
|
|
} else {
|
|
++unexpected;
|
|
}
|
|
}
|
|
|
|
// Tests that were "skipped as expected" are considered equal to "expected" below,
|
|
// because that's the expected outcome.
|
|
//
|
|
// However, we specifically differentiate the case of "only skipped"
|
|
// and show it as "skipped" in all reporters.
|
|
//
|
|
// More exotic cases like "failed on first run and skipped on retry" are flaky.
|
|
if (expected === 0 && unexpected === 0) return 'skipped'; // all results were skipped or interrupted
|
|
if (unexpected === 0) return 'expected'; // no failures, just expected+skipped
|
|
if (expected === 0 && skipped === 0) return 'unexpected'; // only failures
|
|
return 'flaky'; // expected+unexpected or skipped+unexpected
|
|
} |