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.
This commit is contained in:
parent
a6b320e362
commit
cf8c14f884
|
|
@ -75,11 +75,16 @@ export const AttachmentLink: React.FunctionComponent<{
|
||||||
attachment: TestAttachment,
|
attachment: TestAttachment,
|
||||||
href?: string,
|
href?: string,
|
||||||
linkName?: string,
|
linkName?: string,
|
||||||
}> = ({ attachment, href, linkName }) => {
|
openInNewTab?: boolean,
|
||||||
|
}> = ({ attachment, href, linkName, openInNewTab }) => {
|
||||||
return <TreeItem title={<span>
|
return <TreeItem title={<span>
|
||||||
{attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()}
|
{attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()}
|
||||||
{attachment.path && <a href={href || attachment.path} download={downloadFileNameForAttachment(attachment)}>{linkName || attachment.name}</a>}
|
{attachment.path && <a href={href || attachment.path} download={downloadFileNameForAttachment(attachment)}>{linkName || attachment.name}</a>}
|
||||||
{!attachment.path && <span>{linkifyText(attachment.name)}</span>}
|
{!attachment.path && (
|
||||||
|
openInNewTab
|
||||||
|
? <a href={URL.createObjectURL(new Blob([attachment.body!], { type: attachment.contentType }))} target='_blank' rel='noreferrer' onClick={e => e.stopPropagation()}>{attachment.name}</a>
|
||||||
|
: <span>{linkifyText(attachment.name)}</span>
|
||||||
|
)}
|
||||||
</span>} loadChildren={attachment.body ? () => {
|
</span>} loadChildren={attachment.body ? () => {
|
||||||
return [<div key={1} className='attachment-body'><CopyToClipboard value={attachment.body!}/>{linkifyText(attachment.body!)}</div>];
|
return [<div key={1} className='attachment-body'><CopyToClipboard value={attachment.body!}/>{linkifyText(attachment.body!)}</div>];
|
||||||
} : undefined} depth={0} style={{ lineHeight: '32px' }}></TreeItem>;
|
} : undefined} depth={0} style={{ lineHeight: '32px' }}></TreeItem>;
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,16 @@ export const TestResultView: React.FC<{
|
||||||
anchor: 'video' | 'diff' | '',
|
anchor: 'video' | 'diff' | '',
|
||||||
}> = ({ result, anchor }) => {
|
}> = ({ result, anchor }) => {
|
||||||
|
|
||||||
const { screenshots, videos, traces, otherAttachments, diffs } = React.useMemo(() => {
|
const { screenshots, videos, traces, otherAttachments, diffs, htmls } = React.useMemo(() => {
|
||||||
const attachments = result?.attachments || [];
|
const attachments = result?.attachments || [];
|
||||||
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
|
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
|
||||||
const videos = attachments.filter(a => a.name === 'video');
|
const videos = attachments.filter(a => a.name === 'video');
|
||||||
const traces = attachments.filter(a => a.name === 'trace');
|
const traces = attachments.filter(a => a.name === 'trace');
|
||||||
|
const htmls = attachments.filter(a => a.contentType.startsWith('text/html'));
|
||||||
const otherAttachments = new Set<TestAttachment>(attachments);
|
const otherAttachments = new Set<TestAttachment>(attachments);
|
||||||
[...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a));
|
[...screenshots, ...videos, ...traces, ...htmls].forEach(a => otherAttachments.delete(a));
|
||||||
const diffs = groupImageDiffs(screenshots);
|
const diffs = groupImageDiffs(screenshots);
|
||||||
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs };
|
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, htmls };
|
||||||
}, [result]);
|
}, [result]);
|
||||||
|
|
||||||
const videoRef = React.useRef<HTMLDivElement>(null);
|
const videoRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -135,7 +136,10 @@ export const TestResultView: React.FC<{
|
||||||
</div>)}
|
</div>)}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{!!otherAttachments.size && <AutoChip header='Attachments'>
|
{!!(otherAttachments.size + htmls.length) && <AutoChip header='Attachments'>
|
||||||
|
{[...htmls].map((a, i) => (
|
||||||
|
<AttachmentLink key={`html-link-${i}`} attachment={a} openInNewTab />)
|
||||||
|
)}
|
||||||
{[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
|
{[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
||||||
|
|
@ -802,6 +802,32 @@ for (const useIntermediateMergeReport of [false] as const) {
|
||||||
await expect(page.locator('.attachment-body')).toHaveText(['foo', '{"foo":1}', 'utf16 encoded']);
|
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: '<h1>Axe Report</h1>',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { 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) => {
|
test('should use file-browser friendly extensions for buffer attachments based on contentType', async ({ runInlineTest, showReport, page }, testInfo) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue