diff --git a/packages/playwright-core/src/web/htmlReport/htmlReport.tsx b/packages/playwright-core/src/web/htmlReport/htmlReport.tsx index f522edac5e..deeacb58b5 100644 --- a/packages/playwright-core/src/web/htmlReport/htmlReport.tsx +++ b/packages/playwright-core/src/web/htmlReport/htmlReport.tsx @@ -317,9 +317,14 @@ const AttachmentLink: React.FunctionComponent<{ href?: string, }> = ({ attachment, href }) => { return - + {attachment.contentType === kMissingContentType ? + : + + } {attachment.path && {attachment.name}} {attachment.body && {attachment.name}} } loadChildren={attachment.body ? () => { @@ -610,3 +615,5 @@ type SearchValues = { project: string; status: 'passed' | 'failed' | 'flaky' | 'skipped'; }; + +const kMissingContentType = 'x-playwright/missing'; diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts index 86fa30d49e..6658fa3d37 100644 --- a/packages/playwright-test/src/reporters/html.ts +++ b/packages/playwright-test/src/reporters/html.ts @@ -104,6 +104,8 @@ type TestEntry = { testCaseSummary: TestCaseSummary }; +const kMissingContentType = 'x-playwright/missing'; + class HtmlReporter implements Reporter { private config!: FullConfig; private suite!: Suite; @@ -385,6 +387,11 @@ class HtmlBuilder { fs.mkdirSync(path.join(this._reportFolder, 'data'), { recursive: true }); fs.writeFileSync(path.join(this._reportFolder, 'data', sha1), buffer); } catch (e) { + return { + name: `Missing attachment "${a.name}"`, + contentType: kMissingContentType, + body: `Attachment file ${fileName} is missing`, + }; } return { name: a.name, diff --git a/packages/playwright-test/src/reporters/junit.ts b/packages/playwright-test/src/reporters/junit.ts index 79c068f53d..a4fa9bc5e2 100644 --- a/packages/playwright-test/src/reporters/junit.ts +++ b/packages/playwright-test/src/reporters/junit.ts @@ -158,8 +158,11 @@ class JUnitReporter implements Reporter { if (!attachment.path) continue; try { + const attachmentPath = path.relative(this.config.rootDir, attachment.path); if (fs.existsSync(attachment.path)) - systemOut.push(`\n[[ATTACHMENT|${path.relative(this.config.rootDir, attachment.path)}]]\n`); + systemOut.push(`\n[[ATTACHMENT|${attachmentPath}]]\n`); + else + systemErr.push(`\nWarning: attachment ${attachmentPath} is missing`); } catch (e) { } } diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index ddc71cc0cf..c203d4ce50 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -67,7 +67,7 @@ test('should generate report', async ({ runInlineTest, showReport, page }) => { await expect(page.locator('.test-summary.outcome-skipped >> text=skipped')).toBeVisible(); }); -test('should not throw when attachment is missing', async ({ runInlineTest }) => { +test('should not throw when attachment is missing', async ({ runInlineTest, page, showReport }, testInfo) => { const result = await runInlineTest({ 'playwright.config.ts': ` module.exports = { preserveOutput: 'failures-only' }; @@ -83,6 +83,12 @@ test('should not throw when attachment is missing', async ({ runInlineTest }) => }, { reporter: 'dot,html' }); expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); + + await showReport(); + await page.click('text=passes'); + await page.locator('text=Missing attachment "screenshot"').click(); + const screenshotFile = testInfo.outputPath('test-results' , 'a-passes', 'screenshot.png'); + await expect(page.locator('.attachment-body')).toHaveText(`Attachment file ${screenshotFile} is missing`); }); test('should include image diff', async ({ runInlineTest, page, showReport }) => {