- update native web_engine sources and rebuilt wasm artifacts - add task cards, status notes and tooling scripts - add web e2e specs, tutorial UI sources and tutorial assets - sync golden task manifests and audit records Co-Authored-By: Claude Code <noreply@anthropic.com>
86 lines
4.5 KiB
TypeScript
86 lines
4.5 KiB
TypeScript
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 fixturePath = path.join(root, "tests/files/web/nonmesh_scene.blend");
|
|
const fixture = fs.readFileSync(fixturePath);
|
|
const fixtureSha256 = crypto.createHash("sha256").update(fixture).digest("hex");
|
|
|
|
test("WBV2 reads non-mesh geometry and attributes from Blender Main without JS simulation", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await page.goto("/?engine=manual");
|
|
const result = await page.evaluate(async (bytes) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const summarize = (snapshot: any) => (snapshot.nonMeshData ?? []).map((data: any) => ({
|
|
id: data.id,
|
|
name: data.name,
|
|
type: data.type,
|
|
geometryStatus: data.geometryStatus,
|
|
pointCount: data.pointCount,
|
|
splineCount: data.splineCount,
|
|
controlPointsLength: data.controlPoints?.length ?? 0,
|
|
splineOffsetsLength: data.splineOffsets?.length ?? 0,
|
|
pointWeightsLength: data.pointWeights?.length ?? 0,
|
|
handlePointsLength: data.handlePoints?.length ?? 0,
|
|
splineTypes: data.splineTypes ?? [],
|
|
attributes: (data.attributes ?? []).map((attribute: any) => ({ name: attribute.name, domain: attribute.domain, dataType: attribute.dataType })).sort((a: any, b: any) => JSON.stringify(a).localeCompare(JSON.stringify(b))),
|
|
attributeValueNames: (data.attributeValues ?? []).map((attribute: any) => attribute.name).sort(),
|
|
textLength: typeof data.text === "string" ? data.text.length : 0,
|
|
fontCharacterCount: data.fontCharacters?.length ?? 0,
|
|
fontTextBoxCount: data.fontTextBoxes?.length ?? 0,
|
|
volumeGridCount: data.volumeGrids?.length ?? 0,
|
|
})).sort((a: any, b: any) => `${a.type}:${a.name}`.localeCompare(`${b.type}:${b.name}`));
|
|
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
|
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const data = summarize(opened.snapshot);
|
|
if (data.length === 0) throw new Error("Blender Main returned no non-mesh data");
|
|
const saved = await client.saveBlend();
|
|
client.terminate();
|
|
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
|
return {
|
|
browser: "chromium",
|
|
fixtureSha256: await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes)).then((hash) => Array.from(new Uint8Array(hash), (value) => value.toString(16).padStart(2, "0")).join("")),
|
|
data,
|
|
reopenedData: summarize(reopened.snapshot),
|
|
openedRevision: opened.snapshot.revision,
|
|
reopenedRevision: reopened.snapshot.revision,
|
|
};
|
|
}
|
|
finally {
|
|
client.terminate();
|
|
reopenedClient.terminate();
|
|
}
|
|
}, Array.from(fixture));
|
|
|
|
fs.mkdirSync(path.join(root, "tests/golden/WBV2-P1101-000"), { recursive: true });
|
|
fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1101-000/nonmesh-main-reader-production.json"), `${JSON.stringify({
|
|
schemaVersion: 1,
|
|
task: "WBV2-P1101-000",
|
|
operation: "NONMESH_MAIN_READER_CHROMIUM",
|
|
browser: "chromium",
|
|
queueMutation: false,
|
|
sourceRnaStructs: {
|
|
"rna-struct:CurvePoint": "nonMeshData[type=CURVES].pointCount + attributes[name=position]",
|
|
"rna-struct:SplinePoint": "nonMeshData[type=CURVE].pointWeights",
|
|
"rna-struct:SplinePoints": "nonMeshData[type=CURVE].pointCount",
|
|
"rna-struct:AttributeGroupCurves": "nonMeshData[type=CURVES].attributes",
|
|
"rna-struct:AttributeGroupPointCloud": "nonMeshData[type=POINT_CLOUD].attributes",
|
|
},
|
|
...result,
|
|
}, null, 2)}\n`);
|
|
expect(result.browser).toBe("chromium");
|
|
expect(result.fixtureSha256).toBe(fixtureSha256);
|
|
expect(result.openedRevision).toBe(1);
|
|
expect(result.reopenedRevision).toBe(1);
|
|
expect(result.data).toEqual(result.reopenedData);
|
|
expect(result.data.some((data: any) => ["CURVE", "SURFACE"].includes(data.type) && data.pointCount > 0)).toBe(true);
|
|
expect(result.data.some((data: any) => data.type === "FONT" && data.textLength > 0)).toBe(true);
|
|
expect(result.data.some((data: any) => data.type === "POINT_CLOUD" && data.attributes.length > 0)).toBe(true);
|
|
expect(result.data.some((data: any) => data.type === "CURVES" && data.pointCount > 0 && data.attributes.some((attribute: any) => attribute.name === "position"))).toBe(true);
|
|
expect(result.data.some((data: any) => data.type === "VOLUME")).toBe(true);
|
|
});
|