mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-04 16:44:11 +03:00
chore(tracing): add tracing to APIRequestContext (#11502)
This commit is contained in:
parent
8a7e4f9814
commit
ab9d5a0dc4
@ -64,7 +64,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
this._contexts.add(context);
|
||||
context._logger = options.logger || this._logger;
|
||||
context._setBrowserType(this._browserType);
|
||||
context._localUtils = this._localUtils;
|
||||
context.tracing._localUtils = this._localUtils;
|
||||
await this._browserType._onDidCreateContext?.(context);
|
||||
return context;
|
||||
}
|
||||
|
@ -38,14 +38,12 @@ import type { BrowserType } from './browserType';
|
||||
import { Artifact } from './artifact';
|
||||
import { APIRequestContext } from './fetch';
|
||||
import { createInstrumentation } from './clientInstrumentation';
|
||||
import { LocalUtils } from './localUtils';
|
||||
|
||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
||||
_pages = new Set<Page>();
|
||||
private _routes: network.RouteHandler[] = [];
|
||||
readonly _browser: Browser | null = null;
|
||||
private _browserType: BrowserType | undefined;
|
||||
_localUtils!: LocalUtils;
|
||||
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
|
||||
_timeoutSettings = new TimeoutSettings();
|
||||
_ownerPage: Page | undefined;
|
||||
@ -71,7 +69,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
if (parent instanceof Browser)
|
||||
this._browser = parent;
|
||||
this._isChromium = this._browser?._name === 'chromium';
|
||||
this.tracing = new Tracing(this);
|
||||
this.tracing = Tracing.from(initializer.tracing);
|
||||
this.request = APIRequestContext.from(initializer.APIRequestContext);
|
||||
|
||||
this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding)));
|
||||
|
@ -109,7 +109,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
context._options = contextParams;
|
||||
context._logger = logger;
|
||||
context._setBrowserType(this);
|
||||
context._localUtils = this._playwright._utils;
|
||||
context.tracing._localUtils = this._playwright._utils;
|
||||
await this._onDidCreateContext?.(context);
|
||||
return context;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import { EventEmitter } from 'events';
|
||||
import { JsonPipe } from './jsonPipe';
|
||||
import { APIRequestContext } from './fetch';
|
||||
import { LocalUtils } from './localUtils';
|
||||
import { Tracing } from './tracing';
|
||||
|
||||
class Root extends ChannelOwner<channels.RootChannel> {
|
||||
constructor(connection: Connection) {
|
||||
@ -254,6 +255,9 @@ export class Connection extends EventEmitter {
|
||||
case 'Selectors':
|
||||
result = new SelectorsOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'Tracing':
|
||||
result = new Tracing(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'WebSocket':
|
||||
result = new WebSocket(parent, type, guid, initializer);
|
||||
break;
|
||||
|
@ -29,6 +29,7 @@ import { RawHeaders } from './network';
|
||||
import { FilePayload, Headers, StorageState } from './types';
|
||||
import { Playwright } from './playwright';
|
||||
import { createInstrumentation } from './clientInstrumentation';
|
||||
import { Tracing } from './tracing';
|
||||
|
||||
export type FetchOptions = {
|
||||
params?: { [key: string]: string; },
|
||||
@ -71,6 +72,7 @@ export class APIRequest implements api.APIRequest {
|
||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||
storageState,
|
||||
})).request);
|
||||
context._tracing._localUtils = this._playwright._utils;
|
||||
this._contexts.add(context);
|
||||
await this._onDidCreateContext?.(context);
|
||||
return context;
|
||||
@ -79,6 +81,7 @@ export class APIRequest implements api.APIRequest {
|
||||
|
||||
export class APIRequestContext extends ChannelOwner<channels.APIRequestContextChannel> implements api.APIRequestContext {
|
||||
private _request?: APIRequest;
|
||||
readonly _tracing: Tracing;
|
||||
|
||||
static from(channel: channels.APIRequestContextChannel): APIRequestContext {
|
||||
return (channel as any)._object;
|
||||
@ -88,6 +91,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||
super(parent, type, guid, initializer, createInstrumentation());
|
||||
if (parent instanceof APIRequest)
|
||||
this._request = parent;
|
||||
this._tracing = Tracing.from(initializer.tracing);
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
|
@ -17,24 +17,29 @@
|
||||
import * as api from '../../types/types';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Artifact } from './artifact';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { LocalUtils } from './localUtils';
|
||||
|
||||
export class Tracing implements api.Tracing {
|
||||
private _context: BrowserContext;
|
||||
export class Tracing extends ChannelOwner<channels.TracingChannel> implements api.Tracing {
|
||||
_localUtils!: LocalUtils;
|
||||
|
||||
constructor(channel: BrowserContext) {
|
||||
this._context = channel;
|
||||
static from(channel: channels.TracingChannel): Tracing {
|
||||
return (channel as any)._object;
|
||||
}
|
||||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.TracingInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
async start(options: { name?: string, title?: string, snapshots?: boolean, screenshots?: boolean, sources?: boolean } = {}) {
|
||||
await this._context._wrapApiCall(async () => {
|
||||
await this._context._channel.tracingStart(options);
|
||||
await this._context._channel.tracingStartChunk({ title: options.title });
|
||||
await this._wrapApiCall(async () => {
|
||||
await this._channel.tracingStart(options);
|
||||
await this._channel.tracingStartChunk({ title: options.title });
|
||||
});
|
||||
}
|
||||
|
||||
async startChunk(options: { title?: string } = {}) {
|
||||
await this._context._channel.tracingStartChunk(options);
|
||||
await this._channel.tracingStartChunk(options);
|
||||
}
|
||||
|
||||
async stopChunk(options: { path?: string } = {}) {
|
||||
@ -42,16 +47,16 @@ export class Tracing implements api.Tracing {
|
||||
}
|
||||
|
||||
async stop(options: { path?: string } = {}) {
|
||||
await this._context._wrapApiCall(async () => {
|
||||
await this._wrapApiCall(async () => {
|
||||
await this._doStopChunk(options.path);
|
||||
await this._context._channel.tracingStop();
|
||||
await this._channel.tracingStop();
|
||||
});
|
||||
}
|
||||
|
||||
private async _doStopChunk(filePath: string | undefined) {
|
||||
const isLocal = !this._context._connection.isRemote();
|
||||
const isLocal = !this._connection.isRemote();
|
||||
|
||||
let mode: channels.BrowserContextTracingStopChunkParams['mode'] = 'doNotSave';
|
||||
let mode: channels.TracingTracingStopChunkParams['mode'] = 'doNotSave';
|
||||
if (filePath) {
|
||||
if (isLocal)
|
||||
mode = 'compressTraceAndSources';
|
||||
@ -59,7 +64,7 @@ export class Tracing implements api.Tracing {
|
||||
mode = 'compressTrace';
|
||||
}
|
||||
|
||||
const result = await this._context._channel.tracingStopChunk({ mode });
|
||||
const result = await this._channel.tracingStopChunk({ mode });
|
||||
if (!filePath) {
|
||||
// Not interested in artifacts.
|
||||
return;
|
||||
@ -76,6 +81,6 @@ export class Tracing implements api.Tracing {
|
||||
|
||||
// Add local sources to the remote trace if necessary.
|
||||
if (result.sourceEntries?.length)
|
||||
await this._context._localUtils.zip(filePath, result.sourceEntries);
|
||||
await this._localUtils.zip(filePath, result.sourceEntries);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import { CallMetadata } from '../server/instrumentation';
|
||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||
import { Artifact } from '../server/artifact';
|
||||
import { Request, Response } from '../server/network';
|
||||
import { TracingDispatcher } from './tracingDispatcher';
|
||||
|
||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel> implements channels.BrowserContextChannel {
|
||||
_type_EventTarget = true;
|
||||
@ -37,6 +38,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
super(scope, context, 'BrowserContext', {
|
||||
isChromium: context._browser.options.isChromium,
|
||||
APIRequestContext: APIRequestContextDispatcher.from(scope, context.fetchRequest),
|
||||
tracing: TracingDispatcher.from(scope, context.tracing),
|
||||
}, true);
|
||||
this._context = context;
|
||||
// Note: when launching persistent context, dispatcher is created very late,
|
||||
@ -189,23 +191,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page ? params.page as PageDispatcher : params.frame as FrameDispatcher)._object)) };
|
||||
}
|
||||
|
||||
async tracingStart(params: channels.BrowserContextTracingStartParams): Promise<channels.BrowserContextTracingStartResult> {
|
||||
await this._context.tracing.start(params);
|
||||
}
|
||||
|
||||
async tracingStartChunk(params: channels.BrowserContextTracingStartChunkParams): Promise<channels.BrowserContextTracingStartChunkResult> {
|
||||
await this._context.tracing.startChunk(params);
|
||||
}
|
||||
|
||||
async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> {
|
||||
const { artifact, sourceEntries } = await this._context.tracing.stopChunk(params);
|
||||
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, sourceEntries };
|
||||
}
|
||||
|
||||
async tracingStop(params: channels.BrowserContextTracingStopParams): Promise<channels.BrowserContextTracingStopResult> {
|
||||
await this._context.tracing.stop();
|
||||
}
|
||||
|
||||
async harExport(params: channels.BrowserContextHarExportParams): Promise<channels.BrowserContextHarExportResult> {
|
||||
const artifact = await this._context._harRecorder?.export();
|
||||
if (!artifact)
|
||||
|
@ -20,6 +20,7 @@ import { CallMetadata } from '../server/instrumentation';
|
||||
import { Request, Response, Route, WebSocket } from '../server/network';
|
||||
import { Dispatcher, DispatcherScope, existingDispatcher, lookupNullableDispatcher } from './dispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
import { TracingDispatcher } from './tracingDispatcher';
|
||||
|
||||
export class RequestDispatcher extends Dispatcher<Request, channels.RequestChannel> implements channels.RequestChannel {
|
||||
_type_Request: boolean;
|
||||
@ -163,7 +164,9 @@ export class APIRequestContextDispatcher extends Dispatcher<APIRequestContext, c
|
||||
}
|
||||
|
||||
private constructor(scope: DispatcherScope, request: APIRequestContext) {
|
||||
super(scope, request, 'APIRequestContext', {}, true);
|
||||
super(scope, request, 'APIRequestContext', {
|
||||
tracing: TracingDispatcher.from(scope, request.tracing()),
|
||||
}, true);
|
||||
request.once(APIRequestContext.Events.Dispose, () => {
|
||||
if (!this._disposed)
|
||||
super._dispose();
|
||||
@ -175,7 +178,7 @@ export class APIRequestContextDispatcher extends Dispatcher<APIRequestContext, c
|
||||
}
|
||||
|
||||
async dispose(params?: channels.APIRequestContextDisposeParams): Promise<void> {
|
||||
this._object.dispose();
|
||||
await this._object.dispose();
|
||||
}
|
||||
|
||||
async fetch(params: channels.APIRequestContextFetchParams, metadata: CallMetadata): Promise<channels.APIRequestContextFetchResult> {
|
||||
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Tracing } from '../server/trace/recorder/tracing';
|
||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||
import { Dispatcher, DispatcherScope, existingDispatcher } from './dispatcher';
|
||||
|
||||
export class TracingDispatcher extends Dispatcher<Tracing, channels.TracingChannel> implements channels.TracingChannel {
|
||||
_type_Tracing = true;
|
||||
|
||||
static from(scope: DispatcherScope, tracing: Tracing): TracingDispatcher {
|
||||
const result = existingDispatcher<TracingDispatcher>(tracing);
|
||||
return result || new TracingDispatcher(scope, tracing);
|
||||
}
|
||||
|
||||
constructor(scope: DispatcherScope, tracing: Tracing) {
|
||||
super(scope, tracing, 'Tracing', {}, true);
|
||||
tracing.on(Tracing.Events.Dispose, () => this._dispose());
|
||||
}
|
||||
|
||||
async tracingStart(params: channels.TracingTracingStartParams): Promise<channels.TracingTracingStartResult> {
|
||||
await this._object.start(params);
|
||||
}
|
||||
|
||||
async tracingStartChunk(params: channels.TracingTracingStartChunkParams): Promise<channels.TracingTracingStartChunkResult> {
|
||||
await this._object.startChunk(params);
|
||||
}
|
||||
|
||||
async tracingStopChunk(params: channels.TracingTracingStopChunkParams): Promise<channels.TracingTracingStopChunkResult> {
|
||||
const { artifact, sourceEntries } = await this._object.stopChunk(params);
|
||||
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, sourceEntries };
|
||||
}
|
||||
|
||||
async tracingStop(params: channels.TracingTracingStopParams): Promise<channels.TracingTracingStopResult> {
|
||||
await this._object.stop();
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,7 @@ export type InitializerTraits<T> =
|
||||
T extends CDPSessionChannel ? CDPSessionInitializer :
|
||||
T extends StreamChannel ? StreamInitializer :
|
||||
T extends ArtifactChannel ? ArtifactInitializer :
|
||||
T extends TracingChannel ? TracingInitializer :
|
||||
T extends DialogChannel ? DialogInitializer :
|
||||
T extends BindingCallChannel ? BindingCallInitializer :
|
||||
T extends ConsoleMessageChannel ? ConsoleMessageInitializer :
|
||||
@ -66,6 +67,7 @@ export type EventsTraits<T> =
|
||||
T extends CDPSessionChannel ? CDPSessionEvents :
|
||||
T extends StreamChannel ? StreamEvents :
|
||||
T extends ArtifactChannel ? ArtifactEvents :
|
||||
T extends TracingChannel ? TracingEvents :
|
||||
T extends DialogChannel ? DialogEvents :
|
||||
T extends BindingCallChannel ? BindingCallEvents :
|
||||
T extends ConsoleMessageChannel ? ConsoleMessageEvents :
|
||||
@ -100,6 +102,7 @@ export type EventTargetTraits<T> =
|
||||
T extends CDPSessionChannel ? CDPSessionEventTarget :
|
||||
T extends StreamChannel ? StreamEventTarget :
|
||||
T extends ArtifactChannel ? ArtifactEventTarget :
|
||||
T extends TracingChannel ? TracingEventTarget :
|
||||
T extends DialogChannel ? DialogEventTarget :
|
||||
T extends BindingCallChannel ? BindingCallEventTarget :
|
||||
T extends ConsoleMessageChannel ? ConsoleMessageEventTarget :
|
||||
@ -262,7 +265,9 @@ export type FormField = {
|
||||
};
|
||||
|
||||
// ----------- APIRequestContext -----------
|
||||
export type APIRequestContextInitializer = {};
|
||||
export type APIRequestContextInitializer = {
|
||||
tracing: TracingChannel,
|
||||
};
|
||||
export interface APIRequestContextEventTarget {
|
||||
}
|
||||
export interface APIRequestContextChannel extends APIRequestContextEventTarget, Channel {
|
||||
@ -506,6 +511,7 @@ export type PlaywrightNewRequestParams = {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
},
|
||||
tracesDir?: string,
|
||||
};
|
||||
export type PlaywrightNewRequestOptions = {
|
||||
baseURL?: string,
|
||||
@ -527,6 +533,7 @@ export type PlaywrightNewRequestOptions = {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
},
|
||||
tracesDir?: string,
|
||||
};
|
||||
export type PlaywrightNewRequestResult = {
|
||||
request: APIRequestContextChannel,
|
||||
@ -1010,6 +1017,7 @@ export interface EventTargetEvents {
|
||||
export type BrowserContextInitializer = {
|
||||
isChromium: boolean,
|
||||
APIRequestContext: APIRequestContextChannel,
|
||||
tracing: TracingChannel,
|
||||
};
|
||||
export interface BrowserContextEventTarget {
|
||||
on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this;
|
||||
@ -1046,10 +1054,6 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
|
||||
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
|
||||
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
||||
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
|
||||
tracingStart(params: BrowserContextTracingStartParams, metadata?: Metadata): Promise<BrowserContextTracingStartResult>;
|
||||
tracingStartChunk(params: BrowserContextTracingStartChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStartChunkResult>;
|
||||
tracingStopChunk(params: BrowserContextTracingStopChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStopChunkResult>;
|
||||
tracingStop(params?: BrowserContextTracingStopParams, metadata?: Metadata): Promise<BrowserContextTracingStopResult>;
|
||||
harExport(params?: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>;
|
||||
}
|
||||
export type BrowserContextBindingCallEvent = {
|
||||
@ -1249,39 +1253,6 @@ export type BrowserContextNewCDPSessionOptions = {
|
||||
export type BrowserContextNewCDPSessionResult = {
|
||||
session: CDPSessionChannel,
|
||||
};
|
||||
export type BrowserContextTracingStartParams = {
|
||||
name?: string,
|
||||
snapshots?: boolean,
|
||||
screenshots?: boolean,
|
||||
sources?: boolean,
|
||||
};
|
||||
export type BrowserContextTracingStartOptions = {
|
||||
name?: string,
|
||||
snapshots?: boolean,
|
||||
screenshots?: boolean,
|
||||
sources?: boolean,
|
||||
};
|
||||
export type BrowserContextTracingStartResult = void;
|
||||
export type BrowserContextTracingStartChunkParams = {
|
||||
title?: string,
|
||||
};
|
||||
export type BrowserContextTracingStartChunkOptions = {
|
||||
title?: string,
|
||||
};
|
||||
export type BrowserContextTracingStartChunkResult = void;
|
||||
export type BrowserContextTracingStopChunkParams = {
|
||||
mode: 'doNotSave' | 'compressTrace' | 'compressTraceAndSources',
|
||||
};
|
||||
export type BrowserContextTracingStopChunkOptions = {
|
||||
|
||||
};
|
||||
export type BrowserContextTracingStopChunkResult = {
|
||||
artifact?: ArtifactChannel,
|
||||
sourceEntries?: NameValue[],
|
||||
};
|
||||
export type BrowserContextTracingStopParams = {};
|
||||
export type BrowserContextTracingStopOptions = {};
|
||||
export type BrowserContextTracingStopResult = void;
|
||||
export type BrowserContextHarExportParams = {};
|
||||
export type BrowserContextHarExportOptions = {};
|
||||
export type BrowserContextHarExportResult = {
|
||||
@ -3224,6 +3195,54 @@ export type DialogDismissResult = void;
|
||||
export interface DialogEvents {
|
||||
}
|
||||
|
||||
// ----------- Tracing -----------
|
||||
export type TracingInitializer = {};
|
||||
export interface TracingEventTarget {
|
||||
}
|
||||
export interface TracingChannel extends TracingEventTarget, Channel {
|
||||
_type_Tracing: boolean;
|
||||
tracingStart(params: TracingTracingStartParams, metadata?: Metadata): Promise<TracingTracingStartResult>;
|
||||
tracingStartChunk(params: TracingTracingStartChunkParams, metadata?: Metadata): Promise<TracingTracingStartChunkResult>;
|
||||
tracingStopChunk(params: TracingTracingStopChunkParams, metadata?: Metadata): Promise<TracingTracingStopChunkResult>;
|
||||
tracingStop(params?: TracingTracingStopParams, metadata?: Metadata): Promise<TracingTracingStopResult>;
|
||||
}
|
||||
export type TracingTracingStartParams = {
|
||||
name?: string,
|
||||
snapshots?: boolean,
|
||||
screenshots?: boolean,
|
||||
sources?: boolean,
|
||||
};
|
||||
export type TracingTracingStartOptions = {
|
||||
name?: string,
|
||||
snapshots?: boolean,
|
||||
screenshots?: boolean,
|
||||
sources?: boolean,
|
||||
};
|
||||
export type TracingTracingStartResult = void;
|
||||
export type TracingTracingStartChunkParams = {
|
||||
title?: string,
|
||||
};
|
||||
export type TracingTracingStartChunkOptions = {
|
||||
title?: string,
|
||||
};
|
||||
export type TracingTracingStartChunkResult = void;
|
||||
export type TracingTracingStopChunkParams = {
|
||||
mode: 'doNotSave' | 'compressTrace' | 'compressTraceAndSources',
|
||||
};
|
||||
export type TracingTracingStopChunkOptions = {
|
||||
|
||||
};
|
||||
export type TracingTracingStopChunkResult = {
|
||||
artifact?: ArtifactChannel,
|
||||
sourceEntries?: NameValue[],
|
||||
};
|
||||
export type TracingTracingStopParams = {};
|
||||
export type TracingTracingStopOptions = {};
|
||||
export type TracingTracingStopResult = void;
|
||||
|
||||
export interface TracingEvents {
|
||||
}
|
||||
|
||||
// ----------- Artifact -----------
|
||||
export type ArtifactInitializer = {
|
||||
absolutePath: string,
|
||||
|
@ -230,6 +230,9 @@ FormField:
|
||||
APIRequestContext:
|
||||
type: interface
|
||||
|
||||
initializer:
|
||||
tracing: Tracing
|
||||
|
||||
commands:
|
||||
|
||||
fetch:
|
||||
@ -538,6 +541,7 @@ Playwright:
|
||||
origins:
|
||||
type: array
|
||||
items: OriginStorage
|
||||
tracesDir: string?
|
||||
|
||||
returns:
|
||||
request: APIRequestContext
|
||||
@ -707,6 +711,7 @@ BrowserContext:
|
||||
initializer:
|
||||
isChromium: boolean
|
||||
APIRequestContext: APIRequestContext
|
||||
tracing: Tracing
|
||||
|
||||
commands:
|
||||
|
||||
@ -822,34 +827,6 @@ BrowserContext:
|
||||
returns:
|
||||
session: CDPSession
|
||||
|
||||
tracingStart:
|
||||
parameters:
|
||||
name: string?
|
||||
snapshots: boolean?
|
||||
screenshots: boolean?
|
||||
sources: boolean?
|
||||
|
||||
tracingStartChunk:
|
||||
parameters:
|
||||
title: string?
|
||||
|
||||
tracingStopChunk:
|
||||
parameters:
|
||||
mode:
|
||||
type: enum
|
||||
literals:
|
||||
- doNotSave
|
||||
- compressTrace
|
||||
- compressTraceAndSources
|
||||
returns:
|
||||
# The artifact may be missing if the browser closes while tracing is beeing stopped.
|
||||
artifact: Artifact?
|
||||
sourceEntries:
|
||||
type: array?
|
||||
items: NameValue
|
||||
|
||||
tracingStop:
|
||||
|
||||
harExport:
|
||||
returns:
|
||||
artifact: Artifact
|
||||
@ -2530,6 +2507,39 @@ Dialog:
|
||||
dismiss:
|
||||
|
||||
|
||||
Tracing:
|
||||
type: interface
|
||||
|
||||
commands:
|
||||
|
||||
tracingStart:
|
||||
parameters:
|
||||
name: string?
|
||||
snapshots: boolean?
|
||||
screenshots: boolean?
|
||||
sources: boolean?
|
||||
|
||||
tracingStartChunk:
|
||||
parameters:
|
||||
title: string?
|
||||
|
||||
tracingStopChunk:
|
||||
parameters:
|
||||
mode:
|
||||
type: enum
|
||||
literals:
|
||||
- doNotSave
|
||||
- compressTrace
|
||||
- compressTraceAndSources
|
||||
returns:
|
||||
# The artifact may be missing if the browser closes while tracing is beeing stopped.
|
||||
artifact: Artifact?
|
||||
sourceEntries:
|
||||
type: array?
|
||||
items: NameValue
|
||||
|
||||
tracingStop:
|
||||
|
||||
|
||||
Artifact:
|
||||
type: interface
|
||||
|
@ -236,6 +236,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
cookies: tArray(tType('NetworkCookie')),
|
||||
origins: tArray(tType('OriginStorage')),
|
||||
})),
|
||||
tracesDir: tOptional(tString),
|
||||
});
|
||||
scheme.PlaywrightHideHighlightParams = tOptional(tObject({}));
|
||||
scheme.SelectorsRegisterParams = tObject({
|
||||
@ -500,19 +501,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
page: tOptional(tChannel('Page')),
|
||||
frame: tOptional(tChannel('Frame')),
|
||||
});
|
||||
scheme.BrowserContextTracingStartParams = tObject({
|
||||
name: tOptional(tString),
|
||||
snapshots: tOptional(tBoolean),
|
||||
screenshots: tOptional(tBoolean),
|
||||
sources: tOptional(tBoolean),
|
||||
});
|
||||
scheme.BrowserContextTracingStartChunkParams = tObject({
|
||||
title: tOptional(tString),
|
||||
});
|
||||
scheme.BrowserContextTracingStopChunkParams = tObject({
|
||||
mode: tEnum(['doNotSave', 'compressTrace', 'compressTraceAndSources']),
|
||||
});
|
||||
scheme.BrowserContextTracingStopParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextHarExportParams = tOptional(tObject({}));
|
||||
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
||||
timeout: tOptional(tNumber),
|
||||
@ -1167,6 +1155,19 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
promptText: tOptional(tString),
|
||||
});
|
||||
scheme.DialogDismissParams = tOptional(tObject({}));
|
||||
scheme.TracingTracingStartParams = tObject({
|
||||
name: tOptional(tString),
|
||||
snapshots: tOptional(tBoolean),
|
||||
screenshots: tOptional(tBoolean),
|
||||
sources: tOptional(tBoolean),
|
||||
});
|
||||
scheme.TracingTracingStartChunkParams = tObject({
|
||||
title: tOptional(tString),
|
||||
});
|
||||
scheme.TracingTracingStopChunkParams = tObject({
|
||||
mode: tEnum(['doNotSave', 'compressTrace', 'compressTraceAndSources']),
|
||||
});
|
||||
scheme.TracingTracingStopParams = tOptional(tObject({}));
|
||||
scheme.ArtifactPathAfterFinishedParams = tOptional(tObject({}));
|
||||
scheme.ArtifactSaveAsParams = tObject({
|
||||
path: tString,
|
||||
|
@ -81,7 +81,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
if (this._options.recordHar)
|
||||
this._harRecorder = new HarRecorder(this, { ...this._options.recordHar, path: path.join(this._browser.options.artifactsDir, `${createGuid()}.har`) });
|
||||
|
||||
this.tracing = new Tracing(this);
|
||||
this.tracing = new Tracing(this, browser.options.tracesDir);
|
||||
}
|
||||
|
||||
isPersistentContext(): boolean {
|
||||
@ -138,6 +138,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this._closedStatus = 'closed';
|
||||
this._deleteAllDownloads();
|
||||
this._downloads.clear();
|
||||
this.tracing.dispose();
|
||||
if (this._isPersistentContext)
|
||||
this._onClosePersistent();
|
||||
this._closePromiseFulfill!(new Error('Context closed'));
|
||||
@ -283,7 +284,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this._closedStatus = 'closing';
|
||||
|
||||
await this._harRecorder?.flush();
|
||||
await this.tracing.dispose();
|
||||
await this.tracing.flush();
|
||||
|
||||
// Cleanup.
|
||||
const promises: Promise<void>[] = [];
|
||||
|
@ -17,7 +17,6 @@
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { Progress, ProgressController } from './progress';
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||
import { pipeline, Readable, Transform } from 'stream';
|
||||
import url from 'url';
|
||||
@ -31,6 +30,8 @@ import { CookieStore, domainMatches } from './cookieStore';
|
||||
import { MultipartFormData } from './formData';
|
||||
import { CallMetadata, SdkObject } from './instrumentation';
|
||||
import { Playwright } from './playwright';
|
||||
import { Progress, ProgressController } from './progress';
|
||||
import { Tracing } from './trace/recorder/tracing';
|
||||
import * as types from './types';
|
||||
import { HeadersArray, ProxySettings } from './types';
|
||||
|
||||
@ -101,7 +102,9 @@ export abstract class APIRequestContext extends SdkObject {
|
||||
this.fetchLog.delete(fetchUid);
|
||||
}
|
||||
|
||||
abstract dispose(): void;
|
||||
abstract tracing(): Tracing;
|
||||
|
||||
abstract dispose(): Promise<void>;
|
||||
|
||||
abstract _defaultOptions(): FetchRequestOptions;
|
||||
abstract _addCookies(cookies: types.NetworkCookie[]): Promise<void>;
|
||||
@ -404,7 +407,11 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
|
||||
context.once(BrowserContext.Events.Close, () => this._disposeImpl());
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
override tracing() {
|
||||
return this._context.tracing;
|
||||
}
|
||||
|
||||
override async dispose() {
|
||||
this.fetchResponses.clear();
|
||||
}
|
||||
|
||||
@ -438,9 +445,11 @@ export class GlobalAPIRequestContext extends APIRequestContext {
|
||||
private readonly _cookieStore: CookieStore = new CookieStore();
|
||||
private readonly _options: FetchRequestOptions;
|
||||
private readonly _origins: channels.OriginStorage[] | undefined;
|
||||
private readonly _tracing: Tracing;
|
||||
|
||||
constructor(playwright: Playwright, options: channels.PlaywrightNewRequestOptions) {
|
||||
super(playwright);
|
||||
this.attribution.context = this;
|
||||
const timeoutSettings = new TimeoutSettings();
|
||||
if (options.timeout !== undefined)
|
||||
timeoutSettings.setDefaultTimeout(options.timeout);
|
||||
@ -464,10 +473,17 @@ export class GlobalAPIRequestContext extends APIRequestContext {
|
||||
proxy,
|
||||
timeoutSettings,
|
||||
};
|
||||
|
||||
this._tracing = new Tracing(this, options.tracesDir);
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
override tracing() {
|
||||
return this._tracing;
|
||||
}
|
||||
|
||||
override async dispose() {
|
||||
await this._tracing.flush();
|
||||
await this._tracing.deleteTmpTracesDir();
|
||||
this._tracing.dispose();
|
||||
this._disposeImpl();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { createGuid } from '../utils/utils';
|
||||
import type { APIRequestContext } from './fetch';
|
||||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { BrowserType } from './browserType';
|
||||
@ -27,7 +28,7 @@ export type Attribution = {
|
||||
isInternal: boolean,
|
||||
browserType?: BrowserType;
|
||||
browser?: Browser;
|
||||
context?: BrowserContext;
|
||||
context?: BrowserContext | APIRequestContext;
|
||||
page?: Page;
|
||||
frame?: Frame;
|
||||
};
|
||||
@ -50,7 +51,7 @@ export class SdkObject extends EventEmitter {
|
||||
}
|
||||
|
||||
export interface Instrumentation {
|
||||
addListener(listener: InstrumentationListener, context: BrowserContext | null): void;
|
||||
addListener(listener: InstrumentationListener, context: BrowserContext | APIRequestContext | null): void;
|
||||
removeListener(listener: InstrumentationListener): void;
|
||||
onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
||||
@ -72,11 +73,11 @@ export interface InstrumentationListener {
|
||||
}
|
||||
|
||||
export function createInstrumentation(): Instrumentation {
|
||||
const listeners = new Map<InstrumentationListener, BrowserContext | null>();
|
||||
const listeners = new Map<InstrumentationListener, BrowserContext | APIRequestContext | null>();
|
||||
return new Proxy({}, {
|
||||
get: (obj: any, prop: string) => {
|
||||
if (prop === 'addListener')
|
||||
return (listener: InstrumentationListener, context: BrowserContext | null) => listeners.set(listener, context);
|
||||
return (listener: InstrumentationListener, context: BrowserContext | APIRequestContext | null) => listeners.set(listener, context);
|
||||
if (prop === 'removeListener')
|
||||
return (listener: InstrumentationListener) => listeners.delete(listener);
|
||||
if (!prop.startsWith('on'))
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { APIRequestContext } from '../../fetch';
|
||||
import { Artifact } from '../../artifact';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import * as har from './har';
|
||||
@ -32,7 +33,7 @@ export class HarRecorder {
|
||||
private _tracer: HarTracer;
|
||||
private _entries: har.Entry[] = [];
|
||||
|
||||
constructor(context: BrowserContext, options: HarOptions) {
|
||||
constructor(context: BrowserContext | APIRequestContext, options: HarOptions) {
|
||||
this._artifact = new Artifact(context, options.path);
|
||||
this._options = options;
|
||||
this._tracer = new HarTracer(context, this, {
|
||||
|
@ -40,7 +40,7 @@ type HarTracerOptions = {
|
||||
};
|
||||
|
||||
export class HarTracer {
|
||||
private _context: BrowserContext;
|
||||
private _context: BrowserContext | APIRequestContext;
|
||||
private _barrierPromises = new Set<Promise<void>>();
|
||||
private _delegate: HarTracerDelegate;
|
||||
private _options: HarTracerOptions;
|
||||
@ -49,7 +49,7 @@ export class HarTracer {
|
||||
private _started = false;
|
||||
private _entrySymbol: symbol;
|
||||
|
||||
constructor(context: BrowserContext, delegate: HarTracerDelegate, options: HarTracerOptions) {
|
||||
constructor(context: BrowserContext | APIRequestContext, delegate: HarTracerDelegate, options: HarTracerOptions) {
|
||||
this._context = context;
|
||||
this._delegate = delegate;
|
||||
this._options = options;
|
||||
@ -60,15 +60,20 @@ export class HarTracer {
|
||||
if (this._started)
|
||||
return;
|
||||
this._started = true;
|
||||
const apiRequest = this._context instanceof APIRequestContext ? this._context : this._context.fetchRequest;
|
||||
this._eventListeners = [
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, (page: Page) => this._ensurePageEntry(page)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFinished, ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Response, (response: network.Response) => this._onResponse(response)),
|
||||
eventsHelper.addEventListener(this._context.fetchRequest, APIRequestContext.Events.Request, (event: APIRequestEvent) => this._onAPIRequest(event)),
|
||||
eventsHelper.addEventListener(this._context.fetchRequest, APIRequestContext.Events.RequestFinished, (event: APIRequestFinishedEvent) => this._onAPIRequestFinished(event)),
|
||||
eventsHelper.addEventListener(apiRequest, APIRequestContext.Events.Request, (event: APIRequestEvent) => this._onAPIRequest(event)),
|
||||
eventsHelper.addEventListener(apiRequest, APIRequestContext.Events.RequestFinished, (event: APIRequestFinishedEvent) => this._onAPIRequestFinished(event)),
|
||||
];
|
||||
if (this._context instanceof BrowserContext) {
|
||||
this._eventListeners.push(
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, (page: Page) => this._ensurePageEntry(page)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFinished, ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)),
|
||||
eventsHelper.addEventListener(this._context, BrowserContext.Events.Response, (response: network.Response) => this._onResponse(response)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _entryForRequest(request: network.Request | APIRequestEvent): har.Entry | undefined {
|
||||
@ -370,6 +375,7 @@ export class HarTracer {
|
||||
eventsHelper.removeEventListeners(this._eventListeners);
|
||||
this._barrierPromises.clear();
|
||||
|
||||
const context = this._context instanceof BrowserContext ? this._context : undefined;
|
||||
const log: har.Log = {
|
||||
version: '1.2',
|
||||
creator: {
|
||||
@ -377,8 +383,8 @@ export class HarTracer {
|
||||
version: require('../../../../package.json')['version'],
|
||||
},
|
||||
browser: {
|
||||
name: this._context._browser.options.name,
|
||||
version: this._context._browser.version()
|
||||
name: context?._browser.options.name || '',
|
||||
version: context?._browser.version() || ''
|
||||
},
|
||||
pages: Array.from(this._pageEntries.values()),
|
||||
entries: [],
|
||||
|
@ -16,13 +16,14 @@
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import fs from 'fs';
|
||||
import { APIRequestContext } from '../../fetch';
|
||||
import path from 'path';
|
||||
import yazl from 'yazl';
|
||||
import { NameValue } from '../../../common/types';
|
||||
import { commandsWithTracingSnapshots, BrowserContextTracingStopChunkParams } from '../../../protocol/channels';
|
||||
import { commandsWithTracingSnapshots, TracingTracingStopChunkParams } from '../../../protocol/channels';
|
||||
import { ManualPromise } from '../../../utils/async';
|
||||
import { eventsHelper, RegisteredListener } from '../../../utils/eventsHelper';
|
||||
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
|
||||
import { assert, calculateSha1, createGuid, mkdirIfNeeded, monotonicTime, removeFolders } from '../../../utils/utils';
|
||||
import { Artifact } from '../../artifact';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { ElementHandle } from '../../dom';
|
||||
@ -47,6 +48,8 @@ type RecordingState = {
|
||||
traceName: string,
|
||||
networkFile: string,
|
||||
traceFile: string,
|
||||
tracesDir: string,
|
||||
resourcesDir: string,
|
||||
filesCount: number,
|
||||
networkSha1s: Set<string>,
|
||||
traceSha1s: Set<string>,
|
||||
@ -56,25 +59,28 @@ type RecordingState = {
|
||||
|
||||
const kScreencastOptions = { width: 800, height: 600, quality: 90 };
|
||||
|
||||
export class Tracing implements InstrumentationListener, SnapshotterDelegate, HarTracerDelegate {
|
||||
export class Tracing extends SdkObject implements InstrumentationListener, SnapshotterDelegate, HarTracerDelegate {
|
||||
static Events = {
|
||||
Dispose: 'dispose',
|
||||
};
|
||||
|
||||
private _writeChain = Promise.resolve();
|
||||
private _snapshotter: Snapshotter;
|
||||
private _snapshotter?: Snapshotter;
|
||||
private _harTracer: HarTracer;
|
||||
private _screencastListeners: RegisteredListener[] = [];
|
||||
private _pendingCalls = new Map<string, { sdkObject: SdkObject, metadata: CallMetadata, beforeSnapshot: Promise<void>, actionSnapshot?: Promise<void>, afterSnapshot?: Promise<void> }>();
|
||||
private _context: BrowserContext;
|
||||
private _resourcesDir: string;
|
||||
private _context: BrowserContext | APIRequestContext;
|
||||
private _state: RecordingState | undefined;
|
||||
private _isStopping = false;
|
||||
private _tracesDir: string;
|
||||
private _precreatedTracesDir: string | undefined;
|
||||
private _tracesTmpDir: string | undefined;
|
||||
private _allResources = new Set<string>();
|
||||
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
||||
|
||||
constructor(context: BrowserContext) {
|
||||
constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) {
|
||||
super(context, 'Tracing');
|
||||
this._context = context;
|
||||
this._tracesDir = context._browser.options.tracesDir;
|
||||
this._resourcesDir = path.join(this._tracesDir, 'resources');
|
||||
this._snapshotter = new Snapshotter(context, this);
|
||||
this._precreatedTracesDir = tracesDir;
|
||||
this._harTracer = new HarTracer(context, this, {
|
||||
content: 'sha1',
|
||||
waitForContentOnStop: false,
|
||||
@ -83,14 +89,20 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
this._contextCreatedEvent = {
|
||||
version: VERSION,
|
||||
type: 'context-options',
|
||||
browserName: this._context._browser.options.name,
|
||||
options: this._context._options,
|
||||
browserName: '',
|
||||
options: {},
|
||||
platform: process.platform,
|
||||
wallTime: 0,
|
||||
};
|
||||
if (context instanceof BrowserContext) {
|
||||
this._snapshotter = new Snapshotter(context, this);
|
||||
assert(tracesDir, 'tracesDir must be specified for BrowserContext');
|
||||
this._contextCreatedEvent.browserName = context._browser.options.name;
|
||||
this._contextCreatedEvent.options = context._options;
|
||||
}
|
||||
}
|
||||
|
||||
start(options: TracerOptions) {
|
||||
async start(options: TracerOptions) {
|
||||
if (this._isStopping)
|
||||
throw new Error('Cannot start tracing while stopping');
|
||||
if (this._state) {
|
||||
@ -99,14 +111,18 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
throw new Error('Tracing has been already started with different options');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: passing the same name for two contexts makes them write into a single file
|
||||
// and conflict.
|
||||
const traceName = options.name || createGuid();
|
||||
const traceFile = path.join(this._tracesDir, traceName + '.trace');
|
||||
const networkFile = path.join(this._tracesDir, traceName + '.network');
|
||||
this._state = { options, traceName, traceFile, networkFile, filesCount: 0, traceSha1s: new Set(), networkSha1s: new Set(), sources: new Set(), recording: false };
|
||||
this._writeChain = fs.promises.mkdir(this._resourcesDir, { recursive: true }).then(() => fs.promises.writeFile(networkFile, ''));
|
||||
// Init the state synchrounously.
|
||||
this._state = { options, traceName, traceFile: '', networkFile: '', tracesDir: '', resourcesDir: '', filesCount: 0, traceSha1s: new Set(), networkSha1s: new Set(), sources: new Set(), recording: false };
|
||||
const state = this._state;
|
||||
|
||||
state.tracesDir = await this._createTracesDirIfNeeded();
|
||||
state.resourcesDir = path.join(state.tracesDir, 'resources');
|
||||
state.traceFile = path.join(state.tracesDir, traceName + '.trace');
|
||||
state.networkFile = path.join(state.tracesDir, traceName + '.network');
|
||||
this._writeChain = fs.promises.mkdir(state.resourcesDir, { recursive: true }).then(() => fs.promises.writeFile(state.networkFile, ''));
|
||||
if (options.snapshots)
|
||||
this._harTracer.start();
|
||||
}
|
||||
@ -123,7 +139,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
const state = this._state;
|
||||
const suffix = state.filesCount ? `-${state.filesCount}` : ``;
|
||||
state.filesCount++;
|
||||
state.traceFile = path.join(this._tracesDir, `${state.traceName}${suffix}.trace`);
|
||||
state.traceFile = path.join(state.tracesDir, `${state.traceName}${suffix}.trace`);
|
||||
state.recording = true;
|
||||
|
||||
this._appendTraceOperation(async () => {
|
||||
@ -135,10 +151,12 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
if (state.options.screenshots)
|
||||
this._startScreencast();
|
||||
if (state.options.snapshots)
|
||||
await this._snapshotter.start();
|
||||
await this._snapshotter?.start();
|
||||
}
|
||||
|
||||
private _startScreencast() {
|
||||
if (!(this._context instanceof BrowserContext))
|
||||
return;
|
||||
for (const page of this._context.pages())
|
||||
this._startScreencastInPage(page);
|
||||
this._screencastListeners.push(
|
||||
@ -148,6 +166,8 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
|
||||
private _stopScreencast() {
|
||||
eventsHelper.removeEventListeners(this._screencastListeners);
|
||||
if (!(this._context instanceof BrowserContext))
|
||||
return;
|
||||
for (const page of this._context.pages())
|
||||
page.setScreencastOptions(null);
|
||||
}
|
||||
@ -164,12 +184,29 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
this._state = undefined;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
this._snapshotter.dispose();
|
||||
async deleteTmpTracesDir() {
|
||||
if (this._tracesTmpDir)
|
||||
await removeFolders([this._tracesTmpDir]);
|
||||
}
|
||||
|
||||
private async _createTracesDirIfNeeded() {
|
||||
if (this._precreatedTracesDir)
|
||||
return this._precreatedTracesDir;
|
||||
this._tracesTmpDir = await fs.promises.mkdtemp('playwright-tracing-');
|
||||
return this._tracesTmpDir;
|
||||
}
|
||||
|
||||
async flush() {
|
||||
this._snapshotter?.dispose();
|
||||
await this._writeChain;
|
||||
}
|
||||
|
||||
async stopChunk(params: BrowserContextTracingStopChunkParams): Promise<{ artifact: Artifact | null, sourceEntries: NameValue[] | undefined }> {
|
||||
async dispose() {
|
||||
this._snapshotter?.dispose();
|
||||
this.emit(Tracing.Events.Dispose);
|
||||
}
|
||||
|
||||
async stopChunk(params: TracingTracingStopChunkParams): Promise<{ artifact: Artifact | null, sourceEntries: NameValue[] | undefined }> {
|
||||
if (this._isStopping)
|
||||
throw new Error(`Tracing is already stopping`);
|
||||
this._isStopping = true;
|
||||
@ -200,7 +237,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
}
|
||||
|
||||
if (state.options.snapshots)
|
||||
await this._snapshotter.stop();
|
||||
await this._snapshotter?.stop();
|
||||
|
||||
// Chain the export operation against write operations,
|
||||
// so that neither trace files nor sha1s change during the export.
|
||||
@ -216,7 +253,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
entries.push({ name: 'trace.trace', value: state.traceFile });
|
||||
entries.push({ name: 'trace.network', value: networkFile });
|
||||
for (const sha1 of new Set([...state.traceSha1s, ...state.networkSha1s]))
|
||||
entries.push({ name: path.join('resources', sha1), value: path.join(this._resourcesDir, sha1) });
|
||||
entries.push({ name: path.join('resources', sha1), value: path.join(state.resourcesDir, sha1) });
|
||||
|
||||
let sourceEntries: NameValue[] | undefined;
|
||||
if (state.sources.size) {
|
||||
@ -260,6 +297,8 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
}
|
||||
|
||||
async _captureSnapshot(name: 'before' | 'after' | 'action' | 'event', sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle) {
|
||||
if (!this._snapshotter)
|
||||
return;
|
||||
if (!sdkObject.attribution.page)
|
||||
return;
|
||||
if (!this._snapshotter.started())
|
||||
@ -376,8 +415,8 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
if (this._allResources.has(sha1))
|
||||
return;
|
||||
this._allResources.add(sha1);
|
||||
const resourcePath = path.join(this._state!.resourcesDir, sha1);
|
||||
this._appendTraceOperation(async () => {
|
||||
const resourcePath = path.join(this._resourcesDir, sha1);
|
||||
try {
|
||||
// Perhaps we've already written this resource?
|
||||
await fs.promises.access(resourcePath);
|
||||
@ -394,7 +433,9 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
let error: Error | undefined;
|
||||
let result: T | undefined;
|
||||
this._writeChain = this._writeChain.then(async () => {
|
||||
if (!this._context._browser.isConnected())
|
||||
// This check is here because closing the browser removes the tracesDir and tracing
|
||||
// dies trying to archive.
|
||||
if (this._context instanceof BrowserContext && !this._context._browser.isConnected())
|
||||
return;
|
||||
try {
|
||||
result = await cb();
|
||||
|
@ -64,7 +64,8 @@ it('should scope context handles', async ({ browserType, server }) => {
|
||||
{ _guid: 'request', objects: [] },
|
||||
{ _guid: 'response', objects: [] },
|
||||
] },
|
||||
{ _guid: 'fetchRequest', objects: [] }
|
||||
{ _guid: 'fetchRequest', objects: [] },
|
||||
{ _guid: 'Tracing', objects: [] }
|
||||
] },
|
||||
] },
|
||||
{ _guid: 'electron', objects: [] },
|
||||
@ -153,7 +154,8 @@ it('should scope browser handles', async ({ browserType }) => {
|
||||
{
|
||||
_guid: 'browser', objects: [
|
||||
{ _guid: 'browser-context', objects: [] },
|
||||
{ _guid: 'fetchRequest', objects: [] }
|
||||
{ _guid: 'fetchRequest', objects: [] },
|
||||
{ _guid: 'Tracing', objects: [] }
|
||||
]
|
||||
},
|
||||
]
|
||||
|
@ -210,6 +210,7 @@ DEPS['src/server/electron/'] = [...DEPS['src/server/'], 'src/server/chromium/'];
|
||||
|
||||
DEPS['src/server/playwright.ts'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/server/webkit/', 'src/server/firefox/', 'src/server/android/', 'src/server/electron/'];
|
||||
DEPS['src/server/browserContext.ts'] = [...DEPS['src/server/'], 'src/server/trace/recorder/tracing.ts'];
|
||||
DEPS['src/server/fetch.ts'] = [...DEPS['src/server/'], 'src/server/trace/recorder/tracing.ts'];
|
||||
DEPS['src/cli/driver.ts'] = DEPS['src/inProcessFactory.ts'] = DEPS['src/browserServerImpl.ts'] = ['src/**'];
|
||||
|
||||
// Tracing is a client/server plugin, nothing should depend on it.
|
||||
|
Loading…
Reference in New Issue
Block a user