fix(html): render text attachments as text (#10778)
This commit is contained in:
parent
287a2eaee8
commit
4d683cef7f
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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']);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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' }
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue