chore: move github reporter formatting out of base (#33213)

This commit is contained in:
Yury Semikhatsky 2024-10-22 11:45:12 -07:00 committed by GitHub
parent 29ca54eb38
commit 8ec981c394
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 88 deletions

View File

@ -23,12 +23,6 @@ import { resolveReporterOutputPath } from '../util';
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
export const kOutputSymbol = Symbol('output'); export const kOutputSymbol = Symbol('output');
type Annotation = {
title: string;
message: string;
location?: Location;
};
type ErrorDetails = { type ErrorDetails = {
message: string; message: string;
location?: Location; location?: Location;
@ -260,9 +254,7 @@ export class BaseReporter implements ReporterV2 {
private _printFailures(failures: TestCase[]) { private _printFailures(failures: TestCase[]) {
console.log(''); console.log('');
failures.forEach((test, index) => { failures.forEach((test, index) => {
console.log(formatFailure(this.config, test, { console.log(formatFailure(this.config, test, index + 1));
index: index + 1,
}).message);
}); });
} }
@ -285,14 +277,8 @@ export class BaseReporter implements ReporterV2 {
} }
} }
export function formatFailure(config: FullConfig, test: TestCase, options: {index?: number, includeStdio?: boolean, includeAttachments?: boolean} = {}): { export function formatFailure(config: FullConfig, test: TestCase, index?: number): string {
message: string,
annotations: Annotation[]
} {
const { index, includeStdio, includeAttachments = true } = options;
const lines: string[] = []; const lines: string[] = [];
const title = formatTestTitle(config, test);
const annotations: Annotation[] = [];
const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' }); const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' });
lines.push(colors.red(header)); lines.push(colors.red(header));
for (const result of test.results) { for (const result of test.results) {
@ -307,62 +293,48 @@ export function formatFailure(config: FullConfig, test: TestCase, options: {inde
} }
resultLines.push(...retryLines); resultLines.push(...retryLines);
resultLines.push(...errors.map(error => '\n' + error.message)); resultLines.push(...errors.map(error => '\n' + error.message));
if (includeAttachments) { for (let i = 0; i < result.attachments.length; ++i) {
for (let i = 0; i < result.attachments.length; ++i) { const attachment = result.attachments[i];
const attachment = result.attachments[i]; const hasPrintableContent = attachment.contentType.startsWith('text/');
const hasPrintableContent = attachment.contentType.startsWith('text/'); if (!attachment.path && !hasPrintableContent)
if (!attachment.path && !hasPrintableContent) continue;
continue;
resultLines.push('');
resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
if (attachment.path) {
const relativePath = path.relative(process.cwd(), attachment.path);
resultLines.push(colors.cyan(` ${relativePath}`));
// Make this extensible
if (attachment.name === 'trace') {
const packageManagerCommand = getPackageManagerExecCommand();
resultLines.push(colors.cyan(` Usage:`));
resultLines.push('');
resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(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 as any)[kOutputSymbol] || []) as TestResultOutput[];
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('');
resultLines.push(colors.gray(separator('--- Test output')) + '\n\n' + outputText + '\n' + separator()); resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
} if (attachment.path) {
for (const error of errors) { const relativePath = path.relative(process.cwd(), attachment.path);
annotations.push({ resultLines.push(colors.cyan(` ${relativePath}`));
location: error.location, // Make this extensible
title, if (attachment.name === 'trace') {
message: [header, ...retryLines, error.message].join('\n'), const packageManagerCommand = getPackageManagerExecCommand();
}); resultLines.push(colors.cyan(` Usage:`));
resultLines.push('');
resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(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(' ')));
} }
lines.push(...resultLines); lines.push(...resultLines);
} }
lines.push(''); lines.push('');
return { return lines.join('\n');
message: lines.join('\n'), }
annotations
}; export function formatRetry(result: TestResult) {
const retryLines = [];
if (result.retry) {
retryLines.push('');
retryLines.push(colors.gray(separator(` Retry #${result.retry}`)));
}
return retryLines;
} }
function quotePathIfNeeded(path: string): string { function quotePathIfNeeded(path: string): string {
@ -428,7 +400,7 @@ export function formatTestTitle(config: FullConfig, test: TestCase, step?: TestS
return `${projectTitle}${location} ${titles.join(' ')}${stepSuffix(step)}${tags}`; return `${projectTitle}${location} ${titles.join(' ')}${stepSuffix(step)}${tags}`;
} }
function formatTestHeader(config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string { export function formatTestHeader(config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string {
const title = formatTestTitle(config, test); const title = formatTestTitle(config, test);
const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`; const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
let fullHeader = header; let fullHeader = header;

View File

@ -16,7 +16,7 @@
import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; import { ms as milliseconds } from 'playwright-core/lib/utilsBundle';
import path from 'path'; import path from 'path';
import { BaseReporter, formatError, formatFailure, stripAnsiEscapes } from './base'; import { BaseReporter, colors, formatError, formatResultFailure, formatRetry, formatTestHeader, formatTestTitle, stripAnsiEscapes } from './base';
import type { TestCase, FullResult, TestError } from '../../types/testReporter'; import type { TestCase, FullResult, TestError } from '../../types/testReporter';
type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error'; type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error';
@ -100,22 +100,23 @@ export class GitHubReporter extends BaseReporter {
private _printFailureAnnotations(failures: TestCase[]) { private _printFailureAnnotations(failures: TestCase[]) {
failures.forEach((test, index) => { failures.forEach((test, index) => {
const { annotations } = formatFailure(this.config, test, { const title = formatTestTitle(this.config, test);
index: index + 1, const header = formatTestHeader(this.config, test, { indent: ' ', index: index + 1, mode: 'error' });
includeStdio: true, for (const result of test.results) {
includeAttachments: false, const errors = formatResultFailure(test, result, ' ', colors.enabled);
}); for (const error of errors) {
annotations.forEach(({ location, title, message }) => { const options: GitHubLogOptions = {
const options: GitHubLogOptions = { file: workspaceRelativePath(error.location?.file || test.location.file),
file: workspaceRelativePath(location?.file || test.location.file), title,
title, };
}; if (error.location) {
if (location) { options.line = error.location.line;
options.line = location.line; options.col = error.location.column;
options.col = location.column; }
const message = [header, ...formatRetry(result), error.message].join('\n');
this.githubLogger.error(message, options);
} }
this.githubLogger.error(message, options); }
});
}); });
} }
} }

View File

@ -188,7 +188,7 @@ class JUnitReporter implements ReporterV2 {
message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`, message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
type: 'FAILURE', type: 'FAILURE',
}, },
text: stripAnsiEscapes(formatFailure(this.config, test).message) text: stripAnsiEscapes(formatFailure(this.config, test))
}); });
} }

View File

@ -82,9 +82,7 @@ class LineReporter extends BaseReporter {
if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) { if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) {
if (!process.env.PW_TEST_DEBUG_REPORTERS) if (!process.env.PW_TEST_DEBUG_REPORTERS)
process.stdout.write(`\u001B[1A\u001B[2K`); process.stdout.write(`\u001B[1A\u001B[2K`);
console.log(formatFailure(this.config, test, { console.log(formatFailure(this.config, test, ++this._failures));
index: ++this._failures
}).message);
console.log(); console.log();
} }
} }