diff --git a/packages/trace-viewer/src/sw/snapshotRenderer.ts b/packages/trace-viewer/src/sw/snapshotRenderer.ts index a25bcf5d1d1e8..6b73cff5e981d 100644 --- a/packages/trace-viewer/src/sw/snapshotRenderer.ts +++ b/packages/trace-viewer/src/sw/snapshotRenderer.ts @@ -253,6 +253,7 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string const scrollTops: Element[] = []; const scrollLefts: Element[] = []; const targetElements: Element[] = []; + const canvasElements: HTMLCanvasElement[] = []; const visit = (root: Document | ShadowRoot) => { // Collect all scrolled elements for later use. @@ -301,72 +302,6 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string } } - const canvases = root.querySelectorAll('canvas'); - if (canvases.length > 0 && screenshotURL) { - function drawWarningBackground(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) { - function createCheckerboardPattern() { - const pattern = document.createElement('canvas'); - pattern.width = pattern.width / Math.floor(pattern.width / 24); - pattern.height = pattern.height / Math.floor(pattern.height / 24); - const context = pattern.getContext('2d')!; - context.fillStyle = 'lightgray'; - context.fillRect(0, 0, pattern.width, pattern.height); - context.fillStyle = 'black'; - context.fillRect(0, 0, pattern.width / 2, pattern.height / 2); - context.fillRect(pattern.width / 2, pattern.height / 2, pattern.width, pattern.height); - return context.createPattern(pattern, 'repeat')!; - } - - context.fillStyle = createCheckerboardPattern(); - context.fillRect(0, 0, canvas.width, canvas.height); - } - - function drawWarningIcon(context: CanvasRenderingContext2D) { - context.fillStyle = 'red'; - context.font = '24px serif'; - context.textBaseline = 'top'; - context.fillText('⚠', 0, 0); - } - - const img = new Image(); - img.onload = () => { - for (const canvas of canvases) { - const context = canvas.getContext('2d')!; - - const boundingRect = canvas.getBoundingClientRect(); - const xStart = boundingRect.left / window.innerWidth; - const yStart = boundingRect.top / window.innerHeight; - const xEnd = boundingRect.right / window.innerWidth; - const yEnd = boundingRect.bottom / window.innerHeight; - - drawWarningBackground(context, canvas); - context.drawImage(img, xStart * img.width, yStart * img.height, (xEnd - xStart) * img.width, (yEnd - yStart) * img.height, 0, 0, canvas.width, canvas.height); - drawWarningIcon(context); - if (isUnderTest) - // eslint-disable-next-line no-console - console.log(`canvas drawn:`, JSON.stringify([xStart, yStart, xEnd, yEnd].map(v => Math.floor(v * 100)))); - - if (xEnd > 1 || yEnd > 1) { - if (xStart > 1 || yStart > 1) - canvas.title = `Playwright couldn't capture full canvas contents because it's located partially outside the viewport.`; - else - canvas.title = `Playwright couldn't capture canvas contents because it's located outside the viewport.`; - } else { - canvas.title = `Canvas contents are displayed on a best-effort basis based on viewport screenshots taken during test execution.`; - } - } - }; - img.onerror = () => { - for (const canvas of canvases) { - const context = canvas.getContext('2d')!; - drawWarningBackground(context, canvas); - drawWarningIcon(context); - canvas.title = `Playwright couldn't show canvas contents because the screenshot failed to load.`; - } - }; - img.src = screenshotURL; - } - { const body = root.querySelector(`body[__playwright_custom_elements__]`); if (body && window.customElements) { @@ -394,6 +329,8 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string } (root as any).adoptedStyleSheets = adoptedSheets; } + + canvasElements.push(...root.querySelectorAll('canvas')); }; const onLoad = () => { @@ -461,6 +398,71 @@ function snapshotScript(screenshotURL: string | undefined, ...targetIds: (string } } } + + if (canvasElements.length > 0 && screenshotURL) { + function drawWarningBackground(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) { + function createCheckerboardPattern() { + const pattern = document.createElement('canvas'); + pattern.width = pattern.width / Math.floor(pattern.width / 24); + pattern.height = pattern.height / Math.floor(pattern.height / 24); + const context = pattern.getContext('2d')!; + context.fillStyle = 'lightgray'; + context.fillRect(0, 0, pattern.width, pattern.height); + context.fillStyle = 'black'; + context.fillRect(0, 0, pattern.width / 2, pattern.height / 2); + context.fillRect(pattern.width / 2, pattern.height / 2, pattern.width, pattern.height); + return context.createPattern(pattern, 'repeat')!; + } + + context.fillStyle = createCheckerboardPattern(); + context.fillRect(0, 0, canvas.width, canvas.height); + } + + function drawWarningIcon(context: CanvasRenderingContext2D) { + context.fillStyle = 'red'; + context.font = '24px serif'; + context.textBaseline = 'top'; + context.fillText('⚠', 0, 0); + } + + const img = new Image(); + img.onload = () => { + for (const canvas of canvasElements) { + const context = canvas.getContext('2d')!; + + const boundingRect = canvas.getBoundingClientRect(); + const xStart = boundingRect.left / window.innerWidth; + const yStart = boundingRect.top / window.innerHeight; + const xEnd = boundingRect.right / window.innerWidth; + const yEnd = boundingRect.bottom / window.innerHeight; + + drawWarningBackground(context, canvas); + context.drawImage(img, xStart * img.width, yStart * img.height, (xEnd - xStart) * img.width, (yEnd - yStart) * img.height, 0, 0, canvas.width, canvas.height); + drawWarningIcon(context); + if (isUnderTest) + // eslint-disable-next-line no-console + console.log(`canvas drawn:`, JSON.stringify([xStart, yStart, xEnd, yEnd].map(v => Math.floor(v * 100)))); + + if (xEnd > 1 || yEnd > 1) { + if (xStart > 1 || yStart > 1) + canvas.title = `Playwright couldn't capture full canvas contents because it's located partially outside the viewport.`; + else + canvas.title = `Playwright couldn't capture canvas contents because it's located outside the viewport.`; + } else { + canvas.title = `Canvas contents are displayed on a best-effort basis based on viewport screenshots taken during test execution.`; + } + } + }; + img.onerror = () => { + for (const canvas of canvasElements) { + const context = canvas.getContext('2d')!; + drawWarningBackground(context, canvas); + drawWarningIcon(context); + canvas.title = `Playwright couldn't show canvas contents because the screenshot failed to load.`; + } + }; + img.src = screenshotURL; + } }; const onDOMContentLoaded = () => visit(document); diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 5bc17da8f078b..1ab3a3c9ea31f 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -1445,6 +1445,8 @@ test('canvas clipping', async ({ runAndTrace, page, server }) => { await page.waitForTimeout(1000); // ensure we could take a screenshot }); + await traceViewer.page.pause(); + const msg = await traceViewer.page.waitForEvent('console', { predicate: msg => msg.text().startsWith('canvas drawn:') }); expect(msg.text()).toEqual('canvas drawn: [0,91,12,111]');