Files
workinf_Blender_Wasm/web/tests/e2e/wbv2-adjust-last-operation-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

66 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";
import { exposeWebTools, observeNative } from "../fixtures/native-ui-workflow";
const root = path.resolve(import.meta.dirname, "../../..");
const receiptDir = path.join(root, "tests/golden/WBV2-P1425-001/verification");
test("Adjust Last Operation re-runs a creation with edited native properties", async ({ page }) => {
test.setTimeout(120_000);
await observeNative(page);
await page.goto("/");
await page.getByTestId("blend-file-input").setInputFiles(path.join(root, "tests/files/web/basic_scene.blend"));
await expect(page.getByTestId("engine-status")).toContainText(/SceneIR r\d+/, { timeout: 60_000 });
await exposeWebTools(page);
await page.getByRole("button", { name: "添加", exact: true }).click();
await page.getByRole("menuitem", { name: "网格", exact: true }).click();
await page.getByRole("menuitem", { name: "圆环", exact: true }).click();
await expect(page.getByTestId("engine-status")).toContainText(/已执行:mesh\.primitive_circle_add/, { timeout: 60_000 });
await expect(page.getByRole("treeitem", { name: "Circle", exact: true })).toBeVisible({ timeout: 60_000 });
await page.getByRole("button", { name: "调整上一步操作", exact: true }).click();
const panel = page.locator(".last-operator-panel");
await expect(panel.locator('[data-property="vertices"] input')).toBeVisible({ timeout: 60_000 });
await panel.locator('[data-property="vertices"] input').fill("8");
await panel.getByRole("button", { name: "应用", exact: true }).click();
await expect(page.getByTestId("engine-status")).toContainText(/已调整:mesh\.primitive_circle_add/, { timeout: 60_000 });
await page.getByRole("button", { name: "调整上一步操作", exact: true }).click();
await expect(panel.locator('[data-property="location"] input').first()).toBeVisible({ timeout: 60_000 });
await panel.locator('[data-property="location"] input').first().fill("3");
await panel.getByRole("button", { name: "应用", exact: true }).click();
await expect(page.getByRole("button", { name: "调整上一步操作", exact: true })).toBeVisible({ timeout: 60_000 });
await expect(page.getByTestId("engine-status")).toContainText(/已调整:mesh\.primitive_circle_add/, { timeout: 60_000 });
await page.getByRole("button", { name: "调整上一步操作", exact: true }).click();
await panel.getByRole("button", { name: "完整属性…", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "操作搜索" });
await expect(dialog.locator(".operator-property-form")).toBeVisible({ timeout: 60_000 });
const verticesInput = dialog.locator('.operator-property-form input[type="number"]').first();
await expect(verticesInput).toHaveValue("8");
await verticesInput.fill("12");
await dialog.getByRole("button", { name: "执行", exact: true }).click();
await expect(dialog).toBeHidden({ timeout: 60_000 });
await expect(page.getByTestId("engine-status")).toContainText(/已调整:mesh\.primitive_circle_add/, { timeout: 60_000 });
const call = (command: unknown) => page.evaluate((input) => new Promise<any>((resolve, reject) => {
const worker = (window as unknown as { __nativeWorkflowEngine: Worker }).__nativeWorkflowEngine;
const requestId = `adjust-${crypto.randomUUID()}`;
const receive = (event: MessageEvent) => {
if (event.data?.requestId !== requestId || event.data?.kind !== "result") return;
worker.removeEventListener("message", receive);
if (event.data.ok) resolve(event.data.result); else reject(new Error(String(event.data.error?.message ?? event.data.error)));
};
worker.addEventListener("message", receive);
worker.postMessage({ requestId, command: input });
}), command);
const read = await call({ type: "jsonCommand", cmd: "pythonExec", params: { source: 'import bpy, json\nprint("CIRCLES:" + json.dumps([(item.name, len(item.data.vertices)) for item in bpy.data.objects if item.name.startswith("Circle")]))\nprint("LOCATION:" + json.dumps([(item.name, [round(value, 3) for value in item.location]) for item in bpy.data.objects if item.name.startswith("Circle")]))', filename: "AdjustLastOperation.py", console: false } });
const output = String(read.json?.output ?? "");
const circlesLine = output.split("\n").find((line) => line.startsWith("CIRCLES:"));
const locationLine = output.split("\n").find((line) => line.startsWith("LOCATION:"));
const circles = circlesLine ? JSON.parse(circlesLine.slice("CIRCLES:".length)) as [string, number][] : null;
const locations = locationLine ? JSON.parse(locationLine.slice("LOCATION:".length)) as [string, number[]][] : null;
fs.mkdirSync(receiptDir, { recursive: true });
fs.writeFileSync(path.join(receiptDir, "adjust-last-operation-production.json"), JSON.stringify({ schemaVersion: 1, browser: "chromium", scope: "Add Circle -> adjust vertices=8 -> location X=3 -> full-property form vertices=12 -> delete created object + native re-invoke", output, state: read.json?.state, circles, locations }, null, 2));
expect(read.json?.state).toBe("DONE");
expect(circles).toEqual([["Circle", 12]]);
expect(locations?.[0]?.[1]?.[0]).toBe(3);
});