Files
next-tjwater-frontend/tests/browser/agent-progress-style.e2e.ts
T

113 lines
6.9 KiB
TypeScript

import { expect, test, type Locator } from "@playwright/test";
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { responseFixtureWriter } from "./support/responses-fixture";
async function readRowStyle(row: Locator) {
return row.evaluate((element) => {
const style = getComputedStyle(element);
const text = element.querySelector("span.min-w-0 > span")!;
const icon = element.querySelector('[role="img"]')!;
return {
columns: style.gridTemplateColumns.split(" ")[0],
gap: style.columnGap,
fontSize: style.fontSize,
titleWeight: getComputedStyle(text).fontWeight,
titleLineHeight: getComputedStyle(text).lineHeight,
iconWidth: icon.getBoundingClientRect().width,
iconHeight: icon.getBoundingClientRect().height
};
});
}
for (const width of [1440, 320]) {
test(`live progress matches archived rows and retains folding at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
await mockAgentApi(page);
const steps = [
{ id: "start", title: "已收到请求", status: "completed", detail: "正在核对当前项目。" },
{ id: "context", title: "Agent 正在处理请求", status: "running", detail: "Agent 正在执行推理、工具调用或结果整理。" },
{ id: "data", title: "获取业务数据", status: "running", detail: "正在核对附近阀门、关联管道与隔离范围。" },
{ id: "isolation", phase: "tool", title: "阀门隔离分析", status: "running", tool: {
name: "mcp_tjwater_analyze_valve_isolation", call_id: "isolation-call", input: { link_ids: [533793, 486121, 523616] }
} }
];
let finish!: () => void;
const server = createServer((_request, response) => {
response.writeHead(200, { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" });
const write = responseFixtureWriter();
const send = (chunk: Record<string, unknown>) => response.write(write(chunk));
send({ type: "start", messageId: "consistent-progress" });
for (const step of steps) send({ type: "data-progress", data: step });
finish = () => {
for (const step of steps) send({ type: "data-progress", data: { ...step, status: "completed" } });
send({ type: "data-final_answer", data: { content: "已核对附近可用于隔离的阀门。" } });
send({ type: "finish", finishReason: "stop" });
response.end();
};
});
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
await page.route("**/api/v1/responses", (route) => route.continue({ url: `http://127.0.0.1:${(server.address() as AddressInfo).port}` }));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill("找下附近可用的阀门,用于隔离");
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
const message = panel.locator('[data-agent-message-id="consistent-progress"]');
const progress = message.locator("[data-agent-progress]");
const recent = progress.getByRole("list", { name: "最近执行进度" });
await expect(recent.locator("li")).toHaveCount(3);
const container = message.locator("details[data-agent-live-process]");
await expect(container).toHaveCSS("background-color", "rgb(255, 255, 255)");
await expect(container).toHaveCSS("border-radius", "12px");
await expect(container).toHaveCSS("border-top-width", "1px");
await expect(progress).toHaveCSS("border-top-width", "0px");
const containerStyle = await container.evaluate((element) => {
const style = getComputedStyle(element);
return [style.borderColor, style.borderRadius, style.backgroundColor];
});
const liveRow = recent.locator("li").filter({ hasText: "Agent 正在处理请求" });
const liveStyle = await readRowStyle(liveRow);
expect(liveStyle).toMatchObject({ columns: "24px", gap: "8px", fontSize: "14px", titleWeight: "500", iconWidth: 24, iconHeight: 24 });
await expect(liveRow.getByRole("img", { name: "进行中", exact: true })).toBeVisible();
await progress.getByRole("button", { name: "展开执行进度", exact: true }).click();
await expect(progress.getByRole("list", { name: "全部执行进度" }).locator("li")).toHaveCount(4);
await progress.getByRole("button", { name: "收起执行进度", exact: true }).click();
await expect(recent.locator("li")).toHaveCount(3);
const tool = progress.locator("details[data-agent-tool]");
await expect(tool).not.toHaveAttribute("open");
await tool.locator("summary").click();
await expect(tool.locator("pre")).toContainText("533793");
await message.screenshot({ path: testInfo.outputPath(`live-progress-${width}.png`) });
finish();
const archived = message.locator("details[data-agent-process]");
await expect(archived).not.toHaveAttribute("open");
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
await expect(archived).toHaveAttribute("open", "");
await expect(archived).toHaveCSS("background-color", "rgb(255, 255, 255)");
expect(await archived.evaluate((element) => {
const style = getComputedStyle(element);
return [style.borderColor, style.borderRadius, style.backgroundColor];
})).toEqual(containerStyle);
const archivedRow = archived.getByRole("list", { name: "全部执行进度" }).locator("li").filter({ hasText: "Agent 正在处理请求" });
expect(await readRowStyle(archivedRow)).toEqual(liveStyle);
await expect(archivedRow.getByRole("img", { name: "已完成", exact: true })).toBeVisible();
await expect(tool).toHaveAttribute("open", "");
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
await expect(progress).toBeHidden();
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
await expect(progress).toBeVisible();
await message.screenshot({ path: testInfo.outputPath(`archived-progress-${width}.png`) });
expect(await progress.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
} finally {
server.closeAllConnections();
await new Promise<void>((resolve) => server.close(() => resolve()));
}
});
}