feat(report): render attachment as a part of failure (#8903)
This commit is contained in:
parent
b76e993951
commit
1925c85dfb
|
|
@ -153,17 +153,41 @@ export class BaseReporter implements Reporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatFailure(config: FullConfig, test: TestCase, index?: number, stdio?: boolean): string {
|
export function formatFailure(config: FullConfig, test: TestCase, index?: number, stdio?: boolean): string {
|
||||||
const tokens: string[] = [];
|
const lines: string[] = [];
|
||||||
tokens.push(formatTestHeader(config, test, ' ', index));
|
lines.push(formatTestHeader(config, test, ' ', index));
|
||||||
for (const result of test.results) {
|
for (const result of test.results) {
|
||||||
const resultTokens = formatResultFailure(test, result, ' ');
|
const resultTokens = formatResultFailure(test, result, ' ');
|
||||||
if (!resultTokens.length)
|
if (!resultTokens.length)
|
||||||
continue;
|
continue;
|
||||||
if (result.retry) {
|
if (result.retry) {
|
||||||
tokens.push('');
|
lines.push('');
|
||||||
tokens.push(colors.gray(pad(` Retry #${result.retry}`, '-')));
|
lines.push(colors.gray(pad(` Retry #${result.retry}`, '-')));
|
||||||
|
}
|
||||||
|
lines.push(...resultTokens);
|
||||||
|
for (let i = 0; i < result.attachments.length; ++i) {
|
||||||
|
const attachment = result.attachments[i];
|
||||||
|
lines.push('');
|
||||||
|
lines.push(colors.cyan(pad(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`, '-')));
|
||||||
|
if (attachment.path) {
|
||||||
|
const relativePath = path.relative(process.cwd(), attachment.path);
|
||||||
|
lines.push(colors.cyan(` ${relativePath}`));
|
||||||
|
// Make this extensible
|
||||||
|
if (attachment.name === 'trace') {
|
||||||
|
lines.push(colors.cyan(` Usage:`));
|
||||||
|
lines.push('');
|
||||||
|
lines.push(colors.cyan(` npx playwright show-trace ${relativePath}`));
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attachment.contentType.startsWith('text/')) {
|
||||||
|
let text = attachment.body!.toString();
|
||||||
|
if (text.length > 300)
|
||||||
|
text = text.slice(0, 300) + '...';
|
||||||
|
lines.push(colors.cyan(` ${text}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push(colors.cyan(pad(' ', '-')));
|
||||||
}
|
}
|
||||||
tokens.push(...resultTokens);
|
|
||||||
const output = ((result as any)[kOutputSymbol] || []) as TestResultOutput[];
|
const output = ((result as any)[kOutputSymbol] || []) as TestResultOutput[];
|
||||||
if (stdio && output.length) {
|
if (stdio && output.length) {
|
||||||
const outputText = output.map(({ chunk, type }) => {
|
const outputText = output.map(({ chunk, type }) => {
|
||||||
|
|
@ -172,12 +196,12 @@ export function formatFailure(config: FullConfig, test: TestCase, index?: number
|
||||||
return colors.red(stripAnsiEscapes(text));
|
return colors.red(stripAnsiEscapes(text));
|
||||||
return text;
|
return text;
|
||||||
}).join('');
|
}).join('');
|
||||||
tokens.push('');
|
lines.push('');
|
||||||
tokens.push(colors.gray(pad('--- Test output', '-')) + '\n\n' + outputText + '\n' + pad('', '-'));
|
lines.push(colors.gray(pad('--- Test output', '-')) + '\n\n' + outputText + '\n' + pad('', '-'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens.push('');
|
lines.push('');
|
||||||
return tokens.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatResultFailure(test: TestCase, result: TestResult, initialIndent: string): string[] {
|
export function formatResultFailure(test: TestCase, result: TestResult, initialIndent: string): string[] {
|
||||||
|
|
|
||||||
|
|
@ -151,8 +151,11 @@ export const playwrightFixtures: Fixtures<PlaywrightTestOptions & PlaywrightTest
|
||||||
});
|
});
|
||||||
await Promise.all(contexts.map(async context => {
|
await Promise.all(contexts.map(async context => {
|
||||||
const videos = context.pages().map(p => p.video()).filter(Boolean);
|
const videos = context.pages().map(p => p.video()).filter(Boolean);
|
||||||
if (!(context as any)._closed && trace)
|
if (!(context as any)._closed && trace) {
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
|
await context.tracing.stop({ path: tracePath });
|
||||||
|
testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
||||||
|
}
|
||||||
await context.close();
|
await context.close();
|
||||||
for (const v of videos) {
|
for (const v of videos) {
|
||||||
const videoPath = await v.path().catch(() => null);
|
const videoPath = await v.path().catch(() => null);
|
||||||
|
|
|
||||||
81
tests/playwright-test/attachment-reporter.spec.ts
Normal file
81
tests/playwright-test/attachment-reporter.spec.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect, stripAscii } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test('render text attachment', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('one', async ({}, testInfo) => {
|
||||||
|
testInfo.attachments.push({
|
||||||
|
name: 'attachment',
|
||||||
|
body: Buffer.from('Hello world'),
|
||||||
|
contentType: 'text/plain'
|
||||||
|
});
|
||||||
|
expect(1).toBe(0);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'line' });
|
||||||
|
const text = stripAscii(result.output);
|
||||||
|
expect(text).toContain(' attachment #1: attachment (text/plain) ---------------------------------------------------------');
|
||||||
|
expect(text).toContain(' Hello world');
|
||||||
|
expect(text).toContain(' ------------------------------------------------------------------------------------------------');
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render screenshot attachment', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('one', async ({}, testInfo) => {
|
||||||
|
testInfo.attachments.push({
|
||||||
|
name: 'screenshot',
|
||||||
|
path: testInfo.outputPath('some/path.png'),
|
||||||
|
contentType: 'image/png'
|
||||||
|
});
|
||||||
|
expect(1).toBe(0);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'line' });
|
||||||
|
const text = stripAscii(result.output).replace(/\\/g, '/');
|
||||||
|
expect(text).toContain(' attachment #1: screenshot (image/png) ----------------------------------------------------------');
|
||||||
|
expect(text).toContain(' test-results/a-one/some/path.png');
|
||||||
|
expect(text).toContain(' ------------------------------------------------------------------------------------------------');
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render trace attachment', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('one', async ({}, testInfo) => {
|
||||||
|
testInfo.attachments.push({
|
||||||
|
name: 'trace',
|
||||||
|
path: testInfo.outputPath('trace.zip'),
|
||||||
|
contentType: 'application/zip'
|
||||||
|
});
|
||||||
|
expect(1).toBe(0);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'line' });
|
||||||
|
const text = stripAscii(result.output).replace(/\\/g, '/');
|
||||||
|
expect(text).toContain(' attachment #1: trace (application/zip) ---------------------------------------------------------');
|
||||||
|
expect(text).toContain(' test-results/a-one/trace.zip');
|
||||||
|
expect(text).toContain('npx playwright show-trace test-results/a-one/trace.zip');
|
||||||
|
expect(text).toContain(' ------------------------------------------------------------------------------------------------');
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue