diff --git a/packages/trace-viewer/src/sw/main.ts b/packages/trace-viewer/src/sw/main.ts index 0753f211c3..30600a2f4c 100644 --- a/packages/trace-viewer/src/sw/main.ts +++ b/packages/trace-viewer/src/sw/main.ts @@ -123,12 +123,19 @@ async function doFetch(event: FetchEvent): Promise { const { snapshotServer } = loadedTraces.get(traceUrl!) || {}; if (!snapshotServer) return new Response(null, { status: 404 }); - const response = snapshotServer.serveSnapshot(relativePath, url.searchParams, url.href, self.registration.scope); + const response = snapshotServer.serveSnapshot(relativePath, url.searchParams, url.href); if (isDeployedAsHttps) response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests'); return response; } + if (relativePath.startsWith('/closest-screenshot/')) { + const { snapshotServer } = loadedTraces.get(traceUrl!) || {}; + if (!snapshotServer) + return new Response(null, { status: 404 }); + return snapshotServer.serveClosestScreenshot(relativePath, url.searchParams); + } + if (relativePath.startsWith('/sha1/')) { // Sha1 for sources is based on the file path, can't load it of a random model. const sha1 = relativePath.slice('/sha1/'.length); diff --git a/packages/trace-viewer/src/sw/snapshotRenderer.ts b/packages/trace-viewer/src/sw/snapshotRenderer.ts index 6b73cff5e9..d33571afa1 100644 --- a/packages/trace-viewer/src/sw/snapshotRenderer.ts +++ b/packages/trace-viewer/src/sw/snapshotRenderer.ts @@ -78,7 +78,7 @@ export class SnapshotRenderer { return this._snapshots[this._index].viewport; } - render(screenshotUrl: string | undefined): RenderedFrameSnapshot { + render(): RenderedFrameSnapshot { const result: string[] = []; const visit = (n: NodeSnapshot, snapshotIndex: number, parentTag: string | undefined, parentAttrs: [string, string][] | undefined) => { // Text node. @@ -159,7 +159,7 @@ export class SnapshotRenderer { const prefix = snapshot.doctype ? `` : ''; return prefix + [ '', - `` + `` ].join('') + html; }); @@ -242,8 +242,8 @@ function snapshotNodes(snapshot: FrameSnapshot): NodeSnapshot[] { return (snapshot as any)._nodes; } -function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string | undefined)[]) { - function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, screenshotURL: string | undefined, ...targetIds: (string | undefined)[]) { +function snapshotScript(...targetIds: (string | undefined)[]) { + function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) { const isUnderTest = new URLSearchParams(location.search).has('isUnderTest'); const kPointerWarningTitle = 'Recorded click position in absolute coordinates did not' + @@ -399,7 +399,7 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string } } - if (canvasElements.length > 0 && screenshotURL) { + if (canvasElements.length > 0) { function drawWarningBackground(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) { function createCheckerboardPattern() { const pattern = document.createElement('canvas'); @@ -461,7 +461,7 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string canvas.title = `Playwright couldn't show canvas contents because the screenshot failed to load.`; } }; - img.src = screenshotURL; + img.src = location.href.replace('/snapshot', '/closest-screenshot'); } }; @@ -471,7 +471,7 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string window.addEventListener('DOMContentLoaded', onDOMContentLoaded); } - return `\n(${applyPlaywrightAttributes.toString()})(${unwrapPopoutUrl.toString()}, ${JSON.stringify(screenshotURL)}${targetIds.map(id => `, "${id}"`).join('')})`; + return `\n(${applyPlaywrightAttributes.toString()})(${unwrapPopoutUrl.toString()}${targetIds.map(id => `, "${id}"`).join('')})`; } diff --git a/packages/trace-viewer/src/sw/snapshotServer.ts b/packages/trace-viewer/src/sw/snapshotServer.ts index dcff29e509..9eed676479 100644 --- a/packages/trace-viewer/src/sw/snapshotServer.ts +++ b/packages/trace-viewer/src/sw/snapshotServer.ts @@ -43,25 +43,37 @@ export class SnapshotServer { this._pages = new Map(contextEntries.flatMap(c => c.pages.map(p => [p.pageId, p]))); } - serveSnapshot(pathname: string, searchParams: URLSearchParams, snapshotUrl: string, swScope: string): Response { + serveSnapshot(pathname: string, searchParams: URLSearchParams, snapshotUrl: string): Response { const snapshot = this._snapshot(pathname.substring('/snapshot'.length), searchParams); if (!snapshot) return new Response(null, { status: 404 }); - let screenshotUrl: URL | undefined; - const { wallTime, timestamp, pageId } = snapshot.snapshot(); - const page = this._pages.get(pageId); - if (page) { - const closestFrame = (wallTime && page.screencastFrames[0]?.frameSwapWallTime) ? findClosest(page.screencastFrames, frame => frame.frameSwapWallTime!, wallTime) : findClosest(page.screencastFrames, frame => frame.timestamp, timestamp); - if (closestFrame) - screenshotUrl = new URL(`./sha1/${closestFrame.sha1}`, swScope); - } - - const renderedSnapshot = snapshot.render(screenshotUrl?.toString()); + const renderedSnapshot = snapshot.render(); this._snapshotIds.set(snapshotUrl, snapshot); return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); } + async serveClosestScreenshot(pathname: string, searchParams: URLSearchParams): Promise { + const snapshot = this._snapshot(pathname.substring('/closest-screenshot'.length), searchParams); + if (!snapshot) + return new Response(null, { status: 404 }); + + const { wallTime, timestamp, pageId } = snapshot.snapshot(); + const page = this._pages.get(pageId); + if (!page) + return new Response(null, { status: 404 }); + + const closestFrame = (wallTime && page.screencastFrames[0]?.frameSwapWallTime) ? findClosest(page.screencastFrames, frame => frame.frameSwapWallTime!, wallTime) : findClosest(page.screencastFrames, frame => frame.timestamp, timestamp); + if (!closestFrame) + return new Response(null, { status: 404 }); + + const blob = await this._resourceLoader(closestFrame.sha1); + if (!blob) + return new Response(null, { status: 404 }); + + return new Response(blob); + } + serveSnapshotInfo(pathname: string, searchParams: URLSearchParams): Response { const snapshot = this._snapshot(pathname.substring('/snapshotInfo'.length), searchParams); return this._respondWithJson(snapshot ? {