import { expect, test } from "@playwright/test"; import path from "node:path"; const basicBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/basic_scene.blend"); test("recovers the main-thread Chromium viewport after a real WebGL context loss", async ({ page }) => { await page.goto("/"); await page.setInputFiles("[data-testid=blend-file-input]", basicBlend); await expect(page.getByTestId("scene-stats")).toContainText("Objects 3"); const canvas = page.locator("canvas.viewport-canvas"); await expect(canvas).toHaveAttribute("data-device-status", "ready"); const result = await canvas.evaluate(async (element) => { const gl = element.getContext("webgl2") ?? element.getContext("webgl"); const extension = gl?.getExtension("WEBGL_lose_context"); if (!gl || !extension) return { supported: false, pixels: 0 }; const waitFor = (status: string): Promise => new Promise((resolve, reject) => { const started = performance.now(); const poll = (): void => { if (element.dataset.deviceStatus === status) { resolve(); return; } if (performance.now() - started > 10_000) { reject(new Error(`Timed out waiting for device status ${status}`)); return; } requestAnimationFrame(poll); }; poll(); }); extension.loseContext(); await waitFor("lost"); extension.restoreContext(); await waitFor("ready"); const pixels = new Uint8Array(16 * 16 * 4); gl.readPixels(0, 0, 16, 16, gl.RGBA, gl.UNSIGNED_BYTE, pixels); return { supported: true, pixels: pixels.reduce((total, value) => total + (value > 0 ? 1 : 0), 0) }; }); expect(result.supported).toBe(true); expect(result.pixels).toBeGreaterThan(0); await page.getByRole("button", { name: "添加立方体" }).click(); await expect(page.getByTestId("engine-status")).toContainText("SceneIR r2 (4 objects)"); });