mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00: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;
 | 
