import { expect, test } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; const root = path.resolve(import.meta.dirname, "../../.."); const identity = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02B/identity.json"), "utf8")); const desktop = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02H/desktop-report.json"), "utf8")); const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02H/manifest.json"), "utf8")); const preview = fs.readFileSync(path.join(root, "tests/golden/M12-02B/preview.png")); test("M12-02H main-thread and Offscreen preview displays match the Blender RGBA8 metrics", async ({ page }) => { await page.goto("/"); const result = await page.evaluate(async ({ identityValue, bytes, desktopValue, thresholds }) => { const { displayAssetPreview } = await import("/src/assets/AssetPreviewDisplay.ts"); const { compareRenderImages } = await import("/src/three-adapter/render-image-comparison.ts"); document.body.replaceChildren(); document.body.style.margin = "0"; const canvas = document.createElement("canvas"); canvas.id = "m12-asset-preview"; canvas.setAttribute("aria-label", "Asset preview"); canvas.style.width = "128px"; canvas.style.height = "128px"; canvas.style.imageRendering = "pixelated"; document.body.append(canvas); const encoded = new Uint8Array(bytes).buffer; const main = await displayAssetPreview(canvas, "MAIN_THREAD_CANVAS_2D", identityValue, encoded.slice(0)); const workerResult = await new Promise<{ receipt: unknown; pixels: Uint8Array }>((resolve, reject) => { const worker = new Worker("/src/workers/asset-preview-display-test.worker.ts", { type: "module" }); worker.onmessage = (event) => { worker.terminate(); event.data.ok ? resolve(event.data) : reject(new Error(event.data.error)); }; worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); }; worker.postMessage({ identity: identityValue, bytes: encoded.slice(0) }); }); const reference = new Uint8Array(desktopValue.pixelCount * 4); for (let offset = 0; offset < reference.byteLength; offset += 4) reference.set(desktopValue.referencePixel, offset); const mainMetrics = compareRenderImages(reference, main.pixels, desktopValue.width, desktopValue.height, thresholds); const offscreenPixels = new Uint8Array(workerResult.pixels); const offscreenMetrics = compareRenderImages(reference, offscreenPixels, desktopValue.width, desktopValue.height, thresholds); const wrong = new Uint8Array(main.pixels); wrong[0] ^= 0xff; const wrongMetrics = compareRenderImages(reference, wrong, desktopValue.width, desktopValue.height, thresholds); return { main: { receipt: main.receipt, pixels: Array.from(main.pixels) }, offscreen: { receipt: workerResult.receipt, pixels: Array.from(offscreenPixels) }, mainMetrics, offscreenMetrics, wrongMetrics, }; }, { identityValue: identity, bytes: [...preview], desktopValue: desktop, thresholds: manifest.thresholds }); const canvas = page.locator("#m12-asset-preview"); await expect(canvas).toBeVisible(); await expect(canvas).toHaveAttribute("width", "8"); await expect(canvas).toHaveAttribute("height", "8"); await expect(canvas).toHaveAttribute("data-asset-preview-status", "ready"); await expect(canvas).toHaveAttribute("data-asset-preview-backend", "MAIN_THREAD_CANVAS_2D"); await expect(canvas).toHaveCSS("width", "128px"); await expect(canvas).toHaveCSS("height", "128px"); expect(result.main.receipt).toMatchObject({ status: "READY", backend: "MAIN_THREAD_CANVAS_2D", width: 8, height: 8, pixelByteLength: 256, pixelSha256: desktop.rgbaSha256, nonTransparentPixels: 64, bitmapClosed: true, }); expect(result.offscreen.receipt).toMatchObject({ status: "READY", backend: "OFFSCREEN_CANVAS_2D", width: 8, height: 8, pixelByteLength: 256, pixelSha256: desktop.rgbaSha256, nonTransparentPixels: 64, bitmapClosed: true, }); expect(result.main.pixels).toEqual(result.offscreen.pixels); for (const metrics of [result.mainMetrics, result.offscreenMetrics]) { expect(metrics.status).toBe("READY"); expect(metrics.errorCode).toBeNull(); expect(metrics.meanAbsoluteError).toBe(0); expect(metrics.rootMeanSquaredError).toBe(0); expect(metrics.p95ChannelError).toBe(0); expect(metrics.maxChannelError).toBe(0); expect(metrics.badPixelRatio).toBe(0); expect(metrics.foregroundIntersectionOverUnion).toBe(1); expect(metrics.alphaCoverageDeltaRatio).toBe(0); } expect(result.wrongMetrics.status).toBe("BLOCKED"); expect(result.wrongMetrics.errorCode).toBe("RENDER_REFERENCE_MISMATCH"); });