feat(reporter): links in attachment names, attachments name only (#31714)

* Allow calling `test.info().attach('My text');` without options (no
path nor body).
* Highlight links in attachment names:

<img width="992" alt="image"
src="https://github.com/user-attachments/assets/770e7876-3e43-4434-8cf1-194ad6ae5819">

Fixes https://github.com/microsoft/playwright/issues/31284
This commit is contained in:
Yury Semikhatsky 2024-07-17 09:30:49 -07:00 committed by GitHub
parent f4399f7f06
commit 3f15fe8518
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 32 additions and 5 deletions

View File

@ -78,7 +78,7 @@ export const AttachmentLink: React.FunctionComponent<{
return <TreeItem title={<span>
{attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()}
{attachment.path && <a href={href || attachment.path} download={downloadFileNameForAttachment(attachment)}>{linkName || attachment.name}</a>}
{attachment.body && <span>{linkifyText(attachment.name)}</span>}
{!attachment.path && <span>{linkifyText(attachment.name)}</span>}
</span>} loadChildren={attachment.body ? () => {
return [<div className='attachment-body'><CopyToClipboard value={attachment.body!}/>{linkifyText(attachment.body!)}</div>];
} : undefined} depth={0} style={{ lineHeight: '32px' }}></TreeItem>;

View File

@ -132,6 +132,9 @@ const resultWithAttachment: TestResult = {
name: 'first attachment',
body: 'The body with https://playwright.dev/docs/intro link and https://github.com/microsoft/playwright/issues/31284.',
contentType: 'text/plain'
}, {
name: 'attachment with inline link https://github.com/microsoft/playwright/issues/31284',
contentType: 'text/plain'
}],
status: 'passed',
};
@ -157,4 +160,11 @@ test('should correctly render links in attachments', async ({ mount }) => {
await expect(body).toBeVisible();
await expect(body.locator('a').filter({ hasText: 'playwright.dev' })).toHaveAttribute('href', 'https://playwright.dev/docs/intro');
await expect(body.locator('a').filter({ hasText: 'github.com' })).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
});
});
test('should correctly render links in attachment name', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} run={0} anchor=''></TestCaseView>);
const link = component.getByText('attachment with inline link').locator('a');
await expect(link).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
await expect(link).toHaveText('https://github.com/microsoft/playwright/issues/31284');
});

View File

@ -319,7 +319,7 @@ export function formatFailure(config: FullConfig, test: TestCase, options: {inde
if (includeAttachments) {
for (let i = 0; i < result.attachments.length; ++i) {
const attachment = result.attachments[i];
const hasPrintableContent = attachment.contentType.startsWith('text/') && attachment.body;
const hasPrintableContent = attachment.contentType.startsWith('text/');
if (!attachment.path && !hasPrintableContent)
continue;
resultLines.push('');

View File

@ -256,6 +256,8 @@ export function resolveReporterOutputPath(defaultValue: string, configDir: strin
}
export async function normalizeAndSaveAttachment(outputPath: string, name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}): Promise<{ name: string; path?: string | undefined; body?: Buffer | undefined; contentType: string; }> {
if (options.path === undefined && options.body === undefined)
return { name, contentType: 'text/plain' };
if ((options.path !== undefined ? 1 : 0) + (options.body !== undefined ? 1 : 0) !== 1)
throw new Error(`Exactly one of "path" and "body" must be specified`);
if (options.path !== undefined) {

View File

@ -99,8 +99,8 @@ test(`testInfo.attach errors`, async ({ runInlineTest }) => {
const text = result.output.replace(/\\/g, '/');
expect(text).toMatch(/Error: ENOENT: no such file or directory, copyfile '.*foo.txt.*'/);
expect(text).toContain(`Exactly one of "path" and "body" must be specified`);
expect(result.passed).toBe(0);
expect(result.failed).toBe(3);
expect(result.passed).toBe(1);
expect(result.failed).toBe(2);
expect(result.exitCode).toBe(1);
});
@ -175,6 +175,21 @@ test(`testInfo.attach allow empty string body`, async ({ runInlineTest }) => {
expect(result.output).toMatch(/^.*attachment #1: name \(text\/plain\).*\n.*\n.*──────/gm);
});
test(`testInfo.attach allow without options`, async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('success', async ({}, testInfo) => {
await testInfo.attach('Full name');
expect(0).toBe(1);
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.output).toMatch(/^.*attachment #1: Full name \(text\/plain\).*\n.*──────/gm);
});
test(`testInfo.attach allow empty buffer body`, async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `