132 lines
9.4 KiB
TypeScript
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);
|
|
});
|
|
}
|