diff --git a/packages/playwright-test/src/reporters/blob.ts b/packages/playwright-test/src/reporters/blob.ts index 3edcbce30a..36a27270b1 100644 --- a/packages/playwright-test/src/reporters/blob.ts +++ b/packages/playwright-test/src/reporters/blob.ts @@ -21,6 +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 { TeleReporterEmitter } from './teleEmitter'; @@ -29,8 +30,12 @@ type BlobReporterOptions = { outputDir?: string; }; +export type BlobReportMetadata = { + projectSuffix?: string; +}; + export class BlobReporter extends TeleReporterEmitter { - private _messages: any[] = []; + private _messages: JsonEvent[] = []; private _options: BlobReporterOptions; private _salt: string; private _copyFilePromises = new Set>(); @@ -42,6 +47,13 @@ export class BlobReporter extends TeleReporterEmitter { super(message => this._messages.push(message)); this._options = options; this._salt = createGuid(); + + this._messages.push({ + method: 'onBlobReportMetadata', + params: { + projectSuffix: process.env.PWTEST_BLOB_SUFFIX, + } + }); } printsToStdio() { @@ -66,11 +78,6 @@ export class BlobReporter extends TeleReporterEmitter { ]); } - override _serializeProjectName(name: string): string { - const suffix = process.env.PWTEST_BLOB_SUFFIX; - return name + (suffix ? suffix : ''); - } - override _serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] { return attachments.map(attachment => { if (!attachment.path || !fs.statSync(attachment.path).isFile()) diff --git a/packages/playwright-test/src/reporters/merge.ts b/packages/playwright-test/src/reporters/merge.ts index db58ea3d62..a7041e9849 100644 --- a/packages/playwright-test/src/reporters/merge.ts +++ b/packages/playwright-test/src/reporters/merge.ts @@ -19,7 +19,8 @@ import path from 'path'; import type { ReporterDescription } from '../../types/test'; import type { FullResult } from '../../types/testReporter'; import type { FullConfigInternal } from '../common/config'; -import { TeleReporterReceiver, type JsonEvent, type JsonProject, type JsonSuite, type JsonTestResultEnd, type JsonConfig } from '../isomorphic/teleReceiver'; +import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver'; +import { TeleReporterReceiver } from '../isomorphic/teleReceiver'; import { createReporters } from '../runner/reporters'; import { Multiplexer } from './multiplexer'; @@ -65,6 +66,8 @@ async function mergeEvents(dir: string, shardReportFiles: string[]) { beginEvents.push(event); else if (event.method === 'onEnd') endEvents.push(event); + else if (event.method === 'onBlobReportMetadata') + new ProjectNamePatcher(event.params.projectSuffix).patchEvents(parsedEvents); else events.push(event); } @@ -153,3 +156,61 @@ async function sortedShardFiles(dir: string) { const files = await fs.promises.readdir(dir); return files.filter(file => file.endsWith('.jsonl')).sort(); } + +class ProjectNamePatcher { + constructor(private _projectNameSuffix: string) { + } + + patchEvents(events: JsonEvent[]) { + if (!this._projectNameSuffix) + return; + for (const event of events) { + const { method, params } = event; + switch (method) { + case 'onBegin': + this._onBegin(params.config, params.projects); + continue; + case 'onTestBegin': + case 'onStepBegin': + case 'onStepEnd': + case 'onStdIO': + params.testId = this._mapTestId(params.testId); + continue; + case 'onTestEnd': + params.test.testId = this._mapTestId(params.test.testId); + continue; + } + } + } + + private _onBegin(config: JsonConfig, projects: JsonProject[]) { + for (const project of projects) + project.name += this._projectNameSuffix; + this._updateProjectIds(projects); + for (const project of projects) + project.suites.forEach(suite => this._updateTestIds(suite)); + } + + private _updateProjectIds(projects: JsonProject[]) { + const usedNames = new Set(); + for (const p of projects) { + for (let i = 0; i < projects.length; ++i) { + const candidate = p.name + (i ? i : ''); + if (usedNames.has(candidate)) + continue; + p.id = candidate; + usedNames.add(candidate); + break; + } + } + } + + private _updateTestIds(suite: JsonSuite) { + suite.tests.forEach(test => test.testId = this._mapTestId(test.testId)); + suite.suites.forEach(suite => this._updateTestIds(suite)); + } + + private _mapTestId(testId: string): string { + return testId + '-' + this._projectNameSuffix; + } +} diff --git a/packages/playwright-test/src/reporters/teleEmitter.ts b/packages/playwright-test/src/reporters/teleEmitter.ts index c6d53a94d8..5e9339d7b0 100644 --- a/packages/playwright-test/src/reporters/teleEmitter.ts +++ b/packages/playwright-test/src/reporters/teleEmitter.ts @@ -14,32 +14,26 @@ * limitations under the License. */ -import type { FullConfig, FullResult, Reporter, TestError, TestResult, TestStep, Location } from '../../types/testReporter'; -import type { Suite, TestCase } from '../common/test'; -import type { JsonConfig, JsonProject, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver'; -import type { SuitePrivate } from '../../types/reporterPrivate'; -import { FullConfigInternal } from '../common/config'; -import { createGuid } from 'playwright-core/lib/utils'; -import { serializeRegexPatterns } from '../isomorphic/teleReceiver'; import path from 'path'; -import type { FullProject } from '../../types/test'; +import { createGuid } from 'playwright-core/lib/utils'; +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, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver'; +import { serializeRegexPatterns } from '../isomorphic/teleReceiver'; export class TeleReporterEmitter implements Reporter { - private _messageSink: (message: any) => void; + private _messageSink: (message: JsonEvent) => void; private _rootDir!: string; - constructor(messageSink: (message: any) => void) { + constructor(messageSink: (message: JsonEvent) => void) { this._messageSink = messageSink; } onBegin(config: FullConfig, suite: Suite) { this._rootDir = config.rootDir; - const projects: any[] = []; - const projectIds = this._uniqueProjectIds(config.projects); - for (const projectSuite of suite.suites) { - const report = this._serializeProject(projectSuite, projectIds); - projects.push(report); - } + const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite)); this._messageSink({ method: 'onBegin', params: { config: this._serializeConfig(config), projects } }); } @@ -138,29 +132,12 @@ export class TeleReporterEmitter implements Reporter { }; } - private _uniqueProjectIds(projects: FullProject[]): Map { - const usedNames = new Set(); - const result = new Map(); - for (const p of projects) { - const name = this._serializeProjectName(p.name); - for (let i = 0; i < projects.length; ++i) { - const candidate = name + (i ? i : ''); - if (usedNames.has(candidate)) - continue; - result.set(p, candidate); - usedNames.add(candidate); - break; - } - } - return result; - } - - private _serializeProject(suite: Suite, projectIds: Map): JsonProject { + private _serializeProject(suite: Suite): JsonProject { const project = suite.project()!; const report: JsonProject = { - id: projectIds.get(project)!, + id: FullProjectInternal.from(project).id, metadata: project.metadata, - name: this._serializeProjectName(project.name), + name: project.name, outputDir: this._relativePath(project.outputDir), repeatEach: project.repeatEach, retries: project.retries, @@ -180,10 +157,6 @@ export class TeleReporterEmitter implements Reporter { return report; } - _serializeProjectName(name: string): string { - return name; - } - private _serializeSuite(suite: Suite): JsonSuite { const result = { type: suite._type,