diff --git a/packages/playwright-test/src/common/config.ts b/packages/playwright-test/src/common/config.ts index ac608920d8..177bdc36ad 100644 --- a/packages/playwright-test/src/common/config.ts +++ b/packages/playwright-test/src/common/config.ts @@ -154,6 +154,10 @@ export class FullProjectInternal { deps: FullProjectInternal[] = []; teardown: FullProjectInternal | undefined; + static from(config: FullProject): FullProjectInternal { + return (config as any)[projectInternalSymbol]; + } + constructor(configDir: string, config: Config, fullConfig: FullConfigInternal, projectConfig: Project, configCLIOverrides: ConfigCLIOverrides, throwawayArtifactsPath: string) { this.fullConfig = fullConfig; const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir); diff --git a/packages/playwright-test/src/reporters/base.ts b/packages/playwright-test/src/reporters/base.ts index 5820f75e46..0eb11ad397 100644 --- a/packages/playwright-test/src/reporters/base.ts +++ b/packages/playwright-test/src/reporters/base.ts @@ -19,7 +19,6 @@ import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location, Reporter } from '../../types/testReporter'; import type { SuitePrivate } from '../../types/reporterPrivate'; import { monotonicTime } from 'playwright-core/lib/utils'; -import type { FullProject } from '../../types/test'; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); @@ -498,23 +497,6 @@ function fitToWidth(line: string, width: number, prefix?: string): string { return taken.reverse().join(''); } -export function uniqueProjectIds(projects: FullProject[]): Map { - const usedNames = new Set(); - const result = new Map(); - for (const p of projects) { - const name = 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; -} - function belongsToNodeModules(file: string) { return file.includes(`${path.sep}node_modules${path.sep}`); } diff --git a/packages/playwright-test/src/reporters/json.ts b/packages/playwright-test/src/reporters/json.ts index bfe462b9e5..33fd3a58cd 100644 --- a/packages/playwright-test/src/reporters/json.ts +++ b/packages/playwright-test/src/reporters/json.ts @@ -17,10 +17,10 @@ import fs from 'fs'; import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, Reporter, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter'; -import { formatError, prepareErrorStack, uniqueProjectIds } from './base'; +import { formatError, prepareErrorStack } from './base'; import { MultiMap } from 'playwright-core/lib/utils'; import { assert } from 'playwright-core/lib/utils'; -import type { FullProject } from '../../types/test'; +import { FullProjectInternal } from '../common/config'; export function toPosixPath(aPath: string): string { return aPath.split(path.sep).join(path.posix.sep); @@ -54,7 +54,6 @@ class JSONReporter implements Reporter { } private _serializeReport(): JSONReport { - const projectIds = uniqueProjectIds(this.config.projects); return { config: { ...removePrivateFields(this.config), @@ -65,7 +64,7 @@ class JSONReporter implements Reporter { repeatEach: project.repeatEach, retries: project.retries, metadata: project.metadata, - id: projectIds.get(project)!, + id: FullProjectInternal.from(project).id, name: project.name, testDir: toPosixPath(project.testDir), testIgnore: serializePatterns(project.testIgnore), @@ -74,15 +73,15 @@ class JSONReporter implements Reporter { }; }) }, - suites: this._mergeSuites(this.suite.suites, projectIds), + suites: this._mergeSuites(this.suite.suites), errors: this._errors }; } - private _mergeSuites(suites: Suite[], projectIds: Map): JSONReportSuite[] { + private _mergeSuites(suites: Suite[]): JSONReportSuite[] { const fileSuites = new MultiMap(); for (const projectSuite of suites) { - const projectId = projectIds.get(projectSuite.project()!)!; + const projectId = FullProjectInternal.from(projectSuite.project()!).id; const projectName = projectSuite.project()!.name; for (const fileSuite of projectSuite.suites) { const file = fileSuite.location!.file; diff --git a/packages/playwright-test/src/reporters/teleEmitter.ts b/packages/playwright-test/src/reporters/teleEmitter.ts index 48b32bdeab..c6d53a94d8 100644 --- a/packages/playwright-test/src/reporters/teleEmitter.ts +++ b/packages/playwright-test/src/reporters/teleEmitter.ts @@ -23,7 +23,6 @@ import { createGuid } from 'playwright-core/lib/utils'; import { serializeRegexPatterns } from '../isomorphic/teleReceiver'; import path from 'path'; import type { FullProject } from '../../types/test'; -import { uniqueProjectIds } from './base'; export class TeleReporterEmitter implements Reporter { private _messageSink: (message: any) => void; @@ -36,7 +35,7 @@ export class TeleReporterEmitter implements Reporter { onBegin(config: FullConfig, suite: Suite) { this._rootDir = config.rootDir; const projects: any[] = []; - const projectIds = uniqueProjectIds(config.projects); + const projectIds = this._uniqueProjectIds(config.projects); for (const projectSuite of suite.suites) { const report = this._serializeProject(projectSuite, projectIds); projects.push(report); @@ -139,6 +138,23 @@ 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 { const project = suite.project()!; const report: JsonProject = { diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 133bf10766..9f8c3e6709 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -908,7 +908,6 @@ test('custom project suffix', async ({ runInlineTest, mergeReports }) => { `, 'playwright.config.ts': ` module.exports = { - retries: 1, reporter: 'blob', projects: [ { name: 'foo' }, @@ -918,9 +917,7 @@ test('custom project suffix', async ({ runInlineTest, mergeReports }) => { `, 'a.test.js': ` import { test, expect } from '@playwright/test'; - test('math 1', async ({}) => { - expect(1 + 1).toBe(2); - }); + test('math 1', async ({}) => {}); `, }; @@ -930,3 +927,40 @@ test('custom project suffix', async ({ runInlineTest, mergeReports }) => { expect(exitCode).toBe(0); expect(output).toContain(`projects: [ 'foo-suffix', 'bar-suffix' ]`); }); + +test('same project different suffixes', async ({ runInlineTest, mergeReports }) => { + const files = { + 'echo-reporter.js': ` + import fs from 'fs'; + + class EchoReporter { + onBegin(config, suite) { + const projects = suite.suites.map(s => s.project().name); + projects.sort(); + console.log('projects:', projects); + } + } + module.exports = EchoReporter; + `, + 'playwright.config.ts': ` + module.exports = { + reporter: 'blob', + projects: [ + { name: 'foo' }, + ] + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1', async ({}) => {}); + `, + }; + + await runInlineTest(files, undefined, { PWTEST_BLOB_SUFFIX: '-first' }); + await runInlineTest(files, undefined, { PWTEST_BLOB_SUFFIX: '-second' }); + + const reportDir = test.info().outputPath('blob-report'); + const { exitCode, output } = await mergeReports(reportDir, {}, { additionalArgs: ['--reporter', test.info().outputPath('echo-reporter.js')] }); + expect(exitCode).toBe(0); + expect(output).toContain(`projects: [ 'foo-first', 'foo-second' ]`); +});