diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index c19c0a33d9..86a1fcfcea 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -353,7 +353,9 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps const entries: NameValue[] = []; entries.push({ name: 'trace.trace', value: this._state.traceFile }); - entries.push({ name: 'trace.network', value: newNetworkFile }); + // Prefix network trace with the context guid, it can be used later to deduplicate + // network traces from the same context saved for beforeAll/test/afterAll hooks. + entries.push({ name: `${this._context.guid}.trace.network`, value: newNetworkFile }); for (const sha1 of new Set([...this._state.traceSha1s, ...this._state.networkSha1s])) entries.push({ name: path.join('resources', sha1), value: path.join(this._state.resourcesDir, sha1) }); diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index fde5ea5f3f..c0440b5424 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -318,6 +318,7 @@ async function mergeTraceFiles(fileName: string, temporaryTraceFiles: string[]) const mergePromise = new ManualPromise(); const zipFile = new yazl.ZipFile(); const entryNames = new Set(); + const networkTraceEntries = new Map(); (zipFile as any as EventEmitter).on('error', error => mergePromise.reject(error)); for (let i = temporaryTraceFiles.length - 1; i >= 0; --i) { @@ -335,6 +336,15 @@ async function mergeTraceFiles(fileName: string, temporaryTraceFiles: string[]) // Keep the name for test traces so that the last test trace // that contains most of the information is kept in the trace. // Note the reverse order of the iteration (from new traces to old). + } else if (entry.fileName.endsWith('.trace.network')) { + // Network trace file name format is .trace.network + // Use only latest trace for each context if there are multiple traces, + // it will contain all previous events. + entryName = networkTraceEntries.get(entry.fileName); + if (!entryName) { + entryName = i + '-trace.network'; + networkTraceEntries.set(entry.fileName, entryName); + } } else if (entry.fileName.match(/[\d-]*trace\./)) { entryName = i + '-' + entry.fileName; } diff --git a/tests/playwright-test/ui-mode-test-network-tab.spec.ts b/tests/playwright-test/ui-mode-test-network-tab.spec.ts index e1b3bef9ed..6905dec505 100644 --- a/tests/playwright-test/ui-mode-test-network-tab.spec.ts +++ b/tests/playwright-test/ui-mode-test-network-tab.spec.ts @@ -160,3 +160,39 @@ test('should display list of query parameters (only if present)', async ({ runUI await expect(page.getByText('Query String Parameters')).not.toBeVisible(); }); + +test('should not duplicate network entries from beforeAll', { + annotation: [ + { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34404' }, + { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33106' }, + ] +}, async ({ runUITest, server }) => { + const { page } = await runUITest({ + 'playwright.config.ts': ` + module.exports = { use: { trace: 'on' } }; + `, + 'a.spec.ts': ` + import { test as base, expect, request, type APIRequestContext } from '@playwright/test'; + + const test = base.extend<{}, { apiRequest: APIRequestContext }>({ + apiRequest: [async ({ }, use) => { + const apiContext = await request.newContext(); + await use(apiContext); + await apiContext.dispose(); + }, { scope: 'worker' }] + }); + + test.beforeAll(async ({ apiRequest }) => { + await apiRequest.get("${server.EMPTY_PAGE}"); + }); + + test('first test', async ({ }) => { }); + + test.afterAll(async ({ apiRequest }) => { }); + `, + }); + + await page.getByText('first test').dblclick(); + await page.getByText('Network', { exact: true }).click(); + await expect(page.getByTestId('network-list').getByText('empty.html')).toHaveCount(1); +});