From cf8c14f884b6f24966350a5f49b1580c3e183d21 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 2 Sep 2024 08:35:53 +0200 Subject: [PATCH] feat(html reporter): open html attachments in new tab (#32389) Closes https://github.com/microsoft/playwright/issues/32281. HTML attachments get a linkified name that opens the attachment in a new tab. --- packages/html-reporter/src/links.tsx | 9 +++++-- packages/html-reporter/src/testResultView.tsx | 12 ++++++--- tests/playwright-test/reporter-html.spec.ts | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index 419a8725ac..8c1dcc85dc 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -75,11 +75,16 @@ export const AttachmentLink: React.FunctionComponent<{ attachment: TestAttachment, href?: string, linkName?: string, -}> = ({ attachment, href, linkName }) => { + openInNewTab?: boolean, +}> = ({ attachment, href, linkName, openInNewTab }) => { return {attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()} {attachment.path && {linkName || attachment.name}} - {!attachment.path && {linkifyText(attachment.name)}} + {!attachment.path && ( + openInNewTab + ? e.stopPropagation()}>{attachment.name} + : {linkifyText(attachment.name)} + )} } loadChildren={attachment.body ? () => { return [
{linkifyText(attachment.body!)}
]; } : undefined} depth={0} style={{ lineHeight: '32px' }}>
; diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index 1ec8c65a1e..8ee36d0cda 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -67,15 +67,16 @@ export const TestResultView: React.FC<{ anchor: 'video' | 'diff' | '', }> = ({ result, anchor }) => { - const { screenshots, videos, traces, otherAttachments, diffs } = React.useMemo(() => { + const { screenshots, videos, traces, otherAttachments, diffs, htmls } = React.useMemo(() => { const attachments = result?.attachments || []; const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); const videos = attachments.filter(a => a.name === 'video'); const traces = attachments.filter(a => a.name === 'trace'); + const htmls = attachments.filter(a => a.contentType.startsWith('text/html')); const otherAttachments = new Set(attachments); - [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); + [...screenshots, ...videos, ...traces, ...htmls].forEach(a => otherAttachments.delete(a)); const diffs = groupImageDiffs(screenshots); - return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs }; + return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, htmls }; }, [result]); const videoRef = React.useRef(null); @@ -135,7 +136,10 @@ export const TestResultView: React.FC<{ )} } - {!!otherAttachments.size && + {!!(otherAttachments.size + htmls.length) && + {[...htmls].map((a, i) => ( + ) + )} {[...otherAttachments].map((a, i) => )} } ; diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 768cd28fc2..1f5da44a84 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -802,6 +802,32 @@ for (const useIntermediateMergeReport of [false] as const) { await expect(page.locator('.attachment-body')).toHaveText(['foo', '{"foo":1}', 'utf16 encoded']); }); + test('should have link for opening HTML attachments in new tab', async ({ runInlineTest, page, showReport }) => { + const result = await runInlineTest({ + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('passing', async ({ page }, testInfo) => { + testInfo.attach('axe-report.html', { + contentType: 'text/html', + body: '

Axe Report

', + }); + }); + `, + }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); + expect(result.exitCode).toBe(0); + + await showReport(); + await page.getByText('passing', { exact: true }).click(); + + const [newTab] = await Promise.all([ + page.waitForEvent('popup'), + page.getByText('axe-report.html', { exact: true }).click(), + ]); + + await expect(newTab).toHaveURL(/^blob:/); + await expect(newTab.getByText('Axe Report')).toBeVisible(); + }); + test('should use file-browser friendly extensions for buffer attachments based on contentType', async ({ runInlineTest, showReport, page }, testInfo) => { const result = await runInlineTest({ 'a.test.js': `