diff --git a/packages/playwright-core/src/utils/traceUtils.ts b/packages/playwright-core/src/utils/traceUtils.ts index b39d7c9998..e6ca9de7b7 100644 --- a/packages/playwright-core/src/utils/traceUtils.ts +++ b/packages/playwright-core/src/utils/traceUtils.ts @@ -116,16 +116,24 @@ export async function saveTraceFile(fileName: string, traceEvents: TraceEvent[], const sha1s = new Set(); for (const event of traceEvents.filter(e => e.type === 'after') as AfterActionTraceEvent[]) { - for (const attachment of (event.attachments || []).filter(a => !!a.path)) { - await fs.promises.readFile(attachment.path!).then(content => { - const sha1 = calculateSha1(content); - attachment.sha1 = sha1; - delete attachment.path; - if (sha1s.has(sha1)) - return; - sha1s.add(sha1); - zipFile.addBuffer(content, 'resources/' + sha1); - }).catch(); + for (const attachment of (event.attachments || [])) { + let contentPromise: Promise | undefined; + if (attachment.path) + contentPromise = fs.promises.readFile(attachment.path); + else if (attachment.base64) + contentPromise = Promise.resolve(Buffer.from(attachment.base64, 'base64')); + if (!contentPromise) + continue; + + const content = await contentPromise; + const sha1 = calculateSha1(content); + attachment.sha1 = sha1; + delete attachment.path; + delete attachment.base64; + if (sha1s.has(sha1)) + continue; + sha1s.add(sha1); + zipFile.addBuffer(content, 'resources/' + sha1); } } diff --git a/packages/playwright-test/src/worker/testInfo.ts b/packages/playwright-test/src/worker/testInfo.ts index 110657222a..dbb0d53747 100644 --- a/packages/playwright-test/src/worker/testInfo.ts +++ b/packages/playwright-test/src/worker/testInfo.ts @@ -332,7 +332,7 @@ export class TestInfoImpl implements TestInfo { async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) { const step = this._addStep({ - title: 'attach', + title: `attach "${name}"`, category: 'attach', wallTime: Date.now(), }); @@ -404,7 +404,7 @@ function serializeAttachments(attachments: TestInfo['attachments'], initialAttac name: a.name, contentType: a.contentType, path: a.path, - body: a.body?.toString('base64'), + base64: a.body?.toString('base64'), }; }); } diff --git a/packages/trace/src/trace.ts b/packages/trace/src/trace.ts index c70734d93c..0348281ff6 100644 --- a/packages/trace/src/trace.ts +++ b/packages/trace/src/trace.ts @@ -85,7 +85,7 @@ export type AfterActionTraceEvent = { contentType: string; path?: string; sha1?: string; - body?: string; // base64 + base64?: string; }[]; result?: any; }; diff --git a/tests/playwright-test/ui-mode-test-attachments.spec.ts b/tests/playwright-test/ui-mode-test-attachments.spec.ts index d4dcfa45da..b6cceb0d5f 100644 --- a/tests/playwright-test/ui-mode-test-attachments.spec.ts +++ b/tests/playwright-test/ui-mode-test-attachments.spec.ts @@ -16,9 +16,7 @@ import { test, expect } from './ui-mode-fixtures'; -test.describe.configure({ mode: 'parallel' }); - -test('should contain attachments', async ({ runUITest }) => { +test('should contain file attachment', async ({ runUITest }) => { const { page } = await runUITest({ 'a.test.ts': ` import { test } from '@playwright/test'; @@ -31,7 +29,7 @@ test('should contain attachments', async ({ runUITest }) => { await page.getByTitle('Run all').click(); await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); await page.getByText('Attachments').click(); - await page.getByText('attach', { exact: true }).click(); + await page.getByText('attach "note"', { exact: true }).click(); const popupPromise = page.waitForEvent('popup'); await page.getByRole('link', { name: 'note' }).click(); const popup = await popupPromise; @@ -39,3 +37,25 @@ test('should contain attachments', async ({ runUITest }) => { const content = await popup.content(); expect(content).toContain('attach test'); }); + +test('should contain string attachment', async ({ runUITest }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test } from '@playwright/test'; + test('attach test', async () => { + await test.info().attach('note', { body: 'text42' }); + }); + `, + }); + await page.getByText('attach test').click(); + await page.getByTitle('Run all').click(); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + await page.getByText('Attachments').click(); + await page.getByText('attach "note"', { exact: true }).click(); + const popupPromise = page.waitForEvent('popup'); + await page.getByRole('link', { name: 'note' }).click(); + const popup = await popupPromise; + await popup.waitForLoadState(); + const content = await popup.content(); + expect(content).toContain('text42'); +});