Skip to content

Commit fb3d960

Browse files
committed
chore: simplify trace sw
1 parent 2c95eb1 commit fb3d960

File tree

14 files changed

+137
-244
lines changed

14 files changed

+137
-244
lines changed

packages/playwright-core/src/cli/program.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,13 @@ program
375375
});
376376

377377
program
378-
.command('show-trace [trace...]')
378+
.command('show-trace [trace]')
379379
.option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium')
380380
.option('-h, --host <host>', 'Host to serve trace on; specifying this option opens trace in a browser tab')
381381
.option('-p, --port <port>', 'Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab')
382382
.option('--stdin', 'Accept trace URLs over stdin to update the viewer')
383383
.description('show trace viewer')
384-
.action(function(traces, options) {
384+
.action(function(trace, options) {
385385
if (options.browser === 'cr')
386386
options.browser = 'chromium';
387387
if (options.browser === 'ff')
@@ -396,9 +396,9 @@ program
396396
};
397397

398398
if (options.port !== undefined || options.host !== undefined)
399-
runTraceInBrowser(traces, openOptions).catch(logErrorAndExit);
399+
runTraceInBrowser(trace, openOptions).catch(logErrorAndExit);
400400
else
401-
runTraceViewerApp(traces, options.browser, openOptions, true).catch(logErrorAndExit);
401+
runTraceViewerApp(trace, options.browser, openOptions, true).catch(logErrorAndExit);
402402
}).addHelpText('afterAll', `
403403
Examples:
404404

packages/playwright-core/src/server/trace/viewer/traceViewer.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ export type TraceViewerAppOptions = {
5252
persistentContextOptions?: Parameters<BrowserType['launchPersistentContext']>[2];
5353
};
5454

55-
function validateTraceUrls(traceUrls: string[]) {
56-
for (const traceUrl of traceUrls) {
57-
let traceFile = traceUrl;
58-
// If .json is requested, we'll synthesize it.
59-
if (traceUrl.endsWith('.json'))
60-
traceFile = traceUrl.substring(0, traceUrl.length - '.json'.length);
61-
62-
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceFile) && !fs.existsSync(traceFile + '.trace'))
63-
throw new Error(`Trace file ${traceUrl} does not exist!`);
64-
}
55+
function validateTraceUrl(traceUrl: string | undefined) {
56+
if (!traceUrl)
57+
return;
58+
let traceFile = traceUrl;
59+
// If .json is requested, we'll synthesize it.
60+
if (traceUrl.endsWith('.json'))
61+
traceFile = traceUrl.substring(0, traceUrl.length - '.json'.length);
62+
63+
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceFile) && !fs.existsSync(traceFile + '.trace'))
64+
throw new Error(`Trace file ${traceUrl} does not exist!`);
6565
}
6666

6767
export async function startTraceViewerServer(options?: TraceViewerServerOptions): Promise<HttpServer> {
@@ -108,11 +108,11 @@ export async function startTraceViewerServer(options?: TraceViewerServerOptions)
108108
return server;
109109
}
110110

111-
export async function installRootRedirect(server: HttpServer, traceUrls: string[], options: TraceViewerRedirectOptions) {
111+
export async function installRootRedirect(server: HttpServer, traceUrl: string | undefined, options: TraceViewerRedirectOptions) {
112112
const params = new URLSearchParams();
113113
if (path.sep !== path.posix.sep)
114114
params.set('pathSeparator', path.sep);
115-
for (const traceUrl of traceUrls)
115+
if (traceUrl)
116116
params.append('trace', traceUrl);
117117
if (server.wsGuid())
118118
params.append('ws', server.wsGuid()!);
@@ -146,20 +146,20 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
146146
});
147147
}
148148

149-
export async function runTraceViewerApp(traceUrls: string[], browserName: string, options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) {
150-
validateTraceUrls(traceUrls);
149+
export async function runTraceViewerApp(traceUrl: string | undefined, browserName: string, options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) {
150+
validateTraceUrl(traceUrl);
151151
const server = await startTraceViewerServer(options);
152-
await installRootRedirect(server, traceUrls, options);
152+
await installRootRedirect(server, traceUrl, options);
153153
const page = await openTraceViewerApp(server.urlPrefix('precise'), browserName, options);
154154
if (exitOnClose)
155155
page.on('close', () => gracefullyProcessExitDoNotHang(0));
156156
return page;
157157
}
158158

159-
export async function runTraceInBrowser(traceUrls: string[], options: TraceViewerServerOptions) {
160-
validateTraceUrls(traceUrls);
159+
export async function runTraceInBrowser(traceUrl: string | undefined, options: TraceViewerServerOptions) {
160+
validateTraceUrl(traceUrl);
161161
const server = await startTraceViewerServer(options);
162-
await installRootRedirect(server, traceUrls, options);
162+
await installRootRedirect(server, traceUrl, options);
163163
await openTraceInBrowser(server.urlPrefix('human-readable'));
164164
}
165165

packages/playwright/src/runner/testServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export class TestServerDispatcher implements TestServerInterface {
262262
export async function runUIMode(configFile: string | undefined, configCLIOverrides: ConfigCLIOverrides, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise<reporterTypes.FullResult['status']> {
263263
const configLocation = resolveConfigLocation(configFile);
264264
return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server: HttpServer, cancelPromise: ManualPromise<void>) => {
265-
await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' });
265+
await installRootRedirect(server, undefined, { ...options, webApp: 'uiMode.html' });
266266
if (options.host !== undefined || options.port !== undefined) {
267267
await openTraceInBrowser(server.urlPrefix('human-readable'));
268268
} else {

packages/trace-viewer/src/sw/main.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ const scopePath = new URL(self.registration.scope).pathname;
3636

3737
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>();
3838

39-
const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string>, traceViewerServer: TraceViewerServer }>();
39+
type ClientData = {
40+
traceUrl: string;
41+
traceViewerServer: TraceViewerServer
42+
};
43+
const clientIdToTraceUrls = new Map<string, ClientData>();
4044

41-
async function loadTrace(traceUrl: string, traceFileName: string | null, client: any | undefined, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
42-
await gc();
45+
async function loadTrace(traceUrl: string, traceFileName: string | null, client: any | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
4346
const clientId = client?.id ?? '';
44-
let data = clientIdToTraceUrls.get(clientId);
45-
if (!data) {
46-
const clientURL = new URL(client?.url ?? self.registration.scope);
47-
const traceViewerServerBaseUrl = new URL(clientURL.searchParams.get('server') ?? '../', clientURL);
48-
data = { limit, traceUrls: new Set(), traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl) };
49-
clientIdToTraceUrls.set(clientId, data);
50-
}
51-
data.traceUrls.add(traceUrl);
47+
const clientURL = new URL(client?.url ?? self.registration.scope);
48+
const traceViewerServerBaseUrl = new URL(clientURL.searchParams.get('server') ?? '../', clientURL);
49+
const data: ClientData = { traceUrl, traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl) };
50+
clientIdToTraceUrls.set(clientId, data);
51+
await gc();
5252

5353
const traceModel = new TraceModel();
5454
try {
@@ -106,8 +106,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
106106

107107
if (relativePath === '/contexts') {
108108
try {
109-
const limit = url.searchParams.has('limit') ? +url.searchParams.get('limit')! : undefined;
110-
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), client, limit, (done: number, total: number) => {
109+
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), client, (done: number, total: number) => {
111110
client.postMessage({ method: 'progress', params: { done, total } });
112111
});
113112
return new Response(JSON.stringify(traceModel!.contextEntries), {
@@ -209,12 +208,7 @@ async function gc() {
209208
clientIdToTraceUrls.delete(clientId);
210209
continue;
211210
}
212-
if (data.limit !== undefined) {
213-
const ordered = [...data.traceUrls];
214-
// Leave the newest requested traces.
215-
data.traceUrls = new Set(ordered.slice(ordered.length - data.limit));
216-
}
217-
data.traceUrls.forEach(url => usedTraces.add(url));
211+
usedTraces.add(data.traceUrl);
218212
}
219213

220214
for (const traceUrl of loadedTraces.keys()) {

packages/trace-viewer/src/sw/traceModel.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ export class TraceModel {
6161
let done = 0;
6262
for (const ordinal of ordinals) {
6363
const contextEntry = createEmptyContext();
64-
contextEntry.traceUrl = backend.traceURL();
6564
contextEntry.hasSource = hasSource;
6665
const modernizer = new TraceModernizer(contextEntry, this._snapshotStorage);
6766

@@ -138,7 +137,6 @@ function stripEncodingFromContentType(contentType: string) {
138137
function createEmptyContext(): ContextEntry {
139138
return {
140139
origin: 'testRunner',
141-
traceUrl: '',
142140
startTime: Number.MAX_SAFE_INTEGER,
143141
wallTime: Number.MAX_SAFE_INTEGER,
144142
endTime: 0,

packages/trace-viewer/src/types/entries.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import type * as trace from '@trace/trace';
2222

2323
export type ContextEntry = {
2424
origin: 'testRunner'|'library';
25-
traceUrl: string;
2625
startTime: number;
2726
endTime: number;
2827
browserName: string;

packages/trace-viewer/src/ui/liveWorkbenchLoader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const LiveWorkbenchLoader: React.FC<{ traceJson: string }> = ({ traceJson
3636
const model = await loadSingleTraceFile(traceJson);
3737
setModel(model);
3838
} catch {
39-
const model = new MultiTraceModel([]);
39+
const model = new MultiTraceModel('', []);
4040
setModel(model);
4141
} finally {
4242
setCounter(counter + 1);
@@ -54,8 +54,7 @@ export const LiveWorkbenchLoader: React.FC<{ traceJson: string }> = ({ traceJson
5454
async function loadSingleTraceFile(traceJson: string): Promise<MultiTraceModel> {
5555
const params = new URLSearchParams();
5656
params.set('trace', traceJson);
57-
params.set('limit', '1');
5857
const response = await fetch(`contexts?${params.toString()}`);
5958
const contextEntries = await response.json() as ContextEntry[];
60-
return new MultiTraceModel(contextEntries);
59+
return new MultiTraceModel(traceJson, contextEntries);
6160
}

packages/trace-viewer/src/ui/modelUtil.ts

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,14 @@ export class MultiTraceModel {
8585
readonly sources: Map<string, SourceModel>;
8686
resources: ResourceSnapshot[];
8787
readonly actionCounters: Map<string, number>;
88+
readonly traceUrl: string;
8889

8990

90-
constructor(contexts: ContextEntry[]) {
91+
constructor(traceUrl: string, contexts: ContextEntry[]) {
9192
contexts.forEach(contextEntry => indexModel(contextEntry));
9293
const libraryContext = contexts.find(context => context.origin === 'library');
9394

95+
this.traceUrl = traceUrl;
9496
this.browserName = libraryContext?.browserName || '';
9597
this.sdkLanguage = libraryContext?.sdkLanguage;
9698
this.channel = libraryContext?.channel;
@@ -110,7 +112,7 @@ export class MultiTraceModel {
110112
this.hasSource = contexts.some(c => c.hasSource);
111113
this.hasStepData = contexts.some(context => context.origin === 'testRunner');
112114
this.resources = [...contexts.map(c => c.resources)].flat();
113-
this.attachments = this.actions.flatMap(action => action.attachments?.map(attachment => ({ ...attachment, traceUrl: action.context.traceUrl })) ?? []);
115+
this.attachments = this.actions.flatMap(action => action.attachments?.map(attachment => ({ ...attachment, traceUrl })) ?? []);
114116
this.visibleAttachments = this.attachments.filter(attachment => !attachment.name.startsWith('_'));
115117

116118
this.events.sort((a1, a2) => a1.time - a2.time);
@@ -179,30 +181,9 @@ function indexModel(context: ContextEntry) {
179181
}
180182

181183
function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
182-
const traceFileToContexts = new Map<string, ContextEntry[]>();
183-
for (const context of contexts) {
184-
const traceFile = context.traceUrl;
185-
let list = traceFileToContexts.get(traceFile);
186-
if (!list) {
187-
list = [];
188-
traceFileToContexts.set(traceFile, list);
189-
}
190-
list.push(context);
191-
}
192-
193184
const result: ActionTraceEventInContext[] = [];
194-
let traceFileId = 0;
195-
for (const [, contexts] of traceFileToContexts) {
196-
// Action ids are unique only within a trace file. If there are
197-
// traces from more than one file we make the ids unique across the
198-
// files. The code does not update snapshot ids as they are always
199-
// retrieved from a particular trace file.
200-
if (traceFileToContexts.size > 1)
201-
makeCallIdsUniqueAcrossTraceFiles(contexts, ++traceFileId);
202-
// Align action times across runner and library contexts within each trace file.
203-
const actions = mergeActionsAndUpdateTimingSameTrace(contexts);
204-
result.push(...actions);
205-
}
185+
const actions = mergeActionsAndUpdateTimingSameTrace(contexts);
186+
result.push(...actions);
206187

207188
result.sort((a1, a2) => {
208189
if (a2.parentId === a1.callId)
@@ -229,17 +210,6 @@ function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
229210
return result;
230211
}
231212

232-
function makeCallIdsUniqueAcrossTraceFiles(contexts: ContextEntry[], traceFileId: number) {
233-
for (const context of contexts) {
234-
for (const action of context.actions) {
235-
if (action.callId)
236-
action.callId = `${traceFileId}:${action.callId}`;
237-
if (action.parentId)
238-
action.parentId = `${traceFileId}:${action.parentId}`;
239-
}
240-
}
241-
}
242-
243213
let lastTmpStepId = 0;
244214

245215
function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionTraceEventInContext[] {

packages/trace-viewer/src/ui/snapshotTab.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import './snapshotTab.css';
1818
import * as React from 'react';
1919
import type { ActionTraceEvent } from '@trace/trace';
20-
import { context, type MultiTraceModel, nextActionByStartTime, previousActionByEndTime } from './modelUtil';
20+
import { type MultiTraceModel, nextActionByStartTime, previousActionByEndTime } from './modelUtil';
2121
import { Toolbar } from '@web/components/toolbar';
2222
import { ToolbarButton } from '@web/components/toolbarButton';
2323
import { clsx, useMeasure, useSetting } from '@web/uiUtils';
@@ -47,7 +47,7 @@ export const SnapshotTabsView: React.FunctionComponent<{
4747
setIsInspecting: (isInspecting: boolean) => void,
4848
highlightedElement: HighlightedElement,
4949
setHighlightedElement: (element: HighlightedElement) => void,
50-
}> = ({ action, sdkLanguage, testIdAttributeName, isInspecting, setIsInspecting, highlightedElement, setHighlightedElement }) => {
50+
}> = ({ action, model, sdkLanguage, testIdAttributeName, isInspecting, setIsInspecting, highlightedElement, setHighlightedElement }) => {
5151
const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action');
5252

5353
const [shouldPopulateCanvasFromScreenshot] = useSetting('shouldPopulateCanvasFromScreenshot', false);
@@ -57,8 +57,8 @@ export const SnapshotTabsView: React.FunctionComponent<{
5757
}, [action]);
5858
const { snapshotInfoUrl, snapshotUrl, popoutUrl } = React.useMemo(() => {
5959
const snapshot = snapshots[snapshotTab];
60-
return snapshot ? extendSnapshot(snapshot, shouldPopulateCanvasFromScreenshot) : { snapshotInfoUrl: undefined, snapshotUrl: undefined, popoutUrl: undefined };
61-
}, [snapshots, snapshotTab, shouldPopulateCanvasFromScreenshot]);
60+
return model && snapshot ? extendSnapshot(model.traceUrl, snapshot, shouldPopulateCanvasFromScreenshot) : { snapshotInfoUrl: undefined, snapshotUrl: undefined, popoutUrl: undefined };
61+
}, [snapshots, snapshotTab, shouldPopulateCanvasFromScreenshot, model]);
6262

6363
const snapshotUrls = React.useMemo((): SnapshotUrls | undefined => snapshotInfoUrl !== undefined ? { snapshotInfoUrl, snapshotUrl, popoutUrl } : undefined, [snapshotInfoUrl, snapshotUrl, popoutUrl]);
6464

@@ -414,9 +414,9 @@ export function collectSnapshots(action: ActionTraceEvent | undefined): Snapshot
414414
const isUnderTest = new URLSearchParams(window.location.search).has('isUnderTest');
415415
const serverParam = new URLSearchParams(window.location.search).get('server');
416416

417-
export function extendSnapshot(snapshot: Snapshot, shouldPopulateCanvasFromScreenshot: boolean): SnapshotUrls {
417+
export function extendSnapshot(traceUrl: string, snapshot: Snapshot, shouldPopulateCanvasFromScreenshot: boolean): SnapshotUrls {
418418
const params = new URLSearchParams();
419-
params.set('trace', context(snapshot.action).traceUrl);
419+
params.set('trace', traceUrl);
420420
params.set('name', snapshot.snapshotName);
421421
if (isUnderTest)
422422
params.set('isUnderTest', 'true');
@@ -436,7 +436,7 @@ export function extendSnapshot(snapshot: Snapshot, shouldPopulateCanvasFromScree
436436
popoutParams.set('r', snapshotUrl);
437437
if (serverParam)
438438
popoutParams.set('server', serverParam);
439-
popoutParams.set('trace', context(snapshot.action).traceUrl);
439+
popoutParams.set('trace', traceUrl);
440440
if (snapshot.point) {
441441
popoutParams.set('pointX', String(snapshot.point.x));
442442
popoutParams.set('pointY', String(snapshot.point.y));

packages/trace-viewer/src/ui/uiModeTraceView.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const TraceView: React.FC<{
7575
const model = await loadSingleTraceFile(traceLocation);
7676
setModel({ model, isLive: true });
7777
} catch {
78-
const model = new MultiTraceModel([]);
78+
const model = new MultiTraceModel('', []);
7979
model.errorDescriptors.push(...result.errors.flatMap(error => !!error.message ? [{ message: error.message }] : []));
8080
setModel({ model, isLive: false });
8181
} finally {
@@ -113,8 +113,7 @@ const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefi
113113
async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> {
114114
const params = new URLSearchParams();
115115
params.set('trace', url);
116-
params.set('limit', '1');
117116
const response = await fetch(`contexts?${params.toString()}`);
118117
const contextEntries = await response.json() as ContextEntry[];
119-
return new MultiTraceModel(contextEntries);
118+
return new MultiTraceModel(url, contextEntries);
120119
}

0 commit comments

Comments
 (0)