Files
next-tjwater-frontend/tests/browser/agent-answer-transition.e2e.ts
T

114 lines
7.0 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { responseFixtureWriter } from "./support/responses-fixture";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
const finalText = "已完成三根管道的压力核查。结果属于模型计算,仍需现场核验。";
async function openStream(page: Page, width: number) {
await page.setViewportSize({ width, height: 900 });
await mockWorkbenchContext(page);
await mockAgentApi(page);
let publishFinal!: () => void;
let finish!: () => void;
const server = createServer((_request, response) => {
response.writeHead(200, { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" });
const encode = responseFixtureWriter();
const send = (chunk: Record<string, unknown>) => response.write(encode(chunk));
send({ type: "start", messageId: "answer-transition" });
send({ type: "text-start", id: "process-text" });
send({ type: "text-delta", id: "process-text", delta: Array.from({ length: 8 }, (_, i) => `第${i + 1}阶段:正在核对管道压力与模拟时段,本阶段数据仅用于分析过程。`).join("\n\n") });
for (let i = 0; i < 6; i++) send({ type: "data-progress", data: { id: `step-${i}`, phase: "tool", title: `核对资料步骤 ${i + 1}`, detail: "查询监测记录并检查时段与单位。", status: i === 5 ? "running" : "completed" } });
send({ type: "text-end", id: "process-text" });
publishFinal = () => send({ type: "data-final_answer", data: { content: finalText, ui_block_ids: [] } });
finish = () => { send({ type: "finish", finishReason: "stop" }); response.end(); };
});
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
await page.route("**/api/v1/responses", (route) => route.continue({ url: `http://127.0.0.1:${(server.address() as AddressInfo).port}/stream` }));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await panel.locator("textarea").fill("核对压力");
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
const message = panel.locator('[data-agent-message-id="answer-transition"]');
await expect(message.getByText("核对资料步骤 6", { exact: true })).toBeVisible();
return { panel, message, publishFinal: () => publishFinal(), finish: () => finish(), close: async () => {
server.closeAllConnections();
await new Promise<void>((resolve) => server.close(() => resolve()));
} };
}
for (const width of [1440, 320]) {
for (const reducedMotion of ["reduce", "no-preference"] as const) {
test(`final report settles once before transport close at ${width}px with ${reducedMotion}`, async ({ page }, testInfo) => {
await page.emulateMedia({ reducedMotion });
const stream = await openStream(page, width);
try {
const { message } = stream;
await message.locator("[data-agent-progress]").evaluate((node) => { node.setAttribute("data-original-progress", "true"); });
stream.publishFinal();
await expect(message.getByText(finalText, { exact: true })).toBeVisible();
await expect(message.getByRole("button", { name: "复制回复", exact: true })).toBeVisible();
await expect(message.getByText("正在回复", { exact: true })).toHaveCount(0);
await expect(message.locator("details[data-agent-process]")).not.toHaveAttribute("open");
const settled = await message.boundingBox();
await message.screenshot({ path: testInfo.outputPath(`report-ready-${width}-${reducedMotion}.png`) });
stream.finish();
await expect(stream.panel.getByRole("button", { name: "发送 Agent 指令", exact: true })).toBeVisible();
const after = await message.boundingBox();
expect(Math.abs(after!.height - settled!.height)).toBeLessThanOrEqual(1);
expect(Math.abs(after!.y - settled!.y)).toBeLessThanOrEqual(1);
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
const process = message.locator("details[data-agent-process]");
await expect(process.getByText(/第1阶段:/)).toBeVisible();
await expect(process.locator('[data-original-progress="true"]')).toBeVisible();
} finally { await stream.close(); }
});
}
}
for (const reducedMotion of ["reduce", "no-preference"] as const) test(`reading a live process still folds on completion and retains tool details with ${reducedMotion}`, async ({ page }) => {
await page.emulateMedia({ reducedMotion });
const stream = await openStream(page, 1440);
try {
const { message, panel } = stream;
const tool = message.locator("details[data-agent-tool]").last();
await tool.locator("summary").click();
const detail = tool.getByText("查询监测记录并检查时段与单位。", { exact: true });
await expect(detail).toBeVisible();
await expect(tool).toHaveAttribute("data-disclosure-motion", "idle");
const scroll = panel.locator(".agent-conversation-scroll");
await scroll.hover();
await page.mouse.wheel(0, -300);
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const reading = message.getByText(/第1阶段:/).first();
await expect(reading).toBeInViewport();
// Finish the reader's wheel movement and any preceding disclosure layout
// before measuring the separate final-report handoff.
let lastTop: number | undefined;
await expect.poll(async () => {
const top = (await reading.boundingBox())!.y;
const stable = top === lastTop;
lastTop = top;
return stable;
}).toBe(true);
stream.publishFinal();
await expect(message.getByRole("button", { name: "复制回复", exact: true })).toBeVisible();
const process = message.locator("details[data-agent-process]");
await expect(process).not.toHaveAttribute("open");
await expect(detail).toBeHidden();
await expect(message.locator("[data-answer-phase]")).toHaveAttribute("data-answer-phase", "settled");
await expect(message.getByText(finalText, { exact: true })).toBeInViewport();
stream.finish();
await expect(panel.getByRole("button", { name: "发送 Agent 指令", exact: true })).toBeVisible();
await expect(process).not.toHaveAttribute("open");
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
await expect(process).toHaveAttribute("open", "");
await expect(process.locator("details[data-agent-tool]").last()).toHaveAttribute("open", "");
await expect(detail).toBeVisible();
await message.getByRole("button", { name: "查看处理过程", exact: true }).click();
await expect(process).not.toHaveAttribute("open");
} finally { await stream.close(); }
});