mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-16 15:52:24 +03:00
272 lines
11 KiB
JavaScript
272 lines
11 KiB
JavaScript
/**
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
*
|
|
* 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 fs = require('fs');
|
|
const path = require('path');
|
|
const colors = require('colors/safe');
|
|
const {MatchError} = require('./Matchers.js');
|
|
|
|
class Reporter {
|
|
constructor(delegate, options = {}) {
|
|
const {
|
|
showSlowTests = 3,
|
|
showMarkedAsFailingTests = Infinity,
|
|
verbose = false,
|
|
summary = true,
|
|
lineBreak = 0,
|
|
} = options;
|
|
this._filePathToLines = new Map();
|
|
this._delegate = delegate;
|
|
this._showSlowTests = showSlowTests;
|
|
this._showMarkedAsFailingTests = showMarkedAsFailingTests;
|
|
this._verbose = verbose;
|
|
this._summary = summary;
|
|
this._lineBreak = lineBreak;
|
|
this._testCounter = 0;
|
|
}
|
|
|
|
onStarted(testRuns) {
|
|
this._testCounter = 0;
|
|
this._timestamp = Date.now();
|
|
if (!this._delegate.hasFocusedTestsOrSuitesOrFiles()) {
|
|
console.log(`Running all ${colors.yellow(testRuns.length)} tests on ${colors.yellow(this._delegate.parallel())} worker${this._delegate.parallel() > 1 ? 's' : ''}:\n`);
|
|
} else {
|
|
console.log(`Running ${colors.yellow(testRuns.length)} focused tests out of total ${colors.yellow(this._delegate.testCount())} on ${colors.yellow(this._delegate.parallel())} worker${this._delegate.parallel() > 1 ? 's' : ''}`);
|
|
console.log('');
|
|
const focusedFilePaths = this._delegate.focusedFilePaths();
|
|
if (focusedFilePaths.length) {
|
|
console.log('Focused Files:');
|
|
for (let i = 0; i < focusedFilePaths.length; ++i)
|
|
console.log(` ${i + 1}) ${colors.yellow(path.basename(focusedFilePaths[i]))}`);
|
|
console.log('');
|
|
}
|
|
const focusedEntities = [
|
|
...this._delegate.focusedSuites(),
|
|
...this._delegate.focusedTests(),
|
|
];
|
|
|
|
if (focusedEntities.length) {
|
|
console.log('Focused Suites and Tests:');
|
|
for (let i = 0; i < focusedEntities.length; ++i)
|
|
console.log(` ${i + 1}) ${focusedEntities[i].fullName()} (${formatLocation(focusedEntities[i].location())})`);
|
|
console.log('');
|
|
}
|
|
}
|
|
}
|
|
|
|
_printFailedResult(result) {
|
|
console.log(colors.red(`## ${result.result.toUpperCase()} ##`));
|
|
if (result.message) {
|
|
console.log('Message:');
|
|
console.log(` ${colors.red(result.message)}`);
|
|
}
|
|
|
|
for (let i = 0; i < result.errors.length; i++) {
|
|
const { message, error, runs } = result.errors[i];
|
|
console.log(`\n${colors.magenta('NON-TEST ERROR #' + i)}: ${message}`);
|
|
if (error && error.stack)
|
|
console.log(padLines(error.stack, 2));
|
|
const lastRuns = runs.slice(runs.length - Math.min(10, runs.length));
|
|
if (lastRuns.length)
|
|
console.log(`WORKER STATE`);
|
|
for (let j = 0; j < lastRuns.length; j++)
|
|
this._printVerboseTestRunResult(j, lastRuns[j]);
|
|
}
|
|
console.log('');
|
|
console.log('');
|
|
}
|
|
|
|
onFinished(result) {
|
|
this._printTestResults(result);
|
|
if (!result.ok())
|
|
this._printFailedResult(result);
|
|
process.exitCode = result.exitCode;
|
|
}
|
|
|
|
_printTestResults(result) {
|
|
// 2 newlines after completing all tests.
|
|
console.log('\n');
|
|
|
|
const runs = result.runs;
|
|
const failedRuns = runs.filter(run => run.isFailure());
|
|
const executedRuns = runs.filter(run => run.result());
|
|
const okRuns = runs.filter(run => run.ok());
|
|
const skippedRuns = runs.filter(run => run.result() === 'skipped');
|
|
const markedAsFailingRuns = runs.filter(run => run.result() === 'markedAsFailing');
|
|
|
|
if (this._summary && failedRuns.length > 0) {
|
|
console.log('\nFailures:');
|
|
for (let i = 0; i < failedRuns.length; ++i) {
|
|
this._printVerboseTestRunResult(i + 1, failedRuns[i]);
|
|
console.log('');
|
|
}
|
|
}
|
|
|
|
if (this._showMarkedAsFailingTests && this._summary && markedAsFailingRuns.length) {
|
|
if (markedAsFailingRuns.length > 0) {
|
|
console.log('\nMarked as failing:');
|
|
markedAsFailingRuns.slice(0, this._showMarkedAsFailingTests).forEach((testRun, index) => {
|
|
console.log(`${index + 1}) ${testRun.test().fullName()} (${formatLocation(testRun.test().location())})`);
|
|
});
|
|
}
|
|
if (this._showMarkedAsFailingTests < markedAsFailingRuns.length) {
|
|
console.log('');
|
|
console.log(`... and ${colors.yellow(markedAsFailingRuns.length - this._showMarkedAsFailingTests)} more marked as failing tests ...`);
|
|
}
|
|
}
|
|
|
|
if (this._showSlowTests) {
|
|
const slowRuns = okRuns.sort((a, b) => b.duration() - a.duration()).slice(0, this._showSlowTests);
|
|
console.log(`\nSlowest tests:`);
|
|
for (let i = 0; i < slowRuns.length; ++i) {
|
|
const run = slowRuns[i];
|
|
console.log(` (${i + 1}) ${colors.yellow((run.duration() / 1000) + 's')} - ${run.test().fullName()} (${formatLocation(run.test().location())})`);
|
|
}
|
|
}
|
|
|
|
let summaryText = '';
|
|
if (failedRuns.length || markedAsFailingRuns.length) {
|
|
const summary = [`ok - ${colors.green(okRuns.length)}`];
|
|
if (failedRuns.length)
|
|
summary.push(`failed - ${colors.red(failedRuns.length)}`);
|
|
if (markedAsFailingRuns.length)
|
|
summary.push(`marked as failing - ${colors.yellow(markedAsFailingRuns.length)}`);
|
|
if (skippedRuns.length)
|
|
summary.push(`skipped - ${colors.yellow(skippedRuns.length)}`);
|
|
summaryText = ` (${summary.join(', ')})`;
|
|
}
|
|
|
|
console.log(`\nRan ${executedRuns.length}${summaryText} of ${runs.length} test${runs.length > 1 ? 's' : ''}`);
|
|
const milliseconds = Date.now() - this._timestamp;
|
|
const seconds = milliseconds / 1000;
|
|
console.log(`Finished in ${colors.yellow(seconds)} seconds`);
|
|
}
|
|
|
|
onTestRunStarted(testRun) {
|
|
}
|
|
|
|
onTestRunFinished(testRun) {
|
|
++this._testCounter;
|
|
if (this._verbose) {
|
|
this._printVerboseTestRunResult(this._testCounter, testRun);
|
|
} else {
|
|
if (testRun.result() === 'ok')
|
|
process.stdout.write(colors.green('\u00B7'));
|
|
else if (testRun.result() === 'skipped')
|
|
process.stdout.write(colors.yellow('\u00B7'));
|
|
else if (testRun.result() === 'markedAsFailing')
|
|
process.stdout.write(colors.yellow('\u00D7'));
|
|
else if (testRun.result() === 'failed')
|
|
process.stdout.write(colors.red('F'));
|
|
else if (testRun.result() === 'crashed')
|
|
process.stdout.write(colors.red('C'));
|
|
else if (testRun.result() === 'terminated')
|
|
process.stdout.write(colors.magenta('.'));
|
|
else if (testRun.result() === 'timedout')
|
|
process.stdout.write(colors.red('T'));
|
|
if (this._lineBreak && !(this._testCounter % this._lineBreak))
|
|
process.stdout.write('\n');
|
|
}
|
|
}
|
|
|
|
_printVerboseTestRunResult(resultIndex, testRun) {
|
|
const test = testRun.test();
|
|
let prefix = `${resultIndex})`;
|
|
if (this._delegate.parallel() > 1)
|
|
prefix += ' ' + colors.gray(`[worker = ${testRun.workerId()}]`);
|
|
if (testRun.result() === 'ok') {
|
|
console.log(`${prefix} ${colors.green('[OK]')} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
} else if (testRun.result() === 'terminated') {
|
|
console.log(`${prefix} ${colors.magenta('[TERMINATED]')} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
} else if (testRun.result() === 'crashed') {
|
|
console.log(`${prefix} ${colors.red('[CRASHED]')} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
} else if (testRun.result() === 'skipped') {
|
|
} else if (testRun.result() === 'markedAsFailing') {
|
|
console.log(`${prefix} ${colors.yellow('[MARKED AS FAILING]')} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
} else if (testRun.result() === 'timedout') {
|
|
console.log(`${prefix} ${colors.red(`[TIMEOUT ${test.timeout()}ms]`)} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
const output = testRun.output();
|
|
if (output.length) {
|
|
console.log(' Output:');
|
|
for (const line of output)
|
|
console.log(' ' + line);
|
|
}
|
|
} else if (testRun.result() === 'failed') {
|
|
console.log(`${prefix} ${colors.red('[FAIL]')} ${test.fullName()} (${formatLocation(test.location())})`);
|
|
if (testRun.error() instanceof MatchError) {
|
|
const location = testRun.error().location;
|
|
let lines = this._filePathToLines.get(location.filePath());
|
|
if (!lines) {
|
|
try {
|
|
lines = fs.readFileSync(location.filePath(), 'utf8').split('\n');
|
|
} catch (e) {
|
|
lines = [];
|
|
}
|
|
this._filePathToLines.set(location.filePath(), lines);
|
|
}
|
|
const lineNumber = location.lineNumber();
|
|
if (lineNumber < lines.length) {
|
|
const lineNumberLength = (lineNumber + 1 + '').length;
|
|
const FROM = Math.max(0, lineNumber - 5);
|
|
const snippet = lines.slice(FROM, lineNumber).map((line, index) => ` ${(FROM + index + 1 + '').padStart(lineNumberLength, ' ')} | ${line}`).join('\n');
|
|
const pointer = ` ` + ' '.repeat(lineNumberLength) + ' ' + '~'.repeat(location.columnNumber() - 1) + '^';
|
|
console.log('\n' + snippet + '\n' + colors.grey(pointer) + '\n');
|
|
}
|
|
console.log(padLines(testRun.error().formatter(), 4));
|
|
console.log('');
|
|
} else {
|
|
console.log(' Message:');
|
|
let message = '' + (testRun.error().message || testRun.error());
|
|
if (testRun.error().stack && message.includes(testRun.error().stack))
|
|
message = message.substring(0, message.indexOf(testRun.error().stack));
|
|
if (message)
|
|
console.log(` ${colors.red(message)}`);
|
|
if (testRun.error().stack) {
|
|
console.log(' Stack:');
|
|
let stack = testRun.error().stack;
|
|
// Highlight first test location, if any.
|
|
const match = stack.match(new RegExp(test.location().filePath() + ':(\\d+):(\\d+)'));
|
|
if (match) {
|
|
const [, line, column] = match;
|
|
const fileName = `${test.location().fileName()}:${line}:${column}`;
|
|
stack = stack.substring(0, match.index) + stack.substring(match.index).replace(fileName, colors.yellow(fileName));
|
|
}
|
|
console.log(padLines(stack, 4));
|
|
}
|
|
}
|
|
const output = testRun.output();
|
|
if (output.length) {
|
|
console.log(' Output:');
|
|
for (const line of output)
|
|
console.log(' ' + line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function formatLocation(location) {
|
|
if (!location)
|
|
return '';
|
|
return colors.yellow(`${location.toDetailedString()}`);
|
|
}
|
|
|
|
function padLines(text, spaces = 0) {
|
|
const indent = ' '.repeat(spaces);
|
|
return text.split('\n').map(line => indent + line).join('\n');
|
|
}
|
|
|
|
module.exports = Reporter;
|