diff --git a/packages/playwright-test/src/reporters/blob.ts b/packages/playwright-test/src/reporters/blob.ts index 8269a93695..d407399eee 100644 --- a/packages/playwright-test/src/reporters/blob.ts +++ b/packages/playwright-test/src/reporters/blob.ts @@ -32,6 +32,7 @@ type BlobReporterOptions = { export class BlobReporter extends TeleReporterEmitter { private _messages: any[] = []; private _options: BlobReporterOptions; + private _salt: string; private _copyFilePromises = new Set>(); private _outputDir!: string; @@ -40,6 +41,7 @@ export class BlobReporter extends TeleReporterEmitter { constructor(options: BlobReporterOptions) { super(message => this._messages.push(message)); this._options = options; + this._salt = createGuid(); } override onBegin(config: FullConfig<{}, {}>, suite: Suite): void { @@ -64,7 +66,8 @@ export class BlobReporter extends TeleReporterEmitter { return attachments.map(attachment => { if (!attachment.path || !fs.statSync(attachment.path).isFile()) return attachment; - const sha1 = calculateSha1(attachment.path); + // Add run guid to avoid clashes between shards. + const sha1 = calculateSha1(attachment.path + this._salt); const extension = mime.getExtension(attachment.contentType) || 'dat'; const newPath = `resources/${sha1}.${extension}`; this._startCopyingFile(attachment.path, path.join(this._outputDir, newPath)); diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 1a58c1eca2..7359f733e2 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -507,6 +507,77 @@ test('generate html with attachment urls', async ({ runInlineTest, mergeReports, await expect(page.getByTestId('action-list').locator('div').filter({ hasText: /^expect\.toBe$/ })).toBeVisible(); }); +test('resource names should not clash between runs', async ({ runInlineTest, showReport, mergeReports, page }) => { + test.slow(); + const reportDir = test.info().outputPath('blob-report'); + const files = { + 'playwright.config.ts': ` + module.exports = { + reporter: [['blob', { outputDir: '${reportDir.replace(/\\/g, '/')}' }]] + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs'; + import path from 'path'; + + test('first', async ({}) => { + const attachmentPath = path.join(test.info().config.rootDir, 'foo.txt'); + fs.writeFileSync(attachmentPath, 'hello!'); + test.info().attachments.push({ name: 'file-attachment', path: attachmentPath, contentType: 'text/plain' }); + }); + `, + 'b.test.js': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs'; + import path from 'path'; + + test('failing 2', async ({}) => { + const attachmentPath = path.join(test.info().config.rootDir, 'foo.txt'); + fs.writeFileSync(attachmentPath, 'bye!'); + test.info().attachments.push({ name: 'file-attachment', path: attachmentPath, contentType: 'text/plain' }); + }); + ` + }; + await runInlineTest(files, { shard: `1/2` }); + await runInlineTest(files, { shard: `2/2` }); + + const reportFiles = await fs.promises.readdir(reportDir); + reportFiles.sort(); + expect(reportFiles).toEqual([expect.stringMatching(/report-1-of-2.*.jsonl/), expect.stringMatching(/report-2-of-2.*.jsonl/), 'resources']); + + const { exitCode } = await mergeReports(reportDir, {}, { additionalArgs: ['--reporter', 'html'] }); + expect(exitCode).toBe(0); + + await showReport(); + + // Check first attachment content. + { + await page.getByText('first').click(); + await expect(page.getByText('file-attachment')).toBeVisible(); + + const popupPromise = page.waitForEvent('popup'); + await page.getByText('file-attachment').click(); + const popup = await popupPromise; + await expect(popup.locator('body')).toHaveText('hello!'); + await popup.close(); + await page.goBack(); + } + + // Check second attachment content. + { + await page.getByText('failing 2').click(); + await expect(page.getByText('file-attachment')).toBeVisible(); + + const popupPromise = page.waitForEvent('popup'); + await page.getByText('file-attachment').click(); + const popup = await popupPromise; + await expect(popup.locator('body')).toHaveText('bye!'); + await popup.close(); + await page.goBack(); + } +}); + test('multiple output reports', async ({ runInlineTest, mergeReports, showReport, page }) => { test.slow(); const reportDir = test.info().outputPath('blob-report');