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