diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 6e3825365a..40ce2af375 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -271,6 +271,7 @@ export class TestTracing { this._appendTraceEvent({ type: 'before', callId, + stepId: callId, parentId, startTime: monotonicTime(), class: 'Test', diff --git a/packages/trace-viewer/src/sw/traceModernizer.ts b/packages/trace-viewer/src/sw/traceModernizer.ts index 56c182ba64..d1e683ebb4 100644 --- a/packages/trace-viewer/src/sw/traceModernizer.ts +++ b/packages/trace-viewer/src/sw/traceModernizer.ts @@ -385,7 +385,7 @@ export class TraceModernizer { const event: traceV7.ContextCreatedTraceEvent = { type: 'context-options', origin: 'testRunner', - version: 7, + version: 6, browserName: '', options: {}, platform: 'unknown', @@ -396,17 +396,23 @@ export class TraceModernizer { }; result.push(event); } + for (const event of events) { if (event.type === 'context-options') { result.push({ ...event, monotonicTime: 0, origin: 'library', contextId: '' }); continue; } - // Take wall and monotonic time from the first event. - if (!this._contextEntry.wallTime && event.type === 'before') - this._contextEntry.wallTime = event.wallTime; - if (!this._contextEntry.startTime && event.type === 'before') - this._contextEntry.startTime = event.startTime; - result.push(event); + if (event.type === 'before' || event.type === 'action') { + // Take wall and monotonic time from the first event. + if (!this._contextEntry.wallTime) + this._contextEntry.wallTime = event.wallTime; + const eventAsV6 = event as traceV6.BeforeActionTraceEvent; + const eventAsV7 = event as traceV7.BeforeActionTraceEvent; + eventAsV7.stepId = `${eventAsV6.apiName}@${eventAsV6.wallTime}`; + result.push(eventAsV7); + } else { + result.push(event); + } } return result; } @@ -414,10 +420,18 @@ export class TraceModernizer { _modernize_7_to_8(events: traceV7.TraceEvent[]): traceV8.TraceEvent[] { const result: traceV8.TraceEvent[] = []; for (const event of events) { - result.push(event); - if (event.type !== 'before' || !event.apiName) - continue; - (event as traceV8.BeforeActionTraceEvent).title = event.apiName; + if (event.type === 'before' || event.type === 'action') { + const eventAsV7 = event as traceV7.BeforeActionTraceEvent; + const eventAsV8 = event as traceV8.BeforeActionTraceEvent; + if (eventAsV7.apiName) { + eventAsV8.title = eventAsV7.apiName; + delete (eventAsV8 as any).apiName; + } + eventAsV8.stepId = eventAsV7.stepId ?? eventAsV7.callId; + result.push(eventAsV8); + } else { + result.push(event); + } } return result; } diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts index 92f18bacb9..a15167527e 100644 --- a/packages/trace-viewer/src/ui/modelUtil.ts +++ b/packages/trace-viewer/src/ui/modelUtil.ts @@ -226,6 +226,8 @@ function makeCallIdsUniqueAcrossTraceFiles(contexts: ContextEntry[], traceFileId } } +let lastTmpStepId = 0; + function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionTraceEventInContext[] { const map = new Map(); @@ -239,18 +241,10 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT }).flat(); } - // Library actions are replaced with corresponding test runner steps. Matching with - // the test runner steps enables us to find parent steps. - // - In the newer versions the actions are matched by explicit step id stored in the - // library context actions. - // - In the older versions the step id is not stored and the match is perfomed based on - // action name and wallTime. - const matchByStepId = libraryContexts.some(c => c.actions.some(a => !!a.stepId)); - for (const context of libraryContexts) { for (const action of context.actions) { - const key = matchByStepId ? action.stepId! : `${action.title}@${(action as any).wallTime}`; - map.set(key, { ...action, context }); + // Never merge stepless events. + map.set(action.stepId || `tmp-step@${++lastTmpStepId}`, { ...action, context }); } } @@ -258,15 +252,14 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT // Step aka test runner contexts have startTime/endTime as client-side times. // Adjust startTime/endTime on the library contexts to align them with the test // runner steps. - const delta = monotonicTimeDeltaBetweenLibraryAndRunner(testRunnerContexts, map, matchByStepId); + const delta = monotonicTimeDeltaBetweenLibraryAndRunner(testRunnerContexts, map); if (delta) adjustMonotonicTime(libraryContexts, delta); const nonPrimaryIdToPrimaryId = new Map(); for (const context of testRunnerContexts) { for (const action of context.actions) { - const key = matchByStepId ? action.callId : `${action.title}@${(action as any).wallTime}`; - const existing = map.get(key); + const existing = action.stepId && map.get(action.stepId); if (existing) { nonPrimaryIdToPrimaryId.set(action.callId, existing.callId); if (action.error) @@ -285,7 +278,7 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT } if (action.parentId) action.parentId = nonPrimaryIdToPrimaryId.get(action.parentId) ?? action.parentId; - map.set(key, { ...action, context }); + map.set(action.stepId || `tmp-step@${++lastTmpStepId}`, { ...action, context }); } } return [...map.values()]; @@ -316,7 +309,7 @@ function adjustMonotonicTime(contexts: ContextEntry[], monotonicTimeDelta: numbe } } -function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts: ContextEntry[], libraryActions: Map, matchByStepId: boolean) { +function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts: ContextEntry[], libraryActions: Map) { // We cannot rely on wall time or monotonic time to be the in sync // between library and test runner contexts. So we find first action // that is present in both runner and library contexts and use it @@ -326,8 +319,7 @@ function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts: ContextEn for (const action of context.actions) { if (!action.startTime) continue; - const key = matchByStepId ? action.callId! : `${action.title}@${(action as any).wallTime}`; - const libraryAction = libraryActions.get(key); + const libraryAction = action.stepId ? libraryActions.get(action.stepId) : undefined; if (libraryAction) return action.startTime - libraryAction.startTime; }