Files
workinf_Blender_Wasm/web/tests/e2e/wbv2-modifier-source-roundtrip-production.spec.ts
mes123456 f3e21c6b98
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
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

124 lines
6.3 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 fixtures = [
"modifier_generate_scene.blend",
"modifier_deform_scene.blend",
"modifier_deform_curve_scene.blend",
"modifier_extended_native.blend",
"modifier_physics_scene.blend",
"modifier_geometry_nodes_scene.blend",
"rigged_shape_scene.blend",
];
const sourceModules = {
"modifier-source:MOD_array": "ARRAY",
"modifier-source:MOD_armature": "ARMATURE",
"modifier-source:MOD_bevel": "BEVEL",
"modifier-source:MOD_boolean": "BOOLEAN",
"modifier-source:MOD_build": "BUILD",
"modifier-source:MOD_cast": "CAST",
"modifier-source:MOD_cast_web": "CAST",
"modifier-source:MOD_cloth": "CLOTH",
"modifier-source:MOD_collision": "COLLISION",
"modifier-source:MOD_curve": "CURVE",
"modifier-source:MOD_displace": "DISPLACE",
"modifier-source:MOD_displace_web": "DISPLACE",
"modifier-source:MOD_edgesplit": "EDGE_SPLIT",
"modifier-source:MOD_hook": "HOOK",
"modifier-source:MOD_lattice": "LATTICE",
"modifier-source:MOD_meshdeform": "MESH_DEFORM",
"modifier-source:MOD_mirror": "MIRROR",
"modifier-source:MOD_nodes": "NODES",
"modifier-source:MOD_nodes_web": "NODES",
"modifier-source:MOD_screw": "SCREW",
"modifier-source:MOD_solidify": "SOLIDIFY",
"modifier-source:MOD_simpledeform": "SIMPLE_DEFORM",
"modifier-source:MOD_smooth": "SMOOTH",
"modifier-source:MOD_softbody": "SOFT_BODY",
"modifier-source:MOD_subsurf": "SUBSURF",
"modifier-source:MOD_triangulate": "TRIANGULATE",
"modifier-source:MOD_surfacedeform": "SURFACE_DEFORM",
"modifier-source:MOD_wave": "WAVE",
"modifier-source:MOD_weld": "WELD",
"modifier-source:MOD_wireframe": "WIREFRAME",
};
const modifierRnaStructs = {
"rna-struct:ArrayModifier": "ARRAY",
"rna-struct:ArmatureModifier": "ARMATURE",
"rna-struct:BevelModifier": "BEVEL",
"rna-struct:BooleanModifier": "BOOLEAN",
"rna-struct:BuildModifier": "BUILD",
"rna-struct:CastModifier": "CAST",
"rna-struct:ClothModifier": "CLOTH",
"rna-struct:CollisionModifier": "COLLISION",
"rna-struct:CurveModifier": "CURVE",
"rna-struct:DisplaceModifier": "DISPLACE",
"rna-struct:EdgeSplitModifier": "EDGE_SPLIT",
"rna-struct:HookModifier": "HOOK",
"rna-struct:LatticeModifier": "LATTICE",
"rna-struct:MeshDeformModifier": "MESH_DEFORM",
"rna-struct:MirrorModifier": "MIRROR",
"rna-struct:NodesModifier": "NODES",
"rna-struct:ScrewModifier": "SCREW",
"rna-struct:SolidifyModifier": "SOLIDIFY",
"rna-struct:SimpleDeformModifier": "SIMPLE_DEFORM",
"rna-struct:SmoothModifier": "SMOOTH",
"rna-struct:SoftBodyModifier": "SOFT_BODY",
"rna-struct:SubsurfModifier": "SUBSURF",
"rna-struct:SurfaceDeformModifier": "SURFACE_DEFORM",
"rna-struct:TriangulateModifier": "TRIANGULATE",
"rna-struct:WaveModifier": "WAVE",
"rna-struct:WeldModifier": "WELD",
"rna-struct:WireframeModifier": "WIREFRAME",
};
const fixtureBytes = Object.fromEntries(fixtures.map((name) => [name, fs.readFileSync(path.join(root, "tests/files/web", name))]));
const fixtureSha256 = Object.fromEntries(Object.entries(fixtureBytes).map(([name, bytes]) => [name, crypto.createHash("sha256").update(bytes).digest("hex")]));
test("WBV2 modifier source modules preserve concrete Blender DNA through Chromium save/reopen", async ({ page }) => {
test.setTimeout(180_000);
await page.goto("/");
const result = await page.evaluate(async (input) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const canonical = (snapshot: any) => snapshot.meshes.flatMap((mesh: any) => (mesh.modifierStack ?? []).map((modifier: any) => ({ meshId: mesh.id, uuid: modifier.uuid, type: modifier.type, enabled: modifier.enabled, evaluationStatus: modifier.evaluationStatus })));
const output: any = {};
for (const [name, bytes] of Object.entries(input.fixtures as Record<string, number[]>)) {
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 before = canonical(opened.snapshot);
const target = before[0];
const edited = await client.applyCommand({ type: "setModifierVisibility", meshId: target.meshId, modifierUuid: target.uuid, showViewport: false, showRender: false, showEditMode: true, showOnCage: true } as any);
const afterEdit = canonical(edited.snapshot);
const saved = await client.saveBlend();
const reopened = await reopenedClient.openBlend(saved.slice(0));
const after = canonical(reopened.snapshot);
output[name] = { before, afterEdit, after, exact: JSON.stringify(afterEdit) === JSON.stringify(after), savedSha256: await crypto.subtle.digest("SHA-256", saved).then((hash) => Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("")) };
}
catch (error: any) {
output[name] = { error: { code: error?.code, message: error?.message, detail: String(error) } };
}
finally {
client.terminate();
reopenedClient.terminate();
}
}
return output;
}, { fixtures: Object.fromEntries(Object.entries(fixtureBytes).map(([name, bytes]) => [name, Array.from(bytes)])) });
fs.mkdirSync(path.join(root, "tests/golden/WBV2-P1107-000"), { recursive: true });
fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1107-000/modifier-source-roundtrip-production.json"), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P1107-000", operation: "MODIFIER_SOURCE_DNA_ROUNDTRIP_MAIN_CHROMIUM", browser: "chromium", queueMutation: false, sourceModules, modifierRnaStructs, fixtureSha256, ...result }, null, 2)}\n`);
const observedTypes = new Set(Object.values(result).flatMap((entry: any) => entry?.afterEdit?.map((modifier: any) => modifier.type) ?? []));
for (const type of new Set(Object.values(sourceModules))) expect(observedTypes.has(type)).toBe(true);
for (const type of new Set(Object.values(modifierRnaStructs))) expect(observedTypes.has(type)).toBe(true);
for (const name of fixtures) {
expect(result[name]?.error).toBeUndefined();
expect(result[name]?.exact).toBe(true);
expect(result[name]?.before.length).toBeGreaterThan(0);
expect(result[name]?.afterEdit).toEqual(result[name]?.after);
}
});