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"); test("modifier parameters evaluate and Apply bakes the result", 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 }); const reopened = new WebEngineClient({ timeoutMs: 60_000 }); try { const open = await client.openBlend(Uint8Array.from(bytes).buffer); const node = open.snapshot.nodes.find((n) => n.name === "BasicCube"); if (!node?.dataId) throw new Error("BasicCube is missing"); const meshId = node.dataId; const meshState = (snapshot: typeof open.snapshot) => { const mesh = snapshot.meshes.find((item) => item.id === meshId); return { vertexCount: mesh?.vertexCount ?? null, stack: mesh?.modifierStack ?? [] }; }; const base = meshState(open.snapshot); const added = await client.applyCommand({ type: "addModifier", objectId: node.id, meshId, modifierType: "SUBSURF", baseRevision: open.snapshot.revision } as never); const levelOne = meshState(added.snapshot); const modifier = levelOne.stack.find((item) => item.type === "SUBSURF"); if (!modifier?.uuid) throw new Error("SUBSURF was not published"); const configured = await client.applyCommand({ type: "setModifierParameters", meshId, modifierUuid: modifier.uuid, parameters: { levels: 2, render_levels: 2 }, baseRevision: added.snapshot.revision } as never); const levelTwo = meshState(configured.snapshot); const configuredModifier = levelTwo.stack.find((item) => item.uuid === modifier.uuid); const evaluateVerts = async () => { const depsgraph = await client.evaluateDepsgraph(); return depsgraph.depsgraph.meshes.find((item) => item.meshId === meshId || item.objectId === node.id)?.vertexCount ?? null; }; const evalVerts = await evaluateVerts(); const undone = await client.applyCommand({ type: "undo" } as never); const undoStack = meshState(undone.snapshot).stack; const evalUndoVerts = await evaluateVerts(); const redone = await client.applyCommand({ type: "redo" } as never); const redoStack = meshState(redone.snapshot).stack; const evalRedoVerts = await evaluateVerts(); const payload = JSON.stringify({ operator: "object.modifier_apply", properties: { modifier: modifier.name, single_user: true, report: true }, baseRevision: redone.snapshot.revision, sceneName: redone.snapshot.scenes?.[0]?.name ?? "Scene", areaType: "VIEW_3D", mode: "OBJECT", native: true, selectedObjects: [node.name], activeObject: node.name, meshSelection: null, nonMeshSelection: null }); const source = `import json, web_blender\nweb_blender.desktop_operator(json.loads(${JSON.stringify(payload)}))`; const applied = await client.executeEmbeddedPython(source, "ModifierApply.py", false); const afterApply = meshState(applied.snapshot); const saved = await client.saveBlend(); client.terminate(); const reopen = await reopened.openBlend(saved); const afterReopen = meshState(reopen.snapshot); return { base, levelOne, levelTwo, evalVerts, evalUndoVerts, evalRedoVerts, undoLevels: undoStack.find((item) => item.uuid === modifier.uuid)?.parameters?.levels ?? null, redoLevels: redoStack.find((item) => item.uuid === modifier.uuid)?.parameters?.levels ?? null, configuredParameters: configuredModifier?.parameters ?? null, applyState: applied.python.state, afterApply, afterReopen }; } 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.mkdirSync(receiptDir, { recursive: true }); fs.writeFileSync(path.join(receiptDir, "modifier-parameters-apply-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "SUBSURF parameters levels=2 -> undo/redo -> native object.modifier_apply -> save/reopen", ...result }, null, 2)); expect(result.error).toBeUndefined(); expect(result.applyState).toBe("DONE"); expect(result.configuredParameters?.levels).toBe(2); expect(result.undoLevels).toBe(1); expect(result.evalUndoVerts).toBeLessThan(result.evalVerts); expect(result.redoLevels).toBe(2); expect(result.evalRedoVerts).toBe(result.evalVerts); expect(result.levelTwo.stack.some((item: { uuid: string }) => item.uuid)).toBe(true); expect(result.afterApply.stack).toEqual([]); expect(result.afterReopen.stack).toEqual([]); expect(result.evalVerts).toBeGreaterThan(result.base.vertexCount); expect(result.afterApply.vertexCount).toBe(result.evalVerts); expect(result.afterReopen.vertexCount).toBe(result.evalVerts); });