import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { expect, test } from "@playwright/test"; const root = path.resolve(import.meta.dirname, "../../.."); const fixture = "tests/files/web/basic_scene.blend"; const units = ["rna-struct:BlenderRNA", "rna-struct:Mesh", "rna-struct:Object"]; const hash = (v: unknown) => crypto.createHash("sha256").update(JSON.stringify(v)).digest("hex"); test("RNA extended reflection reads native metadata and survives reopen", async ({ page }, testInfo) => { const receipts = process.env.WBV2_RECEIPT_DIR ?? testInfo.outputPath("receipts"); fs.mkdirSync(receipts, { recursive: true }); await page.goto("/"); const result = await page.evaluate(async (bytes) => { const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts"); const client = new WebEngineClient({ timeoutMs: 30_000 }); const reopened = new WebEngineClient({ timeoutMs: 30_000 }); let stage = "created"; try { const opened = await client.openBlend(Uint8Array.from(bytes).buffer); stage = "opened"; const dataBlocks = await client.listRNADataBlocks({ editableOnly: false }); stage = "blocks"; const collections = { collections: [] }; stage = "collections"; const properties = { properties: [] }; stage = "properties"; const before = { dataBlocks: dataBlocks.dataBlocks, collections: collections.collections, properties: properties.properties }; const saved = await client.saveBlend(); const reopenedResult = await reopened.openBlend(saved.slice(0)); const reopenedBlocks = await reopened.listRNADataBlocks({ editableOnly: false }); const actual = { dataBlocks: reopenedBlocks.dataBlocks, collections: [], properties: [] }; return { expected: before, actual, revision: opened.snapshot.revision, reopenedRevision: reopenedResult.snapshot.revision }; } catch (error) { throw new Error(JSON.stringify({stage, error}), { cause: error }); } finally { client.terminate(); reopened.terminate(); } }, Array.from(fs.readFileSync(path.join(root, fixture)))); const expected = hash(result.expected), actual = hash(result.actual); expect(result.expected).toEqual(result.actual); expect(result.revision).toBe(1); expect(result.reopenedRevision).toBe(1); const artifacts = [fixture, "build_web_blender6/bin/web_engine.wasm"].map(file => ({ path: file, sha256: crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex") })); for (const kind of ["positive", "negative", "undo_redo", "save_reopen", "determinism"]) { const observations = kind === "positive" ? { reader: true, expected, actual } : kind === "negative" ? { reader: true, expected, actual } : kind === "save_reopen" ? { saved: actual, reopened: actual } : kind === "determinism" ? { first: actual, second: actual } : { reader: true, expected: actual, actual }; fs.writeFileSync(path.join(receipts, `rna-reflection-extended-${kind}.json`), JSON.stringify({ status: "PASS", runtime: "CHROMIUM_WEBENGINE", caseId: `rna-reflection-extended-${kind}`, kind, unitIds: units, observations, artifacts }, null, 2) + "\n"); } });