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 }) => {