import { expect, test } from "@playwright/test"; const storageKey = "canvas.image-toolbar.v1"; test("image toolbar customization preserves actions, saves preferences and respects enabled tools", async ({ page, context, }) => { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await context.addInitScript(() => localStorage.setItem("theme", "light")); const settings = { canvasEnabled: true, agentEnabled: true, layerSplitEnabled: true, angleEditEnabled: true, membershipEnabled: false, canvasToolbarConfig: {} as Record, }; await context.route("**/api/**", async (route) => { const path = new URL(route.request().url()).pathname; let data: unknown = {}; if (path.includes("validate-session")) data = { valid: true, success: true }; else if (path === "/api/auth/session") data = { user: { id: "toolbar-test-user", name: "设计师", type: "admin" }, expires: "2099-01-01", }; else if (path === "/api/site-settings") data = settings; else if (path === "/api/models") data = { models: [ { id: "image-test", modelId: "gpt-image-2", providerType: "openai", name: "GPT Image", type: "image", enabled: true, supportsImageGen: true, imageGenConfig: { creditCostPerImage: 15 }, }, ], }; else if ( path === "/api/canvas/toolbar-test" && route.request().method() === "GET" ) data = { success: true, data: { id: "toolbar-test", title: "工具栏测试", canvasData: { nodes: [ { id: "toolbar-image", type: "image", position: { x: 220, y: 160 }, size: { width: 640, height: 420 }, data: { status: "completed", src: new URL("/toolbar-test.svg", page.url()).href, title: "工具栏测试图", originalWidth: 640, originalHeight: 420, }, }, ], edges: [], viewport: { x: 0, y: 0, zoom: 1 }, }, }, }; else if (path.startsWith("/api/canvas/")) data = { success: true }; else if (path.includes("sessions")) data = { sessions: [] }; else if (path.includes("messages")) data = { messages: [] }; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(data), }); }); await context.route("**/toolbar-test.svg", (route) => route.fulfill({ contentType: "image/svg+xml", body: '', }) ); const node = page.locator('[data-canvas-node-id="toolbar-image"]'); const bar = page.locator('[data-selected-image-toolbar="true"]'); const dialog = page.getByRole("dialog"); const keys = () => bar .locator("button[data-tool-key]") .evaluateAll((buttons) => buttons.map((button) => button.getAttribute("data-tool-key")) ); const selectImage = async () => { await node.waitFor(); await node.click({ position: { x: 80, y: 80 } }); await expect(bar).toBeVisible(); }; const customize = async () => { await bar.getByRole("button", { name: "更多操作", exact: true }).click(); await page .getByRole("menuitem", { name: "自定义工具栏", exact: true }) .click(); await expect(dialog).toBeVisible(); }; await page.goto("/canvas/toolbar-test", { waitUntil: "commit" }); await selectImage(); const initial = await keys(); await expect(bar).toHaveAttribute("data-show-tool-names", "false"); await bar.getByRole("button", { name: "高清", exact: true }).hover(); await expect(page.getByRole("tooltip")).toContainText("高清"); await page.mouse.move(1000, 100); await customize(); await dialog.getByRole("button", { name: "固定裁剪", exact: true }).click(); await dialog.getByRole("button", { name: "取消", exact: true }).click(); expect(await keys()).toEqual(initial); await customize(); await dialog .getByRole("button", { name: "取消固定高清", exact: true }) .click(); await dialog.getByRole("button", { name: "固定裁剪", exact: true }).click(); await dialog.getByRole("switch", { name: "显示工具名称" }).click(); await dialog .getByRole("button", { name: "裁剪,按左右方向键调整顺序", exact: true }) .focus(); await page.keyboard.press("ArrowLeft"); await dialog.getByRole("button", { name: "保存", exact: true }).click(); const changed = await keys(); expect(changed).toContain("crop"); expect(changed).not.toContain("enhance"); expect(changed.at(-3)).toBe("crop"); await expect(bar).toHaveAttribute("data-show-tool-names", "true"); await bar.getByRole("button", { name: "更多操作", exact: true }).click(); await expect( page.getByRole("menuitem", { name: "高清", exact: true }) ).toBeVisible(); await page.keyboard.press("Escape"); await page.reload({ waitUntil: "commit" }); await selectImage(); expect(await keys()).toEqual(changed); await bar.getByRole("button", { name: "裁剪", exact: true }).click(); await expect( page.getByRole("heading", { name: "裁剪", exact: true }) ).toBeVisible(); await page.getByRole("button", { name: "取消", exact: true }).click(); await expect(bar).toBeVisible(); await bar.getByRole("button", { name: "添加对话", exact: true }).click(); await expect(page.getByText("已添加到对话框", { exact: true })).toBeVisible(); await customize(); await dialog .getByRole("button", { name: "恢复默认工具栏", exact: true }) .click(); await dialog.getByRole("button", { name: "保存", exact: true }).click(); expect(await keys()).toEqual(initial); await page.evaluate((key) => { localStorage.setItem(key, "invalid-json{"); window.dispatchEvent( new StorageEvent("storage", { key, newValue: "invalid-json{" }) ); }, storageKey); expect(await keys()).toEqual(initial); await customize(); const firstHandle = dialog.getByRole("button", { name: "识物,按左右方向键调整顺序", exact: true, }); const secondHandle = dialog.getByRole("button", { name: "高清,按左右方向键调整顺序", exact: true, }); await firstHandle.dragTo(secondHandle); await dialog.getByRole("button", { name: "保存", exact: true }).click(); const draggedOrder = await keys(); expect(draggedOrder.slice(1, 3)).toEqual(["enhance", "pickObject"]); await customize(); await page.setViewportSize({ width: 390, height: 700 }); await expect .poll(async () => (await dialog.boundingBox())?.width) .toBeLessThanOrEqual(358); await expect .poll(async () => (await dialog.boundingBox())?.height) .toBeLessThanOrEqual(652); await expect( dialog.getByRole("button", { name: "保存", exact: true }) ).toBeInViewport(); await dialog.getByRole("button", { name: "取消", exact: true }).click(); await page.setViewportSize({ width: 1512, height: 793 }); settings.canvasToolbarConfig = { selectedImageInlineActionCount: 3, items: { imageEnhance: { label: "自定义高清", iconUrl: "/icons/lovart/model-gpt.svg", enabled: true, }, imageRemoveBackground: { enabled: false }, }, }; await page.reload({ waitUntil: "commit" }); await selectImage(); const customIcon = bar .getByRole("button", { name: "自定义高清", exact: true }) .locator('span[aria-hidden="true"]'); expect( await customIcon.evaluate( (element) => (element as HTMLElement).style.maskImage ) ).toContain("model-gpt.svg"); await customize(); await expect(dialog.getByRole("button", { name: /固定抠图/ })).toHaveCount(0); expect(errors).toEqual([]); });