import { expect, test } from "@playwright/test"; import { mockAgentApi } from "./support/mock-agent-api"; import { mockWorkbenchContext } from "./support/mock-workbench-context"; import { TEST_PROJECT_ID } from "./support/mock-scada-api"; import { readColorAlpha } from "./support/theme-colors"; for (const width of [320, 375, 400, 640]) { test(`conversation title and topic stay distinct at ${width}px`, async ({ page }, testInfo) => { const mobile = width < 400; await page.setViewportSize({ width: mobile ? width : 1440, height: 900 }); await page.emulateMedia({ reducedMotion: "reduce" }); await mockWorkbenchContext(page); const title = "安徽省兴安农牧科技发展有限公司,附近选取不同管道核对供水压力、流量与阀门状态"; const topicName = "兴安农牧供水异常事件与管道压力问题持续跟踪复核"; const session = { id: "long-context", session_id: "long-context", title, topic_id: "long-topic", updated_at: Date.now(), created_at: Date.now() }; await mockAgentApi(page, { sessions: [session] }); const projectHeaders: string[] = []; await page.route("**/api/v1/topics", async (route) => { if (route.request().method() === "OPTIONS") return route.fallback(); projectHeaders.push((await route.request().allHeaders())["x-project-id"]); return route.fulfill({ headers: { "Access-Control-Allow-Origin": "*" }, json: { topics: [ { id: "long-topic", name: topicName, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } ] } }); }); await page.route("**/api/v1/session/long-context?**", (route) => { if (route.request().method() === "OPTIONS") return route.fallback(); return route.fulfill({ headers: { "Access-Control-Allow-Origin": "*" }, json: { ...session, purpose: "ordinary_chat", messages: [], is_streaming: false } }); }); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: mobile ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click(); const panel = page.getByRole("complementary", { name: "Agent 命令面板" }); if (!mobile) { await page.getByRole("separator", { name: "调整 Agent 面板宽度" }).focus(); await page.keyboard.press(width === 400 ? "Home" : "End"); } await panel.getByRole("button", { name: "打开 Agent 历史记录" }).click(); await page.getByRole("dialog", { name: "历史对话", exact: true }).locator(".agent-history-session-trigger").filter({ hasText: title }).click(); const context = panel.locator(".agent-chat-context-bar"); const titleElement = context.getByText(title, { exact: true }); const header = panel.locator(".agent-panel-header"); const topic = header.getByRole("button", { name: "选择主题", exact: true }); await expect(topic).toHaveText(topicName); await expect(topic).toHaveAccessibleDescription(topicName); await expect(context.getByRole("button")).toHaveCount(0); await expect(panel.getByText("主题", { exact: true })).toHaveCount(0); await expect(topic.locator("svg.lucide-folder")).toBeVisible(); const contextBox = (await context.boundingBox())!; const titleBox = (await titleElement.boundingBox())!; const topicBox = (await topic.boundingBox())!; expect(titleBox.width).toBeGreaterThanOrEqual(contextBox.width - 26); expect(titleBox.height).toBeGreaterThanOrEqual(20); if (width < 640) expect(titleBox.height).toBeGreaterThan(20); expect(titleBox.height).toBeLessThanOrEqual(40); expect(titleBox.y).toBeGreaterThanOrEqual(topicBox.y + topicBox.height); const headerBox = (await header.boundingBox())!; const navigationBox = (await header.locator("..").boundingBox())!; const nameBox = (await header.getByText("供水助手", { exact: true }).boundingBox())!; const statusBox = (await header.getByRole("status").boundingBox())!; const dividerBox = (await panel.locator("[data-agent-header-divider]").boundingBox())!; const actionsBox = (await header.getByRole("group", { name: "对话操作" }).boundingBox())!; expect(statusBox.x).toBeGreaterThanOrEqual(nameBox.x + nameBox.width); await expect(header.getByRole("status")).toHaveText("在线"); expect(await header.getByRole("status").evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true); expect(statusBox.x + statusBox.width).toBeLessThanOrEqual(actionsBox.x - 6); expect(Math.abs(statusBox.y + statusBox.height / 2 - nameBox.y - nameBox.height / 2)).toBeLessThanOrEqual(1); expect(topicBox.y).toBeGreaterThanOrEqual(nameBox.y + nameBox.height + 6); expect(dividerBox.height).toBe(1); expect(readColorAlpha(await panel.locator("[data-agent-header-divider]").evaluate((element) => getComputedStyle(element).backgroundColor))).toBeLessThanOrEqual(0.5); await expect(context).toHaveCSS("border-bottom-width", "0px"); expect(titleBox.y).toBeGreaterThanOrEqual(dividerBox.y + dividerBox.height + 12); expect(topicBox.x + topicBox.width).toBeLessThanOrEqual(actionsBox.x - 8); expect(contextBox.y + contextBox.height - navigationBox.y).toBeLessThanOrEqual(146); expect(topicBox.x + topicBox.width).toBeLessThanOrEqual(contextBox.x + contextBox.width - 10); expect(await context.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true); await topic.click(); const menu = page.getByRole("menu"); await expect(menu.getByRole("menuitemradio", { name: topicName, exact: true })).toBeChecked(); await expect(menu.getByRole("menuitemradio", { name: "未分类", exact: true })).not.toBeChecked(); await expect(page.getByRole("menuitem", { name: "新建主题", exact: true })).toBeVisible(); const menuBox = (await menu.boundingBox())!; expect(Math.abs(menuBox.x - topicBox.x)).toBeLessThanOrEqual(1); expect(menuBox.x + menuBox.width).toBeLessThanOrEqual(mobile ? width : 1440); await menu.screenshot({ path: `test-results/agent-context/topic-menu-${width}.png` }); await page.keyboard.press("Escape"); await expect(topic).toBeFocused(); expect(projectHeaders.length).toBeGreaterThan(0); expect(projectHeaders.every((id) => id === TEST_PROJECT_ID)).toBe(true); await testInfo.attach("context-geometry", { contentType: "application/json", body: JSON.stringify({ width, headerBox, navigationBox, contextBox, titleBox, topicBox, statusBox, dividerBox, actionsBox }) }); await titleElement.click(); await panel.screenshot({ path: `test-results/agent-context/context-${width}.png` }); await topic.click(); await page.getByRole("menuitemradio", { name: "未分类", exact: true }).click(); await expect(topic).toHaveText("未分类"); await topic.click(); await page.getByRole("menuitemradio", { name: topicName, exact: true }).click(); await expect(topic).toHaveText(topicName); }); }