import { expect, test } from "@playwright/test"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; const root = path.resolve(import.meta.dirname, "../../.."); const fixturePath = process.env.WBV2_BATCH_FIXTURE ?? "tests/files/web/modifier_geometry_nodes_scene.blend"; const geometryNodesBlend = path.resolve(import.meta.dirname, "../../../", fixturePath); const task = process.env.WBV2_BATCH_TASK ?? "WBV2-P1125-000"; const sourceUnits = JSON.parse(process.env.WBV2_BATCH_UNITS ?? "[]") as string[]; const expectedTypes = JSON.parse(process.env.WBV2_BATCH_TYPES ?? "[]") as string[]; const productionPath = process.env.WBV2_BATCH_PRODUCTION ?? `tests/golden/${task}/geometry-node-reader-production.json`; test("M10-01 production Worker preserves Main Geometry Node topology across save and reopen", async ({ page }) => { test.setTimeout(120_000); await page.goto("/"); const blendBytes = fs.readFileSync(geometryNodesBlend); const result = await page.evaluate(async ({ blendBytes }) => { const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts"); const source = blendBytes.buffer.slice( blendBytes.byteOffset, blendBytes.byteOffset + blendBytes.byteLength, ) as ArrayBuffer; const first = new WebEngineClient({ timeoutMs: 60_000 }); await first.init(); const opened = await first.openBlend(source); const initial = opened.snapshot.geometryNodeGraphs ?? []; const saved = await first.saveBlend(); first.terminate(); const second = new WebEngineClient({ timeoutMs: 60_000 }); await second.init(); const reopened = await second.openBlend(saved); const restored = reopened.snapshot.geometryNodeGraphs ?? []; second.terminate(); return { graphNames: initial.map((graph) => graph.name), nodeTypes: initial.flatMap((graph) => graph.nodes.map((node) => node.type)), nodeCount: initial.reduce((count, graph) => count + graph.nodes.length, 0), linkCount: initial.reduce((count, graph) => count + graph.links.length, 0), defaultCount: initial.reduce((count, graph) => count + graph.nodes.reduce( (nodeCount, node) => nodeCount + node.sockets.filter((socket) => socket.defaultValue !== undefined).length, 0, ), 0), unsupportedPreserved: initial.some((graph) => graph.nodes.some((node) => node.type === "GeometryNodeSimulationOutput")), stable: JSON.stringify(initial) === JSON.stringify(restored), hashes: initial.map((graph) => graph.graphHash), }; }, { blendBytes: new Uint8Array(blendBytes) }); fs.mkdirSync(path.dirname(path.join(root, productionPath)), { recursive: true }); fs.writeFileSync(path.join(root, productionPath), `${JSON.stringify({ schemaVersion: 1, task, operation: "GEOMETRY_NODE_MAIN_READER_CHROMIUM", browser: "chromium", queueMutation: false, fixture: fixturePath, fixtureSha256: crypto.createHash("sha256").update(blendBytes).digest("hex"), sourceUnits, expectedTypes, ...result, }, null, 2)}\n`); if (sourceUnits.length > 0) { expect(result.nodeTypes).toEqual(expect.arrayContaining(expectedTypes)); expect(result.stable).toBe(true); expect(result.hashes.every((hash) => /^[0-9a-f]{64}$/.test(hash ?? ""))).toBe(true); } else { expect(result).toEqual({ graphNames: ["WebGeometryNodes", "WebGeometryNodesSetPosition", "WebGeometryNodesSimulation"], nodeTypes: expect.any(Array), nodeCount: 10, linkCount: 7, defaultCount: 9, unsupportedPreserved: true, stable: true, hashes: result.hashes, }); expect(result.hashes).toHaveLength(3); expect(result.hashes.every((hash) => /^[0-9a-f]{64}$/.test(hash ?? ""))).toBe(true); } });