Files
pikasTech-HWLAB/web/hwlab-cloud-web/tests/workbench-e2e/specs/projection-diagnostics.spec.ts
T

81 lines
5.0 KiB
TypeScript

// SPEC: PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-0104010803 唯一投影 draft-2026-06-19-p2-terminal-sealed-final-response.
// Responsibility: Workbench diagnostic visibility regression, including separation from sealed final response.
import { expect, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
import { selectors, sessionTab } from "../fixtures/selectors";
test.describe("projection diagnostics visibility", () => {
test.use({ scenarioId: "projection-degraded-diagnostics" });
test("turn and trace projection blockers are visible without changing lifecycle", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_projection_degraded");
await expect(page.locator(sessionTab("ses_projection_degraded"))).toHaveAttribute("data-status", "running");
await expect(page.locator(sessionTab("ses_projection_degraded"))).toHaveAttribute("data-projection-health", "degraded");
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
await expect(card).toContainText("状态更新超时");
await expect(card.locator(selectors.traceTimeline)).toHaveAttribute("data-projection-health", "degraded");
await expect(card.locator(".trace-empty")).toContainText("状态更新超时");
await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "cancel");
await saveScreenshot(page, testInfo, "projection-degraded-visible");
});
});
test.describe("SSE projection errors", () => {
test.use({ scenarioId: "projection-sse-error" });
test("workbench.error binds to the current message diagnostic", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_projection_sse_error");
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
await expect(card).toContainText("SSE 投影事件异常");
await expect(page.locator(sessionTab("ses_projection_sse_error"))).toHaveAttribute("data-projection-health", "degraded");
await saveScreenshot(page, testInfo, "projection-sse-error-visible");
});
});
test.describe("REST gap hydration errors", () => {
test.use({ scenarioId: "trace-hydration-timeout-diagnostic" });
test("trace hydration timeout is visible without terminal inference", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_trace_hydration_timeout");
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
await expect(card).toContainText("Trace 更新超时");
await expect(card).toHaveAttribute("data-status", "running");
await expect(card.locator(selectors.traceTimeline)).toHaveAttribute("data-projection-health", "degraded");
await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "cancel");
await saveScreenshot(page, testInfo, "trace-hydration-timeout-visible");
});
});
test.describe("sealed final response diagnostics separation", () => {
test.use({ scenarioId: "sealed-final-response-diagnostics" });
test("keeps completed final response visible while showing diagnostics separately", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_sealed_final_diag");
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
await expect(card.locator(".projection-diagnostic")).toContainText("Trace 更新超时");
const finalResponse = card.locator(".message-text").filter({ hasText: "已完成的 sealed final response 不应被诊断覆盖。" });
await expect(finalResponse).toBeVisible();
await expect(finalResponse).not.toContainText("Trace 更新超时");
await saveScreenshot(page, testInfo, "sealed-final-response-diagnostic-separate");
});
});
test.describe("trace detail final response separation", () => {
test.use({ scenarioId: "trace-final-response-not-message-authority" });
test("keeps trace finalResponse out of the main message projection", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_trace_final_conflict");
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
const messageText = card.locator(".message-markdown.message-text");
await expect(messageText).toContainText("messageProjection 的 sealed final response 才是主消息正文。");
const traceResponse = await page.request.get("/v1/workbench/traces/trc_trace_final_conflict/events?limit=20");
expect(traceResponse.status()).toBe(200);
const tracePayload = await traceResponse.json();
expect(tracePayload.finalResponse.text).toBe("TRACE_DETAIL_CONFLICT_SHOULD_NOT_REPLACE_MESSAGE");
await page.waitForTimeout(500);
await expect(messageText).toContainText("messageProjection 的 sealed final response 才是主消息正文。");
await expect(messageText).not.toContainText("TRACE_DETAIL_CONFLICT_SHOULD_NOT_REPLACE_MESSAGE");
await saveScreenshot(page, testInfo, "trace-final-response-not-message-authority");
});
});