chore: include context options into the trace (#6572)
This commit is contained in:
parent
7b844c5fab
commit
d7c6720ce7
|
|
@ -199,7 +199,7 @@ export class Snapshotter {
|
||||||
const resource: ResourceSnapshot = {
|
const resource: ResourceSnapshot = {
|
||||||
pageId: response.frame()._page.guid,
|
pageId: response.frame()._page.guid,
|
||||||
frameId: response.frame().guid,
|
frameId: response.frame().guid,
|
||||||
resourceId: 'resource@' + createGuid(),
|
resourceId: response.guid,
|
||||||
url,
|
url,
|
||||||
type: response.request().resourceType(),
|
type: response.request().resourceType(),
|
||||||
contentType,
|
contentType,
|
||||||
|
|
|
||||||
|
|
@ -16,60 +16,40 @@
|
||||||
|
|
||||||
import { CallMetadata } from '../../instrumentation';
|
import { CallMetadata } from '../../instrumentation';
|
||||||
import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
|
import { BrowserContextOptions } from '../../types';
|
||||||
|
|
||||||
export type ContextCreatedTraceEvent = {
|
export type ContextCreatedTraceEvent = {
|
||||||
timestamp: number,
|
type: 'context-options',
|
||||||
type: 'context-metadata',
|
|
||||||
browserName: string,
|
browserName: string,
|
||||||
deviceScaleFactor: number,
|
options: BrowserContextOptions
|
||||||
isMobile: boolean,
|
|
||||||
viewportSize?: { width: number, height: number },
|
|
||||||
debugName?: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageCreatedTraceEvent = {
|
|
||||||
timestamp: number,
|
|
||||||
type: 'page-created',
|
|
||||||
pageId: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageDestroyedTraceEvent = {
|
|
||||||
timestamp: number,
|
|
||||||
type: 'page-destroyed',
|
|
||||||
pageId: string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScreencastFrameTraceEvent = {
|
export type ScreencastFrameTraceEvent = {
|
||||||
timestamp: number,
|
|
||||||
type: 'screencast-frame',
|
type: 'screencast-frame',
|
||||||
pageId: string,
|
pageId: string,
|
||||||
sha1: string,
|
sha1: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
|
timestamp: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActionTraceEvent = {
|
export type ActionTraceEvent = {
|
||||||
timestamp: number,
|
|
||||||
type: 'action' | 'event',
|
type: 'action' | 'event',
|
||||||
metadata: CallMetadata,
|
metadata: CallMetadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResourceSnapshotTraceEvent = {
|
export type ResourceSnapshotTraceEvent = {
|
||||||
timestamp: number,
|
|
||||||
type: 'resource-snapshot',
|
type: 'resource-snapshot',
|
||||||
snapshot: ResourceSnapshot,
|
snapshot: ResourceSnapshot,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FrameSnapshotTraceEvent = {
|
export type FrameSnapshotTraceEvent = {
|
||||||
timestamp: number,
|
|
||||||
type: 'frame-snapshot',
|
type: 'frame-snapshot',
|
||||||
snapshot: FrameSnapshot,
|
snapshot: FrameSnapshot,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceEvent =
|
export type TraceEvent =
|
||||||
ContextCreatedTraceEvent |
|
ContextCreatedTraceEvent |
|
||||||
PageCreatedTraceEvent |
|
|
||||||
PageDestroyedTraceEvent |
|
|
||||||
ScreencastFrameTraceEvent |
|
ScreencastFrameTraceEvent |
|
||||||
ActionTraceEvent |
|
ActionTraceEvent |
|
||||||
ResourceSnapshotTraceEvent |
|
ResourceSnapshotTraceEvent |
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
import { Snapshotter, SnapshotterBlob, SnapshotterDelegate } from '../../snapshot/snapshotter';
|
import { Snapshotter, SnapshotterBlob, SnapshotterDelegate } from '../../snapshot/snapshotter';
|
||||||
import { ElementHandle } from '../../dom';
|
import { ElementHandle } from '../../dom';
|
||||||
import { TraceEvent } from '../common/traceEvents';
|
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));
|
||||||
|
|
||||||
|
|
@ -66,18 +65,10 @@ export class TraceSnapshotter extends EventEmitter implements SnapshotterDelegat
|
||||||
}
|
}
|
||||||
|
|
||||||
onResourceSnapshot(snapshot: ResourceSnapshot): void {
|
onResourceSnapshot(snapshot: ResourceSnapshot): void {
|
||||||
this._appendTraceEvent({
|
this._appendTraceEvent({ type: 'resource-snapshot', snapshot });
|
||||||
timestamp: monotonicTime(),
|
|
||||||
type: 'resource-snapshot',
|
|
||||||
snapshot,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
||||||
this._appendTraceEvent({
|
this._appendTraceEvent({ type: 'frame-snapshot', snapshot });
|
||||||
timestamp: monotonicTime(),
|
|
||||||
type: 'frame-snapshot',
|
|
||||||
snapshot,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,9 @@ export class Tracing implements InstrumentationListener {
|
||||||
|
|
||||||
this._appendEventChain = mkdirIfNeeded(this._traceFile);
|
this._appendEventChain = mkdirIfNeeded(this._traceFile);
|
||||||
const event: trace.ContextCreatedTraceEvent = {
|
const event: trace.ContextCreatedTraceEvent = {
|
||||||
timestamp: monotonicTime(),
|
type: 'context-options',
|
||||||
type: 'context-metadata',
|
|
||||||
browserName: this._context._browser.options.name,
|
browserName: this._context._browser.options.name,
|
||||||
isMobile: !!this._context._options.isMobile,
|
options: this._context._options
|
||||||
deviceScaleFactor: this._context._options.deviceScaleFactor || 1,
|
|
||||||
viewportSize: this._context._options.viewport || undefined,
|
|
||||||
debugName: this._context._options._debugName,
|
|
||||||
};
|
};
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
|
|
@ -154,34 +150,18 @@ export class Tracing implements InstrumentationListener {
|
||||||
if (!sdkObject.attribution.page)
|
if (!sdkObject.attribution.page)
|
||||||
return;
|
return;
|
||||||
await this._captureSnapshot('after', sdkObject, metadata);
|
await this._captureSnapshot('after', sdkObject, metadata);
|
||||||
const event: trace.ActionTraceEvent = {
|
const event: trace.ActionTraceEvent = { type: 'action', metadata };
|
||||||
timestamp: metadata.startTime,
|
|
||||||
type: 'action',
|
|
||||||
metadata,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEvent(sdkObject: SdkObject, metadata: CallMetadata) {
|
onEvent(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
if (!sdkObject.attribution.page)
|
if (!sdkObject.attribution.page)
|
||||||
return;
|
return;
|
||||||
const event: trace.ActionTraceEvent = {
|
const event: trace.ActionTraceEvent = { type: 'event', metadata };
|
||||||
timestamp: metadata.startTime,
|
|
||||||
type: 'event',
|
|
||||||
metadata,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onPage(screenshots: boolean | undefined, page: Page) {
|
private _onPage(screenshots: boolean | undefined, page: Page) {
|
||||||
const pageId = page.guid;
|
|
||||||
|
|
||||||
const event: trace.PageCreatedTraceEvent = {
|
|
||||||
timestamp: monotonicTime(),
|
|
||||||
type: 'page-created',
|
|
||||||
pageId,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
|
||||||
if (screenshots)
|
if (screenshots)
|
||||||
page.setScreencastOptions({ width: 800, height: 600, quality: 90 });
|
page.setScreencastOptions({ width: 800, height: 600, quality: 90 });
|
||||||
|
|
||||||
|
|
@ -201,14 +181,6 @@ export class Tracing implements InstrumentationListener {
|
||||||
await fsWriteFileAsync(path.join(this._resourcesDir!, sha1), params.buffer).catch(() => {});
|
await fsWriteFileAsync(path.join(this._resourcesDir!, sha1), params.buffer).catch(() => {});
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
helper.addEventListener(page, Page.Events.Close, () => {
|
|
||||||
const event: trace.PageDestroyedTraceEvent = {
|
|
||||||
timestamp: monotonicTime(),
|
|
||||||
type: 'page-destroyed',
|
|
||||||
pageId,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,25 @@ 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 { BaseSnapshotStorage, SnapshotStorage } from '../../snapshot/snapshotStorage';
|
import { BaseSnapshotStorage, SnapshotStorage } from '../../snapshot/snapshotStorage';
|
||||||
|
import { BrowserContextOptions } from '../../types';
|
||||||
export * as trace from '../common/traceEvents';
|
export * as trace from '../common/traceEvents';
|
||||||
|
|
||||||
export class TraceModel {
|
export class TraceModel {
|
||||||
contextEntry: ContextEntry | undefined;
|
contextEntry: ContextEntry;
|
||||||
pageEntries = new Map<string, PageEntry>();
|
pageEntries = new Map<string, PageEntry>();
|
||||||
contextResources = new Map<string, ContextResources>();
|
contextResources = new Map<string, ContextResources>();
|
||||||
private _snapshotStorage: PersistentSnapshotStorage;
|
private _snapshotStorage: PersistentSnapshotStorage;
|
||||||
|
|
||||||
constructor(snapshotStorage: PersistentSnapshotStorage) {
|
constructor(snapshotStorage: PersistentSnapshotStorage) {
|
||||||
this._snapshotStorage = snapshotStorage;
|
this._snapshotStorage = snapshotStorage;
|
||||||
|
this.contextEntry = {
|
||||||
|
startTime: Number.MAX_VALUE,
|
||||||
|
endTime: Number.MIN_VALUE,
|
||||||
|
browserName: '',
|
||||||
|
options: { sdkLanguage: '' },
|
||||||
|
pages: [],
|
||||||
|
resources: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
||||||
|
|
@ -40,49 +49,41 @@ export class TraceModel {
|
||||||
this.contextEntry!.resources = snapshotStorage.resources();
|
this.contextEntry!.resources = snapshotStorage.resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _pageEntry(pageId: string): PageEntry {
|
||||||
|
let pageEntry = this.pageEntries.get(pageId);
|
||||||
|
if (!pageEntry) {
|
||||||
|
pageEntry = {
|
||||||
|
actions: [],
|
||||||
|
events: [],
|
||||||
|
screencastFrames: [],
|
||||||
|
};
|
||||||
|
this.pageEntries.set(pageId, pageEntry);
|
||||||
|
this.contextEntry.pages.push(pageEntry);
|
||||||
|
}
|
||||||
|
return pageEntry;
|
||||||
|
}
|
||||||
|
|
||||||
appendEvent(event: trace.TraceEvent) {
|
appendEvent(event: trace.TraceEvent) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'context-metadata': {
|
case 'context-options': {
|
||||||
this.contextEntry = {
|
this.contextEntry.browserName = event.browserName;
|
||||||
startTime: Number.MAX_VALUE,
|
this.contextEntry.options = event.options;
|
||||||
endTime: Number.MIN_VALUE,
|
|
||||||
created: event,
|
|
||||||
pages: [],
|
|
||||||
resources: []
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'page-created': {
|
|
||||||
const pageEntry: PageEntry = {
|
|
||||||
created: event,
|
|
||||||
destroyed: undefined as any,
|
|
||||||
actions: [],
|
|
||||||
events: [],
|
|
||||||
screencastFrames: [],
|
|
||||||
};
|
|
||||||
this.pageEntries.set(event.pageId, pageEntry);
|
|
||||||
this.contextEntry!.pages.push(pageEntry);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'page-destroyed': {
|
|
||||||
this.pageEntries.get(event.pageId)!.destroyed = event;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'screencast-frame': {
|
case 'screencast-frame': {
|
||||||
this.pageEntries.get(event.pageId)!.screencastFrames.push(event);
|
this._pageEntry(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!)!;
|
if (metadata.pageId)
|
||||||
pageEntry.actions.push(event);
|
this._pageEntry(metadata.pageId).actions.push(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'event': {
|
case 'event': {
|
||||||
const metadata = event.metadata;
|
const metadata = event.metadata;
|
||||||
const pageEntry = this.pageEntries.get(metadata.pageId!);
|
if (metadata.pageId)
|
||||||
if (pageEntry)
|
this._pageEntry(metadata.pageId).events.push(event);
|
||||||
pageEntry.events.push(event);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'resource-snapshot':
|
case 'resource-snapshot':
|
||||||
|
|
@ -102,14 +103,13 @@ export class TraceModel {
|
||||||
export type ContextEntry = {
|
export type ContextEntry = {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
created: trace.ContextCreatedTraceEvent;
|
browserName: string;
|
||||||
|
options: BrowserContextOptions;
|
||||||
pages: PageEntry[];
|
pages: PageEntry[];
|
||||||
resources: ResourceSnapshot[];
|
resources: ResourceSnapshot[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageEntry = {
|
export type PageEntry = {
|
||||||
created: trace.PageCreatedTraceEvent;
|
|
||||||
destroyed: trace.PageDestroyedTraceEvent;
|
|
||||||
actions: trace.ActionTraceEvent[];
|
actions: trace.ActionTraceEvent[];
|
||||||
events: trace.ActionTraceEvent[];
|
events: trace.ActionTraceEvent[];
|
||||||
screencastFrames: {
|
screencastFrames: {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export const FilmStrip: React.FunctionComponent<{
|
||||||
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
|
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
|
||||||
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
|
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
|
||||||
}
|
}
|
||||||
const previewSize = inscribe(context.created.viewportSize!, { width: 600, height: 600 });
|
const previewSize = inscribe(context.options.viewport!, { width: 600, height: 600 });
|
||||||
|
|
||||||
return <div className='film-strip' ref={ref}>{
|
return <div className='film-strip' ref={ref}>{
|
||||||
context.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane
|
context.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,11 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const actions: ActionTraceEvent[] = [];
|
const actions: ActionTraceEvent[] = [];
|
||||||
for (const page of context.pages)
|
for (const page of context.pages)
|
||||||
actions.push(...page.actions);
|
actions.push(...page.actions);
|
||||||
actions.sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
const nextAction = selectedAction ? actions[actions.indexOf(selectedAction) + 1] : undefined;
|
const nextAction = selectedAction ? actions[actions.indexOf(selectedAction) + 1] : undefined;
|
||||||
return { actions, nextAction };
|
return { actions, nextAction };
|
||||||
}, [context, selectedAction]);
|
}, [context, selectedAction]);
|
||||||
|
|
||||||
const snapshotSize = context.created.viewportSize || { width: 1280, height: 720 };
|
const snapshotSize = context.options.viewport || { width: 1280, height: 720 };
|
||||||
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
||||||
|
|
||||||
return <div className='vbox workbench'>
|
return <div className='vbox workbench'>
|
||||||
|
|
@ -103,14 +102,13 @@ const now = performance.now();
|
||||||
const emptyContext: ContextEntry = {
|
const emptyContext: ContextEntry = {
|
||||||
startTime: now,
|
startTime: now,
|
||||||
endTime: now,
|
endTime: now,
|
||||||
created: {
|
browserName: '',
|
||||||
timestamp: now,
|
options: {
|
||||||
type: 'context-metadata',
|
sdkLanguage: '',
|
||||||
browserName: '',
|
|
||||||
deviceScaleFactor: 1,
|
deviceScaleFactor: 1,
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
viewportSize: { width: 1280, height: 800 },
|
viewport: { width: 1280, height: 800 },
|
||||||
debugName: '<empty>',
|
_debugName: '<empty>',
|
||||||
},
|
},
|
||||||
pages: [],
|
pages: [],
|
||||||
resources: []
|
resources: []
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,7 @@ test('should collect trace', async ({ context, page, server, browserName }, test
|
||||||
await (context as any).tracing.export(testInfo.outputPath('trace.zip'));
|
await (context as any).tracing.export(testInfo.outputPath('trace.zip'));
|
||||||
|
|
||||||
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
|
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
|
||||||
expect(events[0].type).toBe('context-metadata');
|
expect(events[0].type).toBe('context-options');
|
||||||
expect(events[1].type).toBe('page-created');
|
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
|
||||||
|
|
@ -80,8 +79,7 @@ test('should collect two traces', async ({ context, page, server }, testInfo) =>
|
||||||
|
|
||||||
{
|
{
|
||||||
const { events } = await parseTrace(testInfo.outputPath('trace1.zip'));
|
const { events } = await parseTrace(testInfo.outputPath('trace1.zip'));
|
||||||
expect(events[0].type).toBe('context-metadata');
|
expect(events[0].type).toBe('context-options');
|
||||||
expect(events[1].type).toBe('page-created');
|
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
|
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
|
||||||
|
|
@ -91,8 +89,7 @@ test('should collect two traces', async ({ context, page, server }, testInfo) =>
|
||||||
|
|
||||||
{
|
{
|
||||||
const { events } = await parseTrace(testInfo.outputPath('trace2.zip'));
|
const { events } = await parseTrace(testInfo.outputPath('trace2.zip'));
|
||||||
expect(events[0].type).toBe('context-metadata');
|
expect(events[0].type).toBe('context-options');
|
||||||
expect(events[1].type).toBe('page-created');
|
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeFalsy();
|
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeFalsy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeFalsy();
|
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeFalsy();
|
||||||
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeFalsy();
|
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeFalsy();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue