diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 138820baee..66f78bfdc9 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -23,12 +23,6 @@ import { resolveReporterOutputPath } from '../util'; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); -type Annotation = { - title: string; - message: string; - location?: Location; -}; - type ErrorDetails = { message: string; location?: Location; @@ -260,9 +254,7 @@ export class BaseReporter implements ReporterV2 { private _printFailures(failures: TestCase[]) { console.log(''); failures.forEach((test, index) => { - console.log(formatFailure(this.config, test, { - index: index + 1, - }).message); + console.log(formatFailure(this.config, test, index + 1)); }); } @@ -285,14 +277,8 @@ export class BaseReporter implements ReporterV2 { } } -export function formatFailure(config: FullConfig, test: TestCase, options: {index?: number, includeStdio?: boolean, includeAttachments?: boolean} = {}): { - message: string, - annotations: Annotation[] -} { - const { index, includeStdio, includeAttachments = true } = options; +export function formatFailure(config: FullConfig, test: TestCase, index?: number): string { const lines: string[] = []; - const title = formatTestTitle(config, test); - const annotations: Annotation[] = []; const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' }); lines.push(colors.red(header)); for (const result of test.results) { @@ -307,62 +293,48 @@ export function formatFailure(config: FullConfig, test: TestCase, options: {inde } 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/'); - 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.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(''); + for (let i = 0; i < result.attachments.length; ++i) { + const attachment = result.attachments[i]; + const hasPrintableContent = attachment.contentType.startsWith('text/'); + if (!attachment.path && !hasPrintableContent) + continue; 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'), - }); + 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(' '))); } lines.push(...resultLines); } lines.push(''); - return { - message: lines.join('\n'), - annotations - }; + return lines.join('\n'); +} + +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 { @@ -428,7 +400,7 @@ export function formatTestTitle(config: FullConfig, test: TestCase, step?: TestS 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 header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`; let fullHeader = header; diff --git a/packages/playwright/src/reporters/github.ts b/packages/playwright/src/reporters/github.ts index dc03677714..c178cce64d 100644 --- a/packages/playwright/src/reporters/github.ts +++ b/packages/playwright/src/reporters/github.ts @@ -16,7 +16,7 @@ import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; 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'; type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error'; @@ -100,22 +100,23 @@ export class GitHubReporter extends BaseReporter { private _printFailureAnnotations(failures: TestCase[]) { failures.forEach((test, index) => { - const { annotations } = formatFailure(this.config, test, { - index: index + 1, - includeStdio: true, - includeAttachments: false, - }); - annotations.forEach(({ location, title, message }) => { - const options: GitHubLogOptions = { - file: workspaceRelativePath(location?.file || test.location.file), - title, - }; - if (location) { - options.line = location.line; - options.col = location.column; + const title = formatTestTitle(this.config, test); + const header = formatTestHeader(this.config, test, { indent: ' ', index: index + 1, mode: 'error' }); + for (const result of test.results) { + const errors = formatResultFailure(test, result, ' ', colors.enabled); + for (const error of errors) { + const options: GitHubLogOptions = { + file: workspaceRelativePath(error.location?.file || test.location.file), + title, + }; + if (error.location) { + options.line = error.location.line; + options.col = error.location.column; + } + const message = [header, ...formatRetry(result), error.message].join('\n'); + this.githubLogger.error(message, options); } - this.githubLogger.error(message, options); - }); + } }); } } diff --git a/packages/playwright/src/reporters/junit.ts b/packages/playwright/src/reporters/junit.ts index befd652e4b..f193f4f79d 100644 --- a/packages/playwright/src/reporters/junit.ts +++ b/packages/playwright/src/reporters/junit.ts @@ -188,7 +188,7 @@ class JUnitReporter implements ReporterV2 { message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`, type: 'FAILURE', }, - text: stripAnsiEscapes(formatFailure(this.config, test).message) + text: stripAnsiEscapes(formatFailure(this.config, test)) }); } diff --git a/packages/playwright/src/reporters/line.ts b/packages/playwright/src/reporters/line.ts index af4c786e16..de5fc61703 100644 --- a/packages/playwright/src/reporters/line.ts +++ b/packages/playwright/src/reporters/line.ts @@ -82,9 +82,7 @@ class LineReporter extends BaseReporter { if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) { if (!process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write(`\u001B[1A\u001B[2K`); - console.log(formatFailure(this.config, test, { - index: ++this._failures - }).message); + console.log(formatFailure(this.config, test, ++this._failures)); console.log(); } }