fix: serialize attachment to base64 in tele reporter (#23590)

This commit is contained in:
Yury Semikhatsky 2023-06-09 11:52:18 -07:00 committed by GitHub
parent a4d361379f
commit 400c7cd529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 12 deletions

View File

@ -88,12 +88,14 @@ export type JsonTestResultStart = {
startTime: string;
};
export type JsonAttachment = Omit<TestResult['attachments'][0], 'body'> & { base64?: string };
export type JsonTestResultEnd = {
id: string;
duration: number;
status: TestStatus;
errors: TestError[];
attachments: TestResult['attachments'];
attachments: JsonAttachment[];
};
export type JsonTestStepStart = {
@ -228,7 +230,7 @@ export class TeleReporterReceiver {
result.status = payload.status;
result.statusEx = payload.status;
result.errors = payload.errors;
result.attachments = payload.attachments;
result.attachments = this._parseAttachments(payload.attachments);
this._reporter.onTestEnd?.(test, result);
}
@ -321,6 +323,15 @@ export class TeleReporterReceiver {
};
}
private _parseAttachments(attachments: JsonAttachment[]): TestResult['attachments'] {
return attachments.map(a => {
return {
...a,
body: a.base64 && (globalThis as any).Buffer ? Buffer.from(a.base64, 'base64') : undefined,
};
});
}
private _mergeSuitesInto(jsonSuites: JsonSuite[], parent: TeleSuite) {
for (const jsonSuite of jsonSuites) {
let targetSuite = parent.suites.find(s => s.title === jsonSuite.title);

View File

@ -21,7 +21,7 @@ import { mime } from 'playwright-core/lib/utilsBundle';
import { Readable } from 'stream';
import type { FullConfig, FullResult, TestResult } from '../../types/testReporter';
import type { Suite } from '../common/test';
import type { JsonEvent } from '../isomorphic/teleReceiver';
import type { JsonAttachment, JsonEvent } from '../isomorphic/teleReceiver';
import { TeleReporterEmitter } from './teleEmitter';
@ -44,7 +44,7 @@ export class BlobReporter extends TeleReporterEmitter {
private _reportFile!: string;
constructor(options: BlobReporterOptions) {
super(message => this._messages.push(message));
super(message => this._messages.push(message), false);
this._options = options;
this._salt = createGuid();
@ -78,8 +78,8 @@ export class BlobReporter extends TeleReporterEmitter {
]);
}
override _serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] {
return attachments.map(attachment => {
override _serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] {
return super._serializeAttachments(attachments).map(attachment => {
if (!attachment.path || !fs.statSync(attachment.path).isFile())
return attachment;
// Add run guid to avoid clashes between shards.

View File

@ -20,15 +20,17 @@ import type { SuitePrivate } from '../../types/reporterPrivate';
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStep } from '../../types/testReporter';
import { FullConfigInternal, FullProjectInternal } from '../common/config';
import type { Suite, TestCase } from '../common/test';
import type { JsonConfig, JsonEvent, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
import type { JsonAttachment, JsonConfig, JsonEvent, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
export class TeleReporterEmitter implements Reporter {
private _messageSink: (message: JsonEvent) => void;
private _rootDir!: string;
private _receiverIsInBrowser: boolean;
constructor(messageSink: (message: JsonEvent) => void) {
constructor(messageSink: (message: JsonEvent) => void, receiverIsInBrowser: boolean) {
this._messageSink = messageSink;
this._receiverIsInBrowser = receiverIsInBrowser;
}
onBegin(config: FullConfig, suite: Suite) {
@ -199,8 +201,14 @@ export class TeleReporterEmitter implements Reporter {
};
}
_serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] {
return attachments;
_serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] {
return attachments.map(a => {
return {
...a,
// There is no Buffer in the browser, so there is no point in sending the data there.
base64: (a.body && !this._receiverIsInBrowser) ? a.body.toString('base64') : undefined,
};
});
}
private _serializeStepStart(step: TestStep): JsonTestStepStart {

View File

@ -158,7 +158,7 @@ class UIMode {
}
private async _listTests() {
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params));
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true);
const reporter = new InternalReporter([listReporter]);
this._config.cliListOnly = true;
this._config.testIdMatcher = undefined;
@ -183,7 +183,7 @@ class UIMode {
this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);
const reporters = await createReporters(this._config, 'ui');
reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params)));
reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true));
const reporter = new InternalReporter(reporters);
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
const testRun = new TestRun(this._config, reporter);

View File

@ -947,6 +947,59 @@ result.stderr: stderr text
`);
});
test('encode inline attachments', async ({ runInlineTest, mergeReports }) => {
const reportDir = test.info().outputPath('blob-report');
const files = {
'echo-reporter.js': `
import fs from 'fs';
class EchoReporter {
onTestEnd(test, result) {
const attachmentBodies = result.attachments.map(a => a.body?.toString('base64'));
result.attachments.forEach(a => console.log(a.body, 'isBuffer', Buffer.isBuffer(a.body)));
fs.writeFileSync('log.txt', attachmentBodies.join(','));
}
}
module.exports = EchoReporter;
`,
'playwright.config.js': `
module.exports = {
reporter: [['blob']]
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('a test', async ({}) => {
expect(1 + 1).toBe(2);
test.info().attachments.push({
name: 'example.txt',
contentType: 'text/plain',
body: Buffer.from('foo'),
});
test.info().attachments.push({
name: 'example.json',
contentType: 'application/json',
body: Buffer.from(JSON.stringify({ foo: 1 })),
});
test.info().attachments.push({
name: 'example-utf16.txt',
contentType: 'text/plain, charset=utf16le',
body: Buffer.from('utf16 encoded', 'utf16le'),
});
});
`,
};
await runInlineTest(files);
const { exitCode } = await mergeReports(reportDir, {}, { additionalArgs: ['--reporter', test.info().outputPath('echo-reporter.js')] });
expect(exitCode).toBe(0);
const log = fs.readFileSync(test.info().outputPath('log.txt')).toString();
expect(log).toBe(`Zm9v,eyJmb28iOjF9,dQB0AGYAMQA2ACAAZQBuAGMAbwBkAGUAZAA=`);
});
test('preserve steps in html report', async ({ runInlineTest, mergeReports, showReport, page }) => {
const reportDir = test.info().outputPath('blob-report');
const files = {