151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import {
|
|
THREAD_WITH_HISTORY,
|
|
THREAD_WITH_MARKDOWN,
|
|
THREAD_WITH_TODOS,
|
|
openChat,
|
|
reuseThreadChatEntry,
|
|
skipIfMissingThread,
|
|
waitForMessageListReady,
|
|
} from "./support/chat-helpers";
|
|
|
|
async function waitForAnyMessages(
|
|
page: Parameters<typeof openChat>[0],
|
|
timeoutMs = 15_000,
|
|
) {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
const count = await page.locator(".is-user, .is-assistant").count();
|
|
if (count > 0) {
|
|
return count;
|
|
}
|
|
await page.waitForTimeout(300);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
test.describe("聊天工作台 / 消息区与历史", () => {
|
|
test("DF-MSG-001 固定 fixture 历史消息可见", async ({ page }, testInfo) => {
|
|
skipIfMissingThread(
|
|
testInfo,
|
|
THREAD_WITH_HISTORY,
|
|
"FRONTEND_E2E_THREAD_ID",
|
|
);
|
|
await openChat(page, reuseThreadChatEntry(THREAD_WITH_HISTORY!));
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
const messageCount = await waitForAnyMessages(page);
|
|
testInfo.skip(messageCount === 0, "当前历史线程没有可见消息。");
|
|
|
|
await expect(page.locator(".is-user, .is-assistant").first()).toBeVisible();
|
|
});
|
|
|
|
test("DF-MSG-002 Markdown 消息结构可见", async ({ page }, testInfo) => {
|
|
skipIfMissingThread(
|
|
testInfo,
|
|
THREAD_WITH_MARKDOWN,
|
|
"FRONTEND_E2E_MARKDOWN_THREAD_ID",
|
|
);
|
|
await openChat(page, reuseThreadChatEntry(THREAD_WITH_MARKDOWN!));
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
|
|
const messageCount = await waitForAnyMessages(page);
|
|
testInfo.skip(
|
|
messageCount === 0,
|
|
"当前线程没有可用于断言 Markdown 结构的历史消息。",
|
|
);
|
|
|
|
const markdownCandidates = page.locator(
|
|
".is-assistant strong, .is-assistant ul li, .is-assistant ol li, .is-assistant code",
|
|
);
|
|
await expect(markdownCandidates.first()).toBeVisible();
|
|
});
|
|
|
|
test("DF-MSG-003 长历史线程中可出现滚动到底部按钮", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
skipIfMissingThread(
|
|
testInfo,
|
|
THREAD_WITH_HISTORY,
|
|
"FRONTEND_E2E_THREAD_ID",
|
|
);
|
|
await openChat(page, reuseThreadChatEntry(THREAD_WITH_HISTORY!));
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
const messageCount = await waitForAnyMessages(page);
|
|
testInfo.skip(messageCount === 0, "当前历史线程没有可见消息。");
|
|
|
|
const messageLog = page.getByRole("log").first();
|
|
const canScroll = await messageLog.evaluate((element) => {
|
|
const target = element as HTMLElement;
|
|
return target.scrollHeight - target.clientHeight > 20;
|
|
});
|
|
testInfo.skip(
|
|
canScroll === false,
|
|
"当前线程消息区高度不足,无法触发滚动到底部按钮。",
|
|
);
|
|
|
|
await messageLog.hover();
|
|
await page.mouse.wheel(0, -1200);
|
|
await messageLog.evaluate((element) => {
|
|
const target = element as HTMLElement;
|
|
target.scrollTop = Math.max(0, target.scrollTop - 1200);
|
|
});
|
|
|
|
await expect(page.getByTitle("滚动到底部")).toBeVisible();
|
|
});
|
|
|
|
test("DF-MSG-004 刷新前后用户消息关键内容保持一致", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
skipIfMissingThread(
|
|
testInfo,
|
|
THREAD_WITH_HISTORY,
|
|
"FRONTEND_E2E_THREAD_ID",
|
|
);
|
|
await openChat(page, reuseThreadChatEntry(THREAD_WITH_HISTORY!));
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
|
|
const normalizeText = (text: string) => text.replace(/\s+/g, " ").trim();
|
|
const beforeUsers = (await page.locator(".is-user").allTextContents())
|
|
.map(normalizeText)
|
|
.filter(Boolean);
|
|
|
|
testInfo.skip(beforeUsers.length === 0, "当前历史线程没有可见用户消息。");
|
|
|
|
await page.reload();
|
|
await expect(page.locator("textarea[name='message']")).toBeVisible();
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
|
|
const afterUsers = (await page.locator(".is-user").allTextContents())
|
|
.map(normalizeText)
|
|
.filter(Boolean);
|
|
|
|
expect(afterUsers.length).toBe(beforeUsers.length);
|
|
for (const sample of beforeUsers.slice(
|
|
0,
|
|
Math.min(3, beforeUsers.length),
|
|
)) {
|
|
expect(afterUsers.some((text) => text.includes(sample))).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test("DF-MSG-005 含 todos 的线程显示 To-dos 入口", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
skipIfMissingThread(
|
|
testInfo,
|
|
THREAD_WITH_TODOS,
|
|
"FRONTEND_E2E_TODOS_THREAD_ID",
|
|
);
|
|
await openChat(page, reuseThreadChatEntry(THREAD_WITH_TODOS!));
|
|
await waitForMessageListReady(page, { requireMessages: false });
|
|
|
|
const todoButton = page.getByRole("button", { name: /To-dos/i });
|
|
testInfo.skip(
|
|
(await todoButton.count()) === 0,
|
|
"当前线程未展示 To-dos 入口。",
|
|
);
|
|
await expect(todoButton).toBeVisible();
|
|
});
|
|
});
|