fix(html): render text attachments as text (#10778)

This commit is contained in:
Pavel Feldman 2021-12-08 08:51:44 -08:00 committed by GitHub
parent 287a2eaee8
commit 4d683cef7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 16 deletions

View File

@ -22,7 +22,7 @@ import { Transform, TransformCallback } from 'stream';
import { FullConfig, Suite, Reporter } from '../../types/testReporter';
import { HttpServer } from 'playwright-core/lib/utils/httpServer';
import { calculateSha1, removeFolders } from 'playwright-core/lib/utils/utils';
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep } from './raw';
import assert from 'assert';
import yazl from 'yazl';
import { stripAnsiEscapes } from './base';
@ -78,7 +78,13 @@ export type TestCase = TestCaseSummary & {
results: TestResult[];
};
export type TestAttachment = JsonAttachment;
export type TestAttachment = {
name: string;
body?: string;
path?: string;
contentType: string;
};
export type TestResult = {
retry: number;
@ -381,6 +387,19 @@ class HtmlBuilder {
attachments: result.attachments.map(a => {
if (a.name === 'trace')
this._hasTraces = true;
if ((a.name === 'stdout' || a.name === 'stderr') && a.contentType === 'text/plain') {
if (lastAttachment &&
lastAttachment.name === a.name &&
lastAttachment.contentType === a.contentType) {
lastAttachment.body += stripAnsiEscapes(a.body as string);
return null;
}
a.body = stripAnsiEscapes(a.body as string);
lastAttachment = a as TestAttachment;
return a;
}
if (a.path) {
let fileName = a.path;
try {
@ -404,17 +423,39 @@ class HtmlBuilder {
};
}
if ((a.name === 'stdout' || a.name === 'stderr') && a.contentType === 'text/plain') {
if (lastAttachment &&
lastAttachment.name === a.name &&
lastAttachment.contentType === a.contentType) {
lastAttachment.body += stripAnsiEscapes(a.body as string);
return null;
if (a.body instanceof Buffer) {
if (isTextContentType(a.contentType)) {
// Content type is like this: "text/html; charset=UTF-8"
const charset = a.contentType.match(/charset=(.*)/)?.[1];
try {
const body = a.body.toString(charset as any || 'utf-8');
return {
name: a.name,
contentType: a.contentType,
body,
};
} catch (e) {
// Invalid encoding, fall through and save to file.
}
}
a.body = stripAnsiEscapes(a.body as string);
fs.mkdirSync(path.join(this._reportFolder, 'data'), { recursive: true });
const sha1 = calculateSha1(a.body) + '.dat';
fs.writeFileSync(path.join(this._reportFolder, 'data', sha1), a.body);
return {
name: a.name,
contentType: a.contentType,
path: 'data/' + sha1,
body: a.body,
};
}
lastAttachment = a;
return a;
// string
return {
name: a.name,
contentType: a.contentType,
body: a.body,
};
}).filter(Boolean) as TestAttachment[]
};
}
@ -481,4 +522,8 @@ class Base64Encoder extends Transform {
}
}
function isTextContentType(contentType: string) {
return contentType.startsWith('text/') || contentType.startsWith('application/json');
}
export default HtmlReporter;

View File

@ -71,7 +71,7 @@ export type JsonTestCase = {
export type JsonAttachment = {
name: string;
body?: string;
body?: string | Buffer;
path?: string;
contentType: string;
};
@ -245,7 +245,7 @@ class RawReporter {
attachments.push({
name: attachment.name,
contentType: attachment.contentType,
body: attachment.body.toString('base64')
body: attachment.body
});
} else if (attachment.path) {
attachments.push({
@ -274,7 +274,7 @@ class RawReporter {
return {
name: type,
contentType: 'application/octet-stream',
body: chunk.toString('base64')
body: chunk
};
}

View File

@ -294,3 +294,44 @@ test('should render annotations', async ({ runInlineTest, page, showReport }) =>
await page.click('text=skipped test');
await expect(page.locator('.test-case-annotation')).toHaveText('skip: I am not interested in this test');
});
test('should render text attachments as text', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `
const { test } = pwt;
test('passing', async ({ page }, testInfo) => {
testInfo.attachments.push({
name: 'example.txt',
contentType: 'text/plain',
body: Buffer.from('foo'),
});
testInfo.attachments.push({
name: 'example.json',
contentType: 'application/json',
body: Buffer.from(JSON.stringify({ foo: 1 })),
});
testInfo.attachments.push({
name: 'example-utf16.txt',
contentType: 'text/plain, charset=utf16le',
body: Buffer.from('utf16 encoded', 'utf16le'),
});
testInfo.attachments.push({
name: 'example-null.txt',
contentType: 'text/plain, charset=utf16le',
body: null,
});
});
`,
}, { reporter: 'dot,html' });
expect(result.exitCode).toBe(0);
await showReport();
await page.click('text=passing');
await page.click('text=example.txt');
await page.click('text=example.json');
await page.click('text=example-utf16.txt');
await expect(page.locator('.attachment-body')).toHaveText(['foo', '{"foo":1}', 'utf16 encoded']);
});

View File

@ -72,13 +72,13 @@ test('should save stdio', async ({ runInlineTest }, testInfo) => {
{
name: 'stdout',
contentType: 'application/octet-stream',
body: 'AQID'
body: { data: [1, 2, 3], type: 'Buffer' }
},
{ name: 'stderr', contentType: 'text/plain', body: 'STDERR\n' },
{
name: 'stderr',
contentType: 'application/octet-stream',
body: 'BAUG'
body: { data: [4, 5, 6], type: 'Buffer' }
}
]);
});