diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index d6cfb21320..df77370dd2 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -32,6 +32,9 @@ export class TeleReporterEmitter implements ReporterV2 { private _messageSink: (message: teleReceiver.JsonEvent) => void; private _rootDir!: string; private _emitterOptions: TeleReporterEmitterOptions; + // In case there is blob reporter and UI mode, make sure one does override + // the id assigned by the other. + private readonly _idSymbol = Symbol('id'); constructor(messageSink: (message: teleReceiver.JsonEvent) => void, options: TeleReporterEmitterOptions = {}) { this._messageSink = messageSink; @@ -55,7 +58,7 @@ export class TeleReporterEmitter implements ReporterV2 { } onTestBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void { - (result as any)[idSymbol] = createGuid(); + (result as any)[this._idSymbol] = createGuid(); this._messageSink({ method: 'onTestBegin', params: { @@ -82,12 +85,12 @@ export class TeleReporterEmitter implements ReporterV2 { } onStepBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void { - (step as any)[idSymbol] = createGuid(); + (step as any)[this._idSymbol] = createGuid(); this._messageSink({ method: 'onStepBegin', params: { testId: test.id, - resultId: (result as any)[idSymbol], + resultId: (result as any)[this._idSymbol], step: this._serializeStepStart(step) } }); @@ -98,7 +101,7 @@ export class TeleReporterEmitter implements ReporterV2 { method: 'onStepEnd', params: { testId: test.id, - resultId: (result as any)[idSymbol], + resultId: (result as any)[this._idSymbol], step: this._serializeStepEnd(step) } }); @@ -126,7 +129,7 @@ export class TeleReporterEmitter implements ReporterV2 { const data = isBase64 ? chunk.toString('base64') : chunk; this._messageSink({ method: 'onStdIO', - params: { testId: test?.id, resultId: result ? (result as any)[idSymbol] : undefined, type, data, isBase64 } + params: { testId: test?.id, resultId: result ? (result as any)[this._idSymbol] : undefined, type, data, isBase64 } }); } @@ -214,7 +217,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeResultStart(result: reporterTypes.TestResult): teleReceiver.JsonTestResultStart { return { - id: (result as any)[idSymbol], + id: (result as any)[this._idSymbol], retry: result.retry, workerIndex: result.workerIndex, parallelIndex: result.parallelIndex, @@ -224,7 +227,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeResultEnd(result: reporterTypes.TestResult): teleReceiver.JsonTestResultEnd { return { - id: (result as any)[idSymbol], + id: (result as any)[this._idSymbol], duration: result.duration, status: result.status, errors: result.errors, @@ -244,8 +247,8 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeStepStart(step: reporterTypes.TestStep): teleReceiver.JsonTestStepStart { return { - id: (step as any)[idSymbol], - parentStepId: (step.parent as any)?.[idSymbol], + id: (step as any)[this._idSymbol], + parentStepId: (step.parent as any)?.[this._idSymbol], title: step.title, category: step.category, startTime: +step.startTime, @@ -255,7 +258,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeStepEnd(step: reporterTypes.TestStep): teleReceiver.JsonTestStepEnd { return { - id: (step as any)[idSymbol], + id: (step as any)[this._idSymbol], duration: step.duration, error: step.error, }; @@ -280,5 +283,3 @@ export class TeleReporterEmitter implements ReporterV2 { return path.relative(this._rootDir, absolutePath); } } - -const idSymbol = Symbol('id'); diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 61abc43628..ca3020a43f 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -292,6 +292,38 @@ test('should merge blob into blob', async ({ runInlineTest, mergeReports, showRe } }); +test('should produce consistent step ids', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31023' }, +}, async ({ runInlineTest, mergeReports, showReport, page }) => { + const files = { + 'playwright.config.ts': ` + module.exports = { + retries: 1, + reporter: [ + ['blob', { outputFile: 'blob-report/report-1.zip' }], + ['blob', { outputFile: 'blob-report/report-2.zip' }] + ] + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1', async ({}) => { + expect(1 + 1).toBe(2); + }); + ` + }; + await runInlineTest(files); + const reportDir = test.info().outputPath('blob-report'); + const reportFiles = await fs.promises.readdir(reportDir); + reportFiles.sort(); + expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip']); + const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html,json'] }); + expect(exitCode).toBe(0); + await showReport(); + await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('2'); + await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('2'); +}); + test('be able to merge incomplete shards', async ({ runInlineTest, mergeReports, showReport, page }) => { const reportDir = test.info().outputPath('blob-report'); const files = {