-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
🚀 Feature Request
As an improvement on #32248, it would be nice if canvas inside iframes could be rendered too.
I took a look on the way it's currently implemented, and I think that with some minor adjustments it can be easily achievable.
The idea is just to compute the absolute position of the canvas. Normally this would be tricky, if not impossible, due to cross origin constrains, but in snapshot rendering they all have the same origin.
Example
As an example, here's a test that opens a trace with nested canvas and patches it by injecting javascript that computes their position and clips the closest screenshot accordingly:
import { test } from '@playwright/test';
test('iframe canvas', async ({ page }) => {
page.on('framenavigated', async (frame) => {
await frame.evaluate(() => {
const canvasElements = document.getElementsByTagName('canvas');
if (canvasElements.length === 0)
return;
let topFrameWindow: Window = window;
while (topFrameWindow !== topFrameWindow.parent && !topFrameWindow.location.pathname.match(/\/page@[a-z0-9]+$/))
topFrameWindow = topFrameWindow.parent;
const img = new Image();
img.onload = () => {
for (const canvas of canvasElements) {
const context = canvas.getContext('2d')!;
const boundingRect = canvas.getBoundingClientRect();
let left = boundingRect.left + window.scrollX;
let top = boundingRect.top + window.scrollY;
let right = boundingRect.right + window.scrollX;
let bottom = boundingRect.bottom + window.scrollY;
let currentWindow: Window = window;
while (currentWindow !== topFrameWindow) {
const iframe = currentWindow.frameElement!;
currentWindow = currentWindow.parent;
const iframeRect = iframe.getBoundingClientRect();
const xOffset = iframeRect.left + currentWindow.scrollX;
const yOffset = iframeRect.top + currentWindow.scrollY;
left += xOffset;
top += yOffset;
right += xOffset;
bottom += yOffset;
}
const width = topFrameWindow.innerWidth;
const height = topFrameWindow.innerHeight;
left = left / width;
top = top / height;
right = right / width;
bottom = bottom / height;
context.drawImage(img, left * img.width, top * img.height, (right - left) * img.width, (bottom - top) * img.height, 0, 0, canvas.width, canvas.height);
}
};
img.src = location.href.replace('/snapshot', '/closest-screenshot');
}).catch(() => {});
});
await page.goto('https://trace.playwright.dev/?trace=https://raw.githubusercontent.com/ruifigueira/vscode-test-playwright/main/docs/assets/trace.zip');
const actions = page.getByTestId('actions-tree').getByRole('treeitem');
await actions.first().waitFor();
for (const action of await actions.all()) {
await action.click();
await page.waitForTimeout(50);
}
});Notice that this computation no longer relies on __playwright_bounding_rect__.
Motivation
In my use case, I'm using playwright to test a vscode webview with a canvas, and it is always nested inside a two-level iframe structure.
I also think this solution is simpler than the current implementation while covering nested iframe canvas.
I can contribute with a PR if needed.