From 9a5172e6c1b21e11ab1e8b8529871a673fa1ce07 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 10 Aug 2023 14:08:01 -0700 Subject: [PATCH] cherry-pick(#26413): fix(merge): allow reports with same name as input (#26417) --- .../playwright-test/src/reporters/merge.ts | 25 +++++++++- tests/playwright-test/reporter-blob.spec.ts | 48 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/playwright-test/src/reporters/merge.ts b/packages/playwright-test/src/reporters/merge.ts index 124f9de067..448c050033 100644 --- a/packages/playwright-test/src/reporters/merge.ts +++ b/packages/playwright-test/src/reporters/merge.ts @@ -110,15 +110,17 @@ async function extractAndParseReports(dir: string, shardFiles: string[], interna const shardEvents: { file: string, localPath: string, metadata: BlobReportMetadata, parsedEvents: JsonEvent[] }[] = []; await fs.promises.mkdir(path.join(dir, 'resources'), { recursive: true }); + const reportNames = new UniqueFileNameGenerator(); for (const file of shardFiles) { const absolutePath = path.join(dir, file); printStatus(`extracting: ${relativeFilePath(absolutePath)}`); const zipFile = new ZipFile(absolutePath); const entryNames = await zipFile.entries(); for (const entryName of entryNames.sort()) { - const fileName = path.join(dir, entryName); + let fileName = path.join(dir, entryName); const content = await zipFile.read(entryName); if (entryName.endsWith('.jsonl')) { + fileName = reportNames.makeUnique(fileName); const parsedEvents = parseCommonEvents(content); // Passing reviver to JSON.parse doesn't work, as the original strings // keep beeing used. To work around that we traverse the parsed events @@ -285,6 +287,27 @@ function printStatusToStdout(message: string) { process.stdout.write(`${message}\n`); } +class UniqueFileNameGenerator { + private _usedNames = new Set(); + + makeUnique(name: string): string { + if (!this._usedNames.has(name)) { + this._usedNames.add(name); + return name; + } + const extension = path.extname(name); + name = name.substring(0, name.length - extension.length); + let index = 0; + while (true) { + const candidate = `${name}-${++index}${extension}`; + if (!this._usedNames.has(candidate)) { + this._usedNames.add(candidate); + return candidate; + } + } + } +} + class IdsPatcher { constructor(private _stringPool: StringInternPool, private _reportName: string | undefined, private _salt: string) { } diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 1452037e1a..661eba7324 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -1324,3 +1324,51 @@ test('merge-reports should throw if report version is from the future', async ({ expect(output).toContain(`Error: Blob report report-2.zip was created with a newer version of Playwright.`); }); + +test('should merge blob reports with same name', async ({ runInlineTest, mergeReports, showReport, page }) => { + const files = { + 'playwright.config.ts': ` + module.exports = { + retries: 1, + reporter: 'blob' + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1', async ({}) => { + expect(1 + 1).toBe(2); + }); + test('failing 1', async ({}) => { + expect(1).toBe(2); + }); + test('flaky 1', async ({}) => { + expect(test.info().retry).toBe(1); + }); + test.skip('skipped 1', async ({}) => {}); + `, + 'b.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 2', async ({}) => { + expect(1 + 1).toBe(2); + }); + test('failing 2', async ({}) => { + expect(1).toBe(2); + }); + test.skip('skipped 2', async ({}) => {}); + ` + }; + await runInlineTest(files); + const reportZip = test.info().outputPath('blob-report', 'report.zip'); + const allReportsDir = test.info().outputPath('all-blob-reports'); + await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-1.zip')); + await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-2.zip')); + + const { exitCode } = await mergeReports(allReportsDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] }); + expect(exitCode).toBe(0); + await showReport(); + await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('14'); + await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('4'); + await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('4'); + await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('2'); + await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('4'); +}); \ No newline at end of file