feat: add adaptive agent workspace windows
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
|
||||
const EMPTY_RASTER_TILE = Buffer.from(
|
||||
@@ -6,24 +6,19 @@ const EMPTY_RASTER_TILE = Buffer.from(
|
||||
"base64"
|
||||
);
|
||||
|
||||
test.describe("neutral blue mist workbench", () => {
|
||||
test.describe("Main Frame workbench", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.clock.setFixedTime(new Date("2026-07-21T10:40:00+08:00"));
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await prepareDeterministicWorkbench(page);
|
||||
});
|
||||
|
||||
test("desktop light basemap keeps both floating panels legible", async ({ page }) => {
|
||||
test("desktop light basemap keeps the Main Frame workspace legible", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await openWorkbench(page);
|
||||
|
||||
await expectDesktopFloatingGeometry(page);
|
||||
await expectAcrylicAlphas(page, {
|
||||
navigation: 0.64,
|
||||
agentPanel: 0.82,
|
||||
conditionPanel: 0.56,
|
||||
control: 0.74
|
||||
});
|
||||
await expectDesktopMainFrameGeometry(page);
|
||||
await expectSolidMainFrameMaterials(page);
|
||||
await expectLoadedChineseFont(page);
|
||||
await expectBodyContrast(page);
|
||||
await expect(page).toHaveScreenshot("workbench-desktop-light.png", {
|
||||
@@ -32,42 +27,35 @@ test.describe("neutral blue mist workbench", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("desktop satellite basemap strengthens the same floating hierarchy", async ({ page }) => {
|
||||
test("desktop satellite basemap preserves the solid Main Frame hierarchy", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await openWorkbench(page);
|
||||
await selectBasemap(page, "影像");
|
||||
|
||||
await expect(page.locator("main")).toHaveAttribute("data-basemap-tone", "satellite");
|
||||
await expectDesktopFloatingGeometry(page);
|
||||
await expectAcrylicAlphas(page, {
|
||||
navigation: 0.8,
|
||||
agentPanel: 0.88,
|
||||
conditionPanel: 0.72,
|
||||
control: 0.86
|
||||
});
|
||||
await expectDesktopMainFrameGeometry(page);
|
||||
await expectSolidMainFrameMaterials(page);
|
||||
await expect(page).toHaveScreenshot("workbench-desktop-satellite.png", {
|
||||
animations: "disabled",
|
||||
maxDiffPixelRatio: 0.01
|
||||
});
|
||||
});
|
||||
|
||||
test("wide desktop keeps a map corridor at maximum Agent width", async ({ page }) => {
|
||||
test("wide desktop expands the map document when navigation is collapsed", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await openWorkbench(page);
|
||||
|
||||
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]');
|
||||
const conditionPanel = page.getByRole("region", { name: "工况任务", exact: true });
|
||||
const resizeHandle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
|
||||
const navigation = page.getByRole("complementary", { name: "工作台导航" });
|
||||
const workspace = page.locator("#main-workspace");
|
||||
const expandedWorkspace = await workspace.boundingBox();
|
||||
expect(expandedWorkspace).not.toBeNull();
|
||||
await expect(navigation).toHaveCSS("width", "368px");
|
||||
|
||||
await resizeHandle.focus();
|
||||
await page.keyboard.press("End");
|
||||
await expect(agentPanel).toHaveCSS("width", "720px");
|
||||
await expectMapCorridor(agentPanel, conditionPanel, 680);
|
||||
|
||||
await conditionPanel.getByRole("button", { name: "展开工况任务" }).click();
|
||||
await expect(conditionPanel).toHaveCSS("width", "960px");
|
||||
await expect(agentPanel).toHaveCSS("width", "628px");
|
||||
await expectMapCorridor(agentPanel, conditionPanel, 256);
|
||||
await page.getByRole("button", { name: "折叠工作台导航" }).click();
|
||||
await expect(navigation).toHaveCSS("width", "48px");
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0)).toBe(48);
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.width ?? 0)).toBe(1872);
|
||||
expect((await workspace.boundingBox())!.width).toBeGreaterThan(expandedWorkspace!.width);
|
||||
await expect(page).toHaveScreenshot("workbench-desktop-wide-max-agent.png", {
|
||||
animations: "disabled",
|
||||
maxDiffPixelRatio: 0.01
|
||||
@@ -201,41 +189,59 @@ async function selectBasemap(page: Page, label: "浅色" | "影像") {
|
||||
);
|
||||
}
|
||||
|
||||
async function expectDesktopFloatingGeometry(page: Page) {
|
||||
async function expectDesktopMainFrameGeometry(page: Page) {
|
||||
const navigation = page.getByRole("complementary", { name: "工作台导航" });
|
||||
const workspace = page.locator("#main-workspace");
|
||||
const taskbar = page.getByRole("toolbar", { name: "工作区窗口任务栏" });
|
||||
const mapWindow = page.locator("#workspace-map");
|
||||
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]');
|
||||
const agentHeader = agentPanel.locator(".agent-panel-integrated-header");
|
||||
const agentComposer = agentPanel.locator(".agent-panel-composer");
|
||||
const conditionPanel = page.getByRole("region", { name: "工况任务", exact: true });
|
||||
|
||||
await expect(agentPanel).toHaveCSS("width", "500px");
|
||||
await expect(conditionPanel).toHaveCSS("width", "432px");
|
||||
await expect(agentPanel).toHaveCSS("border-radius", "16px");
|
||||
await expect(agentHeader).toHaveCSS("border-radius", "0px");
|
||||
await expect(agentComposer).toHaveCSS("border-radius", "16px");
|
||||
await expect(agentComposer).toHaveCSS("border-top-width", "0px");
|
||||
await expect(conditionPanel).toHaveCSS("border-radius", "16px");
|
||||
await expect(navigation).toHaveCSS("width", "368px");
|
||||
await expect(agentPanel).toHaveCSS("border-radius", "0px");
|
||||
await expect(mapWindow).toHaveCSS("border-radius", "0px");
|
||||
|
||||
const navigationBox = await navigation.boundingBox();
|
||||
const workspaceBox = await workspace.boundingBox();
|
||||
const taskbarBox = await taskbar.boundingBox();
|
||||
const mapBox = await mapWindow.boundingBox();
|
||||
const agentBox = await agentPanel.boundingBox();
|
||||
const conditionBox = await conditionPanel.boundingBox();
|
||||
expect(navigationBox).not.toBeNull();
|
||||
expect(workspaceBox).not.toBeNull();
|
||||
expect(taskbarBox).not.toBeNull();
|
||||
expect(mapBox).not.toBeNull();
|
||||
expect(agentBox).not.toBeNull();
|
||||
expect(conditionBox).not.toBeNull();
|
||||
expect(agentBox!.x).toBe(12);
|
||||
expect(agentBox!.y).toBe(96);
|
||||
expect(agentBox!.y + agentBox!.height).toBe(884);
|
||||
expect(conditionBox!.x).toBe(944);
|
||||
expect(conditionBox!.y).toBe(96);
|
||||
expect(navigationBox!.x).toBe(0);
|
||||
expect(navigationBox!.y).toBe(56);
|
||||
expect(workspaceBox!.x).toBe(navigationBox!.x + navigationBox!.width);
|
||||
expect(workspaceBox!.y).toBe(navigationBox!.y);
|
||||
expect(taskbarBox!.x).toBe(workspaceBox!.x);
|
||||
expect(taskbarBox!.y).toBe(workspaceBox!.y + workspaceBox!.height);
|
||||
expect(mapBox).toEqual(workspaceBox);
|
||||
expect(agentBox!.x).toBeGreaterThanOrEqual(48);
|
||||
expect(agentBox!.x).toBeLessThanOrEqual(49);
|
||||
expect(
|
||||
Math.abs(agentBox!.x + agentBox!.width - (navigationBox!.x + navigationBox!.width))
|
||||
).toBeLessThanOrEqual(1);
|
||||
}
|
||||
|
||||
async function expectMapCorridor(
|
||||
agentPanel: Locator,
|
||||
conditionPanel: Locator,
|
||||
minimumWidth: number
|
||||
) {
|
||||
const agentBox = await agentPanel.boundingBox();
|
||||
const conditionBox = await conditionPanel.boundingBox();
|
||||
expect(agentBox).not.toBeNull();
|
||||
expect(conditionBox).not.toBeNull();
|
||||
expect(conditionBox!.x - (agentBox!.x + agentBox!.width)).toBeGreaterThanOrEqual(minimumWidth);
|
||||
async function expectSolidMainFrameMaterials(page: Page) {
|
||||
const materials = await page.evaluate(() => {
|
||||
const navigation = getComputedStyle(document.querySelector('[aria-label="工作台导航"]')!);
|
||||
const agent = getComputedStyle(document.querySelector('aside[aria-label="Agent 命令面板"]')!);
|
||||
return {
|
||||
navigationBackdrop: navigation.backdropFilter,
|
||||
navigationBackground: navigation.backgroundColor,
|
||||
agentBackdrop: agent.backdropFilter,
|
||||
agentBackground: agent.backgroundColor,
|
||||
agentShadow: agent.boxShadow
|
||||
};
|
||||
});
|
||||
expect(materials).toEqual({
|
||||
navigationBackdrop: "none",
|
||||
navigationBackground: "rgb(237, 241, 245)",
|
||||
agentBackdrop: "none",
|
||||
agentBackground: "rgb(247, 249, 251)",
|
||||
agentShadow: "none"
|
||||
});
|
||||
}
|
||||
|
||||
async function expectLoadedChineseFont(page: Page) {
|
||||
@@ -246,39 +252,6 @@ async function expectLoadedChineseFont(page: Page) {
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
async function expectAcrylicAlphas(
|
||||
page: Page,
|
||||
expected: {
|
||||
navigation: number;
|
||||
agentPanel: number;
|
||||
conditionPanel: number;
|
||||
control: number;
|
||||
}
|
||||
) {
|
||||
const alphas = await page.evaluate(() => {
|
||||
return {
|
||||
navigation: alphaOf(document.querySelector(".acrylic-navigation")),
|
||||
agentPanel: alphaOf(document.querySelector('aside[aria-label="Agent 命令面板"]')),
|
||||
conditionPanel: alphaOf(document.querySelector(".scheduled-feed-panel-shell")),
|
||||
control: alphaOf(document.querySelector(".acrylic-control"))
|
||||
};
|
||||
|
||||
function alphaOf(element: Element | null) {
|
||||
if (!element) {
|
||||
return 1;
|
||||
}
|
||||
const color = getComputedStyle(element).backgroundColor;
|
||||
const channels = color.match(/\d+(?:\.\d+)?/g)?.map(Number) ?? [];
|
||||
return channels[3] ?? 1;
|
||||
}
|
||||
});
|
||||
|
||||
expect(alphas.navigation).toBeCloseTo(expected.navigation, 2);
|
||||
expect(alphas.agentPanel).toBeCloseTo(expected.agentPanel, 2);
|
||||
expect(alphas.conditionPanel).toBeCloseTo(expected.conditionPanel, 2);
|
||||
expect(alphas.control).toBeCloseTo(expected.control, 2);
|
||||
}
|
||||
|
||||
async function expectBodyContrast(page: Page) {
|
||||
const contrast = await page
|
||||
.getByText("直接说出你想调查的问题,我会整理监测与空间证据,并将分析结果带回地图。")
|
||||
|
||||
Reference in New Issue
Block a user