feat(workbench): integrate runtime data and map tooling
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import {
|
||||
createAnalysisRuntimeSnapshot,
|
||||
MOCK_NON_SPATIAL_ANALYSIS,
|
||||
MOCK_SPATIAL_ANALYSIS
|
||||
} from "./support/mock-analysis-runtime";
|
||||
import { mockScadaApi } from "./support/mock-scada-api";
|
||||
|
||||
test("the default workspace docks Agent beside the persistent map", async ({ page }) => {
|
||||
test("the workspace starts with Agent collapsed and docks it only when opened", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1600, height: 900 });
|
||||
await prepareMainFrame(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
@@ -12,34 +19,52 @@ test("the default workspace docks Agent beside the persistent map", async ({ pag
|
||||
|
||||
await expect(workspace).toHaveAttribute("data-layout-mode", "default");
|
||||
await expect(map).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 命令面板")).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 命令面板")).toHaveCount(0);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(0);
|
||||
await expect(map.getByRole("navigation", { name: "地图工具" })).toBeVisible();
|
||||
await expect(page.getByRole("region", { name: "工况任务", exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "任务视图" })).toHaveCount(0);
|
||||
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
await expect(panel).toBeVisible();
|
||||
await expectDockedWorkspace(page);
|
||||
await expect(map).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "折叠 Agent 面板" }).click();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(0);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.width).toBe(1600);
|
||||
});
|
||||
|
||||
test("a generated Artifact stays in peek until explicitly opened", async ({ page }) => {
|
||||
test("a persisted Artifact opens and can be reduced to a preview", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1600, height: 900 });
|
||||
await prepareMainFrame(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await page.getByRole("button", { name: "生成 Agent 模拟分析成果" }).click();
|
||||
const peek = page.getByRole("complementary", { name: "Agent 成果预览" });
|
||||
await openFirstArtifact(page);
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "map_split");
|
||||
await expect(page.getByRole("heading", { name: "北区爆管定位" })).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "将成果收起为预览" }).click();
|
||||
const peek = page.getByRole("complementary", { name: "后端分析成果预览" });
|
||||
await expect(peek).toBeVisible();
|
||||
await expect(peek).toContainText("北辰分区压力异常诊断");
|
||||
await expect(peek).toContainText("北区爆管定位");
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "default");
|
||||
await expect(page.getByRole("heading", { name: "北辰分区压力异常诊断" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "北区爆管定位" })).toHaveCount(0);
|
||||
|
||||
await peek.getByRole("button", { name: "打开成果" }).click();
|
||||
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(page.getByRole("heading", { name: "北辰分区压力异常诊断" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "北区爆管定位" })).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Agent expands over an active Artifact without resizing the main workspace", async ({ page }) => {
|
||||
test("Agent docks beside an active Artifact and restores split view when collapsed", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1600, height: 900 });
|
||||
await prepareMainFrame(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
@@ -48,14 +73,32 @@ test("Agent expands over an active Artifact without resizing the main workspace"
|
||||
const workspace = page.locator("#main-workspace");
|
||||
const before = await workspace.boundingBox();
|
||||
expect(before).not.toBeNull();
|
||||
await expect(workspace).toHaveAttribute("data-layout-mode", "map_split");
|
||||
|
||||
await page.getByLabel(/展开 Agent 助手面板/).click();
|
||||
await expect(page.getByLabel("Agent 命令面板")).toBeVisible();
|
||||
const after = await workspace.boundingBox();
|
||||
await expectDockedWorkspace(page);
|
||||
const panelBox = await page.getByLabel("Agent 命令面板").boundingBox();
|
||||
expect(panelBox).not.toBeNull();
|
||||
await expect
|
||||
.poll(async () => (await workspace.boundingBox())?.width)
|
||||
.toBe(before!.width - panelBox!.width);
|
||||
await expect(workspace).toHaveAttribute("data-layout-mode", "focus");
|
||||
await expect(page.locator("#workspace-map")).toBeHidden();
|
||||
const title = page.getByRole("heading", { name: "北区爆管定位" });
|
||||
await expect(title).toBeVisible();
|
||||
const titleBox = await title.boundingBox();
|
||||
expect(titleBox!.x).toBeGreaterThanOrEqual(panelBox!.x + panelBox!.width);
|
||||
|
||||
expect(after).not.toBeNull();
|
||||
expect(after!.x).toBe(before!.x);
|
||||
expect(after!.width).toBe(before!.width);
|
||||
await page.getByRole("button", { name: "折叠 Agent 面板" }).click();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(before!.x);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.width).toBe(before!.width);
|
||||
await expect(workspace).toHaveAttribute("data-layout-mode", "map_split");
|
||||
await expect(page.locator("#workspace-map")).toBeVisible();
|
||||
await expect(title).toBeVisible();
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await page.screenshot({ path: "test-results/workspace-regression/restored-split.png" });
|
||||
});
|
||||
|
||||
test("spatial Artifacts switch between split, focus and peek", async ({ page }) => {
|
||||
@@ -68,29 +111,27 @@ test("spatial Artifacts switch between split, focus and peek", async ({ page })
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "focus");
|
||||
await expect(page.locator("#workspace-map")).toBeHidden();
|
||||
|
||||
await page.getByRole("button", { name: "显示地图" }).click();
|
||||
await page.getByRole("button", { name: "半屏显示" }).click();
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "map_split");
|
||||
await expect(page.locator("#workspace-map")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "将成果收起为预览" }).click();
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "default");
|
||||
await expect(page.getByRole("complementary", { name: "Agent 成果预览" })).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 命令面板")).toBeVisible();
|
||||
await expect(page.getByRole("complementary", { name: "后端分析成果预览" })).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 命令面板")).toHaveCount(0);
|
||||
|
||||
await page.getByRole("button", { name: "销毁预览成果" }).click();
|
||||
await expect(page.getByRole("complementary", { name: "Agent 成果预览" })).toHaveCount(0);
|
||||
await expect(page.getByRole("complementary", { name: "后端分析成果预览" })).toHaveCount(0);
|
||||
await expect(page.locator("#workspace-map")).toBeVisible();
|
||||
});
|
||||
|
||||
test("non-spatial Artifacts open directly in focus mode", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1600, height: 900 });
|
||||
await prepareMainFrame(page);
|
||||
await prepareMainFrame(page, MOCK_NON_SPATIAL_ANALYSIS);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await page.getByRole("button", { name: "生成 Agent 模拟分析成果" }).click();
|
||||
await page.getByRole("button", { name: "销毁预览成果" }).click();
|
||||
await page.getByRole("button", { name: "生成 Agent 模拟分析成果" }).click();
|
||||
await page.getByRole("button", { name: "打开成果" }).click();
|
||||
await page.getByRole("button", { name: "打开最新后端分析成果" }).click();
|
||||
|
||||
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "focus");
|
||||
await expect(page.getByRole("heading", { name: "供水服务分区平衡分析" })).toBeVisible();
|
||||
@@ -126,11 +167,73 @@ test("the condition feed remains floating and starts closed", async ({ page }) =
|
||||
});
|
||||
|
||||
async function openFirstArtifact(page: Page) {
|
||||
await page.getByRole("button", { name: "生成 Agent 模拟分析成果" }).click();
|
||||
await page.getByRole("button", { name: "打开成果" }).click();
|
||||
await page.getByRole("button", { name: "打开最新后端分析成果" }).click();
|
||||
}
|
||||
|
||||
async function prepareMainFrame(page: Page) {
|
||||
test("header actions remain separate across compact and wide breakpoints", async ({ page }) => {
|
||||
await prepareMainFrame(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
for (const width of [1600, 1536, 1440, 1280, 1024, 375, 320, 1800, 1920]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
const titleSize = await page.locator("header h1").evaluate((element) => ({
|
||||
available: element.clientWidth,
|
||||
content: element.scrollWidth
|
||||
}));
|
||||
expect(
|
||||
titleSize.content,
|
||||
`${width}px: header title ${JSON.stringify(titleSize)}`
|
||||
).toBeLessThanOrEqual(titleSize.available);
|
||||
const buttons = await page
|
||||
.locator("header button")
|
||||
.filter({ visible: true })
|
||||
.evaluateAll((elements) =>
|
||||
elements
|
||||
.map((element) => {
|
||||
const box = element.getBoundingClientRect();
|
||||
return {
|
||||
name: element.getAttribute("aria-label") ?? element.textContent,
|
||||
left: box.left,
|
||||
right: box.right
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.left - b.left)
|
||||
);
|
||||
for (let index = 0; index < buttons.length; index += 1) {
|
||||
const button = buttons[index];
|
||||
expect(button.left, `${width}px: ${button.name} left edge`).toBeGreaterThanOrEqual(0);
|
||||
expect(button.right, `${width}px: ${button.name} right edge`).toBeLessThanOrEqual(width);
|
||||
if (index)
|
||||
expect(
|
||||
button.left,
|
||||
`${width}px: ${buttons[index - 1].name} overlaps ${button.name}`
|
||||
).toBeGreaterThanOrEqual(buttons[index - 1].right);
|
||||
}
|
||||
await page
|
||||
.locator("header")
|
||||
.screenshot({ path: `test-results/workspace-regression/${width}-header.png` });
|
||||
}
|
||||
});
|
||||
|
||||
async function expectDockedWorkspace(page: Page) {
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => {
|
||||
const panel = document
|
||||
.querySelector('aside[aria-label="Agent 命令面板"]')!
|
||||
.getBoundingClientRect();
|
||||
const workspace = document.querySelector("#main-workspace")!.getBoundingClientRect();
|
||||
return {
|
||||
panelLeft: panel.left,
|
||||
gap: workspace.left - panel.right,
|
||||
combinedWidth: panel.width + workspace.width
|
||||
};
|
||||
})
|
||||
)
|
||||
.toEqual({ panelLeft: 0, gap: 0, combinedWidth: page.viewportSize()!.width });
|
||||
}
|
||||
|
||||
async function prepareMainFrame(page: Page, latestAnalysis: unknown = MOCK_SPATIAL_ANALYSIS) {
|
||||
await mockAgentApi(page);
|
||||
await page.route("**/runtime-config.js", async (route) => {
|
||||
await route.fulfill({
|
||||
@@ -150,5 +253,5 @@ async function prepareMainFrame(page: Page) {
|
||||
await page.route("https://workspace-map.invalid/**", async (route) => {
|
||||
await route.fulfill({ status: 204 });
|
||||
});
|
||||
await mockScadaApi(page);
|
||||
await mockScadaApi(page, [], createAnalysisRuntimeSnapshot(latestAnalysis));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user