mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix: serialize attachment to base64 in tele reporter (#23590)
This commit is contained in:
parent
a4d361379f
commit
400c7cd529
@ -88,12 +88,14 @@ export type JsonTestResultStart = {
|
|||||||
startTime: string;
|
startTime: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type JsonAttachment = Omit<TestResult['attachments'][0], 'body'> & { base64?: string };
|
||||||
|
|
||||||
export type JsonTestResultEnd = {
|
export type JsonTestResultEnd = {
|
||||||
id: string;
|
id: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
status: TestStatus;
|
status: TestStatus;
|
||||||
errors: TestError[];
|
errors: TestError[];
|
||||||
attachments: TestResult['attachments'];
|
attachments: JsonAttachment[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestStepStart = {
|
export type JsonTestStepStart = {
|
||||||
@ -228,7 +230,7 @@ export class TeleReporterReceiver {
|
|||||||
result.status = payload.status;
|
result.status = payload.status;
|
||||||
result.statusEx = payload.status;
|
result.statusEx = payload.status;
|
||||||
result.errors = payload.errors;
|
result.errors = payload.errors;
|
||||||
result.attachments = payload.attachments;
|
result.attachments = this._parseAttachments(payload.attachments);
|
||||||
this._reporter.onTestEnd?.(test, result);
|
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) {
|
private _mergeSuitesInto(jsonSuites: JsonSuite[], parent: TeleSuite) {
|
||||||
for (const jsonSuite of jsonSuites) {
|
for (const jsonSuite of jsonSuites) {
|
||||||
let targetSuite = parent.suites.find(s => s.title === jsonSuite.title);
|
let targetSuite = parent.suites.find(s => s.title === jsonSuite.title);
|
||||||
|
@ -21,7 +21,7 @@ import { mime } from 'playwright-core/lib/utilsBundle';
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import type { FullConfig, FullResult, TestResult } from '../../types/testReporter';
|
import type { FullConfig, FullResult, TestResult } from '../../types/testReporter';
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import type { JsonEvent } from '../isomorphic/teleReceiver';
|
import type { JsonAttachment, JsonEvent } from '../isomorphic/teleReceiver';
|
||||||
import { TeleReporterEmitter } from './teleEmitter';
|
import { TeleReporterEmitter } from './teleEmitter';
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||||||
private _reportFile!: string;
|
private _reportFile!: string;
|
||||||
|
|
||||||
constructor(options: BlobReporterOptions) {
|
constructor(options: BlobReporterOptions) {
|
||||||
super(message => this._messages.push(message));
|
super(message => this._messages.push(message), false);
|
||||||
this._options = options;
|
this._options = options;
|
||||||
this._salt = createGuid();
|
this._salt = createGuid();
|
||||||
|
|
||||||
@ -78,8 +78,8 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
override _serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] {
|
override _serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] {
|
||||||
return attachments.map(attachment => {
|
return super._serializeAttachments(attachments).map(attachment => {
|
||||||
if (!attachment.path || !fs.statSync(attachment.path).isFile())
|
if (!attachment.path || !fs.statSync(attachment.path).isFile())
|
||||||
return attachment;
|
return attachment;
|
||||||
// Add run guid to avoid clashes between shards.
|
// Add run guid to avoid clashes between shards.
|
||||||
|
@ -20,15 +20,17 @@ import type { SuitePrivate } from '../../types/reporterPrivate';
|
|||||||
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStep } from '../../types/testReporter';
|
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||||
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||||
import type { Suite, TestCase } from '../common/test';
|
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';
|
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||||
|
|
||||||
export class TeleReporterEmitter implements Reporter {
|
export class TeleReporterEmitter implements Reporter {
|
||||||
private _messageSink: (message: JsonEvent) => void;
|
private _messageSink: (message: JsonEvent) => void;
|
||||||
private _rootDir!: string;
|
private _rootDir!: string;
|
||||||
|
private _receiverIsInBrowser: boolean;
|
||||||
|
|
||||||
constructor(messageSink: (message: JsonEvent) => void) {
|
constructor(messageSink: (message: JsonEvent) => void, receiverIsInBrowser: boolean) {
|
||||||
this._messageSink = messageSink;
|
this._messageSink = messageSink;
|
||||||
|
this._receiverIsInBrowser = receiverIsInBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
@ -199,8 +201,14 @@ export class TeleReporterEmitter implements Reporter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] {
|
_serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] {
|
||||||
return attachments;
|
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 {
|
private _serializeStepStart(step: TestStep): JsonTestStepStart {
|
||||||
|
@ -158,7 +158,7 @@ class UIMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _listTests() {
|
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]);
|
const reporter = new InternalReporter([listReporter]);
|
||||||
this._config.cliListOnly = true;
|
this._config.cliListOnly = true;
|
||||||
this._config.testIdMatcher = undefined;
|
this._config.testIdMatcher = undefined;
|
||||||
@ -183,7 +183,7 @@ class UIMode {
|
|||||||
this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);
|
this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);
|
||||||
|
|
||||||
const reporters = await createReporters(this._config, 'ui');
|
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 reporter = new InternalReporter(reporters);
|
||||||
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
|
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
|
||||||
const testRun = new TestRun(this._config, reporter);
|
const testRun = new TestRun(this._config, reporter);
|
||||||
|
@ -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 }) => {
|
test('preserve steps in html report', async ({ runInlineTest, mergeReports, showReport, page }) => {
|
||||||
const reportDir = test.info().outputPath('blob-report');
|
const reportDir = test.info().outputPath('blob-report');
|
||||||
const files = {
|
const files = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user