import { expect, test } from "@playwright/test"; import { mockWorkbenchContext } from "./support/mock-workbench-context"; const TODAY = shanghaiTimestamp(10, 30); const TODAY_10_10 = shanghaiTimestamp(10, 10); const TODAY_10_20 = shanghaiTimestamp(10, 20); const TODAY_10_25 = shanghaiTimestamp(10, 25); const TODAY_10_35 = shanghaiTimestamp(10, 35); const TODAY_11_00 = shanghaiTimestamp(11, 0); const YESTERDAY_10_10 = shanghaiTimestamp(10, 10, -1); const YESTERDAY_10_20 = shanghaiTimestamp(10, 20, -1); test("history mode opens on the latest day with backend work orders", async ({ page }) => { await mockWorkbenchContext(page, { availability: "ready", source: "scheduled_tasks", scenario_availability: "not_configured", artifact_availability: "not_configured", schedule_availability: "ready", work_order_availability: "ready", alert_availability: "ready", generated_at: "2026-09-18T02:30:00Z", conditions: [{ id: "backend-schedule-demo-day-next", task_id: "backend-schedule", session_id: "scheduled-condition-backend-schedule-demo-day", title: "历史 SCADA 诊断计划", summary: "来自后端持久化的目标明确的诊断计划。", status: "running", risk_level: "normal", scheduled_at: "2026-08-17T09:40:00Z", updated_at: "2026-08-17T09:40:00Z", detail: "按计划复核 SCADA 缺测与越界记录。", recommendation: null, evidence: ["调度 ID:demo-day"], model_name: "TJWater scheduler" }], alerts: [], scenarios: [], latest_analysis: null, work_orders: [{ id: "wo-history-1", code: "WO-20260817-001", scheduled_at: "2026-08-17T09:40:00Z", title: "历史工单:阀门复核", summary: "来自后端工单投影的历史预约任务。", status: "pending", risk_level: "normal", updated_at: "2026-08-17T09:35:00Z", detail: "复核阀门状态并回传结果。", recommendation: null, duration_minutes: 30, source: "工单系统", dispatcher: "调度中心", priority: "normal", location: "北区阀门 V-17", assignee: "北区班组", feedback_requirements: ["回传阀门状态"], activity: [] }] }, undefined, {}, [], { runtimeMode: "history" }); await page.goto("/", { waitUntil: "domcontentloaded" }); await expect(page.getByText("历史 2026-08-17 17:40", { exact: true })).toBeVisible(); await expect(page.getByRole("region", { name: "运行时间轴" })).toContainText("2026.08.17"); await page.getByRole("button", { name: "工况任务", exact: true }).click(); const panel = page.getByRole("region", { name: "工况任务", exact: true }); await expect(panel).toContainText("SCADA 数据诊断"); await panel.getByRole("button", { name: "展开工况任务" }).click(); await panel.getByRole("button", { name: /预约任务/ }).click(); await expect(panel).toContainText("历史工单:阀门复核"); await expect(panel).not.toContainText("历史 SCADA 诊断计划"); }); test("keeps mock conditions while retaining explicit backend alerts", async ({ page }) => { await mockWorkbenchContext(page, { availability: "ready", source: "scheduled_tasks", scenario_availability: "not_configured", artifact_availability: "not_configured", schedule_availability: "not_configured", work_order_availability: "not_configured", alert_availability: "ready", generated_at: TODAY, conditions: [{ id: "schedule-run-test-1", task_id: "backend-schedule", session_id: "scheduled-condition-schedule-run-test-1", title: "演示 合成低压调查", summary: "来自后端持久化 Action Run 的 演示 沙箱测试状态。", status: "error", risk_level: "critical", scheduled_at: TODAY_10_20, updated_at: TODAY_10_25, detail: "演示 测试运行状态:已失败。", recommendation: "请在 演示 调查任务中检查失败步骤与运行日志。", evidence: ["运行状态:failed"], model_name: "TJWater scheduler" }], alerts: [{ id: "scheduled-condition-alert-schedule-run-test-1", condition_id: "schedule-run-test-1", source: { task_type: "analysis_task", task_id: "backend-schedule", run_id: "schedule-run-test-1" }, title: "演示 测试待复核:演示 合成低压调查", description: "运行失败。演示 测试运行状态:已失败。", occurred_at: TODAY_10_25, severity: "critical" }], scenarios: [], latest_analysis: null, work_orders: [] }); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "工况任务", exact: true }).click(); const panel = page.getByRole("region", { name: "工况任务", exact: true }); await expect(panel).toContainText("SCADA 数据诊断"); await expect(panel).not.toContainText("演示 合成低压调查"); const alertButton = page.getByRole("button", { name: "查看异常处置面板,当前 1 条待复核工况" }); await alertButton.focus(); await page.keyboard.press("Enter"); await expect(page.getByText("演示 测试待复核:演示 合成低压调查")).toBeVisible(); }); test("opens the latest persisted analysis as the active visualization", async ({ page }) => { const latestAnalysis = { run_id: "analysis-1", name: "北区爆管定位", run_type: "burst_location", run_type_label: "爆管定位", status: "completed", status_label: "已完成", created_by: "alice", created_at: TODAY_10_20, started_at: TODAY_10_10, results: [{ result_type: "burst_location", created_at: TODAY_10_20, facts: [{ label: "关联管段", value: "P-42" }] }], map_payload: { type: "FeatureCollection", features: [{ type: "Feature", properties: { label: "后端爆管定位" }, geometry: { type: "Point", coordinates: [121.5, 30.9] } }] } }; const historicalAnalysis = { ...latestAnalysis, run_id: "analysis-2", name: "南区历史定位", created_at: YESTERDAY_10_20, started_at: YESTERDAY_10_10, results: [{ result_type: "burst_location", created_at: YESTERDAY_10_20, facts: [{ label: "关联管段", value: "P-older" }] }], map_payload: { type: "FeatureCollection", features: [{ type: "Feature", properties: { label: "历史运行几何" }, geometry: { type: "Point", coordinates: [121.4, 30.8] } }] } }; await mockWorkbenchContext(page, { availability: "ready", source: "analysis_runs", scenario_availability: "ready", artifact_availability: "ready", schedule_availability: "ready", work_order_availability: "not_configured", alert_availability: "not_configured", generated_at: TODAY, conditions: [{ id: "analysis-run-analysis-1", task_id: "backend-analysis", session_id: "scheduled-condition-analysis-run-analysis-1", title: "北区爆管定位", summary: "来自项目后端持久化分析运行:爆管定位。", status: "completed", risk_level: "normal", scheduled_at: TODAY_10_10, updated_at: TODAY_10_20, detail: "分析类型:爆管定位;后端状态:已完成。", recommendation: null, evidence: ["运行 ID:analysis-1"], model_name: "TJWater analysis runtime" }, { id: "analysis-run-analysis-2", task_id: "backend-analysis", session_id: "scheduled-condition-analysis-run-analysis-2", title: "南区历史定位", summary: "来自项目后端持久化分析运行:爆管定位。", status: "completed", risk_level: "normal", scheduled_at: YESTERDAY_10_10, updated_at: YESTERDAY_10_20, detail: "分析类型:爆管定位;后端状态:已完成。", recommendation: null, evidence: ["运行 ID:analysis-2"], model_name: "TJWater analysis runtime" }], alerts: [], scenarios: [ { id: "analysis-run-analysis-1", run_id: "analysis-1", name: "北区爆管定位", description: "爆管定位 · 已完成 · 创建人 alice", status: "review", run_type: "burst_location", run_type_label: "爆管定位", run_status: "completed", created_at: TODAY_10_20 }, { id: "analysis-run-analysis-2", run_id: "analysis-2", name: "南区历史定位", description: "爆管定位 · 已完成 · 创建人 alice", status: "review", run_type: "burst_location", run_type_label: "爆管定位", run_status: "completed", created_at: YESTERDAY_10_20 } ], latest_analysis: latestAnalysis, work_orders: [] }, undefined, { "analysis-1": latestAnalysis, "analysis-2": historicalAnalysis }, [{ run_id: "analysis-1", name: "北区爆管定位", run_type: "burst_location", run_type_label: "爆管定位", status: "completed", status_label: "已完成", created_by: "alice", created_at: TODAY_10_20, started_at: TODAY_10_10, has_results: true }, { run_id: "analysis-2", name: "南区历史定位", run_type: "burst_location", run_type_label: "爆管定位", status: "completed", status_label: "已完成", created_by: "alice", created_at: YESTERDAY_10_20, started_at: YESTERDAY_10_10, has_results: true }, ...Array.from({ length: 9 }, (_, index) => ({ run_id: `analysis-${index + 3}`, name: `批次分析 ${index + 3}`, run_type: "pressure_analysis", run_type_label: "压力分析", status: "completed", status_label: "已完成", created_by: "scheduler", created_at: YESTERDAY_10_20, started_at: YESTERDAY_10_10, has_results: index % 2 === 0 }))]); await page.goto("/", { waitUntil: "domcontentloaded" }); await expect(page.getByRole("button", { name: "切换模拟方案" })).toHaveCount(0); await expect(page.getByRole("button", { name: /当前分析视图/ })).toHaveCount(0); await page.getByRole("button", { name: "工况任务", exact: true }).click(); const conditionPanel = page.getByRole("region", { name: "工况任务", exact: true }); await expect(conditionPanel).not.toContainText("北区爆管定位"); await expect(conditionPanel).not.toContainText("南区历史定位"); await expect(conditionPanel).toContainText("SCADA 数据诊断"); await page.getByRole("button", { name: "工况任务", exact: true }).click(); await expect(page.getByRole("region", { name: "工况任务", exact: true })).toHaveCount(0); await page.getByRole("button", { name: "打开最新后端分析成果" }).click(); await expect(page.getByRole("complementary", { name: "后端分析成果预览" })).toHaveCount(0); await expect(page.getByRole("heading", { name: "北区爆管定位" })).toBeVisible(); await expect(page.getByText("Backend Analysis")).toBeVisible(); await expect(page.getByText("关联管段:P-42")).toBeVisible(); const analysisViewButton = page.getByRole("button", { name: "当前分析视图:北区爆管定位" }); await expect(analysisViewButton).toContainText("分析视图 · 北区爆管定位"); await expect.poll(() => page.evaluate(() => window.__waterNetworkMap ?.querySourceFeatures("simulation-backend-analysis") .map((feature) => feature.properties?.label) ?? [] )).toContain("后端爆管定位"); await analysisViewButton.click(); await expect(page.getByText(/爆管定位 · 项目后端分析运行 · 后端分析运行/)).toBeVisible(); await page.getByText("关闭分析视图", { exact: true }).click(); await expect(page.getByRole("button", { name: /当前分析视图/ })).toHaveCount(0); await expect.poll(() => page.evaluate(() => window.__waterNetworkMap ?.querySourceFeatures("simulation-backend-analysis").length ?? 0 )).toBe(0); await page.getByRole("button", { name: "项目分析成果" }).click(); const resultsPanel = page.getByRole("complementary", { name: "项目分析成果" }); await resultsPanel.getByText("浏览项目分析成果", { exact: true }).click(); await expect(resultsPanel.getByRole("button", { name: "打开分析成果:北区爆管定位" })).toBeVisible(); await expect(resultsPanel.getByRole("button", { name: "打开分析成果:批次分析 11" })).toHaveCount(0); await resultsPanel.getByRole("button", { name: "加载更多成果" }).click(); await expect(resultsPanel.getByRole("button", { name: "打开分析成果:批次分析 11" })).toBeVisible(); await resultsPanel.getByRole("button", { name: "打开分析成果:南区历史定位" }).click(); await expect(resultsPanel).toHaveCount(0); await expect(page.getByRole("heading", { name: "南区历史定位" })).toBeVisible(); await expect(page.getByRole("button", { name: "当前分析视图:南区历史定位" })).toBeVisible(); await expect.poll(() => page.evaluate(() => window.__waterNetworkMap ?.querySourceFeatures("simulation-backend-analysis") .map((feature) => feature.properties?.label) ?? [] )).toContain("历史运行几何"); }); test("keeps a non-spatial backend result in the analysis workspace without demo geometry", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockWorkbenchContext(page, { availability: "ready", source: "analysis_runs", scenario_availability: "ready", artifact_availability: "ready", schedule_availability: "not_configured", work_order_availability: "not_configured", alert_availability: "not_configured", generated_at: TODAY, conditions: [], alerts: [], scenarios: [], latest_analysis: { run_id: "analysis-table-only", name: "压力统计结果", run_type: "pressure_analysis", run_type_label: "压力分析", status: "completed", status_label: "已完成", created_by: "alice", created_at: TODAY_10_20, started_at: TODAY_10_10, results: [{ result_type: "pressure_analysis", created_at: TODAY_10_20, facts: [{ label: "结果状态", value: "已完成" }] }], map_payload: null }, work_orders: [] }); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "打开最新后端分析成果" }).click(); await expect(page.getByRole("heading", { name: "压力统计结果" })).toBeVisible(); await page.getByRole("button", { name: "当前分析视图:压力统计结果" }).click(); await expect(page.getByText("仅看地图", { exact: true })).toHaveCount(0); await page.getByRole("menuitem", { name: /半屏显示/ }).click(); await expect(page.getByRole("menu")).toHaveCount(0); await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "map_split"); await expect(page.locator("#workspace-map")).toBeVisible(); await expect(page.locator("#artifact-workspace")).toBeVisible(); await expect.poll(() => page.evaluate(() => ({ backend: window.__waterNetworkMap ?.querySourceFeatures("simulation-backend-analysis").length ?? 0, demo: window.__waterNetworkMap ?.querySourceFeatures("simulation-impact-area").length ?? 0 }))).toEqual({ backend: 0, demo: 0 }); }); test("keeps mock conditions while using backend work orders and alerts", async ({ page }) => { await mockWorkbenchContext(page, { availability: "ready", source: "scheduled_tasks", scenario_availability: "not_configured", artifact_availability: "not_configured", schedule_availability: "ready", work_order_availability: "ready", alert_availability: "ready", generated_at: TODAY, conditions: [{ id: "backend-schedule-diagnosis-next", task_id: "backend-schedule", session_id: "scheduled-condition-backend-schedule-diagnosis", title: "SCADA 后端定时诊断", summary: "来自项目数据库的持久化调度定义。", status: "pending", risk_level: "normal", scheduled_at: TODAY_10_35, updated_at: TODAY, detail: "每 5 分钟触发一次。", recommendation: null, evidence: ["调度 ID:diagnosis-5m"], model_name: "TJWater scheduler" }], alerts: [{ id: "backend-alert-alarm-1", condition_id: null, source: { task_type: "scheduled_task", task_id: "diagnosis-5m", run_id: "trigger-1" }, title: "后端告警:泵站通信中断", description: "由诊断任务明确推送到项目数据库。", occurred_at: TODAY, severity: "warning" }], scenarios: [], latest_analysis: null, work_orders: [{ id: "wo-backend-1", code: "WO-20260917-001", scheduled_at: TODAY_11_00, title: "后端工单:北区阀门复核", summary: "现场复核阀门开度并回传。", status: "running", risk_level: "attention", updated_at: TODAY, detail: "来自正式工单同步适配器。", recommendation: "完成后复核压力。", duration_minutes: 45, source: "生产工单系统", dispatcher: "调度中心", priority: "urgent", location: "北区阀门 V-1", assignee: "北区班组", feedback_requirements: ["回传阀门开度"], activity: [{ sequence: 1, occurred_at: TODAY, actor: "调度中心", kind: "created", title: "工单创建", detail: "现场复核阀门开度并回传。" }] }] }); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "工况任务", exact: true }).click(); const panel = page.getByRole("region", { name: "工况任务", exact: true }); await expect(panel).toContainText("SCADA 数据诊断"); await expect(panel).not.toContainText("SCADA 后端定时诊断"); await panel.getByRole("button", { name: "展开工况任务" }).click(); await panel.getByRole("button", { name: /预约任务/ }).click(); await expect(panel).toContainText("后端工单:北区阀门复核"); await expect(panel).not.toContainText("工单演示回退"); await panel.getByRole("button", { name: /后端工单:北区阀门复核/ }).click(); const detail = panel.getByTestId("scheduled-condition-detail-scroll"); await expect(detail.getByText("工单报告", { exact: true })).toBeVisible(); await expect(detail.getByText("回传阀门开度", { exact: true })).toBeVisible(); await expect(detail.getByText("活动记录", { exact: true })).toHaveCount(0); await expect(detail.locator("details")).toHaveCount(0); const alertButton = page.getByRole("button", { name: "查看异常处置面板,当前 1 条待复核工况" }); await alertButton.focus(); await page.keyboard.press("Enter"); await expect(page.getByText("来源:后端运行态", { exact: true })).toBeVisible(); await expect(page.getByText("后端告警:泵站通信中断")).toBeVisible(); }); function shanghaiTimestamp(hour: number, minute: number, dayOffset = 0) { const target = new Date(Date.now() + dayOffset * 24 * 60 * 60 * 1000); const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(target); const get = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value ?? ""; return `${get("year")}-${get("month")}-${get("day")}T${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:00+08:00`; }