import { expect, test } from "@playwright/test"; import path from "node:path"; import { fileURLToPath } from "node:url"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const STUDIO = path.resolve(HERE, "../../app/public/tutorial/pbr/studio.blend"); const GLB = path.resolve(HERE, "../../../re/猴头.glb"); test.use({ viewport: { width: 1600, height: 900 } }); async function openStudioAndMonkey(page: import("@playwright/test").Page): Promise { await page.goto("/?ui=web"); await page.locator("main.blender-app").waitFor({ timeout: 120_000 }); await page.locator('[data-testid="blend-file-input"]').setInputFiles(STUDIO); await page.getByText(/Engine: SceneIR/).first().waitFor({ timeout: 120_000 }); await expect.poll(async () => page.getByTestId("scene-stats").innerText(), { timeout: 120_000 }).toMatch(/Objects 6/); await page.locator('[data-testid="blend-file-input"]').setInputFiles(GLB); await expect.poll(async () => page.getByTestId("scene-stats").innerText(), { timeout: 120_000 }).toMatch(/Objects 7/); } async function clickWorkspace(page: import("@playwright/test").Page, name: string): Promise { await page.locator(".workspace-tabs button", { hasText: name }).evaluate((element) => (element as HTMLElement).click()); } async function openCanvas(page: import("@playwright/test").Page): Promise { await clickWorkspace(page, "Geometry Nodes"); const canvas = page.locator('[data-testid="geometry-node-canvas"]'); await canvas.waitFor({ timeout: 60_000 }); return canvas; } async function addTransformAndConnect(page: import("@playwright/test").Page, canvas: import("@playwright/test").Locator): Promise { await canvas.getByRole("button", { name: "添加节点", exact: true }).evaluate((element) => (element as HTMLElement).click()); await expect.poll(async () => canvas.getAttribute("data-node-count"), { timeout: 30_000 }).toBe("3"); const source = page.locator('.react-flow__node[data-id="group-input"] .react-flow__handle.source').first(); const target = page.locator('.react-flow__node[data-id="node-geometrynodetransform"] .react-flow__handle.target').first(); await expect(source).toHaveCount(1); await expect(target).toHaveCount(1); const from = await source.boundingBox(); const to = await target.boundingBox(); if (!from || !to) throw new Error("geometry node handles have no box"); await page.mouse.move(from.x + from.width / 2, from.y + from.height / 2); await page.mouse.down(); await page.mouse.move(to.x + to.width / 2, to.y + to.height / 2, { steps: 12 }); await page.mouse.up(); await expect.poll(async () => canvas.getAttribute("data-link-count"), { timeout: 30_000 }).toBe("2"); } test("Geometry Nodes canvas adds, connects and commits a graph", async ({ page }) => { test.setTimeout(300_000); await openStudioAndMonkey(page); const canvas = await openCanvas(page); const before = Number(await page.locator("main.blender-app").getAttribute("data-project-snapshot-revision") ?? "0"); await addTransformAndConnect(page, canvas); await canvas.getByRole("button", { name: "提交图", exact: true }).evaluate((element) => (element as HTMLElement).click()); await expect.poll(async () => page.locator('[data-testid="geometry-node-graph-count"]').innerText(), { timeout: 60_000 }).toBe("1 graphs"); await expect.poll(async () => canvas.getAttribute("data-node-count"), { timeout: 60_000 }).toBe("3"); await expect.poll(async () => canvas.getAttribute("data-link-count"), { timeout: 60_000 }).toBe("2"); const after = Number(await page.locator("main.blender-app").getAttribute("data-project-snapshot-revision") ?? "0"); expect(after).toBeGreaterThan(before); }); test("Geometry Nodes canvas graph survives an OPFS save and reopen", async ({ page }) => { test.setTimeout(360_000); await openStudioAndMonkey(page); const canvas = await openCanvas(page); await addTransformAndConnect(page, canvas); await canvas.getByRole("button", { name: "提交图", exact: true }).evaluate((element) => (element as HTMLElement).click()); await expect.poll(async () => page.locator('[data-testid="geometry-node-graph-count"]').innerText(), { timeout: 60_000 }).toBe("1 graphs"); await expect.poll(async () => canvas.getAttribute("data-node-count"), { timeout: 60_000 }).toBe("3"); await expect.poll(async () => canvas.getAttribute("data-link-count"), { timeout: 60_000 }).toBe("2"); await page.locator('button[aria-label="保存项目"]').evaluate((element) => (element as HTMLElement).click()); await expect(page.locator("main.blender-app")).toHaveAttribute("data-user-action-save-status", "SUCCEEDED", { timeout: 60_000 }); await page.reload(); await page.locator("main.blender-app").waitFor({ timeout: 120_000 }); await expect.poll(async () => page.locator('[data-testid="recent-projects"] option').count(), { timeout: 60_000 }).toBeGreaterThan(1); await page.locator('[data-testid="recent-projects"]').selectOption({ index: 1 }); const discard = page.getByRole("button", { name: "不保存,继续", exact: true }); if (await discard.count()) await discard.click(); await expect.poll(async () => page.getByTestId("scene-stats").innerText(), { timeout: 120_000 }).toMatch(/Objects 7/); const reopened = await openCanvas(page); await expect.poll(async () => page.locator('[data-testid="geometry-node-graph-count"]').innerText(), { timeout: 60_000 }).toBe("1 graphs"); await expect.poll(async () => reopened.getAttribute("data-node-count"), { timeout: 60_000 }).toBe("3"); await expect.poll(async () => reopened.getAttribute("data-link-count"), { timeout: 60_000 }).toBe("2"); });