feat(workbench): integrate runtime data and map tooling

This commit is contained in:
2026-09-22 10:45:46 +08:00
parent bf44cc0f25
commit bebcf2a06c
163 changed files with 15642 additions and 1312 deletions
@@ -1,8 +1,9 @@
import { expect, test, type Locator } from "@playwright/test";
import { expect, test, type Locator, type Page } from "@playwright/test";
import { mockHierarchicalWorkOrder } from "./support/mock-hierarchical-work-order";
test("scheduled conditions keep acrylic blur outside transformed ancestors", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await openConditionFeed(page);
const panel = page.getByRole("region", { name: "工况任务", exact: true });
await expect(panel).toBeVisible();
@@ -75,14 +76,12 @@ test("scheduled conditions keep acrylic blur outside transformed ancestors", asy
test("narrow desktop keeps Agent and expanded conditions from overlapping", async ({ page }) => {
await page.setViewportSize({ width: 1024, height: 768 });
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
await openConditionFeed(page);
const panel = page.getByRole("region", { name: "工况任务", exact: true });
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]');
const resizeHandle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
await resizeHandle.focus();
await page.keyboard.press("End");
await expect(agentPanel).toHaveCSS("width", "484px");
await expect(agentPanel).toHaveCSS("width", "500px");
const compactAgentBox = await agentPanel.boundingBox();
const compactConditionBox = await panel.boundingBox();
@@ -112,7 +111,7 @@ test("narrow desktop keeps Agent and expanded conditions from overlapping", asyn
test("switching conditions keeps the detail viewport fully expanded", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await openConditionFeed(page);
const panel = page.getByRole("region", { name: "工况任务", exact: true });
await panel.getByRole("button", { name: "展开工况任务" }).click();
@@ -161,7 +160,7 @@ test("switching conditions keeps the detail viewport fully expanded", async ({ p
.getByRole("button", { name: /SCADA 数据诊断/ })
.first()
.click();
const workflowTitle = panel.getByText("SCADA 诊断流程", { exact: true });
const workflowTitle = panel.getByText("演示执行流程 · SCADA 诊断流程", { exact: true });
await expect(workflowTitle).toBeVisible();
await expect(workflowTitle.locator("..").locator("svg")).toHaveCount(0);
});
@@ -169,8 +168,9 @@ test("switching conditions keeps the detail viewport fully expanded", async ({ p
test("keeps execution spinners without breathing badges after selecting timeline tasks", async ({
page
}) => {
await mockHierarchicalWorkOrder(page, "execution");
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await openConditionFeed(page);
const panel = page.getByRole("region", { name: "工况任务", exact: true });
await panel.getByRole("button", { name: "展开工况任务" }).click();
@@ -182,14 +182,14 @@ test("keeps execution spinners without breathing badges after selecting timeline
.first();
const executingWorkOrder = timeline
.locator('button[aria-pressed]')
.filter({ hasText: "系统工单:北辰分区阀门开度调整" });
.filter({ hasText: "演示工单:北辰分区阀门开度调整" });
await expectRunningStatusAnimations(runningCondition);
await runningCondition.click();
await expectRunningStatusAnimations(runningCondition);
await expect(
panel
.getByText("SCADA 诊断流程", { exact: true })
.getByText("演示执行流程 · SCADA 诊断流程", { exact: true })
.locator("..")
.locator(".status-badge .status-dot")
).toHaveCount(0);
@@ -198,18 +198,26 @@ test("keeps execution spinners without breathing badges after selecting timeline
await expect(executingWorkOrder.getByText("执行中", { exact: true })).toBeVisible();
await executingWorkOrder.click();
await expectRunningStatusAnimations(executingWorkOrder);
const detail = panel.getByTestId("scheduled-condition-detail-scroll");
await expect(detail.getByText("子任务", { exact: true })).toBeVisible();
await expect(detail.getByText("1/3 完成", { exact: true })).toBeVisible();
await expect(detail.getByText(/上传阀门操作前后照片/).first()).toBeVisible();
const activeSubtask = detail.locator("details").filter({ hasText: "记录最终开度" });
await expect(activeSubtask).toHaveAttribute("open", "");
await expect(activeSubtask.getByText("执行中", { exact: true })).toBeVisible();
});
test("shows completed field work as a static waiting-for-reply state", async ({ page }) => {
await mockHierarchicalWorkOrder(page, "reply");
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await openConditionFeed(page);
const panel = page.getByRole("region", { name: "工况任务", exact: true });
await panel.getByRole("button", { name: "展开工况任务" }).click();
const awaitingReplyWorkOrder = panel
.getByTestId("scheduled-condition-timeline")
.locator('button[aria-pressed]')
.filter({ hasText: "系统工单:泵站边界复核复令" });
.filter({ hasText: "演示工单:泵站边界复核复令" });
await expect(awaitingReplyWorkOrder.getByText("待复令", { exact: true })).toBeVisible();
await expect(awaitingReplyWorkOrder.locator(".status-icon")).toHaveAttribute(
@@ -225,7 +233,8 @@ test("shows completed field work as a static waiting-for-reply state", async ({
);
await awaitingReplyWorkOrder.click();
await expect(panel.getByText("当前阶段:待复令", { exact: true })).toBeVisible();
await expect(panel.getByText("2/3 完成", { exact: true })).toBeVisible();
await expect(panel.getByText("确认模型边界已同步更新 · 待复令", { exact: true })).toBeVisible();
await expect(
panel
.getByTestId("scheduled-condition-detail-scroll")
@@ -253,7 +262,19 @@ async function readScrollbarPresentation(locator: Locator) {
});
}
async function openConditionFeed(page: Page) {
const button = page.getByRole("button", { name: "工况任务", exact: true });
await button.focus();
await page.keyboard.press("Enter");
}
async function expectAcrylicComposition(panel: Locator) {
await expect.poll(() => panel.evaluate((element) => getComputedStyle(element).transform)).toBe(
"none"
);
await expect.poll(() => panel.evaluate((element) => getComputedStyle(element).willChange)).toBe(
"auto"
);
const composition = await panel.evaluate((element) => {
const rootStyle = getComputedStyle(element);
const blockingAncestors: string[] = [];