fix(trace): duplicate network requests from beforeAll in serial mode
This commit is contained in:
parent
d029b03d9f
commit
9727bc1e2a
|
|
@ -46,8 +46,12 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
await this._startCollectingStacks(traceName);
|
await this._startCollectingStacks(traceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async startChunk(options: { name?: string, title?: string } = {}) {
|
async startChunk(options: { name?: string, title?: string, _resetNetwork?: boolean } = {}) {
|
||||||
const { traceName } = await this._channel.tracingStartChunk(options);
|
const { traceName } = await this._channel.tracingStartChunk({
|
||||||
|
name: options.name,
|
||||||
|
title: options.title,
|
||||||
|
resetNetwork: options._resetNetwork,
|
||||||
|
});
|
||||||
await this._startCollectingStacks(traceName);
|
await this._startCollectingStacks(traceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2294,6 +2294,7 @@ scheme.TracingTracingStartResult = tOptional(tObject({}));
|
||||||
scheme.TracingTracingStartChunkParams = tObject({
|
scheme.TracingTracingStartChunkParams = tObject({
|
||||||
name: tOptional(tString),
|
name: tOptional(tString),
|
||||||
title: tOptional(tString),
|
title: tOptional(tString),
|
||||||
|
resetNetwork: tOptional(tBoolean),
|
||||||
});
|
});
|
||||||
scheme.TracingTracingStartChunkResult = tObject({
|
scheme.TracingTracingStartChunkResult = tObject({
|
||||||
traceName: tString,
|
traceName: tString,
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
this._harTracer.start({ omitScripts: !options.live });
|
this._harTracer.start({ omitScripts: !options.live });
|
||||||
}
|
}
|
||||||
|
|
||||||
async startChunk(options: { name?: string, title?: string } = {}): Promise<{ traceName: string }> {
|
async startChunk(options: { name?: string, title?: string, resetNetwork?: boolean } = {}): Promise<{ traceName: string }> {
|
||||||
if (this._state && this._state.recording)
|
if (this._state && this._state.recording)
|
||||||
await this.stopChunk({ mode: 'discard' });
|
await this.stopChunk({ mode: 'discard' });
|
||||||
|
|
||||||
|
|
@ -184,6 +184,9 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
||||||
|
if (options.resetNetwork)
|
||||||
|
this._fs.writeFile(this._state.networkFile, '');
|
||||||
|
|
||||||
this._context.instrumentation.addListener(this, this._context);
|
this._context.instrumentation.addListener(this, this._context);
|
||||||
this._eventListeners.push(
|
this._eventListeners.push(
|
||||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Console, this._onConsoleMessage.bind(this)),
|
eventsHelper.addEventListener(this._context, BrowserContext.Events.Console, this._onConsoleMessage.bind(this)),
|
||||||
|
|
|
||||||
|
|
@ -685,14 +685,17 @@ class ArtifactsRecorder {
|
||||||
|
|
||||||
private async _startTraceChunkOnContextCreation(tracing: Tracing) {
|
private async _startTraceChunkOnContextCreation(tracing: Tracing) {
|
||||||
const options = this._testInfo._tracing.traceOptions();
|
const options = this._testInfo._tracing.traceOptions();
|
||||||
|
const seenInThisTestSymbol = this._testInfo._tracing.uniqueSymbol();
|
||||||
if (options) {
|
if (options) {
|
||||||
|
const seenInThisTest = !!(tracing as any)[seenInThisTestSymbol];
|
||||||
|
(tracing as any)[seenInThisTestSymbol] = true;
|
||||||
const title = this._testInfo._tracing.traceTitle();
|
const title = this._testInfo._tracing.traceTitle();
|
||||||
const name = this._testInfo._tracing.generateNextTraceRecordingName();
|
const name = this._testInfo._tracing.generateNextTraceRecordingName();
|
||||||
if (!(tracing as any)[kTracingStarted]) {
|
if (!(tracing as any)[kTracingStarted]) {
|
||||||
await tracing.start({ ...options, title, name });
|
await tracing.start({ ...options, title, name });
|
||||||
(tracing as any)[kTracingStarted] = true;
|
(tracing as any)[kTracingStarted] = true;
|
||||||
} else {
|
} else {
|
||||||
await tracing.startChunk({ title, name });
|
await tracing.startChunk({ title, name, _resetNetwork: seenInThisTest } as any);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((tracing as any)[kTracingStarted]) {
|
if ((tracing as any)[kTracingStarted]) {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export class TestTracing {
|
||||||
private _artifactsDir: string;
|
private _artifactsDir: string;
|
||||||
private _tracesDir: string;
|
private _tracesDir: string;
|
||||||
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
||||||
|
private _uniqueSymbol: symbol;
|
||||||
|
|
||||||
constructor(testInfo: TestInfoImpl, artifactsDir: string) {
|
constructor(testInfo: TestInfoImpl, artifactsDir: string) {
|
||||||
this._testInfo = testInfo;
|
this._testInfo = testInfo;
|
||||||
|
|
@ -59,6 +60,7 @@ export class TestTracing {
|
||||||
monotonicTime: monotonicTime(),
|
monotonicTime: monotonicTime(),
|
||||||
sdkLanguage: 'javascript',
|
sdkLanguage: 'javascript',
|
||||||
};
|
};
|
||||||
|
this._uniqueSymbol = Symbol('unique');
|
||||||
this._appendTraceEvent(this._contextCreatedEvent);
|
this._appendTraceEvent(this._contextCreatedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +142,10 @@ export class TestTracing {
|
||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uniqueSymbol() {
|
||||||
|
return this._uniqueSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
async stopIfNeeded() {
|
async stopIfNeeded() {
|
||||||
if (!this._options)
|
if (!this._options)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -4109,10 +4109,12 @@ export type TracingTracingStartResult = void;
|
||||||
export type TracingTracingStartChunkParams = {
|
export type TracingTracingStartChunkParams = {
|
||||||
name?: string,
|
name?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
|
resetNetwork?: boolean,
|
||||||
};
|
};
|
||||||
export type TracingTracingStartChunkOptions = {
|
export type TracingTracingStartChunkOptions = {
|
||||||
name?: string,
|
name?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
|
resetNetwork?: boolean,
|
||||||
};
|
};
|
||||||
export type TracingTracingStartChunkResult = {
|
export type TracingTracingStartChunkResult = {
|
||||||
traceName: string,
|
traceName: string,
|
||||||
|
|
|
||||||
|
|
@ -3196,6 +3196,7 @@ Tracing:
|
||||||
parameters:
|
parameters:
|
||||||
name: string?
|
name: string?
|
||||||
title: string?
|
title: string?
|
||||||
|
resetNetwork: boolean?
|
||||||
returns:
|
returns:
|
||||||
traceName: string
|
traceName: string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { TraceModel } from '../../packages/trace-viewer/src/sw/traceModel';
|
||||||
import type { ActionTreeItem } from '../../packages/trace-viewer/src/ui/modelUtil';
|
import type { ActionTreeItem } from '../../packages/trace-viewer/src/ui/modelUtil';
|
||||||
import { buildActionTree, MultiTraceModel } from '../../packages/trace-viewer/src/ui/modelUtil';
|
import { buildActionTree, MultiTraceModel } from '../../packages/trace-viewer/src/ui/modelUtil';
|
||||||
import type { ActionTraceEvent, ConsoleMessageTraceEvent, EventTraceEvent, TraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent, ConsoleMessageTraceEvent, EventTraceEvent, TraceEvent } from '@trace/trace';
|
||||||
|
import type { ResourceSnapshot } from '@trace/snapshot';
|
||||||
import style from 'ansi-styles';
|
import style from 'ansi-styles';
|
||||||
|
|
||||||
export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> {
|
export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> {
|
||||||
|
|
@ -157,7 +158,7 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[] }> {
|
export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[], network: ResourceSnapshot[] }> {
|
||||||
const backend = new TraceBackend(file);
|
const backend = new TraceBackend(file);
|
||||||
const traceModel = new TraceModel();
|
const traceModel = new TraceModel();
|
||||||
await traceModel.load(backend, () => {});
|
await traceModel.load(backend, () => {});
|
||||||
|
|
@ -179,6 +180,7 @@ export async function parseTrace(file: string): Promise<{ resources: Map<string,
|
||||||
model,
|
model,
|
||||||
traceModel,
|
traceModel,
|
||||||
actionTree,
|
actionTree,
|
||||||
|
network: model.resources,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1271,3 +1271,42 @@ test('should record trace after fixture teardown timeout', {
|
||||||
// Check console events to make sure that library trace is recorded.
|
// Check console events to make sure that library trace is recorded.
|
||||||
expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' }));
|
expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.only('should not duplicate network from beforeAll hook', {
|
||||||
|
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33106' },
|
||||||
|
}, async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
let shared;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
shared = await browser.newPage();
|
||||||
|
await shared.route('**/*', route => route.fulfill({ body: 'hello' }));
|
||||||
|
await shared.goto('https://playwright.dev/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pass1', async ({ page }) => {
|
||||||
|
await page.route('**/*', route => route.fulfill({ body: 'hello' }));
|
||||||
|
await page.goto('https://playwright1.dev/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pass2', async ({ page }) => {
|
||||||
|
await page.route('**/*', route => route.fulfill({ body: 'hello' }));
|
||||||
|
await page.goto('https://playwright2.dev/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async ({}) => {
|
||||||
|
await shared.close();
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { trace: 'on' });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(2);
|
||||||
|
|
||||||
|
const trace1 = await parseTrace(test.info().outputPath('test-results', 'a-pass1', 'trace.zip'));
|
||||||
|
expect(trace1.network.map(r => r.request.url).sort()).toEqual(['https://playwright.dev/', 'https://playwright1.dev/']);
|
||||||
|
|
||||||
|
const trace2 = await parseTrace(test.info().outputPath('test-results', 'a-pass2', 'trace.zip'));
|
||||||
|
expect(trace2.network.map(r => r.request.url).sort()).toEqual(['https://playwright.dev/', 'https://playwright2.dev/']);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue