Files
next-tjwater-frontend/tests/browser/investigation-retention-playback.e2e.ts
T
jiang 03827a2fc0
Generic Container CI/CD / test-build-publish (push) Successful in 1m12s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m13s
feat(workbench): group live presentation steps into expandable categories
Group adjacent live display steps into category menus with merged snapshots, original-step submenus and scoped playback.

The submenu exposes the latest raw step, so explicit selections must remain pinned. Locate failed and cancelled steps using the full group sequence, and prevent an exiting menu from dismissing a trigger-owned reopen. Regression tests reproduce these paths before the fixes.

Validation: lint, typecheck, 1178 unit tests and 10 focused browser cases passed; update the presentation history guide.
2026-11-12 12:54:09 +08:00

158 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from "@playwright/test";
import { MAP_STYLE_TOKENS } from "../../src/features/workbench/map/map-colors";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap, showInvestigationTasks } from "./support/mock-investigation-presentation";
const chart = (index: number) => ({ action: "open_chart", chart_id: "pressure-history", evidence_refs: ["synthetic:pressure"], chart: {
title: `压力对比 ${index}`, chart_type: "line", x_data: ["10:00", "10:15"], series: [{ name: "压力(m)", data: [30, 30 - index] }]
} });
const overlay = { action: "set_overlay", overlay_id: "warning-area", title: "合成影响示意", mode: "geometry", evidence_id: "synthetic:area", data: {
type: "FeatureCollection", features: [{ type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [[[121.48,31.1],[121.49,31.1],[121.49,31.11],[121.48,31.1]]] } }]
} };
async function closeChartWindows(page: Page) {
const closeWindow = page.getByRole("dialog").getByRole("button", { name: "关闭窗口", exact: true });
while (await closeWindow.count()) await closeWindow.first().click();
}
test("successful charts and warning geometry survive stream loss and failed reconnect", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "断线保留展示核查");
const other = investigationFixture(2, "切换后独立调查");
const mock = await mockInvestigationPresentation(page, [task, other]);
await selectInvestigation(page, task.title);
await showInvestigationMap(page);
const panel = page.getByLabel("调查实时展示", { exact: true });
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
await mock.emit(task, chart(1));
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
await mock.emit(task, overlay);
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("合成影响示意");
const geometry = () => page.evaluate(async () => {
const source = window.__waterNetworkMap?.getSource("investigation-presentation-overlay") as { getData: () => Promise<unknown> } | undefined;
return source?.getData();
});
const saved = await geometry();
expect(await page.evaluate(() => window.__waterNetworkMap?.getPaintProperty("investigation-presentation-overlay-fill", "fill-color"))).toBe(MAP_STYLE_TOKENS.state.risk);
let failCursor = true;
await page.route(`**/operational-tasks/${task.task_id}/presentation-cursor`, (route) => failCursor ? route.abort("failed") : route.fallback());
const connection = (available: boolean) => page.evaluate((available) => {
(window as unknown as { setInvestigationTestConnection: (available: boolean) => void }).setInvestigationTestConnection(available);
}, available);
await connection(false);
await expect(panel.getByText("连接中断", { exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
expect(await geometry()).toEqual(saved);
await connection(true);
await expect(panel.getByText("部分未完成", { exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
expect(await geometry()).toEqual(saved);
await testInfo.attach("results-retained-offline", { body: await page.screenshot({ path: testInfo.outputPath("results-retained-offline.png") }), contentType: "image/png" });
failCursor = false;
await connection(false);
await expect(panel.getByText("连接中断", { exact: true })).toBeVisible();
await connection(true);
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
expect(await geometry()).toEqual(saved);
await closeChartWindows(page);
await showInvestigationTasks(page);
await page.getByRole("region", { name: "工况任务", exact: true }).getByText(other.title, { exact: true }).first().click();
await showInvestigationMap(page);
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toHaveCount(0);
await expect.poll(geometry).toEqual({ type: "FeatureCollection", features: [] });
expect(mock.mutations).toEqual([]);
});
test("history playback plays adjacent groups, pauses and seeks merged snapshots while current receives new results", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "只读历史播放核查");
const groupedChart = (index: number) => ({ ...chart(index), chart_id: `pressure-${index}` });
const saved = [groupedChart(1), groupedChart(2), overlay, groupedChart(3), groupedChart(4)].map((command, index) => ({
task_id: task.task_id, event_seq: index + 1, run_id: task.active_run!.run_id,
execution_generation: 1, occurred_at: task.created_at, command }));
const mock = await mockInvestigationPresentation(page, [task], saved);
await selectInvestigation(page, task.title, "keyboard");
await showInvestigationMap(page);
const panel = page.getByLabel("调查实时展示", { exact: true });
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toBeVisible();
await closeChartWindows(page);
await panel.getByRole("button", { name: "播放历史", exact: true }).click();
await panel.getByRole("button", { name: "暂停历史播放", exact: true }).click();
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toHaveCount(0);
const slider = panel.getByRole("slider", { name: "历史播放进度" });
await expect(slider).toHaveAttribute("max", "2");
await expect(slider).toHaveValue("0");
await expect(panel.getByLabel("历史播放位置")).toHaveText("1/3");
await closeChartWindows(page);
await panel.getByRole("combobox", { name: "历史播放速度" }).click();
await page.getByRole("option", { name: "2×", exact: true }).click();
await panel.getByRole("button", { name: "播放历史", exact: true }).click();
await mock.emit(task, groupedChart(5));
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toBeVisible();
await expect(panel.getByRole("button", { name: "播放历史", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 5", exact: true })).toHaveCount(0);
await expect(panel.getByLabel("历史播放位置")).toHaveText("3/3");
await slider.focus();
await page.keyboard.press("ArrowLeft");
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("合成影响示意");
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toHaveCount(0);
await expect(panel.getByLabel("历史播放位置")).toHaveText("2/3");
await page.keyboard.press("Home");
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
await expect(panel.getByLabel("历史播放位置")).toHaveText("1/3");
await closeChartWindows(page);
await testInfo.attach("history-playback-desktop", { body: await panel.screenshot({ path: testInfo.outputPath("history-playback-desktop.png") }), contentType: "image/png" });
await page.setViewportSize({ width: 375, height: 720 });
await showInvestigationMap(page);
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toBeVisible();
expect(await panel.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
await testInfo.attach("history-playback-narrow", { body: await panel.screenshot({ path: testInfo.outputPath("history-playback-narrow.png") }), contentType: "image/png" });
await closeChartWindows(page);
await panel.getByRole("button", { name: "下一个动作", exact: true }).click();
await expect(panel.getByLabel("历史播放位置")).toHaveText("2/3");
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("合成影响示意");
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
await expect(panel.getByRole("group", { name: "压力对比 5", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});
test("restores a persisted chart when the stream disconnects during display confirmation", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "图表确认阶段断线恢复");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await showInvestigationMap(page);
const panel = page.getByLabel("调查实时展示", { exact: true });
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
await mock.emit(task, chart(1));
await expect(panel.getByRole("button", { name: "切换展示动作" })).toContainText("当前(最新)");
await page.evaluate(() => {
const observer = new MutationObserver(() => {
const plot = document.querySelector('[data-presentation-scope="investigation"] [data-investigation-chart="pressure-history"]');
if (!plot?.textContent?.includes("压力对比 2")) return;
observer.disconnect();
Object.assign(window, { interruptedChartConfirmation: true });
(window as unknown as { setInvestigationTestConnection: (available: boolean) => void }).setInvestigationTestConnection(false);
});
observer.observe(document.body, { childList: true, characterData: true, subtree: true });
});
await mock.emit(task, chart(2));
await expect.poll(() => page.evaluate(() => (window as unknown as { interruptedChartConfirmation?: boolean }).interruptedChartConfirmation)).toBe(true);
await expect(panel.getByText("连接中断", { exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
await page.evaluate(() => {
(window as unknown as { setInvestigationTestConnection: (available: boolean) => void }).setInvestigationTestConnection(true);
});
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "0");
await testInfo.attach("chart-recovered-after-interrupted-confirmation", {
body: await panel.screenshot({ path: testInfo.outputPath("chart-recovered-after-interrupted-confirmation.png") }), contentType: "image/png",
});
expect(mock.mutations).toEqual([]);
});