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.
This commit is contained in:
Simon Knott 2024-09-10 14:32:33 +02:00 committed by GitHub
parent f8562e4ca7
commit ec40890fd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 22 additions and 12 deletions

View File

@ -898,7 +898,7 @@ class FrameSession {
const buffer = Buffer.from(payload.data, 'base64'); const buffer = Buffer.from(payload.data, 'base64');
this._page.emit(Page.Events.ScreencastFrame, { this._page.emit(Page.Events.ScreencastFrame, {
buffer, buffer,
timestamp: payload.metadata.timestamp, frameSwapWallTime: payload.metadata.timestamp ? payload.metadata.timestamp * 1000 : undefined,
width: payload.metadata.deviceWidth, width: payload.metadata.deviceWidth,
height: payload.metadata.deviceHeight, height: payload.metadata.deviceHeight,
}); });

View File

@ -53,7 +53,7 @@ export class VideoRecorder {
private constructor(page: Page, ffmpegPath: string, progress: Progress) { private constructor(page: Page, ffmpegPath: string, progress: Progress) {
this._progress = progress; this._progress = progress;
this._ffmpegPath = ffmpegPath; 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) { private async _launch(options: types.PageScreencastOptions) {

View File

@ -137,6 +137,7 @@ export class Snapshotter {
html: data.html, html: data.html,
viewport: data.viewport, viewport: data.viewport,
timestamp: monotonicTime(), timestamp: monotonicTime(),
wallTime: data.wallTime,
collectionTime: data.collectionTime, collectionTime: data.collectionTime,
resourceOverrides: [], resourceOverrides: [],
isMainFrame: page.mainFrame() === frame isMainFrame: page.mainFrame() === frame

View File

@ -27,7 +27,7 @@ export type SnapshotData = {
}[], }[],
viewport: { width: number, height: number }, viewport: { width: number, height: number },
url: string, url: string,
timestamp: number, wallTime: number,
collectionTime: number, collectionTime: number,
}; };
@ -572,7 +572,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
height: window.innerHeight, height: window.innerHeight,
}, },
url: location.href, url: location.href,
timestamp, wallTime: Date.now(),
collectionTime: 0, collectionTime: 0,
}; };
@ -589,7 +589,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
result.resourceOverrides.push({ url, content, contentType: 'text/css' },); result.resourceOverrides.push({ url, content, contentType: 'text/css' },);
} }
result.collectionTime = performance.now() - result.timestamp; result.collectionTime = performance.now() - timestamp;
return result; return result;
} }
} }

View File

@ -472,7 +472,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
sha1, sha1,
width: params.width, width: params.width,
height: params.height, height: params.height,
timestamp: monotonicTime() timestamp: monotonicTime(),
frameSwapWallTime: params.frameSwapWallTime,
}; };
// Make sure to write the screencast frame before adding a reference to it. // Make sure to write the screencast frame before adding a reference to it.
this._appendResource(sha1, params.buffer); this._appendResource(sha1, params.buffer);

View File

@ -45,6 +45,7 @@ export type PageEntry = {
screencastFrames: { screencastFrames: {
sha1: string, sha1: string,
timestamp: number, timestamp: number,
frameSwapWallTime?: number,
width: number, width: number,
height: number, height: number,
}[]; }[];

View File

@ -46,6 +46,7 @@ export class SnapshotServer {
viewport: snapshot.viewport(), viewport: snapshot.viewport(),
url: snapshot.snapshot().frameUrl, url: snapshot.snapshot().frameUrl,
timestamp: snapshot.snapshot().timestamp, timestamp: snapshot.snapshot().timestamp,
wallTime: snapshot.snapshot().wallTime,
} : { } : {
error: 'No snapshot found' error: 'No snapshot found'
}); });

View File

@ -31,12 +31,12 @@ import { TabbedPaneTab } from '@web/components/tabbedPane';
import { BrowserFrame } from './browserFrame'; import { BrowserFrame } from './browserFrame';
import { ClickPointer } from './clickPointer'; import { ClickPointer } from './clickPointer';
function findClosest<T extends { timestamp: number }>(items: T[], target: number) { function findClosest<T>(items: T[], metric: (v: T) => number, target: number) {
return items.find((item, index) => { return items.find((item, index) => {
if (index === items.length - 1) if (index === items.length - 1)
return true; return true;
const next = items[index + 1]; 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<HTMLIFrameElement>(null); const iframeRef0 = React.useRef<HTMLIFrameElement>(null);
const iframeRef1 = React.useRef<HTMLIFrameElement>(null); const iframeRef1 = React.useRef<HTMLIFrameElement>(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 }); const loadingRef = React.useRef({ iteration: 0, visibleIframe: 0 });
React.useEffect(() => { React.useEffect(() => {
@ -111,7 +111,7 @@ export const SnapshotTab: React.FunctionComponent<{
const newVisibleIframe = 1 - loadingRef.current.visibleIframe; const newVisibleIframe = 1 - loadingRef.current.visibleIframe;
loadingRef.current.iteration = thisIteration; loadingRef.current.iteration = thisIteration;
const newSnapshotInfo = { url: '', viewport: kDefaultViewport, timestamp: undefined }; const newSnapshotInfo = { url: '', viewport: kDefaultViewport, timestamp: undefined, wallTime: undefined };
if (snapshotInfoUrl) { if (snapshotInfoUrl) {
const response = await fetch(snapshotInfoUrl); const response = await fetch(snapshotInfoUrl);
const info = await response.json(); const info = await response.json();
@ -119,6 +119,7 @@ export const SnapshotTab: React.FunctionComponent<{
newSnapshotInfo.url = info.url; newSnapshotInfo.url = info.url;
newSnapshotInfo.viewport = info.viewport; newSnapshotInfo.viewport = info.viewport;
newSnapshotInfo.timestamp = info.timestamp; newSnapshotInfo.timestamp = info.timestamp;
newSnapshotInfo.wallTime = info.wallTime;
} }
} }
@ -170,10 +171,13 @@ export const SnapshotTab: React.FunctionComponent<{
const page = action ? pageForAction(action) : undefined; const page = action ? pageForAction(action) : undefined;
const screencastFrame = React.useMemo( 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) 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 <div return <div

View File

@ -44,6 +44,7 @@ export type FrameSnapshot = {
frameId: string, frameId: string,
frameUrl: string, frameUrl: string,
timestamp: number, timestamp: number,
wallTime?: number,
collectionTime: number, collectionTime: number,
doctype?: string, doctype?: string,
html: NodeSnapshot, html: NodeSnapshot,

View File

@ -53,6 +53,7 @@ export type ScreencastFrameTraceEvent = {
width: number, width: number,
height: number, height: number,
timestamp: number, timestamp: number,
frameSwapWallTime?: number,
}; };
export type BeforeActionTraceEvent = { export type BeforeActionTraceEvent = {