Files
workinf_Blender_Wasm/web/tests/e2e/wbv2-nested-collection-instance-production.spec.ts
mes123456 d1e6b63d54
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 feature-gap work, docs and tests
- 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>
2026-11-11 10:46:55 +08:00

75 lines
5.2 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("nested collection instances expand and select the nearest instance owner", 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: 60_000 });
try {
const open = await client.openBlend(Uint8Array.from(bytes).buffer);
const sceneName = open.snapshot.scenes?.[0]?.name ?? "Scene";
const mesh = open.snapshot.nodes.find((node) => node.name === "BasicCube");
const outerCollection = open.snapshot.collections.find((collection) => collection.name === "Collection");
if (!mesh || !outerCollection) throw new Error("fixture collection or mesh is missing");
let revision = open.snapshot.revision;
const invoke = async (operator: string, properties: Record<string, unknown>, activeName: string) => {
const payload = JSON.stringify({ operator, properties, baseRevision: revision, sceneName, areaType: "VIEW_3D", mode: "OBJECT", native: true, selectedObjects: [activeName], activeObject: activeName, meshSelection: null, nonMeshSelection: null });
const res = await client.executeEmbeddedPython(`import json, web_blender\nweb_blender.desktop_operator(json.loads(${JSON.stringify(payload)}))`, "NestedInstance.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.snapshot;
};
const createdCollection = await client.applyCommand({ type: "createCollection", name: "NestedInner" } as never);
revision = createdCollection.snapshot.revision;
const inner = createdCollection.snapshot.collections.find((collection) => collection.name === "NestedInner");
if (!inner) throw new Error("inner collection was not created");
const moved = await client.applyCommand({ type: "moveObjectToCollection", objectId: mesh.id, collectionId: inner.id, keepExistingLinks: false } as never);
revision = moved.snapshot.revision;
const withInnerInstance = await invoke("object.collection_instance_add", { name: "NestedInner" }, mesh.name);
const innerInstance = withInnerInstance.nodes.find((node) => node.instanceCollectionId === inner.id);
if (!innerInstance) throw new Error("inner collection instance was not created");
const movedInstance = await client.applyCommand({ type: "moveObjectToCollection", objectId: innerInstance.id, collectionId: outerCollection.id, keepExistingLinks: false } as never);
revision = movedInstance.snapshot.revision;
const withOuterInstance = await invoke("object.collection_instance_add", { name: "Collection" }, mesh.name);
revision = withOuterInstance.revision;
const outerInstance = withOuterInstance.nodes.find((node) => node.instanceCollectionId === outerCollection.id);
if (!outerInstance) throw new Error("outer collection instance was not created");
const canvas = document.createElement("canvas");
canvas.width = 320;
canvas.height = 240;
document.body.appendChild(canvas);
const { ViewportRenderer } = await import("/src/three-adapter/viewport.ts");
const renderer = new ViewportRenderer(canvas);
renderer.setSnapshot(withOuterInstance, [], []);
const renderedCopies = Number(canvas.dataset.collectionInstanceObjects ?? "0");
const renderedHelpers = Number(canvas.dataset.renderHelpers ?? "0");
const clones: Array<{ memberId: string | null; blenderId: string | null }> = [];
renderer.scene.traverse((object) => {
if (object.userData.instanceMemberId) clones.push({ memberId: String(object.userData.instanceMemberId), blenderId: typeof object.userData.blenderId === "string" ? object.userData.blenderId : null });
});
renderer.dispose();
canvas.remove();
return {
innerCollectionId: inner.id,
innerInstanceId: innerInstance.id,
outerInstanceId: outerInstance.id,
renderedCopies,
renderedHelpers,
nestedMeshOwners: clones.filter((clone) => clone.memberId === mesh.id).map((clone) => clone.blenderId),
allClones: clones,
};
} catch (error) { const e = error as { message?: string; code?: string }; return { error: e?.message, code: e?.code }; }
finally { client.terminate(); }
}, { bytes: Array.from(fixture) });
fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1425-001/verification/nested-collection-instance-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "collection instance inside collection instance: recursive expansion + nearest owner picking", ...result }, null, 2));
expect(result.error).toBeUndefined();
expect(result.renderedCopies).toBeGreaterThanOrEqual(3);
expect(result.renderedHelpers).toBeGreaterThan(0);
expect(result.nestedMeshOwners).toContain(result.innerInstanceId);
expect(result.nestedMeshOwners).not.toContain(result.outerInstanceId);
});