113 lines
6.9 KiB
TypeScript
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()));
|
|
}
|
|
});
|
|
}
|