From ec40890fd862e9d94fd6361305546583aad66976 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 10 Sep 2024 14:32:33 +0200 Subject: [PATCH] fix(tracing): use page swap timestamp to find closest screenshot (#32512) Follow-up to https://github.com/microsoft/playwright/pull/32248. When we have it, we should use the page swap timestamp we get from Chromium to find the closest screenshot. --- .../src/server/chromium/crPage.ts | 2 +- .../src/server/chromium/videoRecorder.ts | 2 +- .../src/server/trace/recorder/snapshotter.ts | 1 + .../server/trace/recorder/snapshotterInjected.ts | 6 +++--- .../src/server/trace/recorder/tracing.ts | 3 ++- packages/trace-viewer/src/entries.ts | 1 + packages/trace-viewer/src/snapshotServer.ts | 1 + packages/trace-viewer/src/ui/snapshotTab.tsx | 16 ++++++++++------ packages/trace/src/snapshot.ts | 1 + packages/trace/src/trace.ts | 1 + 10 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 05beba882c..002dfa09be 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -898,7 +898,7 @@ class FrameSession { const buffer = Buffer.from(payload.data, 'base64'); this._page.emit(Page.Events.ScreencastFrame, { buffer, - timestamp: payload.metadata.timestamp, + frameSwapWallTime: payload.metadata.timestamp ? payload.metadata.timestamp * 1000 : undefined, width: payload.metadata.deviceWidth, height: payload.metadata.deviceHeight, }); diff --git a/packages/playwright-core/src/server/chromium/videoRecorder.ts b/packages/playwright-core/src/server/chromium/videoRecorder.ts index 25199413f8..68bfbea037 100644 --- a/packages/playwright-core/src/server/chromium/videoRecorder.ts +++ b/packages/playwright-core/src/server/chromium/videoRecorder.ts @@ -53,7 +53,7 @@ export class VideoRecorder { private constructor(page: Page, ffmpegPath: string, progress: Progress) { this._progress = progress; this._ffmpegPath = ffmpegPath; - page.on(Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.timestamp)); + page.on(Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.frameSwapWallTime / 1000)); } private async _launch(options: types.PageScreencastOptions) { diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts index 48bfea7302..d552c397e0 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts @@ -137,6 +137,7 @@ export class Snapshotter { html: data.html, viewport: data.viewport, timestamp: monotonicTime(), + wallTime: data.wallTime, collectionTime: data.collectionTime, resourceOverrides: [], isMainFrame: page.mainFrame() === frame diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 14e34c8aee..e881d52313 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -27,7 +27,7 @@ export type SnapshotData = { }[], viewport: { width: number, height: number }, url: string, - timestamp: number, + wallTime: number, collectionTime: number, }; @@ -572,7 +572,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: height: window.innerHeight, }, url: location.href, - timestamp, + wallTime: Date.now(), collectionTime: 0, }; @@ -589,7 +589,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: result.resourceOverrides.push({ url, content, contentType: 'text/css' },); } - result.collectionTime = performance.now() - result.timestamp; + result.collectionTime = performance.now() - timestamp; return result; } } diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 1dec4f536a..b09bbe3134 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -472,7 +472,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps sha1, width: params.width, height: params.height, - timestamp: monotonicTime() + timestamp: monotonicTime(), + frameSwapWallTime: params.frameSwapWallTime, }; // Make sure to write the screencast frame before adding a reference to it. this._appendResource(sha1, params.buffer); diff --git a/packages/trace-viewer/src/entries.ts b/packages/trace-viewer/src/entries.ts index 98d39927f7..dbfec63be8 100644 --- a/packages/trace-viewer/src/entries.ts +++ b/packages/trace-viewer/src/entries.ts @@ -45,6 +45,7 @@ export type PageEntry = { screencastFrames: { sha1: string, timestamp: number, + frameSwapWallTime?: number, width: number, height: number, }[]; diff --git a/packages/trace-viewer/src/snapshotServer.ts b/packages/trace-viewer/src/snapshotServer.ts index d41dcbdbbd..4b61104d33 100644 --- a/packages/trace-viewer/src/snapshotServer.ts +++ b/packages/trace-viewer/src/snapshotServer.ts @@ -46,6 +46,7 @@ export class SnapshotServer { viewport: snapshot.viewport(), url: snapshot.snapshot().frameUrl, timestamp: snapshot.snapshot().timestamp, + wallTime: snapshot.snapshot().wallTime, } : { error: 'No snapshot found' }); diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index 05d0cc5918..00fcbf64e9 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -31,12 +31,12 @@ import { TabbedPaneTab } from '@web/components/tabbedPane'; import { BrowserFrame } from './browserFrame'; import { ClickPointer } from './clickPointer'; -function findClosest(items: T[], target: number) { +function findClosest(items: T[], metric: (v: T) => number, target: number) { return items.find((item, index) => { if (index === items.length - 1) return true; const next = items[index + 1]; - return Math.abs(item.timestamp - target) < Math.abs(next.timestamp - target); + return Math.abs(metric(item) - target) < Math.abs(metric(next) - target); }); } @@ -102,7 +102,7 @@ export const SnapshotTab: React.FunctionComponent<{ const iframeRef0 = React.useRef(null); const iframeRef1 = React.useRef(null); - const [snapshotInfo, setSnapshotInfo] = React.useState<{ viewport: typeof kDefaultViewport, url: string, timestamp?: number }>({ viewport: kDefaultViewport, url: '', timestamp: undefined }); + const [snapshotInfo, setSnapshotInfo] = React.useState<{ viewport: typeof kDefaultViewport, url: string, timestamp?: number, wallTime?: undefined }>({ viewport: kDefaultViewport, url: '' }); const loadingRef = React.useRef({ iteration: 0, visibleIframe: 0 }); React.useEffect(() => { @@ -111,7 +111,7 @@ export const SnapshotTab: React.FunctionComponent<{ const newVisibleIframe = 1 - loadingRef.current.visibleIframe; loadingRef.current.iteration = thisIteration; - const newSnapshotInfo = { url: '', viewport: kDefaultViewport, timestamp: undefined }; + const newSnapshotInfo = { url: '', viewport: kDefaultViewport, timestamp: undefined, wallTime: undefined }; if (snapshotInfoUrl) { const response = await fetch(snapshotInfoUrl); const info = await response.json(); @@ -119,6 +119,7 @@ export const SnapshotTab: React.FunctionComponent<{ newSnapshotInfo.url = info.url; newSnapshotInfo.viewport = info.viewport; newSnapshotInfo.timestamp = info.timestamp; + newSnapshotInfo.wallTime = info.wallTime; } } @@ -170,10 +171,13 @@ export const SnapshotTab: React.FunctionComponent<{ const page = action ? pageForAction(action) : undefined; const screencastFrame = React.useMemo( () => { + if (snapshotInfo.wallTime && page?.screencastFrames[0]?.frameSwapWallTime) + return findClosest(page.screencastFrames, frame => frame.frameSwapWallTime!, snapshotInfo.wallTime); + if (snapshotInfo.timestamp && page?.screencastFrames) - return findClosest(page.screencastFrames, snapshotInfo.timestamp); + return findClosest(page.screencastFrames, frame => frame.timestamp, snapshotInfo.timestamp); }, - [page?.screencastFrames, snapshotInfo.timestamp] + [page?.screencastFrames, snapshotInfo.timestamp, snapshotInfo.wallTime] ); return