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 { FullConfig, Suite, Reporter } from '../../types/testReporter';
import { HttpServer } from 'playwright-core/lib/utils/httpServer'; import { HttpServer } from 'playwright-core/lib/utils/httpServer';
import { calculateSha1, removeFolders } from 'playwright-core/lib/utils/utils'; 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 assert from 'assert';
import yazl from 'yazl'; import yazl from 'yazl';
import { stripAnsiEscapes } from './base'; import { stripAnsiEscapes } from './base';
@ -78,7 +78,13 @@ export type TestCase = TestCaseSummary & {
results: TestResult[]; results: TestResult[];
}; };
export type TestAttachment = JsonAttachment; export type TestAttachment = {
name: string;
body?: string;
path?: string;
contentType: string;
};
export type TestResult = { export type TestResult = {
retry: number; retry: number;
@ -381,6 +387,19 @@ class HtmlBuilder {
attachments: result.attachments.map(a => { attachments: result.attachments.map(a => {
if (a.name === 'trace') if (a.name === 'trace')
this._hasTraces = true; 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) { if (a.path) {
let fileName = a.path; let fileName = a.path;
try { try {
@ -404,17 +423,39 @@ class HtmlBuilder {
}; };
} }
if ((a.name === 'stdout' || a.name === 'stderr') && a.contentType === 'text/plain') { if (a.body instanceof Buffer) {
if (lastAttachment && if (isTextContentType(a.contentType)) {
lastAttachment.name === a.name && // Content type is like this: "text/html; charset=UTF-8"
lastAttachment.contentType === a.contentType) { const charset = a.contentType.match(/charset=(.*)/)?.[1];
lastAttachment.body += stripAnsiEscapes(a.body as string); try {
return null; 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[] }).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; export default HtmlReporter;

View file

@ -71,7 +71,7 @@ export type JsonTestCase = {
export type JsonAttachment = { export type JsonAttachment = {
name: string; name: string;
body?: string; body?: string | Buffer;
path?: string; path?: string;
contentType: string; contentType: string;
}; };
@ -245,7 +245,7 @@ class RawReporter {
attachments.push({ attachments.push({
name: attachment.name, name: attachment.name,
contentType: attachment.contentType, contentType: attachment.contentType,
body: attachment.body.toString('base64') body: attachment.body
}); });
} else if (attachment.path) { } else if (attachment.path) {
attachments.push({ attachments.push({
@ -274,7 +274,7 @@ class RawReporter {
return { return {
name: type, name: type,
contentType: 'application/octet-stream', 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 page.click('text=skipped test');
await expect(page.locator('.test-case-annotation')).toHaveText('skip: I am not interested in this 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', name: 'stdout',
contentType: 'application/octet-stream', contentType: 'application/octet-stream',
body: 'AQID' body: { data: [1, 2, 3], type: 'Buffer' }
}, },
{ name: 'stderr', contentType: 'text/plain', body: 'STDERR\n' }, { name: 'stderr', contentType: 'text/plain', body: 'STDERR\n' },
{ {
name: 'stderr', name: 'stderr',
contentType: 'application/octet-stream', contentType: 'application/octet-stream',
body: 'BAUG' body: { data: [4, 5, 6], type: 'Buffer' }
} }
]); ]);
}); });