Files
workinf_Blender_Wasm/web/tests/e2e/wbv2-mesh-topology-production.spec.ts
mes123456 f18f80d0b9
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Continue WBV2 module parity through P11
2026-08-28 05:50:14 +08:00

88 lines
6.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 evidenceRoot = path.join(root, "tests/golden/WBV2-P11-001");
const fixturePath = path.join(root, "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend");
const fixture = fs.readFileSync(fixturePath);
const sourceSha256 = crypto.createHash("sha256").update(fixture).digest("hex");
test("WBV2-P11-001 executes the six Mesh topology operations through compiled WASM Main", async ({ page }) => {
test.setTimeout(240_000);
await page.goto("/");
const result = await page.evaluate(async (input) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const operations = [
{ label: "MERGE", meshId: "mesh:WBV2P2003MergeMesh", selectionMode: "VERT", elementIndices: [0, 1] },
{ label: "DISSOLVE", meshId: "mesh:WBV2P2003DissolveMesh", selectionMode: "EDGE", elementIndices: [0] },
{ label: "EXTRUDE", meshId: "mesh:WBV2P2003ExtrudeMesh", selectionMode: "FACE", elementIndices: [0], offset: [0, 0, 1] },
{ label: "INSET", meshId: "mesh:WBV2P2003InsetMesh", selectionMode: "FACE", elementIndices: [0], amount: 0.2 },
{ label: "BEVEL", meshId: "mesh:WBV2P2003BevelMesh", selectionMode: "EDGE", elementIndices: [0], amount: 0.1, segments: 2 },
{ label: "LOOP_CUT", meshId: "mesh:WBV2P2003LoopCutMesh", selectionMode: "EDGE", elementIndices: [0], segments: 1 },
];
const summary = (snapshot: any, meshId: string) => {
const mesh = snapshot.meshes.find((candidate: any) => candidate.id === meshId);
if (!mesh) throw new Error(`mesh missing: ${meshId}`);
return { id: mesh.id, vertexCount: mesh.vertexCount, edgeCount: mesh.edgeCount, faceCount: mesh.faceCount, triangleCount: mesh.triangleCount, topology: mesh.topology };
};
const digest = async (value: ArrayBuffer): Promise<string> => { const hash = await crypto.subtle.digest("SHA-256", value); return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join(""); };
const errors = async (client: any, commands: any[], revision: number) => {
const values = [];
for (const command of commands) {
const error = await client.applyCommand({ baseRevision: revision, ...command } as any).then(() => null).catch((caught) => ({ code: caught?.code, message: caught?.message }));
values.push(error);
}
return values;
};
const positive = [];
for (const operation of operations) {
const client = new WebEngineClient({ timeoutMs: 60_000 });
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
try {
const opened = await client.openBlend(Uint8Array.from(input.bytes).buffer);
const before = summary(opened.snapshot, operation.meshId);
const baseRevision = opened.snapshot.revision;
const edited = await client.applyCommand({ type: "meshEdit", meshId: operation.meshId, operation: operation.label, selectionMode: operation.selectionMode, elementIndices: operation.elementIndices, ...(operation.offset ? { offset: operation.offset } : {}), ...(operation.amount === undefined ? {} : { amount: operation.amount }), ...(operation.segments === undefined ? {} : { segments: operation.segments }), baseRevision } as any);
const after = summary(edited.snapshot, operation.meshId);
const saved = await client.saveBlend();
const savedSha256 = await digest(saved.slice(0));
const reopened = await reopenedClient.openBlend(saved.slice(0));
const reopenedSummary = summary(reopened.snapshot, operation.meshId);
positive.push({ operation: operation.label, meshId: operation.meshId, baseRevision, afterRevision: edited.snapshot.revision, before, after, reopened: reopenedSummary, savedSha256, exactSaveReopen: JSON.stringify(after) === JSON.stringify(reopenedSummary) });
}
finally { client.terminate(); reopenedClient.terminate(); }
}
const negativeClient = new WebEngineClient({ timeoutMs: 60_000 });
try {
const opened = await negativeClient.openBlend(Uint8Array.from(input.bytes).buffer);
const revision = opened.snapshot.revision;
const before = summary(opened.snapshot, "mesh:WBV2P2003MergeMesh");
const [invalidSelection, incompatible, stale] = await errors(negativeClient, [
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "MERGE", selectionMode: "VERT", elementIndices: [999] },
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "EXTRUDE", selectionMode: "EDGE", elementIndices: [0], offset: [0, 0, 1] },
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "MERGE", selectionMode: "VERT", elementIndices: [0, 1], baseRevision: revision - 1 },
], revision);
const after = summary((await negativeClient.snapshot()).snapshot, "mesh:WBV2P2003MergeMesh");
return { sourceSha256: input.sourceSha256, operations: positive, negative: { invalidSelection, incompatible, stale, before, after, revisionBefore: revision, revisionAfter: (await negativeClient.snapshot()).snapshot.revision } };
}
finally { negativeClient.terminate(); }
}, { bytes: Array.from(fixture), sourceSha256 });
fs.mkdirSync(evidenceRoot, { recursive: true });
const output = process.env.WBV2_MESH_TOPOLOGY_REPORT ?? "mesh-topology-production.json";
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P11-001", operation: "MESH_TOPOLOGY_OPERATION_FAMILY_CHROMIUM", browser: "chromium", queueMutation: false, ...result }, null, 2)}\n`);
expect(result.sourceSha256).toBe(sourceSha256);
expect(result.operations).toHaveLength(6);
expect(result.operations.every((entry) => entry.afterRevision === entry.baseRevision + 1 && entry.exactSaveReopen && entry.after.vertexCount > 0 && entry.after.edgeCount > 0 && entry.after.faceCount > 0)).toBe(true);
expect(result.operations.map((entry) => entry.operation)).toEqual(["MERGE", "DISSOLVE", "EXTRUDE", "INSET", "BEVEL", "LOOP_CUT"]);
expect(result.negative.invalidSelection?.code).toBe("INVALID_ARGUMENT");
expect(result.negative.incompatible?.code).toBe("INVALID_ARGUMENT");
expect(result.negative.stale?.code).toBe("INVALID_ARGUMENT");
expect(result.negative.stale?.message).toContain("REVISION_CONFLICT");
expect(result.negative.revisionAfter).toBe(result.negative.revisionBefore);
expect(result.negative.after).toEqual(result.negative.before);
});