134 lines
7.9 KiB
TypeScript
134 lines
7.9 KiB
TypeScript
import { expect, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
|
|
import { selectors, sessionTab } from "../fixtures/selectors";
|
|
|
|
test("TraceTimeline renders readable rows and hides backend noise", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_completed");
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
const trace = page.locator(`${selectors.traceTimeline}[data-status="completed"]`);
|
|
await expect(trace).toBeVisible();
|
|
await trace.locator("summary.trace-disclosure-summary").click();
|
|
await expect(page.locator(`${selectors.traceRow}[data-row-kind="tool"]`)).toContainText("commandExecution");
|
|
await expect(page.locator(`${selectors.traceRow}[data-terminal="true"]`)).toContainText("构建和 Trace 渲染检查完成");
|
|
await expect(page.locator(selectors.conversationList)).not.toContainText("token_count");
|
|
await page.locator(`${selectors.messageCard}[data-role="agent"] .message-detail-button`).first().click();
|
|
await expect(page.locator(selectors.messageDetailDialog)).toContainText("trc_completed");
|
|
await expect(page.locator(selectors.messageDetailDialog)).toContainText("ses_completed");
|
|
await saveScreenshot(page, testInfo, "trace-rendering-detail");
|
|
});
|
|
|
|
test("trace explorer link is gated by runtime config", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_completed");
|
|
const card = page.locator(`${selectors.messageCard}[data-role="agent"]`).first();
|
|
await card.locator(".message-detail-button").click();
|
|
const dialog = page.locator(selectors.messageDetailDialog);
|
|
await expect(dialog.getByRole("button", { name: "复制 Trace" })).toBeVisible();
|
|
await expect(dialog.getByRole("link", { name: "打开 Trace" })).toHaveCount(0);
|
|
await dialog.getByRole("button", { name: "关闭" }).click();
|
|
|
|
await page.addInitScript(() => {
|
|
window.HWLAB_CLOUD_WEB_CONFIG = {
|
|
displayTime: { timeZone: "Asia/Shanghai", locale: "zh-CN", label: "北京时间" },
|
|
workbench: { traceExplorerUrlTemplate: "/v1/workbench/traces/{trace_id}/events" }
|
|
};
|
|
});
|
|
await page.reload();
|
|
await expect(page.locator(selectors.workspace)).toBeVisible();
|
|
const configuredCard = page.locator(`${selectors.messageCard}[data-role="agent"]`).first();
|
|
await configuredCard.locator(".message-detail-button").click();
|
|
await expect(dialog.getByRole("link", { name: "打开 Trace" })).toHaveAttribute("href", /\/v1\/workbench\/traces\/trc_completed\/events$/u);
|
|
});
|
|
|
|
test.describe("trace disclosure policy", () => {
|
|
test.use({ scenarioId: "event-replay" });
|
|
|
|
test("manual trace disclosure survives terminal lifecycle updates", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page);
|
|
const runningTrace = page.locator(`${selectors.traceTimeline}[data-status="running"]`).first();
|
|
const runningDetails = runningTrace.locator("details.trace-disclosure");
|
|
await expect(runningTrace).toBeVisible();
|
|
await expect(runningDetails).toHaveJSProperty("open", false);
|
|
|
|
await runningTrace.locator("summary.trace-disclosure-summary").click();
|
|
await expect(runningDetails).toHaveJSProperty("open", true);
|
|
|
|
const completedTrace = page.locator(`${selectors.traceTimeline}[data-status="completed"]`).first();
|
|
const completedDetails = completedTrace.locator("details.trace-disclosure");
|
|
await expect(completedTrace).toBeVisible();
|
|
await expect(completedDetails).toHaveJSProperty("open", true);
|
|
await saveScreenshot(page, testInfo, "trace-disclosure-policy-manual-terminal");
|
|
});
|
|
});
|
|
|
|
test.describe("non-terminal assistant progress", () => {
|
|
test.use({ scenarioId: "progress-only-final-response" });
|
|
|
|
test("progress text stays in trace until terminal final response arrives", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
|
const runningCard = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
|
|
await expect(runningCard).toBeVisible();
|
|
await expect(runningCard.locator(selectors.traceTimeline)).toContainText("PROGRESS_ONLY_SHOULD_NOT_RENDER_AS_FINAL");
|
|
const runningFinalTexts = await runningCard.evaluate((node) => Array.from(node.querySelectorAll(".message-markdown.message-text")).map((item) => item.textContent ?? ""));
|
|
expect(runningFinalTexts).toEqual([]);
|
|
await saveScreenshot(page, testInfo, "running-before-final-response");
|
|
|
|
const completedCard = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
|
|
await expect(completedCard.locator(".message-markdown.message-text")).toContainText("FINAL_ONLY_SHOULD_RENDER");
|
|
await expect(completedCard.locator(selectors.traceTimeline)).toBeVisible();
|
|
await saveScreenshot(page, testInfo, "terminal-after-final-response");
|
|
});
|
|
});
|
|
|
|
test.describe("markdown final response", () => {
|
|
test.use({ scenarioId: "markdown-final-response" });
|
|
|
|
test("preserves message whitespace and renders GFM tables in final response and trace", async ({ page }, testInfo) => {
|
|
const sessionId = "ses_markdown_final";
|
|
const traceId = "trc_markdown_final";
|
|
const messagesResponse = await page.request.get(`/v1/workbench/sessions/${sessionId}/messages?limit=100`);
|
|
expect(messagesResponse.status()).toBe(200);
|
|
const messagesPayload = await messagesResponse.json();
|
|
const agentMessage = messagesPayload.messages.find((message: { role?: string }) => message.role === "agent");
|
|
const agentText = String(agentMessage?.text ?? "");
|
|
expect((agentText.match(/\n/gu) ?? []).length).toBeGreaterThan(8);
|
|
expect(agentText).toContain("`go test ./...` 保留两个空格");
|
|
expect(agentMessage?.parts?.[0]?.text).toBe(agentText);
|
|
|
|
const traceResponse = await page.request.get(`/v1/workbench/traces/${traceId}/events?limit=20`);
|
|
expect(traceResponse.status()).toBe(200);
|
|
const tracePayload = await traceResponse.json();
|
|
const assistantEvent = tracePayload.events.find((event: { label?: string }) => event.label === "agentrun:assistant:message");
|
|
const traceText = String(assistantEvent?.message ?? "");
|
|
expect((traceText.match(/\n/gu) ?? []).length).toBeGreaterThan(8);
|
|
expect(traceText).toContain("`go test ./...` 保留两个空格");
|
|
|
|
await gotoWorkbench(page, `/workbench/sessions/${sessionId}`);
|
|
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
|
|
await expect(card.locator(".message-markdown.message-text table")).toHaveCount(2);
|
|
await card.locator("summary.trace-disclosure-summary").click();
|
|
await expect(card.locator(".trace-row-markdown table")).toHaveCount(2);
|
|
await saveScreenshot(page, testInfo, "markdown-final-response-tables");
|
|
});
|
|
});
|
|
|
|
test("failed trace exposes readable failure row", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_failed");
|
|
const trace = page.locator(`${selectors.traceTimeline}[data-status="failed"]`);
|
|
await expect(trace).toBeVisible();
|
|
await trace.locator("summary.trace-disclosure-summary").click();
|
|
await expect(page.locator(`${selectors.traceRow}[data-event-status="blocked"][data-row-kind="tool"]`)).toContainText("missing controlled dependency");
|
|
await expect(page.locator(`${selectors.messageCard}[data-status="failed"]`)).toContainText("缺少受控依赖");
|
|
await saveScreenshot(page, testInfo, "trace-rendering-failed");
|
|
});
|
|
|
|
test.describe("terminal empty trace copy", () => {
|
|
test.use({ scenarioId: "terminal-empty-trace" });
|
|
|
|
test("completed message details do not retain thinking placeholder", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_terminal_empty");
|
|
const card = page.locator(`${selectors.messageCard}[data-status="completed"]`).last();
|
|
await expect(card).toContainText("终态空 Trace 已完成。");
|
|
await expect(card.locator(".trace-empty")).toContainText("运行已完成,暂无可读 Trace");
|
|
expect((await card.textContent()) ?? "").not.toContain("思考中");
|
|
});
|
|
});
|