deerflow2/frontend/tests/e2e/support/chat-helpers.ts

118 lines
3.1 KiB
TypeScript

import { expect, type Page, type TestInfo } from "@playwright/test";
const rawPrimaryThreadId = process.env.FRONTEND_E2E_THREAD_ID?.trim();
function envThread(name: string) {
return process.env[name]?.trim() ?? undefined;
}
export const PRIMARY_THREAD_ID = rawPrimaryThreadId ?? undefined;
export const THREAD_FOR_WELCOME = PRIMARY_THREAD_ID;
export const THREAD_WITH_HISTORY = PRIMARY_THREAD_ID;
export const THREAD_WITH_MARKDOWN = envThread("FRONTEND_E2E_MARKDOWN_THREAD_ID");
export const THREAD_WITH_TODOS = envThread("FRONTEND_E2E_TODOS_THREAD_ID");
export const THREAD_WITH_ARTIFACTS = envThread("FRONTEND_E2E_ARTIFACTS_THREAD_ID");
export const THREAD_WITH_IMAGE_ARTIFACT = envThread(
"FRONTEND_E2E_IMAGE_ARTIFACT_THREAD_ID",
);
export const THREAD_WITH_HTML_ARTIFACT = envThread(
"FRONTEND_E2E_HTML_ARTIFACT_THREAD_ID",
);
export function skipIfMissingThread(
testInfo: TestInfo,
threadId: string | undefined,
label: string,
) {
testInfo.skip(!threadId, `未配置 ${label}`);
}
export function buildChatUrl({
pathThreadId,
xclawUsed,
threadId,
}: {
pathThreadId?: string;
xclawUsed: boolean;
threadId?: string;
}) {
const resolvedThreadId = threadId ?? pathThreadId;
if (!pathThreadId && !resolvedThreadId) {
throw new Error("threadId is required for /workspace/chats/new routes.");
}
const query = new URLSearchParams();
query.set("xclaw_used", String(xclawUsed));
if (resolvedThreadId) {
query.set("thread_id", resolvedThreadId);
}
const basePath = pathThreadId
? `/workspace/chats/${pathThreadId}`
: "/workspace/chats/new";
return `${basePath}?${query.toString()}`;
}
export function invalidNewChatUrl() {
const query = new URLSearchParams();
query.set("xclaw_used", "false");
return `/workspace/chats/new?${query.toString()}`;
}
export function newChatEntry(threadId: string) {
return buildChatUrl({
xclawUsed: false,
threadId,
});
}
export function reuseThreadWelcomeEntry(threadId: string) {
return buildChatUrl({
xclawUsed: false,
threadId,
});
}
export function reuseThreadChatEntry(threadId: string) {
return buildChatUrl({
pathThreadId: threadId,
xclawUsed: true,
threadId,
});
}
export async function openChat(
page: Page,
url: string,
options?: { expectInput?: boolean },
) {
await page.goto(url);
if (options?.expectInput ?? true) {
await expect(page.locator("textarea[name='message']")).toBeVisible();
}
}
export async function sendMessage(page: Page, text: string) {
const textarea = page.locator("textarea[name='message']");
const submit = page.locator("button[aria-label='Submit']");
await textarea.click();
await textarea.fill(text);
await submit.click();
}
export async function waitForMessageListReady(
page: Page,
options?: { requireMessages?: boolean; minMessages?: number },
) {
const { requireMessages = false, minMessages = 1 } = options ?? {};
await expect(page.getByRole("main").first()).toBeVisible();
if (requireMessages) {
await expect
.poll(
async () => await page.locator(".is-user, .is-assistant").count(),
{ timeout: 30_000 },
)
.toBeGreaterThan(minMessages - 1);
}
}