Skip to content

Commit 9a5172e

Browse files
authored
cherry-pick(#26413): fix(merge): allow reports with same name as input (#26417)
1 parent eed73de commit 9a5172e

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

packages/playwright-test/src/reporters/merge.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,17 @@ async function extractAndParseReports(dir: string, shardFiles: string[], interna
110110
const shardEvents: { file: string, localPath: string, metadata: BlobReportMetadata, parsedEvents: JsonEvent[] }[] = [];
111111
await fs.promises.mkdir(path.join(dir, 'resources'), { recursive: true });
112112

113+
const reportNames = new UniqueFileNameGenerator();
113114
for (const file of shardFiles) {
114115
const absolutePath = path.join(dir, file);
115116
printStatus(`extracting: ${relativeFilePath(absolutePath)}`);
116117
const zipFile = new ZipFile(absolutePath);
117118
const entryNames = await zipFile.entries();
118119
for (const entryName of entryNames.sort()) {
119-
const fileName = path.join(dir, entryName);
120+
let fileName = path.join(dir, entryName);
120121
const content = await zipFile.read(entryName);
121122
if (entryName.endsWith('.jsonl')) {
123+
fileName = reportNames.makeUnique(fileName);
122124
const parsedEvents = parseCommonEvents(content);
123125
// Passing reviver to JSON.parse doesn't work, as the original strings
124126
// keep beeing used. To work around that we traverse the parsed events
@@ -285,6 +287,27 @@ function printStatusToStdout(message: string) {
285287
process.stdout.write(`${message}\n`);
286288
}
287289

290+
class UniqueFileNameGenerator {
291+
private _usedNames = new Set<string>();
292+
293+
makeUnique(name: string): string {
294+
if (!this._usedNames.has(name)) {
295+
this._usedNames.add(name);
296+
return name;
297+
}
298+
const extension = path.extname(name);
299+
name = name.substring(0, name.length - extension.length);
300+
let index = 0;
301+
while (true) {
302+
const candidate = `${name}-${++index}${extension}`;
303+
if (!this._usedNames.has(candidate)) {
304+
this._usedNames.add(candidate);
305+
return candidate;
306+
}
307+
}
308+
}
309+
}
310+
288311
class IdsPatcher {
289312
constructor(private _stringPool: StringInternPool, private _reportName: string | undefined, private _salt: string) {
290313
}

tests/playwright-test/reporter-blob.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,3 +1324,51 @@ test('merge-reports should throw if report version is from the future', async ({
13241324
expect(output).toContain(`Error: Blob report report-2.zip was created with a newer version of Playwright.`);
13251325

13261326
});
1327+
1328+
test('should merge blob reports with same name', async ({ runInlineTest, mergeReports, showReport, page }) => {
1329+
const files = {
1330+
'playwright.config.ts': `
1331+
module.exports = {
1332+
retries: 1,
1333+
reporter: 'blob'
1334+
};
1335+
`,
1336+
'a.test.js': `
1337+
import { test, expect } from '@playwright/test';
1338+
test('math 1', async ({}) => {
1339+
expect(1 + 1).toBe(2);
1340+
});
1341+
test('failing 1', async ({}) => {
1342+
expect(1).toBe(2);
1343+
});
1344+
test('flaky 1', async ({}) => {
1345+
expect(test.info().retry).toBe(1);
1346+
});
1347+
test.skip('skipped 1', async ({}) => {});
1348+
`,
1349+
'b.test.js': `
1350+
import { test, expect } from '@playwright/test';
1351+
test('math 2', async ({}) => {
1352+
expect(1 + 1).toBe(2);
1353+
});
1354+
test('failing 2', async ({}) => {
1355+
expect(1).toBe(2);
1356+
});
1357+
test.skip('skipped 2', async ({}) => {});
1358+
`
1359+
};
1360+
await runInlineTest(files);
1361+
const reportZip = test.info().outputPath('blob-report', 'report.zip');
1362+
const allReportsDir = test.info().outputPath('all-blob-reports');
1363+
await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-1.zip'));
1364+
await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-2.zip'));
1365+
1366+
const { exitCode } = await mergeReports(allReportsDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
1367+
expect(exitCode).toBe(0);
1368+
await showReport();
1369+
await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('14');
1370+
await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('4');
1371+
await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('4');
1372+
await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('2');
1373+
await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('4');
1374+
});

0 commit comments

Comments
 (0)