feat(html): parse and render links in HTML report title (#36326)

This commit is contained in:
Adam Gastineau 2025-06-16 07:48:44 -07:00 committed by GitHub
parent 6caf3442fc
commit baded7263b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 3 deletions

View File

@ -20,7 +20,7 @@ import './colors.css';
import './common.css';
import './headerView.css';
import * as icons from './icons';
import { Link, navigate, SearchParamsContext } from './links';
import { Link, LinkifyText, navigate, SearchParamsContext } from './links';
import { statusIcon } from './statusIcon';
import { filterWithToken } from './filter';
@ -35,7 +35,7 @@ export const HeaderView: React.FC<{
<div style={{ flex: 'auto' }}></div>
{rightSuperHeader}
</div>
{title && <div className='header-title'>{title}</div>}
{title && <div className='header-title'><LinkifyText text={title} /></div>}
</div>;
};

View File

@ -114,6 +114,41 @@ export const SearchParamsProvider: React.FunctionComponent<React.PropsWithChildr
return <SearchParamsContext.Provider value={searchParams}>{children}</SearchParamsContext.Provider>;
};
const LINKIFY_REGEX = /https?:\/\/[^\s]+/g;
export const LinkifyText: React.FunctionComponent<{ text: string }> = ({ text }) => {
const parts = React.useMemo(() => {
const matches = [...text.matchAll(LINKIFY_REGEX)];
if (matches.length === 0)
return [text];
const result: Array<React.ReactElement | string> = [];
let lastIndex = 0;
for (const match of matches) {
const url = match[0];
const startIndex = match.index!;
// Add text before the URL
if (startIndex > lastIndex)
result.push(text.slice(lastIndex, startIndex));
result.push(
<a key={startIndex} href={url} target='_blank' rel='noopener noreferrer'>
{url}
</a>
);
lastIndex = startIndex + url.length;
}
// Add any text after the last URL
if (lastIndex < text.length)
result.push(text.slice(lastIndex));
return result;
}, [text]);
return <>{parts}</>;
};
function downloadFileNameForAttachment(attachment: TestAttachment): string {
if (attachment.name.includes('.') || !attachment.path)
return attachment.name;

View File

@ -435,7 +435,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(page.locator('div').filter({ hasText: /^Tracestrace$/ }).getByRole('link').first()).toHaveAttribute('href', /trace=(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
});
test('should display title if provided', async ({ runInlineTest, page, showReport }, testInfo) => {
test('should display report title if provided', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
@ -456,6 +456,31 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(page.locator('.header-title')).toHaveText('Custom report title');
});
test('should process URLs as links in report title', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
reporter: [['html', { title: 'Custom report title https://playwright.dev separator http://microsoft.com end' }], ['line']]
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('fails', async ({ page }) => {
expect(1).toBe(2);
});
`
}, {}, { PLAYWRIGHT_HTML_OPEN: 'never' });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
await showReport();
const anchorLocator = page.locator('.header-title a');
await expect(page.locator('.header-title')).toHaveText('Custom report title https://playwright.dev separator http://microsoft.com end');
await expect(anchorLocator).toHaveCount(2);
await expect(anchorLocator.nth(0)).toHaveAttribute('href', 'https://playwright.dev');
await expect(anchorLocator.nth(1)).toHaveAttribute('href', 'http://microsoft.com');
});
test('should include stdio', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `