chore: include context options into the trace (#6572)

This commit is contained in:
Pavel Feldman 2021-05-13 22:36:34 -07:00 committed by GitHub
parent 7b844c5fab
commit d7c6720ce7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 55 additions and 117 deletions

View file

@ -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,

View file

@ -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 |

View file

@ -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,
});
} }
} }

View file

@ -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);
})
); );
} }

View file

@ -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: {

View file

@ -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

View file

@ -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: []

View file

@ -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();