From 985f932033f77283007833be56b8833e22e7c12b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 8 Feb 2022 12:27:29 -0800 Subject: [PATCH] chore(trace-viewer): introduce MultiTraceModel (#11922) --- .../src/web/traceViewer/entries.ts | 2 - .../src/web/traceViewer/ui/filmStrip.tsx | 5 +- .../src/web/traceViewer/ui/modelUtil.ts | 57 ++++++++++++------- .../src/web/traceViewer/ui/timeline.tsx | 4 +- .../src/web/traceViewer/ui/workbench.tsx | 50 ++++++++-------- 5 files changed, 65 insertions(+), 53 deletions(-) diff --git a/packages/playwright-core/src/web/traceViewer/entries.ts b/packages/playwright-core/src/web/traceViewer/entries.ts index 25d49601f8..9862deafe4 100644 --- a/packages/playwright-core/src/web/traceViewer/entries.ts +++ b/packages/playwright-core/src/web/traceViewer/entries.ts @@ -34,8 +34,6 @@ export type ContextEntry = { hasSource: boolean; }; -export type MergedContexts = Pick; - export type PageEntry = { screencastFrames: { sha1: string, diff --git a/packages/playwright-core/src/web/traceViewer/ui/filmStrip.tsx b/packages/playwright-core/src/web/traceViewer/ui/filmStrip.tsx index 1de1317513..e58ae077f3 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/filmStrip.tsx +++ b/packages/playwright-core/src/web/traceViewer/ui/filmStrip.tsx @@ -19,12 +19,13 @@ import { Boundaries, Size } from '../geometry'; import * as React from 'react'; import { useMeasure } from './helpers'; import { upperBound } from '../../uiUtils'; -import { MergedContexts, PageEntry } from '../entries'; +import { PageEntry } from '../entries'; +import { MultiTraceModel } from './modelUtil'; const tileSize = { width: 200, height: 45 }; export const FilmStrip: React.FunctionComponent<{ - context: MergedContexts, + context: MultiTraceModel, boundaries: Boundaries, previewPoint?: { x: number, clientY: number }, }> = ({ context, boundaries, previewPoint }) => { diff --git a/packages/playwright-core/src/web/traceViewer/ui/modelUtil.ts b/packages/playwright-core/src/web/traceViewer/ui/modelUtil.ts index 7d35d336bf..b0bcb6eb41 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/modelUtil.ts +++ b/packages/playwright-core/src/web/traceViewer/ui/modelUtil.ts @@ -16,14 +16,48 @@ import { ResourceSnapshot } from '../../../server/trace/common/snapshotTypes'; import { ActionTraceEvent } from '../../../server/trace/common/traceEvents'; -import { ContextEntry, MergedContexts, PageEntry } from '../entries'; +import { ContextEntry, PageEntry } from '../entries'; +import * as trace from '../../../server/trace/common/traceEvents'; const contextSymbol = Symbol('context'); const nextSymbol = Symbol('next'); const eventsSymbol = Symbol('events'); const resourcesSymbol = Symbol('resources'); -export function indexModel(context: ContextEntry) { +export class MultiTraceModel { + readonly startTime: number; + readonly endTime: number; + readonly browserName: string; + readonly platform?: string; + readonly wallTime?: number; + readonly title?: string; + readonly options: trace.BrowserContextEventOptions; + readonly pages: PageEntry[]; + readonly actions: trace.ActionTraceEvent[]; + readonly events: trace.ActionTraceEvent[]; + readonly hasSource: boolean; + + constructor(contexts: ContextEntry[]) { + contexts.forEach(contextEntry => indexModel(contextEntry)); + + this.browserName = contexts[0]?.browserName || ''; + this.platform = contexts[0]?.platform || ''; + this.title = contexts[0]?.title || ''; + this.options = contexts[0]?.options || {}; + this.wallTime = contexts.map(c => c.wallTime).reduce((prev, cur) => Math.min(prev || Number.MAX_VALUE, cur!), Number.MAX_VALUE); + this.startTime = contexts.map(c => c.startTime).reduce((prev, cur) => Math.min(prev, cur), Number.MAX_VALUE); + this.endTime = contexts.map(c => c.endTime).reduce((prev, cur) => Math.max(prev, cur), Number.MIN_VALUE); + this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages)); + this.actions = ([] as ActionTraceEvent[]).concat(...contexts.map(c => c.actions)); + this.events = ([] as ActionTraceEvent[]).concat(...contexts.map(c => c.events)); + this.hasSource = contexts.some(c => c.hasSource); + + this.actions.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime); + this.events.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime); + } +} + +function indexModel(context: ContextEntry) { for (const page of context.pages) (page as any)[contextSymbol] = context; for (let i = 0; i < context.actions.length; ++i) { @@ -87,22 +121,3 @@ export function resourcesForAction(action: ActionTraceEvent): ResourceSnapshot[] (action as any)[resourcesSymbol] = result; return result; } - -export function mergeContexts(contexts: ContextEntry[]): MergedContexts { - const newContext: MergedContexts = { - browserName: contexts[0].browserName, - platform: contexts[0].platform, - title: contexts[0].title, - options: contexts[0].options, - wallTime: contexts.map(c => c.wallTime).reduce((prev, cur) => Math.min(prev || Number.MAX_VALUE, cur!), Number.MAX_VALUE), - startTime: contexts.map(c => c.startTime).reduce((prev, cur) => Math.min(prev, cur), Number.MAX_VALUE), - endTime: contexts.map(c => c.endTime).reduce((prev, cur) => Math.max(prev, cur), Number.MIN_VALUE), - pages: ([] as PageEntry[]).concat(...contexts.map(c => c.pages)), - actions: ([] as ActionTraceEvent[]).concat(...contexts.map(c => c.actions)), - events: ([] as ActionTraceEvent[]).concat(...contexts.map(c => c.events)), - hasSource: contexts.some(c => c.hasSource) - }; - newContext.actions.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime); - newContext.events.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime); - return newContext; -} diff --git a/packages/playwright-core/src/web/traceViewer/ui/timeline.tsx b/packages/playwright-core/src/web/traceViewer/ui/timeline.tsx index 523bc5693e..a016700da4 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/timeline.tsx +++ b/packages/playwright-core/src/web/traceViewer/ui/timeline.tsx @@ -18,10 +18,10 @@ import * as React from 'react'; import { ActionTraceEvent } from '../../../server/trace/common/traceEvents'; import { msToString } from '../../uiUtils'; -import { MergedContexts } from '../entries'; import { Boundaries } from '../geometry'; import { FilmStrip } from './filmStrip'; import { useMeasure } from './helpers'; +import { MultiTraceModel } from './modelUtil'; import './timeline.css'; type TimelineBar = { @@ -37,7 +37,7 @@ type TimelineBar = { }; export const Timeline: React.FunctionComponent<{ - context: MergedContexts, + context: MultiTraceModel, boundaries: Boundaries, selectedAction: ActionTraceEvent | undefined, highlightedAction: ActionTraceEvent | undefined, diff --git a/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx b/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx index c59d676e9c..5206530166 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx +++ b/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx @@ -15,7 +15,7 @@ */ import { ActionTraceEvent } from '../../../server/trace/common/traceEvents'; -import { ContextEntry, createEmptyContext, MergedContexts } from '../entries'; +import { ContextEntry } from '../entries'; import { ActionList } from './actionList'; import { TabbedPane } from './tabbedPane'; import { Timeline } from './timeline'; @@ -29,12 +29,13 @@ import { SplitView } from '../../components/splitView'; import { ConsoleTab } from './consoleTab'; import * as modelUtil from './modelUtil'; import { msToString } from '../../uiUtils'; +import { MultiTraceModel } from './modelUtil'; export const Workbench: React.FunctionComponent<{ }> = () => { const [traceURLs, setTraceURLs] = React.useState([]); const [uploadedTraceNames, setUploadedTraceNames] = React.useState([]); - const [contextEntry, setContextEntry] = React.useState(emptyContext); + const [model, setModel] = React.useState(emptyModel); const [selectedAction, setSelectedAction] = React.useState(); const [highlightedAction, setHighlightedAction] = React.useState(); const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState('actions'); @@ -119,20 +120,19 @@ export const Workbench: React.FunctionComponent<{ return; } const contextEntry = await response.json() as ContextEntry; - modelUtil.indexModel(contextEntry); contextEntries.push(contextEntry); } navigator.serviceWorker.removeEventListener('message', swListener); - const contextEntry = modelUtil.mergeContexts(contextEntries); + const model = new MultiTraceModel(contextEntries); setProgress({ done: 0, total: 0 }); - setContextEntry(contextEntry!); + setModel(model); } else { - setContextEntry(emptyContext); + setModel(emptyModel); } })(); }, [traceURLs, uploadedTraceNames]); - const boundaries = { minimum: contextEntry.startTime, maximum: contextEntry.endTime }; + const boundaries = { minimum: model.startTime, maximum: model.endTime }; // Leave some nice free space on the right hand side. @@ -147,19 +147,19 @@ export const Workbench: React.FunctionComponent<{ { id: 'network', title: 'Network', count: networkCount, render: () => }, ]; - if (contextEntry.hasSource) + if (model.hasSource) tabs.push({ id: 'source', title: 'Source', count: 0, render: () => }); return
{ event.preventDefault(); setDragOver(true); }}>
🎭
Playwright
- {contextEntry.title &&
{contextEntry.title}
} + {model.title &&
{model.title}
}
{ @@ -186,21 +186,21 @@ export const Workbench: React.FunctionComponent<{ /> }, { id: 'metadata', title: 'Metadata', count: 0, render: () =>
Time
- {contextEntry.wallTime &&
start time: {new Date(contextEntry.wallTime).toLocaleString()}
} -
duration: {msToString(contextEntry.endTime - contextEntry.startTime)}
+ {model.wallTime &&
start time: {new Date(model.wallTime).toLocaleString()}
} +
duration: {msToString(model.endTime - model.startTime)}
Browser
-
engine: {contextEntry.browserName}
- {contextEntry.platform &&
platform: {contextEntry.platform}
} - {contextEntry.options.userAgent &&
user agent: {contextEntry.options.userAgent}
} +
engine: {model.browserName}
+ {model.platform &&
platform: {model.platform}
} + {model.options.userAgent &&
user agent: {model.options.userAgent}
}
Viewport
- {contextEntry.options.viewport &&
width: {contextEntry.options.viewport.width}
} - {contextEntry.options.viewport &&
height: {contextEntry.options.viewport.height}
} -
is mobile: {String(!!contextEntry.options.isMobile)}
- {contextEntry.options.deviceScaleFactor &&
device scale: {String(contextEntry.options.deviceScaleFactor)}
} + {model.options.viewport &&
width: {model.options.viewport.width}
} + {model.options.viewport &&
height: {model.options.viewport.height}
} +
is mobile: {String(!!model.options.isMobile)}
+ {model.options.deviceScaleFactor &&
device scale: {String(model.options.deviceScaleFactor)}
}
Counts
-
pages: {contextEntry.pages.length}
-
actions: {contextEntry.actions.length}
-
events: {contextEntry.events.length}
+
pages: {model.pages.length}
+
actions: {model.actions.length}
+
events: {model.events.length}
}, ] } selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/> @@ -237,6 +237,4 @@ export const Workbench: React.FunctionComponent<{
; }; -const emptyContext = createEmptyContext(); -emptyContext.startTime = performance.now(); -emptyContext.endTime = emptyContext.startTime; +const emptyModel = new MultiTraceModel([]);