- 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>
106 lines
7.6 KiB
TypeScript
106 lines
7.6 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"));
|
|
const receiptDir = path.join(root, "tests/golden/WBV2-P1425-001/verification");
|
|
fs.mkdirSync(receiptDir, { recursive: true });
|
|
|
|
test("focused creation chain", async ({ page }) => {
|
|
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: 30000 });
|
|
const reopened = new WebEngineClient({ timeoutMs: 30000 });
|
|
try {
|
|
const open = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const created = await client.applyCommand({ type: "createPrimitive", primitive: "CUBE", name: "VerifyCube", location: [1, 2, 3] } as never);
|
|
const node = created.snapshot.nodes.find((n) => n.name === "VerifyCube");
|
|
const undone = await client.applyCommand({ type: "undo" });
|
|
const redone = await client.applyCommand({ type: "redo" });
|
|
const saved = await client.saveBlend();
|
|
const savedBytes = saved.byteLength;
|
|
client.terminate();
|
|
const reopen = await reopened.openBlend(saved);
|
|
return { ok: true, base: open.snapshot.revision, createRevision: created.snapshot.revision, undoRevision: undone.snapshot.revision, redoRevision: redone.snapshot.revision, createdType: node?.type, createdId: node?.id, savedBytes, reopenRevision: reopen.snapshot.revision, reopenHasCube: reopen.snapshot.nodes.some(n => n.name === 'VerifyCube') };
|
|
} catch (error) {
|
|
const e = error as { message?: string; code?: string; detail?: string };
|
|
return { ok: false, message: e?.message, code: e?.code, detail: e?.detail };
|
|
} finally { client.terminate(); reopened.terminate(); }
|
|
}, Array.from(fixture));
|
|
fs.writeFileSync(path.join(receiptDir, "create-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "createPrimitive/undo/redo/save/reopen", ...result }, null, 2));
|
|
expect(result.ok).toBe(true);
|
|
expect(result.createdType).toBe("MESH");
|
|
expect(result.createRevision).toBeGreaterThan(result.base!);
|
|
expect(result.reopenRevision).toBe(1);
|
|
});
|
|
|
|
test("focused hierarchy chain", async ({ page }) => {
|
|
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: 30000 });
|
|
const reopened = new WebEngineClient({ timeoutMs: 30000 });
|
|
try {
|
|
const open = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const rootId = open.snapshot.scenes[0]?.rootCollectionId ?? null;
|
|
const first = await client.applyCommand({ type: "createCollection", name: "VerifyParent", parentCollectionId: rootId } as never);
|
|
const parent = first.snapshot.collections.find((c) => c.name === "VerifyParent");
|
|
const second = await client.applyCommand({ type: "createCollection", name: "VerifyChild", parentCollectionId: parent?.id ?? null } as never);
|
|
const child = second.snapshot.collections.find((c) => c.name === "VerifyChild");
|
|
const moved = await client.applyCommand({ type: "moveCollection", collectionId: child?.id, parentCollectionId: parent?.id } as never);
|
|
const active = await client.applyCommand({ type: "setActiveCollection", collectionId: child?.id } as never);
|
|
const deleted = await client.applyCommand({ type: "deleteCollection", collectionId: child?.id } as never);
|
|
const undone = await client.applyCommand({ type: "undo" });
|
|
const redone = await client.applyCommand({ type: "redo" });
|
|
const saved = await client.saveBlend();
|
|
const savedBytes = saved.byteLength;
|
|
client.terminate();
|
|
const reopen = await reopened.openBlend(saved);
|
|
return { ok: true, base: open.snapshot.revision, parentId: parent?.id, childId: child?.id, createdCollections: first.snapshot.collections.length, movedRevision: moved.snapshot.revision, activeRevision: active.snapshot.revision, deletedRevision: deleted.snapshot.revision, undoRevision: undone.snapshot.revision, redoRevision: redone.snapshot.revision, savedBytes, reopenRevision: reopen.snapshot.revision, reopenedCollections: reopen.snapshot.collections.length, reopenHasParent: reopen.snapshot.collections.some(c => c.name === 'VerifyParent') };
|
|
} catch (error) {
|
|
const e = error as { message?: string; code?: string; detail?: string };
|
|
return { ok: false, message: e?.message, code: e?.code, detail: e?.detail };
|
|
} finally { client.terminate(); reopened.terminate(); }
|
|
}, Array.from(fixture));
|
|
fs.writeFileSync(path.join(receiptDir, "hierarchy-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "createCollection/moveCollection/setActiveCollection/deleteCollection/undo/redo/save/reopen", ...result }, null, 2));
|
|
expect(result.ok).toBe(true);
|
|
expect(result.parentId).toBeTruthy();
|
|
expect(result.childId).toBeTruthy();
|
|
expect(result.reopenRevision).toBe(1);
|
|
});
|
|
|
|
test("focused modeling chain", async ({ page }) => {
|
|
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: 30000 });
|
|
const reopened = new WebEngineClient({ timeoutMs: 30000 });
|
|
try {
|
|
const open = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const node = open.snapshot.nodes.find((n) => n.type === "MESH");
|
|
if (!node?.dataId) throw new Error("mesh fixture is missing");
|
|
const meshId = node.dataId;
|
|
const triangulated = await client.applyCommand({ type: "meshTriangulate", meshId, faceIndices: [0] } as never);
|
|
const undone = await client.applyCommand({ type: "undo" });
|
|
const redone = await client.applyCommand({ type: "redo" });
|
|
const added = await client.applyCommand({ type: "addModifier", objectId: node.id, meshId, modifierType: "SUBSURF", baseRevision: redone.snapshot.revision } as never);
|
|
const modifier = added.snapshot.meshes.find((m) => m.id === meshId)?.modifierStack?.at(-1);
|
|
const removed = await client.applyCommand({ type: "removeModifier", objectId: node.id, meshId, modifierUuid: modifier?.uuid, baseRevision: added.snapshot.revision } as never);
|
|
const saved = await client.saveBlend();
|
|
const savedBytes = saved.byteLength;
|
|
client.terminate();
|
|
const reopen = await reopened.openBlend(saved);
|
|
return { ok: true, base: open.snapshot.revision, meshId, triangulateRevision: triangulated.snapshot.revision, undoRevision: undone.snapshot.revision, redoRevision: redone.snapshot.revision, modifierType: modifier?.type, addRevision: added.snapshot.revision, removeRevision: removed.snapshot.revision, savedBytes, reopenRevision: reopen.snapshot.revision, reopenedNodes: reopen.snapshot.nodes.length };
|
|
} catch (error) {
|
|
const e = error as { message?: string; code?: string; detail?: string };
|
|
return { ok: false, message: e?.message, code: e?.code, detail: e?.detail };
|
|
} finally { client.terminate(); reopened.terminate(); }
|
|
}, Array.from(fixture));
|
|
fs.writeFileSync(path.join(receiptDir, "modeling-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "meshTriangulate/addModifier/removeModifier/undo/redo/save/reopen", ...result }, null, 2));
|
|
expect(result.ok).toBe(true);
|
|
expect(result.modifierType).toBe("SUBSURF");
|
|
expect(result.reopenRevision).toBe(1);
|
|
});
|