123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.colors = exports.BaseReporter = void 0;
- exports.formatError = formatError;
- exports.formatFailure = formatFailure;
- exports.formatResultFailure = formatResultFailure;
- exports.formatTestTitle = formatTestTitle;
- exports.kOutputSymbol = exports.isTTY = void 0;
- exports.prepareErrorStack = prepareErrorStack;
- exports.relativeFilePath = relativeFilePath;
- exports.separator = separator;
- exports.stepSuffix = stepSuffix;
- exports.stripAnsiEscapes = stripAnsiEscapes;
- exports.ttyWidth = void 0;
- var _utilsBundle = require("playwright-core/lib/utilsBundle");
- var _path = _interopRequireDefault(require("path"));
- var _utils = require("playwright-core/lib/utils");
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
- /**
- * 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.
- */
- const kOutputSymbol = exports.kOutputSymbol = Symbol('output');
- const isTTY = exports.isTTY = !!process.env.PWTEST_TTY_WIDTH || process.stdout.isTTY;
- const ttyWidth = exports.ttyWidth = process.env.PWTEST_TTY_WIDTH ? parseInt(process.env.PWTEST_TTY_WIDTH, 10) : process.stdout.columns || 0;
- let useColors = isTTY;
- if (process.env.DEBUG_COLORS === '0' || process.env.DEBUG_COLORS === 'false' || process.env.FORCE_COLOR === '0' || process.env.FORCE_COLOR === 'false') useColors = false;else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR) useColors = true;
- const colors = exports.colors = useColors ? _utilsBundle.colors : {
- bold: t => t,
- cyan: t => t,
- dim: t => t,
- gray: t => t,
- green: t => t,
- red: t => t,
- yellow: t => t,
- enabled: false
- };
- class BaseReporter {
- constructor(options = {}) {
- this.config = void 0;
- this.suite = void 0;
- this.totalTestCount = 0;
- this.result = void 0;
- this.fileDurations = new Map();
- this._omitFailures = void 0;
- this._fatalErrors = [];
- this._failureCount = 0;
- this._omitFailures = options.omitFailures || false;
- }
- version() {
- return 'v2';
- }
- onConfigure(config) {
- this.config = config;
- }
- onBegin(suite) {
- this.suite = suite;
- this.totalTestCount = suite.allTests().length;
- }
- onStdOut(chunk, test, result) {
- this._appendOutput({
- chunk,
- type: 'stdout'
- }, result);
- }
- onStdErr(chunk, test, result) {
- this._appendOutput({
- chunk,
- type: 'stderr'
- }, result);
- }
- _appendOutput(output, result) {
- if (!result) return;
- result[kOutputSymbol] = result[kOutputSymbol] || [];
- result[kOutputSymbol].push(output);
- }
- onTestBegin(test, result) {}
- onTestEnd(test, result) {
- if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount;
- // Ignore any tests that are run in parallel.
- for (let suite = test.parent; suite; suite = suite.parent) {
- if (suite._parallelMode === 'parallel') return;
- }
- const projectName = test.titlePath()[1];
- const relativePath = relativeTestPath(this.config, test);
- const fileAndProject = (projectName ? `[${projectName}] › ` : '') + relativePath;
- const duration = this.fileDurations.get(fileAndProject) || 0;
- this.fileDurations.set(fileAndProject, duration + result.duration);
- }
- onError(error) {
- this._fatalErrors.push(error);
- }
- async onEnd(result) {
- this.result = result;
- }
- onStepBegin(test, result, step) {}
- onStepEnd(test, result, step) {}
- async onExit() {}
- printsToStdio() {
- return true;
- }
- fitToScreen(line, prefix) {
- if (!ttyWidth) {
- // Guard against the case where we cannot determine available width.
- return line;
- }
- return fitToWidth(line, ttyWidth, prefix);
- }
- generateStartingMessage() {
- var _this$config$metadata;
- const jobs = (_this$config$metadata = this.config.metadata.actualWorkers) !== null && _this$config$metadata !== void 0 ? _this$config$metadata : this.config.workers;
- const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
- if (!this.totalTestCount) return '';
- return '\n' + colors.dim('Running ') + this.totalTestCount + colors.dim(` test${this.totalTestCount !== 1 ? 's' : ''} using `) + jobs + colors.dim(` worker${jobs !== 1 ? 's' : ''}${shardDetails}`);
- }
- getSlowTests() {
- if (!this.config.reportSlowTests) return [];
- const fileDurations = [...this.fileDurations.entries()];
- fileDurations.sort((a, b) => b[1] - a[1]);
- const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
- const threshold = this.config.reportSlowTests.threshold;
- return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
- }
- generateSummaryMessage({
- didNotRun,
- skipped,
- expected,
- interrupted,
- unexpected,
- flaky,
- fatalErrors
- }) {
- const tokens = [];
- if (unexpected.length) {
- tokens.push(colors.red(` ${unexpected.length} failed`));
- for (const test of unexpected) tokens.push(colors.red(formatTestHeader(this.config, test, {
- indent: ' '
- })));
- }
- if (interrupted.length) {
- tokens.push(colors.yellow(` ${interrupted.length} interrupted`));
- for (const test of interrupted) tokens.push(colors.yellow(formatTestHeader(this.config, test, {
- indent: ' '
- })));
- }
- if (flaky.length) {
- tokens.push(colors.yellow(` ${flaky.length} flaky`));
- for (const test of flaky) tokens.push(colors.yellow(formatTestHeader(this.config, test, {
- indent: ' '
- })));
- }
- if (skipped) tokens.push(colors.yellow(` ${skipped} skipped`));
- if (didNotRun) tokens.push(colors.yellow(` ${didNotRun} did not run`));
- if (expected) tokens.push(colors.green(` ${expected} passed`) + colors.dim(` (${(0, _utilsBundle.ms)(this.result.duration)})`));
- if (this.result.status === 'timedout') tokens.push(colors.red(` Timed out waiting ${this.config.globalTimeout / 1000}s for the entire test run`));
- if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0) tokens.push(colors.red(` ${fatalErrors.length === 1 ? '1 error was not a part of any test' : fatalErrors.length + ' errors were not a part of any test'}, see above for details`));
- return tokens.join('\n');
- }
- generateSummary() {
- let didNotRun = 0;
- let skipped = 0;
- let expected = 0;
- const interrupted = [];
- const interruptedToPrint = [];
- const unexpected = [];
- const flaky = [];
- this.suite.allTests().forEach(test => {
- switch (test.outcome()) {
- case 'skipped':
- {
- if (test.results.some(result => result.status === 'interrupted')) {
- if (test.results.some(result => !!result.error)) interruptedToPrint.push(test);
- interrupted.push(test);
- } else if (!test.results.length) {
- ++didNotRun;
- } else {
- ++skipped;
- }
- break;
- }
- case 'expected':
- ++expected;
- break;
- case 'unexpected':
- unexpected.push(test);
- break;
- case 'flaky':
- flaky.push(test);
- break;
- }
- });
- const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
- return {
- didNotRun,
- skipped,
- expected,
- interrupted,
- unexpected,
- flaky,
- failuresToPrint,
- fatalErrors: this._fatalErrors
- };
- }
- epilogue(full) {
- const summary = this.generateSummary();
- const summaryMessage = this.generateSummaryMessage(summary);
- if (full && summary.failuresToPrint.length && !this._omitFailures) this._printFailures(summary.failuresToPrint);
- this._printSlowTests();
- this._printMaxFailuresReached();
- this._printSummary(summaryMessage);
- }
- _printFailures(failures) {
- console.log('');
- failures.forEach((test, index) => {
- console.log(formatFailure(this.config, test, {
- index: index + 1
- }).message);
- });
- }
- _printSlowTests() {
- const slowTests = this.getSlowTests();
- slowTests.forEach(([file, duration]) => {
- console.log(colors.yellow(' Slow test file: ') + file + colors.yellow(` (${(0, _utilsBundle.ms)(duration)})`));
- });
- if (slowTests.length) console.log(colors.yellow(' Consider splitting slow test files to speed up parallel execution'));
- }
- _printMaxFailuresReached() {
- if (!this.config.maxFailures) return;
- if (this._failureCount < this.config.maxFailures) return;
- console.log(colors.yellow(`Testing stopped early after ${this.config.maxFailures} maximum allowed failures.`));
- }
- _printSummary(summary) {
- if (summary.trim()) console.log(summary);
- }
- willRetry(test) {
- return test.outcome() === 'unexpected' && test.results.length <= test.retries;
- }
- }
- exports.BaseReporter = BaseReporter;
- function formatFailure(config, test, options = {}) {
- const {
- index,
- includeStdio,
- includeAttachments = true
- } = options;
- const lines = [];
- const title = formatTestTitle(config, test);
- const annotations = [];
- const header = formatTestHeader(config, test, {
- indent: ' ',
- index,
- mode: 'error'
- });
- lines.push(colors.red(header));
- for (const result of test.results) {
- const resultLines = [];
- const errors = formatResultFailure(test, result, ' ', colors.enabled);
- if (!errors.length) continue;
- const retryLines = [];
- if (result.retry) {
- retryLines.push('');
- retryLines.push(colors.gray(separator(` Retry #${result.retry}`)));
- }
- resultLines.push(...retryLines);
- resultLines.push(...errors.map(error => '\n' + error.message));
- if (includeAttachments) {
- for (let i = 0; i < result.attachments.length; ++i) {
- const attachment = result.attachments[i];
- const hasPrintableContent = attachment.contentType.startsWith('text/') && attachment.body;
- if (!attachment.path && !hasPrintableContent) continue;
- resultLines.push('');
- resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
- if (attachment.path) {
- const relativePath = _path.default.relative(process.cwd(), attachment.path);
- resultLines.push(colors.cyan(` ${relativePath}`));
- // Make this extensible
- if (attachment.name === 'trace') {
- const packageManagerCommand = (0, _utils.getPackageManagerExecCommand)();
- resultLines.push(colors.cyan(` Usage:`));
- resultLines.push('');
- resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${relativePath}`));
- resultLines.push('');
- }
- } else {
- if (attachment.contentType.startsWith('text/') && attachment.body) {
- let text = attachment.body.toString();
- if (text.length > 300) text = text.slice(0, 300) + '...';
- for (const line of text.split('\n')) resultLines.push(colors.cyan(` ${line}`));
- }
- }
- resultLines.push(colors.cyan(separator(' ')));
- }
- }
- const output = result[kOutputSymbol] || [];
- if (includeStdio && output.length) {
- const outputText = output.map(({
- chunk,
- type
- }) => {
- const text = chunk.toString('utf8');
- if (type === 'stderr') return colors.red(stripAnsiEscapes(text));
- return text;
- }).join('');
- resultLines.push('');
- resultLines.push(colors.gray(separator('--- Test output')) + '\n\n' + outputText + '\n' + separator());
- }
- for (const error of errors) {
- annotations.push({
- location: error.location,
- title,
- message: [header, ...retryLines, error.message].join('\n')
- });
- }
- lines.push(...resultLines);
- }
- lines.push('');
- return {
- message: lines.join('\n'),
- annotations
- };
- }
- function formatResultFailure(test, result, initialIndent, highlightCode) {
- const errorDetails = [];
- if (result.status === 'passed' && test.expectedStatus === 'failed') {
- errorDetails.push({
- message: indent(colors.red(`Expected to fail, but passed.`), initialIndent)
- });
- }
- if (result.status === 'interrupted') {
- errorDetails.push({
- message: indent(colors.red(`Test was interrupted.`), initialIndent)
- });
- }
- for (const error of result.errors) {
- const formattedError = formatError(error, highlightCode);
- errorDetails.push({
- message: indent(formattedError.message, initialIndent),
- location: formattedError.location
- });
- }
- return errorDetails;
- }
- function relativeFilePath(config, file) {
- return _path.default.relative(config.rootDir, file) || _path.default.basename(file);
- }
- function relativeTestPath(config, test) {
- return relativeFilePath(config, test.location.file);
- }
- function stepSuffix(step) {
- const stepTitles = step ? step.titlePath() : [];
- return stepTitles.map(t => ' › ' + t).join('');
- }
- function formatTestTitle(config, test, step, omitLocation = false) {
- var _step$location$line, _step$location, _step$location$column, _step$location2;
- // root, project, file, ...describes, test
- const [, projectName,, ...titles] = test.titlePath();
- let location;
- if (omitLocation) location = `${relativeTestPath(config, test)}`;else location = `${relativeTestPath(config, test)}:${(_step$location$line = step === null || step === void 0 ? void 0 : (_step$location = step.location) === null || _step$location === void 0 ? void 0 : _step$location.line) !== null && _step$location$line !== void 0 ? _step$location$line : test.location.line}:${(_step$location$column = step === null || step === void 0 ? void 0 : (_step$location2 = step.location) === null || _step$location2 === void 0 ? void 0 : _step$location2.column) !== null && _step$location$column !== void 0 ? _step$location$column : test.location.column}`;
- const projectTitle = projectName ? `[${projectName}] › ` : '';
- return `${projectTitle}${location} › ${titles.join(' › ')}${stepSuffix(step)}`;
- }
- function formatTestHeader(config, test, options = {}) {
- const title = formatTestTitle(config, test);
- const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
- let fullHeader = header;
- // Render the path to the deepest failing test.step.
- if (options.mode === 'error') {
- const stepPaths = new Set();
- for (const result of test.results.filter(r => !!r.errors.length)) {
- const stepPath = [];
- const visit = steps => {
- const errors = steps.filter(s => s.error);
- if (errors.length > 1) return;
- if (errors.length === 1 && errors[0].category === 'test.step') {
- stepPath.push(errors[0].title);
- visit(errors[0].steps);
- }
- };
- visit(result.steps);
- stepPaths.add(['', ...stepPath].join(' › '));
- }
- fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : '');
- }
- return separator(fullHeader);
- }
- function formatError(error, highlightCode) {
- const message = error.message || error.value || '';
- const stack = error.stack;
- if (!stack && !error.location) return {
- message
- };
- const tokens = [];
- // Now that we filter out internals from our stack traces, we can safely render
- // the helper / original exception locations.
- const parsedStack = stack ? prepareErrorStack(stack) : undefined;
- tokens.push((parsedStack === null || parsedStack === void 0 ? void 0 : parsedStack.message) || message);
- if (error.snippet) {
- let snippet = error.snippet;
- if (!highlightCode) snippet = stripAnsiEscapes(snippet);
- tokens.push('');
- tokens.push(snippet);
- }
- if (parsedStack && parsedStack.stackLines.length) {
- tokens.push('');
- tokens.push(colors.dim(parsedStack.stackLines.join('\n')));
- }
- let location = error.location;
- if (parsedStack && !location) location = parsedStack.location;
- return {
- location,
- message: tokens.join('\n')
- };
- }
- function separator(text = '') {
- if (text) text += ' ';
- const columns = Math.min(100, ttyWidth || 100);
- return text + colors.dim('─'.repeat(Math.max(0, columns - text.length)));
- }
- function indent(lines, tab) {
- return lines.replace(/^(?=.+$)/gm, tab);
- }
- function prepareErrorStack(stack) {
- const lines = stack.split('\n');
- let firstStackLine = lines.findIndex(line => line.startsWith(' at '));
- if (firstStackLine === -1) firstStackLine = lines.length;
- const message = lines.slice(0, firstStackLine).join('\n');
- const stackLines = lines.slice(firstStackLine);
- let location;
- for (const line of stackLines) {
- const frame = (0, _utilsBundle.parseStackTraceLine)(line);
- if (!frame || !frame.file) continue;
- if (belongsToNodeModules(frame.file)) continue;
- location = {
- file: frame.file,
- column: frame.column || 0,
- line: frame.line || 0
- };
- break;
- }
- return {
- message,
- stackLines,
- location
- };
- }
- const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
- function stripAnsiEscapes(str) {
- return str.replace(ansiRegex, '');
- }
- // Leaves enough space for the "prefix" to also fit.
- function fitToWidth(line, width, prefix) {
- const prefixLength = prefix ? stripAnsiEscapes(prefix).length : 0;
- width -= prefixLength;
- if (line.length <= width) return line;
- // Even items are plain text, odd items are control sequences.
- const parts = line.split(ansiRegex);
- const taken = [];
- for (let i = parts.length - 1; i >= 0; i--) {
- if (i % 2) {
- // Include all control sequences to preserve formatting.
- taken.push(parts[i]);
- } else {
- let part = parts[i].substring(parts[i].length - width);
- if (part.length < parts[i].length && part.length > 0) {
- // Add ellipsis if we are truncating.
- part = '\u2026' + part.substring(1);
- }
- taken.push(part);
- width -= part.length;
- }
- }
- return taken.reverse().join('');
- }
- function belongsToNodeModules(file) {
- return file.includes(`${_path.default.sep}node_modules${_path.default.sep}`);
- }
|