- 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>
49 lines
3.4 KiB
TypeScript
49 lines
3.4 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const fixture = fs.readFileSync(path.join(root, "tests/files/web/basic_scene.blend"));
|
|
test("mesh edit-mode changes persist through save and reopen", 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 client = new WebEngineClient({ timeoutMs: 30_000 });
|
|
const reopened = new WebEngineClient({ timeoutMs: 30_000 });
|
|
try {
|
|
const open = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const node = open.snapshot.nodes.find((n) => n.name === "BasicCube");
|
|
if (!node) throw new Error("BasicCube is missing");
|
|
let revision = open.snapshot.revision;
|
|
const invoke = async (operator: string, properties: Record<string, unknown>, mode: string) => {
|
|
const payload = JSON.stringify({ operator, properties, baseRevision: revision, sceneName: open.snapshot.scenes?.[0]?.name ?? "Scene", areaType: "VIEW_3D", mode, native: true, selectedObjects: [node.name], activeObject: node.name, meshSelection: null, nonMeshSelection: null });
|
|
const res = await client.executeEmbeddedPython(`import json, web_blender\nweb_blender.desktop_operator(json.loads(${JSON.stringify(payload)}))`, "MeshPersist.py", false);
|
|
revision = res.snapshot.revision;
|
|
if (res.python.state !== "DONE") throw new Error(res.python.error ?? res.python.output ?? res.python.state);
|
|
return res;
|
|
};
|
|
const countEdit = async () => {
|
|
const res = await client.executeEmbeddedPython(`import bpy, bmesh\nob = bpy.data.objects.get("BasicCube")\nbm = bmesh.from_edit_mesh(ob.data)\nprint("VERTS:" + str(len(bm.verts)))`, "CountEdit.py", false);
|
|
return Number(res.python.output.split("\n").find((line) => line.startsWith("VERTS:"))?.slice("VERTS:".length));
|
|
};
|
|
await invoke("object.mode_set", { mode: "EDIT", toggle: false }, "OBJECT");
|
|
await invoke("mesh.select_all", { action: "SELECT" }, "EDIT");
|
|
const before = await countEdit();
|
|
await invoke("mesh.poke", {}, "EDIT");
|
|
const after = await countEdit();
|
|
const saved = await client.saveBlend();
|
|
client.terminate();
|
|
await reopened.openBlend(saved);
|
|
const reopenCount = await reopened.executeEmbeddedPython(`import bpy\nob = bpy.data.objects.get("BasicCube")\nprint("VERTS:" + str(len(ob.data.vertices)))`, "CountReopen.py", false);
|
|
const reopenedVerts = Number(reopenCount.python.output.split("\n").find((line) => line.startsWith("VERTS:"))?.slice("VERTS:".length));
|
|
return { before, after, reopenedVerts };
|
|
} catch (error) { const e = error as { message?: string; code?: string }; return { error: e?.message, code: e?.code }; }
|
|
finally { client.terminate(); reopened.terminate(); }
|
|
}, { bytes: Array.from(fixture) });
|
|
fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1425-001/verification/edit-mode-persistence-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "mesh.select_all -> mesh.poke in Edit mode -> save/reopen", ...result }, null, 2));
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.before).toBe(8);
|
|
expect(result.after).toBeGreaterThan(result.before);
|
|
expect(result.reopenedVerts).toBe(result.after);
|
|
});
|