Files
next-tjwater-frontend/tests/browser/agent-context-bar.e2e.ts
T
jiang 2d8506fea0
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s
feat: add project-aware agent workspace
2026-10-22 18:52:18 +08:00

101 lines
6.8 KiB
TypeScript

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);
});
}