Files
next-tjwater-frontend/tests/browser/workbench-runtime-backend.e2e.ts
T

485 lines
19 KiB
TypeScript
Raw 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 } 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`;
}