chore: start / stop context tracing (#6309)
This commit is contained in:
parent
97cf86d20a
commit
a9219aa8b6
|
|
@ -235,9 +235,6 @@ async function launchContext(options: Options, headless: boolean): Promise<{ bro
|
||||||
if (contextOptions.isMobile && browserType.name() === 'firefox')
|
if (contextOptions.isMobile && browserType.name() === 'firefox')
|
||||||
contextOptions.isMobile = undefined;
|
contextOptions.isMobile = undefined;
|
||||||
|
|
||||||
if (process.env.PWTRACE)
|
|
||||||
(contextOptions as any)._traceDir = path.join(process.cwd(), '.trace');
|
|
||||||
|
|
||||||
// Proxy
|
// Proxy
|
||||||
|
|
||||||
if (options.proxyServer) {
|
if (options.proxyServer) {
|
||||||
|
|
@ -365,8 +362,6 @@ async function open(options: Options, url: string | undefined, language: string)
|
||||||
|
|
||||||
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
||||||
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS);
|
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS);
|
||||||
if (process.env.PWTRACE)
|
|
||||||
contextOptions._traceDir = path.join(process.cwd(), '.trace');
|
|
||||||
await context._enableRecorder({
|
await context._enableRecorder({
|
||||||
language,
|
language,
|
||||||
launchOptions,
|
launchOptions,
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,6 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
|
||||||
|
|
||||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||||
return this._wrapApiCall('browser.newContext', async (channel: channels.BrowserChannel) => {
|
return this._wrapApiCall('browser.newContext', async (channel: channels.BrowserChannel) => {
|
||||||
if (this._isRemote && options._traceDir)
|
|
||||||
throw new Error(`"_traceDir" is not supported in connected browser`);
|
|
||||||
const contextOptions = await prepareBrowserContextParams(options);
|
const contextOptions = await prepareBrowserContextParams(options);
|
||||||
const context = BrowserContext.from((await channel.newContext(contextOptions)).context);
|
const context = BrowserContext.from((await channel.newContext(contextOptions)).context);
|
||||||
context._options = contextOptions;
|
context._options = contextOptions;
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,18 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
this.emit(Events.BrowserContext.Close, this);
|
this.emit(Events.BrowserContext.Close, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _startTracing() {
|
||||||
|
return await this._wrapApiCall('browserContext.startTracing', async (channel: channels.BrowserContextChannel) => {
|
||||||
|
await channel.startTracing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _stopTracing() {
|
||||||
|
return await this._wrapApiCall('browserContext.stopTracing', async (channel: channels.BrowserContextChannel) => {
|
||||||
|
await channel.stopTracing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this._wrapApiCall('browserContext.close', async (channel: channels.BrowserContextChannel) => {
|
await this._wrapApiCall('browserContext.close', async (channel: channels.BrowserContextChannel) => {
|
||||||
|
|
|
||||||
|
|
@ -157,4 +157,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
const crBrowserContext = this._object as CRBrowserContext;
|
const crBrowserContext = this._object as CRBrowserContext;
|
||||||
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) };
|
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startTracing(params: channels.BrowserContextStartTracingParams): Promise<void> {
|
||||||
|
await this._context.startTracing();
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopTracing(): Promise<channels.BrowserContextStopTracingResult> {
|
||||||
|
await this._context.stopTracing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ export type BrowserTypeLaunchParams = {
|
||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
_traceDir?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
firefoxUserPrefs?: any,
|
firefoxUserPrefs?: any,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
|
|
@ -250,6 +251,7 @@ export type BrowserTypeLaunchOptions = {
|
||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
_traceDir?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
firefoxUserPrefs?: any,
|
firefoxUserPrefs?: any,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
|
|
@ -277,6 +279,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
_traceDir?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
sdkLanguage: string,
|
sdkLanguage: string,
|
||||||
noDefaultViewport?: boolean,
|
noDefaultViewport?: boolean,
|
||||||
|
|
@ -311,7 +314,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
@ -347,6 +349,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
_traceDir?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
noDefaultViewport?: boolean,
|
noDefaultViewport?: boolean,
|
||||||
viewport?: {
|
viewport?: {
|
||||||
|
|
@ -380,7 +383,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
@ -470,7 +472,6 @@ export type BrowserNewContextParams = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
@ -527,7 +528,6 @@ export type BrowserNewContextOptions = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
@ -610,6 +610,8 @@ export interface BrowserContextChannel extends Channel {
|
||||||
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
|
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
|
||||||
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
||||||
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
|
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
|
||||||
|
startTracing(params?: BrowserContextStartTracingParams, metadata?: Metadata): Promise<BrowserContextStartTracingResult>;
|
||||||
|
stopTracing(params?: BrowserContextStopTracingParams, metadata?: Metadata): Promise<BrowserContextStopTracingResult>;
|
||||||
}
|
}
|
||||||
export type BrowserContextBindingCallEvent = {
|
export type BrowserContextBindingCallEvent = {
|
||||||
binding: BindingCallChannel,
|
binding: BindingCallChannel,
|
||||||
|
|
@ -786,6 +788,12 @@ export type BrowserContextNewCDPSessionOptions = {
|
||||||
export type BrowserContextNewCDPSessionResult = {
|
export type BrowserContextNewCDPSessionResult = {
|
||||||
session: CDPSessionChannel,
|
session: CDPSessionChannel,
|
||||||
};
|
};
|
||||||
|
export type BrowserContextStartTracingParams = {};
|
||||||
|
export type BrowserContextStartTracingOptions = {};
|
||||||
|
export type BrowserContextStartTracingResult = void;
|
||||||
|
export type BrowserContextStopTracingParams = {};
|
||||||
|
export type BrowserContextStopTracingOptions = {};
|
||||||
|
export type BrowserContextStopTracingResult = void;
|
||||||
|
|
||||||
// ----------- Page -----------
|
// ----------- Page -----------
|
||||||
export type PageInitializer = {
|
export type PageInitializer = {
|
||||||
|
|
@ -2850,7 +2858,6 @@ export type AndroidDeviceLaunchBrowserParams = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
@ -2895,7 +2902,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
recordVideo?: {
|
recordVideo?: {
|
||||||
dir: string,
|
dir: string,
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ LaunchOptions:
|
||||||
username: string?
|
username: string?
|
||||||
password: string?
|
password: string?
|
||||||
downloadsPath: string?
|
downloadsPath: string?
|
||||||
|
_traceDir: string?
|
||||||
chromiumSandbox: boolean?
|
chromiumSandbox: boolean?
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -312,7 +313,6 @@ ContextOptions:
|
||||||
- light
|
- light
|
||||||
- no-preference
|
- no-preference
|
||||||
acceptDownloads: boolean?
|
acceptDownloads: boolean?
|
||||||
_traceDir: string?
|
|
||||||
_debugName: string?
|
_debugName: string?
|
||||||
recordVideo:
|
recordVideo:
|
||||||
type: object?
|
type: object?
|
||||||
|
|
@ -601,6 +601,10 @@ BrowserContext:
|
||||||
returns:
|
returns:
|
||||||
session: CDPSession
|
session: CDPSession
|
||||||
|
|
||||||
|
startTracing:
|
||||||
|
|
||||||
|
stopTracing:
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
|
||||||
bindingCall:
|
bindingCall:
|
||||||
|
|
@ -2321,7 +2325,6 @@ AndroidDevice:
|
||||||
- light
|
- light
|
||||||
- no-preference
|
- no-preference
|
||||||
acceptDownloads: boolean?
|
acceptDownloads: boolean?
|
||||||
_traceDir: string?
|
|
||||||
_debugName: string?
|
_debugName: string?
|
||||||
recordVideo:
|
recordVideo:
|
||||||
type: object?
|
type: object?
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
password: tOptional(tString),
|
password: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
downloadsPath: tOptional(tString),
|
downloadsPath: tOptional(tString),
|
||||||
|
_traceDir: tOptional(tString),
|
||||||
chromiumSandbox: tOptional(tBoolean),
|
chromiumSandbox: tOptional(tBoolean),
|
||||||
firefoxUserPrefs: tOptional(tAny),
|
firefoxUserPrefs: tOptional(tAny),
|
||||||
slowMo: tOptional(tNumber),
|
slowMo: tOptional(tNumber),
|
||||||
|
|
@ -196,6 +197,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
password: tOptional(tString),
|
password: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
downloadsPath: tOptional(tString),
|
downloadsPath: tOptional(tString),
|
||||||
|
_traceDir: tOptional(tString),
|
||||||
chromiumSandbox: tOptional(tBoolean),
|
chromiumSandbox: tOptional(tBoolean),
|
||||||
sdkLanguage: tString,
|
sdkLanguage: tString,
|
||||||
noDefaultViewport: tOptional(tBoolean),
|
noDefaultViewport: tOptional(tBoolean),
|
||||||
|
|
@ -230,7 +232,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
hasTouch: tOptional(tBoolean),
|
hasTouch: tOptional(tBoolean),
|
||||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||||
acceptDownloads: tOptional(tBoolean),
|
acceptDownloads: tOptional(tBoolean),
|
||||||
_traceDir: tOptional(tString),
|
|
||||||
_debugName: tOptional(tString),
|
_debugName: tOptional(tString),
|
||||||
recordVideo: tOptional(tObject({
|
recordVideo: tOptional(tObject({
|
||||||
dir: tString,
|
dir: tString,
|
||||||
|
|
@ -289,7 +290,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
hasTouch: tOptional(tBoolean),
|
hasTouch: tOptional(tBoolean),
|
||||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||||
acceptDownloads: tOptional(tBoolean),
|
acceptDownloads: tOptional(tBoolean),
|
||||||
_traceDir: tOptional(tString),
|
|
||||||
_debugName: tOptional(tString),
|
_debugName: tOptional(tString),
|
||||||
recordVideo: tOptional(tObject({
|
recordVideo: tOptional(tObject({
|
||||||
dir: tString,
|
dir: tString,
|
||||||
|
|
@ -385,6 +385,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.BrowserContextNewCDPSessionParams = tObject({
|
scheme.BrowserContextNewCDPSessionParams = tObject({
|
||||||
page: tChannel('Page'),
|
page: tChannel('Page'),
|
||||||
});
|
});
|
||||||
|
scheme.BrowserContextStartTracingParams = tOptional(tObject({}));
|
||||||
|
scheme.BrowserContextStopTracingParams = tOptional(tObject({}));
|
||||||
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
|
|
@ -1091,7 +1093,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
hasTouch: tOptional(tBoolean),
|
hasTouch: tOptional(tBoolean),
|
||||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||||
acceptDownloads: tOptional(tBoolean),
|
acceptDownloads: tOptional(tBoolean),
|
||||||
_traceDir: tOptional(tString),
|
|
||||||
_debugName: tOptional(tString),
|
_debugName: tOptional(tString),
|
||||||
recordVideo: tOptional(tObject({
|
recordVideo: tOptional(tObject({
|
||||||
dir: tString,
|
dir: tString,
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export type BrowserOptions = PlaywrightOptions & {
|
||||||
isChromium: boolean,
|
isChromium: boolean,
|
||||||
channel?: types.BrowserChannel,
|
channel?: types.BrowserChannel,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
traceDir?: string,
|
||||||
headful?: boolean,
|
headful?: boolean,
|
||||||
persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
|
persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
|
||||||
browserProcess: BrowserProcess,
|
browserProcess: BrowserProcess,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { Debugger } from './supplements/debugger';
|
||||||
import { Tracer } from './trace/recorder/tracer';
|
import { Tracer } from './trace/recorder/tracer';
|
||||||
import { HarTracer } from './supplements/har/harTracer';
|
import { HarTracer } from './supplements/har/harTracer';
|
||||||
import { RecorderSupplement } from './supplements/recorderSupplement';
|
import { RecorderSupplement } from './supplements/recorderSupplement';
|
||||||
|
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||||
|
|
||||||
export abstract class BrowserContext extends SdkObject {
|
export abstract class BrowserContext extends SdkObject {
|
||||||
static Events = {
|
static Events = {
|
||||||
|
|
@ -56,6 +57,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
private _selectors?: Selectors;
|
private _selectors?: Selectors;
|
||||||
private _origins = new Set<string>();
|
private _origins = new Set<string>();
|
||||||
private _harTracer: HarTracer | undefined;
|
private _harTracer: HarTracer | undefined;
|
||||||
|
private _tracer: Tracer | null = null;
|
||||||
|
|
||||||
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
||||||
super(browser, 'browser-context');
|
super(browser, 'browser-context');
|
||||||
|
|
@ -88,10 +90,6 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
const contextDebugger = new Debugger(this);
|
const contextDebugger = new Debugger(this);
|
||||||
this.instrumentation.addListener(contextDebugger);
|
this.instrumentation.addListener(contextDebugger);
|
||||||
|
|
||||||
if (this._options._traceDir)
|
|
||||||
this.instrumentation.addListener(new Tracer(this, this._options._traceDir));
|
|
||||||
|
|
||||||
|
|
||||||
// When PWDEBUG=1, show inspector for each context.
|
// When PWDEBUG=1, show inspector for each context.
|
||||||
if (debugMode() === 'inspector')
|
if (debugMode() === 'inspector')
|
||||||
await RecorderSupplement.show(this, { pauseOnNextStatement: true });
|
await RecorderSupplement.show(this, { pauseOnNextStatement: true });
|
||||||
|
|
@ -103,7 +101,8 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
RecorderSupplement.showInspector(this);
|
RecorderSupplement.showInspector(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.instrumentation.onContextCreated();
|
if (debugMode() === 'console')
|
||||||
|
await this.extendInjectedScript(consoleApiSource.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _ensureVideosPath() {
|
async _ensureVideosPath() {
|
||||||
|
|
@ -264,6 +263,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
this._closedStatus = 'closing';
|
this._closedStatus = 'closing';
|
||||||
|
|
||||||
await this._harTracer?.flush();
|
await this._harTracer?.flush();
|
||||||
|
await this._tracer?.stop();
|
||||||
|
|
||||||
// Cleanup.
|
// Cleanup.
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
@ -292,7 +292,6 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
await this._browser.close();
|
await this._browser.close();
|
||||||
|
|
||||||
// Bookkeeping.
|
// Bookkeeping.
|
||||||
await this.instrumentation.onContextDestroyed();
|
|
||||||
this._didCloseInternal();
|
this._didCloseInternal();
|
||||||
}
|
}
|
||||||
await this._closePromise;
|
await this._closePromise;
|
||||||
|
|
@ -371,6 +370,21 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
this.on(BrowserContext.Events.Page, installInPage);
|
this.on(BrowserContext.Events.Page, installInPage);
|
||||||
return Promise.all(this.pages().map(installInPage));
|
return Promise.all(this.pages().map(installInPage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startTracing() {
|
||||||
|
if (this._tracer)
|
||||||
|
throw new Error('Tracing has already been started');
|
||||||
|
const traceDir = this._browser.options.traceDir;
|
||||||
|
if (!traceDir)
|
||||||
|
throw new Error('Tracing directory is not specified when launching the browser');
|
||||||
|
this._tracer = new Tracer(this, traceDir);
|
||||||
|
await this._tracer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopTracing() {
|
||||||
|
await this._tracer?.stop();
|
||||||
|
this._tracer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertBrowserContextIsNotOwned(context: BrowserContext) {
|
export function assertBrowserContextIsNotOwned(context: BrowserContext) {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
protocolLogger,
|
protocolLogger,
|
||||||
browserLogsCollector,
|
browserLogsCollector,
|
||||||
wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined,
|
wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined,
|
||||||
|
traceDir: options._traceDir,
|
||||||
};
|
};
|
||||||
if (persistent)
|
if (persistent)
|
||||||
validateBrowserContextOptions(persistent, browserOptions);
|
validateBrowserContextOptions(persistent, browserOptions);
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ export class SdkObject extends EventEmitter {
|
||||||
|
|
||||||
export interface Instrumentation {
|
export interface Instrumentation {
|
||||||
addListener(listener: InstrumentationListener): void;
|
addListener(listener: InstrumentationListener): void;
|
||||||
onContextCreated(): Promise<void>;
|
removeListener(listener: InstrumentationListener): void;
|
||||||
onContextDestroyed(): Promise<void>;
|
|
||||||
onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
||||||
onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): void;
|
onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): void;
|
||||||
|
|
@ -79,8 +78,6 @@ export interface Instrumentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstrumentationListener {
|
export interface InstrumentationListener {
|
||||||
onContextCreated?(): Promise<void>;
|
|
||||||
onContextDestroyed?(): Promise<void>;
|
|
||||||
onBeforeCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
onBeforeCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||||
onBeforeInputAction?(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
onBeforeInputAction?(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
||||||
onCallLog?(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): void;
|
onCallLog?(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): void;
|
||||||
|
|
@ -94,6 +91,8 @@ export function createInstrumentation(): Instrumentation {
|
||||||
get: (obj: any, prop: string) => {
|
get: (obj: any, prop: string) => {
|
||||||
if (prop === 'addListener')
|
if (prop === 'addListener')
|
||||||
return (listener: InstrumentationListener) => listeners.push(listener);
|
return (listener: InstrumentationListener) => listeners.push(listener);
|
||||||
|
if (prop === 'removeListener')
|
||||||
|
return (listener: InstrumentationListener) => listeners.splice(listeners.indexOf(listener), 1);
|
||||||
if (!prop.startsWith('on'))
|
if (!prop.startsWith('on'))
|
||||||
return obj[prop];
|
return obj[prop];
|
||||||
return async (...params: any[]) => {
|
return async (...params: any[]) => {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import util from 'util';
|
|
||||||
import { ContextResources, FrameSnapshot, ResourceSnapshot } from './snapshotTypes';
|
import { ContextResources, FrameSnapshot, ResourceSnapshot } from './snapshotTypes';
|
||||||
import { SnapshotRenderer } from './snapshotRenderer';
|
import { SnapshotRenderer } from './snapshotRenderer';
|
||||||
|
|
||||||
|
|
@ -87,27 +84,3 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
|
||||||
return snapshot?.renderer.find(r => r.snapshotName === snapshotName);
|
return snapshot?.renderer.find(r => r.snapshotName === snapshotName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
|
||||||
|
|
||||||
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
|
||||||
private _resourcesDir: string;
|
|
||||||
|
|
||||||
constructor(resourcesDir: string) {
|
|
||||||
super();
|
|
||||||
this._resourcesDir = resourcesDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
async load(tracePrefix: string) {
|
|
||||||
const networkTrace = await fsReadFileAsync(tracePrefix + '-network.trace', 'utf8');
|
|
||||||
const resources = networkTrace.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line)) as ResourceSnapshot[];
|
|
||||||
resources.forEach(r => this.addResource(r));
|
|
||||||
const snapshotTrace = await fsReadFileAsync(path.join(tracePrefix + '-dom.trace'), 'utf8');
|
|
||||||
const snapshots = snapshotTrace.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line)) as FrameSnapshot[];
|
|
||||||
snapshots.forEach(s => this.addFrameSnapshot(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceContent(sha1: string): Buffer | undefined {
|
|
||||||
return fs.readFileSync(path.join(this._resourcesDir, sha1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { EventEmitter } from 'events';
|
||||||
import { debugMode, isUnderTest, monotonicTime } from '../../utils/utils';
|
import { debugMode, isUnderTest, monotonicTime } from '../../utils/utils';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
|
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
|
||||||
import * as consoleApiSource from '../../generated/consoleApiSource';
|
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
|
|
||||||
const symbol = Symbol('Debugger');
|
const symbol = Symbol('Debugger');
|
||||||
|
|
@ -48,11 +47,6 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
|
||||||
return (context as any)[symbol] as Debugger | undefined;
|
return (context as any)[symbol] as Debugger | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onContextCreated() {
|
|
||||||
if (debugMode() === 'console')
|
|
||||||
await this._context.extendInjectedScript(consoleApiSource.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||||
if (shouldPauseOnCall(sdkObject, metadata) || (this._pauseOnNextStatement && shouldPauseOnStep(sdkObject, metadata)))
|
if (shouldPauseOnCall(sdkObject, metadata) || (this._pauseOnNextStatement && shouldPauseOnStep(sdkObject, metadata)))
|
||||||
await this.pause(sdkObject, metadata);
|
await this.pause(sdkObject, metadata);
|
||||||
|
|
|
||||||
|
|
@ -15,42 +15,33 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CallMetadata } from '../../instrumentation';
|
import { CallMetadata } from '../../instrumentation';
|
||||||
|
import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
|
|
||||||
export type ContextCreatedTraceEvent = {
|
export type ContextCreatedTraceEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'context-created',
|
type: 'context-metadata',
|
||||||
browserName: string,
|
browserName: string,
|
||||||
contextId: string,
|
|
||||||
deviceScaleFactor: number,
|
deviceScaleFactor: number,
|
||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
viewportSize?: { width: number, height: number },
|
viewportSize?: { width: number, height: number },
|
||||||
debugName?: string,
|
debugName?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextDestroyedTraceEvent = {
|
|
||||||
timestamp: number,
|
|
||||||
type: 'context-destroyed',
|
|
||||||
contextId: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageCreatedTraceEvent = {
|
export type PageCreatedTraceEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'page-created',
|
type: 'page-created',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageDestroyedTraceEvent = {
|
export type PageDestroyedTraceEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'page-destroyed',
|
type: 'page-destroyed',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScreencastFrameTraceEvent = {
|
export type ScreencastFrameTraceEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'page-screencast-frame',
|
type: 'page-screencast-frame',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
pageTimestamp: number,
|
pageTimestamp: number,
|
||||||
sha1: string,
|
sha1: string,
|
||||||
|
|
@ -61,14 +52,24 @@ export type ScreencastFrameTraceEvent = {
|
||||||
export type ActionTraceEvent = {
|
export type ActionTraceEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'action' | 'event',
|
type: 'action' | 'event',
|
||||||
contextId: string,
|
|
||||||
metadata: CallMetadata,
|
metadata: CallMetadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ResourceSnapshotTraceEvent = {
|
||||||
|
timestamp: number,
|
||||||
|
type: 'resource-snapshot',
|
||||||
|
snapshot: ResourceSnapshot,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FrameSnapshotTraceEvent = {
|
||||||
|
timestamp: number,
|
||||||
|
type: 'frame-snapshot',
|
||||||
|
snapshot: FrameSnapshot,
|
||||||
|
};
|
||||||
|
|
||||||
export type DialogOpenedEvent = {
|
export type DialogOpenedEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'dialog-opened',
|
type: 'dialog-opened',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
dialogType: string,
|
dialogType: string,
|
||||||
message?: string,
|
message?: string,
|
||||||
|
|
@ -77,7 +78,6 @@ export type DialogOpenedEvent = {
|
||||||
export type DialogClosedEvent = {
|
export type DialogClosedEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'dialog-closed',
|
type: 'dialog-closed',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
dialogType: string,
|
dialogType: string,
|
||||||
};
|
};
|
||||||
|
|
@ -85,7 +85,6 @@ export type DialogClosedEvent = {
|
||||||
export type NavigationEvent = {
|
export type NavigationEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'navigation',
|
type: 'navigation',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
url: string,
|
url: string,
|
||||||
sameDocument: boolean,
|
sameDocument: boolean,
|
||||||
|
|
@ -94,17 +93,17 @@ export type NavigationEvent = {
|
||||||
export type LoadEvent = {
|
export type LoadEvent = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
type: 'load',
|
type: 'load',
|
||||||
contextId: string,
|
|
||||||
pageId: string,
|
pageId: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceEvent =
|
export type TraceEvent =
|
||||||
ContextCreatedTraceEvent |
|
ContextCreatedTraceEvent |
|
||||||
ContextDestroyedTraceEvent |
|
|
||||||
PageCreatedTraceEvent |
|
PageCreatedTraceEvent |
|
||||||
PageDestroyedTraceEvent |
|
PageDestroyedTraceEvent |
|
||||||
ScreencastFrameTraceEvent |
|
ScreencastFrameTraceEvent |
|
||||||
ActionTraceEvent |
|
ActionTraceEvent |
|
||||||
|
ResourceSnapshotTraceEvent |
|
||||||
|
FrameSnapshotTraceEvent |
|
||||||
DialogOpenedEvent |
|
DialogOpenedEvent |
|
||||||
DialogClosedEvent |
|
DialogClosedEvent |
|
||||||
NavigationEvent |
|
NavigationEvent |
|
||||||
|
|
|
||||||
|
|
@ -18,41 +18,35 @@ import { EventEmitter } from 'events';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { Page } from '../page';
|
import { Page } from '../../page';
|
||||||
import { FrameSnapshot, ResourceSnapshot } from './snapshotTypes';
|
import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
import { Snapshotter, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
import { Snapshotter, SnapshotterBlob, SnapshotterDelegate } from '../../snapshot/snapshotter';
|
||||||
import { ElementHandle } from '../dom';
|
import { ElementHandle } from '../../dom';
|
||||||
|
import { TraceEvent } from '../common/traceEvents';
|
||||||
|
import { monotonicTime } from '../../../utils/utils';
|
||||||
|
|
||||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||||
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
|
||||||
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
|
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
|
||||||
|
|
||||||
const kSnapshotInterval = 100;
|
export class TraceSnapshotter extends EventEmitter implements SnapshotterDelegate {
|
||||||
|
|
||||||
export class PersistentSnapshotter extends EventEmitter implements SnapshotterDelegate {
|
|
||||||
private _snapshotter: Snapshotter;
|
private _snapshotter: Snapshotter;
|
||||||
private _resourcesDir: string;
|
private _resourcesDir: string;
|
||||||
private _writeArtifactChain = Promise.resolve();
|
private _writeArtifactChain = Promise.resolve();
|
||||||
private _networkTrace: string;
|
private _appendTraceEvent: (traceEvent: TraceEvent) => void;
|
||||||
private _snapshotTrace: string;
|
private _context: BrowserContext;
|
||||||
|
|
||||||
constructor(context: BrowserContext, tracePrefix: string, resourcesDir: string) {
|
constructor(context: BrowserContext, resourcesDir: string, appendTraceEvent: (traceEvent: TraceEvent) => void) {
|
||||||
super();
|
super();
|
||||||
|
this._context = context;
|
||||||
this._resourcesDir = resourcesDir;
|
this._resourcesDir = resourcesDir;
|
||||||
this._networkTrace = tracePrefix + '-network.trace';
|
|
||||||
this._snapshotTrace = tracePrefix + '-dom.trace';
|
|
||||||
this._snapshotter = new Snapshotter(context, this);
|
this._snapshotter = new Snapshotter(context, this);
|
||||||
|
this._appendTraceEvent = appendTraceEvent;
|
||||||
|
this._writeArtifactChain = fsMkdirAsync(resourcesDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(autoSnapshots: boolean): Promise<void> {
|
async start(): Promise<void> {
|
||||||
await fsMkdirAsync(this._resourcesDir, {recursive: true}).catch(() => {});
|
|
||||||
await fsAppendFileAsync(this._networkTrace, Buffer.from([]));
|
|
||||||
await fsAppendFileAsync(this._snapshotTrace, Buffer.from([]));
|
|
||||||
await this._snapshotter.initialize();
|
await this._snapshotter.initialize();
|
||||||
if (autoSnapshots)
|
|
||||||
await this._snapshotter.setAutoSnapshotInterval(kSnapshotInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispose() {
|
async dispose() {
|
||||||
|
|
@ -70,15 +64,19 @@ export class PersistentSnapshotter extends EventEmitter implements SnapshotterDe
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onResourceSnapshot(resource: ResourceSnapshot): void {
|
onResourceSnapshot(snapshot: ResourceSnapshot): void {
|
||||||
this._writeArtifactChain = this._writeArtifactChain.then(async () => {
|
this._appendTraceEvent({
|
||||||
await fsAppendFileAsync(this._networkTrace, JSON.stringify(resource) + '\n');
|
timestamp: monotonicTime(),
|
||||||
|
type: 'resource-snapshot',
|
||||||
|
snapshot,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
||||||
this._writeArtifactChain = this._writeArtifactChain.then(async () => {
|
this._appendTraceEvent({
|
||||||
await fsAppendFileAsync(this._snapshotTrace, JSON.stringify(snapshot) + '\n');
|
timestamp: monotonicTime(),
|
||||||
|
type: 'frame-snapshot',
|
||||||
|
snapshot,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { calculateSha1, createGuid, getFromENV, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
|
import { calculateSha1, getFromENV, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { Dialog } from '../../dialog';
|
import { Dialog } from '../../dialog';
|
||||||
import { ElementHandle } from '../../dom';
|
import { ElementHandle } from '../../dom';
|
||||||
|
|
@ -25,43 +25,57 @@ import { Frame, NavigationEvent } from '../../frames';
|
||||||
import { helper, RegisteredListener } from '../../helper';
|
import { helper, RegisteredListener } from '../../helper';
|
||||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
|
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
|
||||||
import { Page } from '../../page';
|
import { Page } from '../../page';
|
||||||
import { PersistentSnapshotter } from '../../snapshot/persistentSnapshotter';
|
|
||||||
import * as trace from '../common/traceEvents';
|
import * as trace from '../common/traceEvents';
|
||||||
|
import { TraceSnapshotter } from './traceSnapshotter';
|
||||||
|
|
||||||
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
||||||
const envTrace = getFromENV('PWTRACE_RESOURCE_DIR');
|
const envTrace = getFromENV('PWTRACE_RESOURCE_DIR');
|
||||||
|
|
||||||
export class Tracer implements InstrumentationListener {
|
export class Tracer implements InstrumentationListener {
|
||||||
private _contextId: string;
|
|
||||||
private _appendEventChain: Promise<string>;
|
private _appendEventChain: Promise<string>;
|
||||||
private _snapshotter: PersistentSnapshotter;
|
private _snapshotter: TraceSnapshotter;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[] = [];
|
||||||
private _disposed = false;
|
private _disposed = false;
|
||||||
private _pendingCalls = new Map<string, { sdkObject: SdkObject, metadata: CallMetadata }>();
|
private _pendingCalls = new Map<string, { sdkObject: SdkObject, metadata: CallMetadata }>();
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
|
||||||
constructor(context: BrowserContext, traceDir: string) {
|
constructor(context: BrowserContext, traceDir: string) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
|
this._context.instrumentation.addListener(this);
|
||||||
const resourcesDir = envTrace || path.join(traceDir, 'resources');
|
const resourcesDir = envTrace || path.join(traceDir, 'resources');
|
||||||
const tracePrefix = path.join(traceDir, context._options._debugName!);
|
const tracePrefix = path.join(traceDir, context._options._debugName!);
|
||||||
const traceFile = tracePrefix + '-actions.trace';
|
const traceFile = tracePrefix + '.trace';
|
||||||
this._contextId = 'context@' + createGuid();
|
|
||||||
this._appendEventChain = mkdirIfNeeded(traceFile).then(() => traceFile);
|
this._appendEventChain = mkdirIfNeeded(traceFile).then(() => traceFile);
|
||||||
|
this._snapshotter = new TraceSnapshotter(context, resourcesDir, traceEvent => this._appendTraceEvent(traceEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
const event: trace.ContextCreatedTraceEvent = {
|
const event: trace.ContextCreatedTraceEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'context-created',
|
type: 'context-metadata',
|
||||||
browserName: context._browser.options.name,
|
browserName: this._context._browser.options.name,
|
||||||
contextId: this._contextId,
|
isMobile: !!this._context._options.isMobile,
|
||||||
isMobile: !!context._options.isMobile,
|
deviceScaleFactor: this._context._options.deviceScaleFactor || 1,
|
||||||
deviceScaleFactor: context._options.deviceScaleFactor || 1,
|
viewportSize: this._context._options.viewport || undefined,
|
||||||
viewportSize: context._options.viewport || undefined,
|
debugName: this._context._options._debugName,
|
||||||
debugName: context._options._debugName,
|
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
this._snapshotter = new PersistentSnapshotter(context, tracePrefix, resourcesDir);
|
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(context, BrowserContext.Events.Page, this._onPage.bind(this)),
|
helper.addEventListener(this._context, BrowserContext.Events.Page, this._onPage.bind(this)),
|
||||||
];
|
];
|
||||||
|
await this._snapshotter.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
this._disposed = true;
|
||||||
|
this._context.instrumentation.removeListener(this);
|
||||||
|
helper.removeEventListeners(this._eventListeners);
|
||||||
|
await this._snapshotter.dispose();
|
||||||
|
for (const { sdkObject, metadata } of this._pendingCalls.values())
|
||||||
|
this.onAfterCall(sdkObject, metadata);
|
||||||
|
|
||||||
|
// Ensure all writes are finished.
|
||||||
|
await this._appendEventChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
_captureSnapshot(name: 'before' | 'after' | 'action' | 'event', sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle) {
|
_captureSnapshot(name: 'before' | 'after' | 'action' | 'event', sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle) {
|
||||||
|
|
@ -72,10 +86,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
this._snapshotter.captureSnapshot(sdkObject.attribution.page, snapshotName, element);
|
this._snapshotter.captureSnapshot(sdkObject.attribution.page, snapshotName, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onContextCreated(): Promise<void> {
|
|
||||||
await this._snapshotter.start(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
this._captureSnapshot('before', sdkObject, metadata);
|
this._captureSnapshot('before', sdkObject, metadata);
|
||||||
this._pendingCalls.set(metadata.id, { sdkObject, metadata });
|
this._pendingCalls.set(metadata.id, { sdkObject, metadata });
|
||||||
|
|
@ -86,13 +96,14 @@ export class Tracer implements InstrumentationListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
if (!this._pendingCalls.has(metadata.id))
|
||||||
|
return;
|
||||||
this._captureSnapshot('after', sdkObject, metadata);
|
this._captureSnapshot('after', sdkObject, metadata);
|
||||||
if (!sdkObject.attribution.page)
|
if (!sdkObject.attribution.page)
|
||||||
return;
|
return;
|
||||||
const event: trace.ActionTraceEvent = {
|
const event: trace.ActionTraceEvent = {
|
||||||
timestamp: metadata.startTime,
|
timestamp: metadata.startTime,
|
||||||
type: 'action',
|
type: 'action',
|
||||||
contextId: this._contextId,
|
|
||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
@ -105,7 +116,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.ActionTraceEvent = {
|
const event: trace.ActionTraceEvent = {
|
||||||
timestamp: metadata.startTime,
|
timestamp: metadata.startTime,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
contextId: this._contextId,
|
|
||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
@ -117,7 +127,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.PageCreatedTraceEvent = {
|
const event: trace.PageCreatedTraceEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'page-created',
|
type: 'page-created',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
@ -128,7 +137,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.DialogOpenedEvent = {
|
const event: trace.DialogOpenedEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'dialog-opened',
|
type: 'dialog-opened',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
dialogType: dialog.type(),
|
dialogType: dialog.type(),
|
||||||
message: dialog.message(),
|
message: dialog.message(),
|
||||||
|
|
@ -142,7 +150,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.DialogClosedEvent = {
|
const event: trace.DialogClosedEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'dialog-closed',
|
type: 'dialog-closed',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
dialogType: dialog.type(),
|
dialogType: dialog.type(),
|
||||||
};
|
};
|
||||||
|
|
@ -155,7 +162,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.NavigationEvent = {
|
const event: trace.NavigationEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'navigation',
|
type: 'navigation',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
url: navigationEvent.url,
|
url: navigationEvent.url,
|
||||||
sameDocument: !navigationEvent.newDocument,
|
sameDocument: !navigationEvent.newDocument,
|
||||||
|
|
@ -169,7 +175,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.LoadEvent = {
|
const event: trace.LoadEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'load',
|
type: 'load',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
@ -180,7 +185,6 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.ScreencastFrameTraceEvent = {
|
const event: trace.ScreencastFrameTraceEvent = {
|
||||||
type: 'page-screencast-frame',
|
type: 'page-screencast-frame',
|
||||||
pageId: page.guid,
|
pageId: page.guid,
|
||||||
contextId: this._contextId,
|
|
||||||
sha1,
|
sha1,
|
||||||
pageTimestamp: params.timestamp,
|
pageTimestamp: params.timestamp,
|
||||||
width: params.width,
|
width: params.width,
|
||||||
|
|
@ -197,30 +201,12 @@ export class Tracer implements InstrumentationListener {
|
||||||
const event: trace.PageDestroyedTraceEvent = {
|
const event: trace.PageDestroyedTraceEvent = {
|
||||||
timestamp: monotonicTime(),
|
timestamp: monotonicTime(),
|
||||||
type: 'page-destroyed',
|
type: 'page-destroyed',
|
||||||
contextId: this._contextId,
|
|
||||||
pageId,
|
pageId,
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onContextDestroyed() {
|
|
||||||
this._disposed = true;
|
|
||||||
helper.removeEventListeners(this._eventListeners);
|
|
||||||
await this._snapshotter.dispose();
|
|
||||||
for (const { sdkObject, metadata } of this._pendingCalls.values())
|
|
||||||
this.onAfterCall(sdkObject, metadata);
|
|
||||||
const event: trace.ContextDestroyedTraceEvent = {
|
|
||||||
timestamp: monotonicTime(),
|
|
||||||
type: 'context-destroyed',
|
|
||||||
contextId: this._contextId,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
|
||||||
|
|
||||||
// Ensure all writes are finished.
|
|
||||||
await this._appendEventChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _appendTraceEvent(event: any) {
|
private _appendTraceEvent(event: any) {
|
||||||
// Serialize all writes to the trace file.
|
// Serialize all writes to the trace file.
|
||||||
this._appendEventChain = this._appendEventChain.then(async traceFile => {
|
this._appendEventChain = this._appendEventChain.then(async traceFile => {
|
||||||
|
|
|
||||||
|
|
@ -14,24 +14,29 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import * as trace from '../common/traceEvents';
|
import * as trace from '../common/traceEvents';
|
||||||
import { ContextResources, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
import { ContextResources, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
import { SnapshotStorage } from '../../snapshot/snapshotStorage';
|
import { BaseSnapshotStorage, SnapshotStorage } from '../../snapshot/snapshotStorage';
|
||||||
export * as trace from '../common/traceEvents';
|
export * as trace from '../common/traceEvents';
|
||||||
|
|
||||||
export class TraceModel {
|
export class TraceModel {
|
||||||
contextEntries = new Map<string, ContextEntry>();
|
contextEntry: ContextEntry | undefined;
|
||||||
pageEntries = new Map<string, { contextEntry: ContextEntry, pageEntry: PageEntry }>();
|
pageEntries = new Map<string, PageEntry>();
|
||||||
contextResources = new Map<string, ContextResources>();
|
contextResources = new Map<string, ContextResources>();
|
||||||
|
private _snapshotStorage: PersistentSnapshotStorage;
|
||||||
|
|
||||||
|
constructor(snapshotStorage: PersistentSnapshotStorage) {
|
||||||
|
this._snapshotStorage = snapshotStorage;
|
||||||
|
}
|
||||||
|
|
||||||
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
||||||
for (const event of events)
|
for (const event of events)
|
||||||
this.appendEvent(event);
|
this.appendEvent(event);
|
||||||
const actions: ActionEntry[] = [];
|
const actions: ActionEntry[] = [];
|
||||||
for (const context of this.contextEntries.values()) {
|
for (const page of this.contextEntry!.pages)
|
||||||
for (const page of context.pages)
|
actions.push(...page.actions);
|
||||||
actions.push(...page.actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resources = snapshotStorage.resources().reverse();
|
const resources = snapshotStorage.resources().reverse();
|
||||||
actions.reverse();
|
actions.reverse();
|
||||||
|
|
@ -45,19 +50,13 @@ export class TraceModel {
|
||||||
|
|
||||||
appendEvent(event: trace.TraceEvent) {
|
appendEvent(event: trace.TraceEvent) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'context-created': {
|
case 'context-metadata': {
|
||||||
this.contextEntries.set(event.contextId, {
|
this.contextEntry = {
|
||||||
startTime: Number.MAX_VALUE,
|
startTime: Number.MAX_VALUE,
|
||||||
endTime: Number.MIN_VALUE,
|
endTime: Number.MIN_VALUE,
|
||||||
created: event,
|
created: event,
|
||||||
destroyed: undefined as any,
|
|
||||||
pages: [],
|
pages: [],
|
||||||
});
|
};
|
||||||
this.contextResources.set(event.contextId, new Map());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'context-destroyed': {
|
|
||||||
this.contextEntries.get(event.contextId)!.destroyed = event;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'page-created': {
|
case 'page-created': {
|
||||||
|
|
@ -68,22 +67,21 @@ export class TraceModel {
|
||||||
interestingEvents: [],
|
interestingEvents: [],
|
||||||
screencastFrames: [],
|
screencastFrames: [],
|
||||||
};
|
};
|
||||||
const contextEntry = this.contextEntries.get(event.contextId)!;
|
this.pageEntries.set(event.pageId, pageEntry);
|
||||||
this.pageEntries.set(event.pageId, { pageEntry, contextEntry });
|
this.contextEntry!.pages.push(pageEntry);
|
||||||
contextEntry.pages.push(pageEntry);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'page-destroyed': {
|
case 'page-destroyed': {
|
||||||
this.pageEntries.get(event.pageId)!.pageEntry.destroyed = event;
|
this.pageEntries.get(event.pageId)!.destroyed = event;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'page-screencast-frame': {
|
case 'page-screencast-frame': {
|
||||||
this.pageEntries.get(event.pageId)!.pageEntry.screencastFrames.push(event);
|
this.pageEntries.get(event.pageId)!.screencastFrames.push(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'action': {
|
case 'action': {
|
||||||
const metadata = event.metadata;
|
const metadata = event.metadata;
|
||||||
const { pageEntry } = this.pageEntries.get(metadata.pageId!)!;
|
const pageEntry = this.pageEntries.get(metadata.pageId!)!;
|
||||||
const action: ActionEntry = {
|
const action: ActionEntry = {
|
||||||
actionId: metadata.id,
|
actionId: metadata.id,
|
||||||
resources: [],
|
resources: [],
|
||||||
|
|
@ -96,14 +94,19 @@ export class TraceModel {
|
||||||
case 'dialog-closed':
|
case 'dialog-closed':
|
||||||
case 'navigation':
|
case 'navigation':
|
||||||
case 'load': {
|
case 'load': {
|
||||||
const { pageEntry } = this.pageEntries.get(event.pageId)!;
|
const pageEntry = this.pageEntries.get(event.pageId)!;
|
||||||
pageEntry.interestingEvents.push(event);
|
pageEntry.interestingEvents.push(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'resource-snapshot':
|
||||||
|
this._snapshotStorage.addResource(event.snapshot);
|
||||||
|
break;
|
||||||
|
case 'frame-snapshot':
|
||||||
|
this._snapshotStorage.addFrameSnapshot(event.snapshot);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const contextEntry = this.contextEntries.get(event.contextId)!;
|
this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.timestamp);
|
||||||
contextEntry.startTime = Math.min(contextEntry.startTime, event.timestamp);
|
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.timestamp);
|
||||||
contextEntry.endTime = Math.max(contextEntry.endTime, event.timestamp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +114,6 @@ export type ContextEntry = {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
created: trace.ContextCreatedTraceEvent;
|
created: trace.ContextCreatedTraceEvent;
|
||||||
destroyed: trace.ContextDestroyedTraceEvent;
|
|
||||||
pages: PageEntry[];
|
pages: PageEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,3 +136,16 @@ export type ActionEntry = trace.ActionTraceEvent & {
|
||||||
actionId: string;
|
actionId: string;
|
||||||
resources: ResourceSnapshot[]
|
resources: ResourceSnapshot[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
||||||
|
private _resourcesDir: string;
|
||||||
|
|
||||||
|
constructor(resourcesDir: string) {
|
||||||
|
super();
|
||||||
|
this._resourcesDir = resourcesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceContent(sha1: string): Buffer | undefined {
|
||||||
|
return fs.readFileSync(path.join(this._resourcesDir, sha1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,10 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createPlaywright } from '../../playwright';
|
import { createPlaywright } from '../../playwright';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { TraceModel } from './traceModel';
|
import { PersistentSnapshotStorage, TraceModel } from './traceModel';
|
||||||
import { TraceEvent } from '../common/traceEvents';
|
import { TraceEvent } from '../common/traceEvents';
|
||||||
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
||||||
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
||||||
import { PersistentSnapshotStorage } from '../../snapshot/snapshotStorage';
|
|
||||||
import * as consoleApiSource from '../../../generated/consoleApiSource';
|
import * as consoleApiSource from '../../../generated/consoleApiSource';
|
||||||
import { isUnderTest } from '../../../utils/utils';
|
import { isUnderTest } from '../../../utils/utils';
|
||||||
import { internalCallMetadata } from '../../instrumentation';
|
import { internalCallMetadata } from '../../instrumentation';
|
||||||
|
|
@ -51,9 +50,9 @@ class TraceViewer {
|
||||||
// - "/snapshot/pageId/..." - actual snapshot html.
|
// - "/snapshot/pageId/..." - actual snapshot html.
|
||||||
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
|
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
|
||||||
// and translates them into "/resources/<resourceId>".
|
// and translates them into "/resources/<resourceId>".
|
||||||
const actionTraces = fs.readdirSync(traceDir).filter(name => name.endsWith('-actions.trace'));
|
const actionTraces = fs.readdirSync(traceDir).filter(name => name.endsWith('.trace'));
|
||||||
const debugNames = actionTraces.map(name => {
|
const debugNames = actionTraces.map(name => {
|
||||||
const tracePrefix = path.join(traceDir, name.substring(0, name.indexOf('-actions.trace')));
|
const tracePrefix = path.join(traceDir, name.substring(0, name.indexOf('.trace')));
|
||||||
return path.basename(tracePrefix);
|
return path.basename(tracePrefix);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -76,12 +75,11 @@ class TraceViewer {
|
||||||
response.statusCode = 200;
|
response.statusCode = 200;
|
||||||
response.setHeader('Content-Type', 'application/json');
|
response.setHeader('Content-Type', 'application/json');
|
||||||
(async () => {
|
(async () => {
|
||||||
await snapshotStorage.load(tracePrefix);
|
const traceContent = await fsReadFileAsync(tracePrefix + '.trace', 'utf8');
|
||||||
const traceContent = await fsReadFileAsync(tracePrefix + '-actions.trace', 'utf8');
|
|
||||||
const events = traceContent.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line)) as TraceEvent[];
|
const events = traceContent.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line)) as TraceEvent[];
|
||||||
const model = new TraceModel();
|
const model = new TraceModel(snapshotStorage);
|
||||||
model.appendEvents(events, snapshotStorage);
|
model.appendEvents(events, snapshotStorage);
|
||||||
response.end(JSON.stringify(model.contextEntries.values().next().value));
|
response.end(JSON.stringify(model.contextEntry));
|
||||||
})().catch(e => console.error(e));
|
})().catch(e => console.error(e));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,6 @@ export type BrowserContextOptions = {
|
||||||
path: string
|
path: string
|
||||||
},
|
},
|
||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
_traceDir?: string,
|
|
||||||
_debugName?: string,
|
_debugName?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -273,6 +272,7 @@ type LaunchOptionsBase = {
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
useWebSocket?: boolean,
|
useWebSocket?: boolean,
|
||||||
|
_traceDir?: string,
|
||||||
};
|
};
|
||||||
export type LaunchOptions = LaunchOptionsBase & {
|
export type LaunchOptions = LaunchOptionsBase & {
|
||||||
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
||||||
|
|
|
||||||
|
|
@ -103,18 +103,12 @@ const emptyContext: ContextEntry = {
|
||||||
endTime: now,
|
endTime: now,
|
||||||
created: {
|
created: {
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
type: 'context-created',
|
type: 'context-metadata',
|
||||||
browserName: '',
|
browserName: '',
|
||||||
contextId: '<empty>',
|
|
||||||
deviceScaleFactor: 1,
|
deviceScaleFactor: 1,
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
viewportSize: { width: 1280, height: 800 },
|
viewportSize: { width: 1280, height: 800 },
|
||||||
debugName: '<empty>',
|
debugName: '<empty>',
|
||||||
},
|
},
|
||||||
destroyed: {
|
|
||||||
timestamp: now,
|
|
||||||
type: 'context-destroyed',
|
|
||||||
contextId: '<empty>',
|
|
||||||
},
|
|
||||||
pages: []
|
pages: []
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -121,10 +121,12 @@ export class PlaywrightEnv implements Env<PlaywrightTestArgs> {
|
||||||
require('../../lib/utils/utils').setUnderTest();
|
require('../../lib/utils/utils').setUnderTest();
|
||||||
this._playwright = await this._mode.setup(workerInfo);
|
this._playwright = await this._mode.setup(workerInfo);
|
||||||
this._browserType = this._playwright[this._browserName];
|
this._browserType = this._playwright[this._browserName];
|
||||||
this._browserOptions = {
|
const options = {
|
||||||
...this._options,
|
...this._options,
|
||||||
|
_traceDir: this._options.traceDir,
|
||||||
handleSIGINT: false,
|
handleSIGINT: false,
|
||||||
};
|
};
|
||||||
|
this._browserOptions = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createUserDataDir() {
|
private async _createUserDataDir() {
|
||||||
|
|
@ -169,8 +171,6 @@ export class PlaywrightEnv implements Env<PlaywrightTestArgs> {
|
||||||
testInfo.data.mode = this._options.mode;
|
testInfo.data.mode = this._options.mode;
|
||||||
if (this._options.video)
|
if (this._options.video)
|
||||||
testInfo.data.video = true;
|
testInfo.data.video = true;
|
||||||
if (this._options.traceDir)
|
|
||||||
testInfo.data.trace = true;
|
|
||||||
return {
|
return {
|
||||||
playwright: this._playwright,
|
playwright: this._playwright,
|
||||||
browserName: this._browserName,
|
browserName: this._browserName,
|
||||||
|
|
@ -236,7 +236,6 @@ export class BrowserEnv extends PlaywrightEnv implements Env<BrowserTestArgs> {
|
||||||
const debugName = path.relative(testInfo.config.outputDir, testInfo.outputPath('')).replace(/[\/\\]/g, '-');
|
const debugName = path.relative(testInfo.config.outputDir, testInfo.outputPath('')).replace(/[\/\\]/g, '-');
|
||||||
const contextOptions = {
|
const contextOptions = {
|
||||||
recordVideo: this._options.video ? { dir: testInfo.outputPath('') } : undefined,
|
recordVideo: this._options.video ? { dir: testInfo.outputPath('') } : undefined,
|
||||||
_traceDir: this._options.traceDir,
|
|
||||||
_debugName: debugName,
|
_debugName: debugName,
|
||||||
...this._contextOptions,
|
...this._contextOptions,
|
||||||
} as BrowserContextOptions;
|
} as BrowserContextOptions;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue