161 lines
8.3 KiB
TypeScript
161 lines
8.3 KiB
TypeScript
import { expect, fakeServerState, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
|
|
import { selectors, sessionTab } from "../fixtures/selectors";
|
|
|
|
type FrameSample = {
|
|
activeSessionId: string | null;
|
|
loading: boolean;
|
|
hasA: boolean;
|
|
hasB: boolean;
|
|
textPreview: string;
|
|
};
|
|
|
|
declare global {
|
|
interface Window {
|
|
__issue1536Sampling?: boolean;
|
|
__issue1536Samples?: FrameSample[];
|
|
}
|
|
}
|
|
|
|
test("session switch persists active session after reload", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page);
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "true");
|
|
await saveScreenshot(page, testInfo, "session-switch-before");
|
|
|
|
await page.locator(sessionTab("ses_completed")).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_completed/u);
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`)).toContainText("构建和 Trace 渲染检查完成");
|
|
await expect.poll(async () => (await fakeServerState(page)).legacyRequestLedger as unknown[]).toHaveLength(0);
|
|
await saveScreenshot(page, testInfo, "session-switch-after");
|
|
|
|
await page.reload();
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
await page.locator(selectors.sessionDetailToggle).click();
|
|
await expect(page.locator(selectors.sessionDetailDialog)).toContainText("ses_completed");
|
|
await expect(page.locator(`${selectors.traceTimeline}[data-status="completed"]`)).toBeVisible();
|
|
await saveScreenshot(page, testInfo, "session-switch-reload");
|
|
});
|
|
|
|
test.describe("delayed session detail projection", () => {
|
|
test.use({ scenarioId: "session-switch-delayed-detail-frame" });
|
|
|
|
test("loading exit never flashes previous session messages under target session", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.conversationList)).toContainText("SESSION_A_ONLY");
|
|
await saveScreenshot(page, testInfo, "session-switch-delayed-before");
|
|
|
|
await page.evaluate(() => {
|
|
const samples: FrameSample[] = [];
|
|
const read = (): void => {
|
|
const active = document.querySelector('.session-tab[data-active="true"]');
|
|
const panel = document.querySelector("#conversation-list");
|
|
const text = panel?.textContent ?? "";
|
|
samples.push({
|
|
activeSessionId: active?.getAttribute("data-session-id") ?? null,
|
|
loading: Boolean(document.querySelector("#conversation-list .conversation-detail-loading")),
|
|
hasA: text.includes("SESSION_A_ONLY"),
|
|
hasB: text.includes("SESSION_B_ONLY"),
|
|
textPreview: text.replace(/\s+/gu, " ").trim().slice(0, 160)
|
|
});
|
|
};
|
|
const loop = (): void => {
|
|
read();
|
|
if (window.__issue1536Sampling === true) requestAnimationFrame(loop);
|
|
};
|
|
window.__issue1536Samples = samples;
|
|
window.__issue1536Sampling = true;
|
|
requestAnimationFrame(loop);
|
|
});
|
|
|
|
await page.locator(sessionTab("ses_completed")).click();
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator("#conversation-list .conversation-detail-loading")).toBeVisible();
|
|
await saveScreenshot(page, testInfo, "session-switch-delayed-loading");
|
|
|
|
await expect(page.locator("#conversation-list .conversation-detail-loading")).toHaveCount(0);
|
|
await saveScreenshot(page, testInfo, "session-switch-delayed-first-non-loading");
|
|
await expect(page.locator(selectors.conversationList)).toContainText("SESSION_B_ONLY");
|
|
await saveScreenshot(page, testInfo, "session-switch-delayed-final");
|
|
|
|
const samples = await page.evaluate(() => {
|
|
window.__issue1536Sampling = false;
|
|
return window.__issue1536Samples ?? [];
|
|
}) as FrameSample[];
|
|
await testInfo.attach("session-switch-delayed-frame-samples.json", {
|
|
body: JSON.stringify(samples, null, 2),
|
|
contentType: "application/json"
|
|
});
|
|
const targetFrames = samples.filter((sample) => sample.activeSessionId === "ses_completed");
|
|
const leakedFrames = targetFrames.filter((sample) => sample.hasA);
|
|
const firstNonLoadingTarget = targetFrames.find((sample) => !sample.loading);
|
|
|
|
expect(leakedFrames, JSON.stringify(leakedFrames.slice(0, 5), null, 2)).toEqual([]);
|
|
expect(firstNonLoadingTarget?.hasB, JSON.stringify(firstNonLoadingTarget ?? null, null, 2)).toBe(true);
|
|
expect(samples.filter((sample) => sample.hasA && sample.hasB), "old and target messages must never share one frame").toEqual([]);
|
|
});
|
|
});
|
|
|
|
test.describe("empty session switch target", () => {
|
|
test.use({ scenarioId: "session-switch-empty-reload" });
|
|
|
|
test("switching from a filled session to an empty session updates URL and reload target", async ({ page }, testInfo) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_completed");
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`)).toBeVisible();
|
|
|
|
await page.locator(sessionTab("ses_empty")).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_empty$/u);
|
|
await expect(page.locator(sessionTab("ses_empty"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.messageCard)).toHaveCount(0);
|
|
await expect.poll(async () => (await fakeServerState(page)).legacyRequestLedger as unknown[]).toHaveLength(0);
|
|
|
|
await page.reload();
|
|
await expect(page.locator(sessionTab("ses_empty"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.messageCard)).toHaveCount(0);
|
|
await saveScreenshot(page, testInfo, "session-switch-empty-reload");
|
|
});
|
|
|
|
test("switching back to a running session reflects URL before reload", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`)).toBeVisible();
|
|
|
|
await page.locator(sessionTab("ses_empty")).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_empty/u);
|
|
await expect(page.locator(selectors.messageCard)).toHaveCount(0);
|
|
|
|
await page.locator(sessionTab("ses_running")).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_running/u);
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`)).toBeVisible();
|
|
|
|
await page.reload();
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`)).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("stale session detail isolation", () => {
|
|
test.use({ scenarioId: "session-switch-detail-404-isolated" });
|
|
|
|
test("session detail 404 keeps current rail usable and does not block later switches", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_completed");
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`)).toBeVisible();
|
|
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_stale_404");
|
|
await expect(page.locator(".conversation-error-hint")).toContainText(/session URL not found|unavailable/u);
|
|
await expect(page.locator(sessionTab("ses_completed"))).toHaveCount(1);
|
|
|
|
await page.locator(sessionTab("ses_empty")).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_empty/u);
|
|
await expect(page.locator(sessionTab("ses_empty"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.messageCard)).toHaveCount(0);
|
|
|
|
await page.reload();
|
|
await expect(page.locator(sessionTab("ses_empty"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.sessionTabs)).not.toHaveAttribute("data-loading", "true");
|
|
});
|
|
});
|