import { expect, test } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; const root = path.resolve(import.meta.dirname, "../../.."); const blend = fs.readFileSync(path.join(root, "tests/files/web/m11_render_reference.blend")); const golden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-05/render-routing.json"), "utf8")); test("M11-05 exposes fail-closed render routing for Web, server and hardware capabilities", async ({ page }) => { await page.goto("/"); const result = await page.evaluate(async (input) => { const { routeRenderExecution } = await import("/src/three-adapter/render-routing.ts"); const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts"); const client = new WebEngineClient({ timeoutMs: 30_000 }); try { const opened = await client.openBlend(Uint8Array.from(input).buffer); const sourceRenderEngine = opened.snapshot.scenes[0]?.renderEngine; if (sourceRenderEngine !== "BLENDER_EEVEE" && sourceRenderEngine !== "BLENDER_EEVEE_NEXT") throw new Error(`Unexpected fixture render engine: ${sourceRenderEngine}`); const base = { schemaVersion: 1 as const, renderEngine: sourceRenderEngine, backend: "WEBGL2" as const, complexity: "BOUNDED" as const }; const bounded = routeRenderExecution(base); const cycles = routeRenderExecution({ ...base, renderEngine: "BLENDER_CYCLES", backend: "CYCLES" }); const complex = routeRenderExecution({ ...base, backend: "EEVEE_COMPLEX", complexity: "COMPLEX" }); const hardware = routeRenderExecution({ ...base, hardwareBackend: "OPTIX" }); const availableServer = routeRenderExecution({ ...base, renderEngine: "BLENDER_CYCLES", backend: "CYCLES" }, { serverRenderAvailable: true }); const unavailableGpu = routeRenderExecution({ ...base, backend: "WEBGPU" }); const unknownEngine = routeRenderExecution({ ...base, renderEngine: "UNKNOWN_ENGINE" }); return { sourceRenderEngine, bounded, cycles, complex, hardware, availableServer, unavailableGpu, unknownEngine }; } finally { client.terminate(); } }, Array.from(blend)); expect(result.sourceRenderEngine).toBe("BLENDER_EEVEE"); expect(result.bounded).toMatchObject(golden.boundedEevee); for (const serverResult of [result.cycles, result.complex, result.hardware]) { expect(serverResult).toMatchObject({ target: "SERVER_JOB", status: "BLOCKED" }); expect(serverResult.issues[0].code).toBe("SERVER_JOB_UNAVAILABLE"); } expect(result.availableServer).toMatchObject({ target: golden.cycles.target, ...golden.cycles.withEndpoint }); expect(result.unavailableGpu).toMatchObject({ target: golden.webgpu.target, status: golden.webgpu.status }); expect(result.unavailableGpu.issues[0].code).toBe(golden.webgpu.code); expect(result.unknownEngine).toMatchObject({ target: golden.unknownEngine.target, status: golden.unknownEngine.status, capability: golden.unknownEngine.capability }); expect(result.unknownEngine.issues[0].code).toBe(golden.unknownEngine.code); });