diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts index d3b7705a86..cfe4f14725 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts @@ -151,6 +151,14 @@ export class Snapshotter { snapshot.resourceOverrides.push({ url, ref: content }); } } + + for (const [sha1, contents] of Object.entries(data.canvasRenderResults)) { + this._delegate.onSnapshotterBlob({ + sha1, buffer: Buffer.from(contents, 'base64') + }); + snapshot.resourceOverrides.push({ url: 'TODO: this is required but unused', sha1 }); + } + this._delegate.onFrameSnapshot(snapshot); }); await Promise.all(snapshots); diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 0354a0394b..057435a728 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -29,6 +29,7 @@ export type SnapshotData = { url: string, timestamp: number, collectionTime: number, + canvasRenderResults: Record, }; export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: boolean) { @@ -86,6 +87,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: private _readingStyleSheet = false; // To avoid invalidating due to our own reads. private _fakeBase: HTMLBaseElement; private _observer: MutationObserver; + private _capturedCanvases = new Set(); constructor() { const invalidateCSSGroupingRule = (rule: CSSGroupingRule) => { @@ -319,14 +321,15 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: this._handleMutations(this._observer.takeRecords()); const definedCustomElements = new Set(); + const canvasRenderResults: Record = {}; const visitNode = (node: Node | ShadowRoot): { equals: boolean, n: NodeSnapshot } | undefined => { const nodeType = node.nodeType; const nodeName = nodeType === Node.DOCUMENT_FRAGMENT_NODE ? 'template' : node.nodeName; if (nodeType !== Node.ELEMENT_NODE && - nodeType !== Node.DOCUMENT_FRAGMENT_NODE && - nodeType !== Node.TEXT_NODE) + nodeType !== Node.DOCUMENT_FRAGMENT_NODE && + nodeType !== Node.TEXT_NODE) return; if (nodeName === 'SCRIPT') return; @@ -406,7 +409,19 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: if (nodeName === 'CANVAS') { const canvas = node as HTMLCanvasElement; - attrs['__playwright_canvas_'] = canvas.toDataURL('image/webp', 1); + const requestedMIME = 'image/webp'; + const dataURL = canvas.toDataURL(requestedMIME); + const actualMIME = dataURL.substring('data:'.length, dataURL.indexOf(';')); + const contentsB64 = dataURL.substring(dataURL.indexOf(',') + 1); + const sha = '' + contentsB64.length; // TODO + + attrs['__playwright_canvas_sha_'] = sha; + attrs['__playwright_canvas_mime_'] = actualMIME; + + if (!this._capturedCanvases.has(sha)) { + this._capturedCanvases.add(sha); + canvasRenderResults[sha] = contentsB64; + } } if (nodeType === Node.DOCUMENT_FRAGMENT_NODE) @@ -579,6 +594,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: url: location.href, timestamp, collectionTime: 0, + canvasRenderResults, }; for (const sheet of this._staleStyleSheets) { diff --git a/packages/trace-viewer/src/snapshotRenderer.ts b/packages/trace-viewer/src/snapshotRenderer.ts index 87cb52b655..d3dd95ebde 100644 --- a/packages/trace-viewer/src/snapshotRenderer.ts +++ b/packages/trace-viewer/src/snapshotRenderer.ts @@ -285,7 +285,13 @@ function snapshotScript(...targetIds: (string | undefined)[]) { const context = canvas.getContext('2d'); context?.drawImage(img, 0, 0); }; - img.src = canvas.getAttribute('__playwright_canvas_')!; + const url = new URL(window.location.href); + const index = url.pathname.lastIndexOf('/snapshot/'); + if (index !== -1) + url.pathname = url.pathname.substring(0, index + 1); + url.pathname += `sha1/${canvas.getAttribute('__playwright_canvas_sha_')}`; + url.searchParams.set('ct', canvas.getAttribute('__playwright_canvas_mime_')!); + img.src = url.toString(); } { diff --git a/packages/trace-viewer/src/sw.ts b/packages/trace-viewer/src/sw.ts index 7888aa6a30..6c06a88488 100644 --- a/packages/trace-viewer/src/sw.ts +++ b/packages/trace-viewer/src/sw.ts @@ -133,7 +133,7 @@ async function doFetch(event: FetchEvent): Promise { // Sha1 for sources is based on the file path, can't load it of a random model. const sha1 = relativePath.slice('/sha1/'.length); for (const trace of loadedTraces.values()) { - const blob = await trace.traceModel.resourceForSha1(sha1); + const blob = await trace.traceModel.resourceForSha1(sha1, url.searchParams.get('ct')); if (blob) return new Response(blob, { status: 200, headers: downloadHeaders(url.searchParams) }); } diff --git a/packages/trace-viewer/src/traceModel.ts b/packages/trace-viewer/src/traceModel.ts index 0fc1a73efa..c0cae40897 100644 --- a/packages/trace-viewer/src/traceModel.ts +++ b/packages/trace-viewer/src/traceModel.ts @@ -112,11 +112,11 @@ export class TraceModel { return this._backend.hasEntry(filename); } - async resourceForSha1(sha1: string): Promise { + async resourceForSha1(sha1: string, contentTypeOverride?: string | null): Promise { const blob = await this._backend.readBlob('resources/' + sha1); if (!blob) return; - return new Blob([blob], { type: this._resourceToContentType.get(sha1) || 'application/octet-stream' }); + return new Blob([blob], { type: contentTypeOverride || this._resourceToContentType.get(sha1) || 'application/octet-stream' }); } storage(): SnapshotStorage { diff --git a/tests/library/trace-viewer.spec.ts-snapshots/should-show-canvas-webgl-works-with-preserveDrawingBuffer-1-chromium.png b/tests/library/trace-viewer.spec.ts-snapshots/should-show-canvas-webgl-works-with-preserveDrawingBuffer-1-chromium.png index c3f2b66b2b..c3f6361bde 100644 Binary files a/tests/library/trace-viewer.spec.ts-snapshots/should-show-canvas-webgl-works-with-preserveDrawingBuffer-1-chromium.png and b/tests/library/trace-viewer.spec.ts-snapshots/should-show-canvas-webgl-works-with-preserveDrawingBuffer-1-chromium.png differ