replace injected screenshotURL with sw-side routing

This commit is contained in:
Simon Knott 2024-10-17 11:27:59 +02:00
parent c70f3726de
commit ccdfeee40e
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
3 changed files with 38 additions and 19 deletions

View file

@ -123,12 +123,19 @@ async function doFetch(event: FetchEvent): Promise<Response> {
const { snapshotServer } = loadedTraces.get(traceUrl!) || {}; const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
if (!snapshotServer) if (!snapshotServer)
return new Response(null, { status: 404 }); 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) if (isDeployedAsHttps)
response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests'); response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests');
return response; 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/')) { if (relativePath.startsWith('/sha1/')) {
// Sha1 for sources is based on the file path, can't load it of a random model. // Sha1 for sources is based on the file path, can't load it of a random model.
const sha1 = relativePath.slice('/sha1/'.length); const sha1 = relativePath.slice('/sha1/'.length);

View file

@ -78,7 +78,7 @@ export class SnapshotRenderer {
return this._snapshots[this._index].viewport; return this._snapshots[this._index].viewport;
} }
render(screenshotUrl: string | undefined): RenderedFrameSnapshot { render(): RenderedFrameSnapshot {
const result: string[] = []; const result: string[] = [];
const visit = (n: NodeSnapshot, snapshotIndex: number, parentTag: string | undefined, parentAttrs: [string, string][] | undefined) => { const visit = (n: NodeSnapshot, snapshotIndex: number, parentTag: string | undefined, parentAttrs: [string, string][] | undefined) => {
// Text node. // Text node.
@ -159,7 +159,7 @@ export class SnapshotRenderer {
const prefix = snapshot.doctype ? `<!DOCTYPE ${snapshot.doctype}>` : ''; const prefix = snapshot.doctype ? `<!DOCTYPE ${snapshot.doctype}>` : '';
return prefix + [ return prefix + [
'<style>*,*::before,*::after { visibility: hidden }</style>', '<style>*,*::before,*::after { visibility: hidden }</style>',
`<script>${snapshotScript(screenshotUrl, this._callId, this.snapshotName)}</script>` `<script>${snapshotScript(this._callId, this.snapshotName)}</script>`
].join('') + html; ].join('') + html;
}); });
@ -242,8 +242,8 @@ function snapshotNodes(snapshot: FrameSnapshot): NodeSnapshot[] {
return (snapshot as any)._nodes; return (snapshot as any)._nodes;
} }
function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string | undefined)[]) { function snapshotScript(...targetIds: (string | undefined)[]) {
function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, screenshotURL: string | undefined, ...targetIds: (string | undefined)[]) { function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) {
const isUnderTest = new URLSearchParams(location.search).has('isUnderTest'); const isUnderTest = new URLSearchParams(location.search).has('isUnderTest');
const kPointerWarningTitle = 'Recorded click position in absolute coordinates did not' + 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 drawWarningBackground(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) {
function createCheckerboardPattern() { function createCheckerboardPattern() {
const pattern = document.createElement('canvas'); 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.`; 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); 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('')})`;
} }

View file

@ -43,25 +43,37 @@ export class SnapshotServer {
this._pages = new Map(contextEntries.flatMap(c => c.pages.map(p => [p.pageId, p]))); 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); const snapshot = this._snapshot(pathname.substring('/snapshot'.length), searchParams);
if (!snapshot) if (!snapshot)
return new Response(null, { status: 404 }); return new Response(null, { status: 404 });
let screenshotUrl: URL | undefined; const renderedSnapshot = snapshot.render();
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());
this._snapshotIds.set(snapshotUrl, snapshot); this._snapshotIds.set(snapshotUrl, snapshot);
return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
} }
async serveClosestScreenshot(pathname: string, searchParams: URLSearchParams): Promise<Response> {
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 { serveSnapshotInfo(pathname: string, searchParams: URLSearchParams): Response {
const snapshot = this._snapshot(pathname.substring('/snapshotInfo'.length), searchParams); const snapshot = this._snapshot(pathname.substring('/snapshotInfo'.length), searchParams);
return this._respondWithJson(snapshot ? { return this._respondWithJson(snapshot ? {