Files
next-tjwater-frontend/tests/browser/agent-process-continuation.e2e.ts
T

132 lines
9.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { responsesFixture } from "./support/responses-fixture";
for (const width of [1440, 320]) {
test(`restored clarification process belongs to its continuation at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
const sessionId = "clarification-process";
const nativeReplyId = `msg_${"b".repeat(32)}`;
const continuationId = `answer_${nativeReplyId}`;
const progress = (id: string, title: string) => ({ type: "data-progress", data: {
id, title, phase: "tool", status: "completed", detail: "合成演练记录。"
} });
const messages: Array<{ id: string; role: string; parts: Array<{ type: string; [key: string]: unknown }> }> = [
{ id: "original-user", role: "user", parts: [{ type: "text", text: "分别模拟三根管道。" }] },
{ id: "clarifying-message", role: "assistant", parts: [progress("query", "核对管道与时段")] },
{ id: nativeReplyId, role: "user", parts: [{ type: "text", text: "用户提交的提问回复:按顺序对应时段" }] },
{ id: continuationId, role: "assistant", parts: [
progress("query", "完成对应时段模拟"),
{ type: "text", text: "三根管道的模拟结果已整理,仅供演练。" }
] }
];
const interaction = {
version: "agent-interaction-request@1", interaction_id: "clarification", message_id: "clarifying-message",
turn_id: "clarification-turn", call_id: "ask", status: "answered", revision: 1, draft_revision: 0,
draft: [], created_at: "2026-10-28T10:00:00Z",
questions: [{ question_id: "mapping", title: "管道与时段对应", body: "请确认对应顺序。", mode: "single", required: true,
allow_custom: false, options: [{ id: "ordered", label: "按顺序对应时段" }] }],
answers: [{ question_id: "mapping", selected_option_ids: ["ordered"], custom_text: "", selected_options: [{ id: "ordered", label: "按顺序对应时段" }] }],
delivery: { native_message_id: nativeReplyId, revision: 2, status: "blocked", error_code: "CONTINUATION_UNCONFIRMED" }
};
messages[2].parts.push({ type: "data-interaction_reply", data: interaction });
let releaseInteractions!: () => void;
let interactionGate = new Promise<void>((resolve) => { releaseInteractions = resolve; });
await mockAgentApi(page, { sessions: [{ session_id: sessionId, title: "补充问题后继续模拟", created_at: Date.now(), updated_at: Date.now() }] });
const headers = { "Access-Control-Allow-Origin": "*" };
let writes = 0;
let finishBeforeNextInput = false;
const queued: Array<{ id: string; text: string; delivery: string; status: string }> = [];
await page.route(`**/api/v1/session/${sessionId}?**`, (route) => route.fulfill({ headers, json: {
session_id: sessionId, title: "补充问题后继续模拟", purpose: "ordinary_chat", status: "active", is_streaming: false, messages
} }));
await page.route(`**/api/v1/session/${sessionId}/interactions**`, async (route) => {
if (route.request().method() !== "GET") writes++;
await interactionGate;
return route.fulfill({ headers, json: { items: [interaction] } });
});
await page.route(`**/api/v1/session/${sessionId}/inputs`, (route) => route.fulfill({ headers, json: { items: queued } }));
await page.route("**/api/v1/responses", (route) => {
writes++;
const body = route.request().postDataJSON();
if (finishBeforeNextInput) {
expect(typeof body.input === "string" ? body.input : body.input[0].content[0].text).toBe("继续检查模拟结果");
if (body.stream === false) {
interaction.delivery.status = "delivered";
return route.fulfill({ headers, status: 409, json: { code: "RESPONSE_FINISHED", message: "Finished before admission" } });
}
return route.fulfill({ headers: { ...headers, "Content-Type": "text/event-stream" }, body: responsesFixture([
{ type: "start", messageId: "next-response" }, { type: "text-start", id: "text" },
{ type: "text-delta", id: "text", delta: "已开始新一轮核查。" }, { type: "text-end", id: "text" }, { type: "finish", finishReason: "stop" }
]) });
}
expect(body.stream).toBe(false);
expect(body.conversation).toBe(sessionId);
const receipt = { id: body.tjwater.input_id, text: body.input, delivery: body.tjwater.delivery, status: "pending" };
queued.push(receipt);
return route.fulfill({ headers, status: 202, json: receipt });
});
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("button", { name: /历史/ }).click();
await page.locator("button.agent-history-session-trigger").filter({ hasText: "补充问题后继续模拟" }).click();
const continuation = panel.locator(`[data-agent-message-id="${continuationId}"]`);
await expect(continuation.getByText("三根管道的模拟结果已整理,仅供演练。", { exact: true })).toBeVisible();
const reply = panel.getByRole("group", { name: "已提交的回答" });
await expect(reply).toHaveCount(1);
await expect(panel.getByText(/用户提交的提问回复/)).toHaveCount(0);
await expect(reply.getByText("按顺序对应时段", { exact: true })).toBeVisible();
releaseInteractions();
await expect(panel.locator('[data-agent-message-id="clarifying-message"]')).toHaveCount(0);
await expect(panel.getByRole("button", { name: "查看处理过程", exact: true })).toHaveCount(1);
const toggle = continuation.getByRole("button", { name: "查看处理过程", exact: true });
await toggle.click();
const process = continuation.locator("details[data-agent-process]");
await expect(process.getByText("核对管道与时段", { exact: true })).toBeVisible();
await expect(process.getByText("完成对应时段模拟", { exact: true })).toBeVisible();
await expect(process.locator("[data-agent-progress]")).toHaveCount(1);
await expect(process.getByRole("list", { name: "全部执行进度" }).locator("li")).toHaveCount(2);
await expect(process.getByRole("list", { name: "全部执行进度" }).locator("[data-tool-name]")).toHaveText(["核对管道与时段", "完成对应时段模拟"]);
await expect(reply.getByText("继续失败,可以重试", { exact: true })).toBeVisible();
await expect(reply.getByRole("button", { name: "重试继续", exact: true })).toBeEnabled();
const reportBox = (await continuation.locator("[data-answer-parts]").first().boundingBox())!;
const processBox = (await process.boundingBox())!;
expect(processBox.y).toBeGreaterThanOrEqual(reportBox.y + reportBox.height);
await panel.screenshot({ path: testInfo.outputPath(`continued-process-${width}.png`) });
// A hard reload must keep the same human card while metadata is delayed again.
interactionGate = new Promise<void>((resolve) => { releaseInteractions = resolve; });
await page.reload({ waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
await panel.getByRole("button", { name: /历史/ }).click();
await page.locator("button.agent-history-session-trigger").filter({ hasText: "补充问题后继续模拟" }).click();
await expect(reply).toHaveCount(1);
await expect(panel.getByText(/用户提交的提问回复/)).toHaveCount(0);
await expect(panel.locator('[data-agent-message-id="clarifying-message"]')).toHaveCount(0);
await expect(toggle).toHaveCount(1);
releaseInteractions();
await expect(reply.getByRole("button", { name: "重试继续", exact: true })).toBeEnabled();
expect(writes).toBe(0);
const prompt = panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" });
await prompt.fill("先核查压力数据");
await panel.getByRole("button", { name: "消息发送方式", exact: true }).click();
await page.getByRole("menuitemradio", { name: "排队处理", exact: true }).click();
await panel.getByRole("button", { name: "将消息加入 Agent 队列", exact: true }).click();
await expect(panel.getByRole("region", { name: "待处理消息队列" }).getByText("先核查压力数据", { exact: true })).toBeVisible();
await prompt.fill("优先检查第二根管道");
await panel.getByRole("button", { name: "消息发送方式", exact: true }).click();
await page.getByRole("menuitemradio", { name: "引导当前任务", exact: true }).click();
await panel.getByRole("button", { name: "引导当前 Agent 任务", exact: true }).click();
await expect.poll(() => queued.map((item) => item.delivery)).toEqual(["queue", "steer"]);
expect(writes).toBe(2);
finishBeforeNextInput = true;
await prompt.fill("继续检查模拟结果");
await panel.getByRole("button", { name: "引导当前 Agent 任务", exact: true }).click();
await expect(panel.getByText("已开始新一轮核查。", { exact: true })).toBeVisible();
expect(writes).toBe(4);
});
}