Files
next-tjwater-frontend/tests/browser/agent-user-message-copy.e2e.ts
T

133 lines
7.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(`native user message copy preserves source text at ${width}px`, async ({ page, context }) => {
await page.setViewportSize({ width, height: 900 });
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await mockWorkbenchContext(page);
await mockAgentApi(page);
let responseIndex = 0;
await page.route("**/api/v1/responses", (route) => route.fulfill({
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" },
body: responsesFixture([
{ type: "start", messageId: `copy-answer-${++responseIndex}` },
{ type: "data-final_answer", data: { content: "已核对管道。" } },
{ type: "finish", finishReason: "stop" }
])
}));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
const prompt = "能直接高亮三根管道吗";
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill(prompt);
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
await expect(panel.getByText("已核对管道。", { exact: true })).toBeVisible();
const answerParagraph = panel.locator(".agent-streaming-response p").first();
await answerParagraph.click({ clickCount: 3 });
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("已核对管道。");
const text = panel.locator(".agent-panel-user-message p");
await text.click({ clickCount: 3 });
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(prompt);
// Exact substrings retain native copy behavior.
await text.evaluate((paragraph) => {
const range = document.createRange();
range.setStart(paragraph.firstChild!, 2);
range.setEnd(paragraph.firstChild!, 6);
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(range);
});
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(prompt.slice(2, 6));
// A selection containing another message must retain its native separators.
const expectedCrossMessage = await panel.evaluate((element) => {
const user = element.querySelector(".agent-panel-user-message p")!;
const answer = element.querySelector(".agent-streaming-response p")!;
const range = document.createRange();
range.setStart(user.firstChild!, 0);
range.setEnd(answer.firstChild!, answer.textContent!.length);
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(range);
return window.getSelection()!.toString();
});
expect(expectedCrossMessage).toContain("已核对管道。");
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(expectedCrossMessage);
// Multiline source text keeps blank lines, indentation and interior spaces.
const multiline = "第一行\n\n 第二行 内容";
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill(multiline);
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
const lastText = panel.locator(".agent-panel-user-message p").last();
await expect(lastText).toHaveText(multiline, { useInnerText: false });
await expect(panel.locator(".agent-streaming-response p")).toHaveCount(2);
await expect(panel.locator(".agent-streaming-response p").last()).toHaveText("已核对管道。");
await panel.evaluate((element) => {
const user = element.querySelectorAll(".agent-panel-user-message p");
const answer = element.querySelectorAll(".agent-streaming-response p");
const range = document.createRange();
range.setStart(user[user.length - 1].firstChild!, 0);
range.setEnd(answer[answer.length - 1].firstChild!, 0);
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(range);
});
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(multiline);
});
}
for (const width of [1440, 320]) {
test(`assistant rich text keeps meaningful native copy at ${width}px`, async ({ page, context }) => {
await page.setViewportSize({ width, height: 900 });
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await mockWorkbenchContext(page);
await mockAgentApi(page);
const markdown = "首段包含**加粗**文字。\n\n第二段保留段落。\n\n```text\n first\n second\n```\n\n| 名称 | 数值 |\n| --- | --- |\n| 流量 | 12 |";
await page.route("**/api/v1/responses", (route) => route.fulfill({
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" },
body: responsesFixture([
{ type: "start", messageId: "rich-copy-answer" },
{ type: "data-final_answer", data: { content: markdown } },
{ type: "finish", finishReason: "stop" }
])
}));
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 response = panel.locator(".agent-streaming-response");
await expect(response.locator("table")).toBeVisible();
await response.locator("p").first().click({ clickCount: 3 });
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("首段包含加粗文字。");
for (const selector of ["p", "pre", "table"]) {
const nativeText = await response.evaluate((element, selector) => {
const nodes = element.querySelectorAll(selector);
const range = document.createRange();
if (selector === "p") {
range.setStart(nodes[0].firstChild!, 0);
range.setEnd(nodes[1].firstChild!, nodes[1].firstChild!.textContent!.length);
} else {
range.selectNodeContents(nodes[0]);
}
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(range);
return window.getSelection()!.toString();
}, selector);
if (selector === "p") expect(nativeText).toContain("\n\n");
if (selector === "pre") expect(nativeText).toContain(" first\n second");
if (selector === "table") expect(nativeText).toContain("\t");
await page.keyboard.press("Control+c");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(nativeText);
}
await expect(panel.getByRole("button", { name: /复制/ }).first()).toBeAttached();
});
}