feat: add project-aware agent workspace
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import { mockWorkbenchContext } from "./support/mock-workbench-context";
|
||||
|
||||
@@ -6,12 +6,17 @@ test.beforeEach(async ({ page }) => {
|
||||
await mockAgentApi(page);
|
||||
});
|
||||
|
||||
async function expectPanelWidth(panel: Locator, width: number) {
|
||||
await expect.poll(async () => Math.round((await panel.boundingBox())!.width)).toBe(width);
|
||||
}
|
||||
|
||||
async function openAgentPanel(page: Page) {
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
await expect(page.getByRole("complementary", { name: "Agent 命令面板" })).toBeVisible();
|
||||
}
|
||||
|
||||
test("the docked Agent collapses and restores the main workspace", async ({ page }) => {
|
||||
await mockWorkbenchContext(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openAgentPanel(page);
|
||||
|
||||
@@ -27,10 +32,44 @@ test("the docked Agent collapses and restores the main workspace", async ({ page
|
||||
|
||||
await page.getByRole("button", { name: "折叠 Agent 面板" }).click();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
await expect(page.getByLabel("Agent 折叠栏").getByLabel("Agent 状态")).toBeVisible();
|
||||
for (const width of [1440, 1024]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
const rail = page.getByLabel("Agent 折叠栏");
|
||||
await expectPanelWidth(rail, 64);
|
||||
await expect.poll(async () => {
|
||||
const [orb, header] = await Promise.all([
|
||||
rail.boundingBox(),
|
||||
page.locator(".workbench-top-bar").boundingBox()
|
||||
]);
|
||||
return {
|
||||
left: Math.round(orb!.x),
|
||||
top: Math.round(orb!.y - header!.y - header!.height),
|
||||
height: Math.round(orb!.height)
|
||||
};
|
||||
}).toEqual({ left: 12, top: 12, height: 64 });
|
||||
}
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.getByLabel("Agent 折叠栏").screenshot({ path: "/tmp/nexttjwater-agent-start-preview/collapsed-persona.png" });
|
||||
await page.screenshot({ path: "/tmp/nexttjwater-obsidian-orb/workbench.png", clip: { x: 0, y: 56, width: 340, height: 190 } });
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0)).toBe(0);
|
||||
|
||||
await page.getByLabel(/展开 Agent 助手面板/).click();
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
await cdp.send("Emulation.setEmulatedMedia", {
|
||||
features: [{ name: "prefers-reduced-transparency", value: "reduce" }]
|
||||
});
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toHaveCSS("backdrop-filter", "none");
|
||||
await cdp.detach();
|
||||
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).focus();
|
||||
await page.keyboard.press("Shift+Tab");
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByRole("button", { name: /展开 Agent 助手面板/ })).toBeFocused();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(agentPanel).toBeVisible();
|
||||
await expect(agentPanel.locator("header").getByLabel("Agent 状态")).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0))
|
||||
.toBe(expandedOffset);
|
||||
@@ -46,41 +85,42 @@ test("dragging the Agent edge keeps the workspace attached and preserves the dra
|
||||
const prompt = panel.getByPlaceholder("询问供水问题,或描述需要处理的任务");
|
||||
await expect(panel.getByText("当前上下文", { exact: true })).toHaveCount(0);
|
||||
await prompt.fill("保留尚未发送的诊断问题");
|
||||
await expect(panel).toHaveCSS("width", "500px");
|
||||
await expectPanelWidth(panel, 500);
|
||||
await page.waitForFunction(() =>
|
||||
Boolean(window.__waterNetworkMap?.getLayer("scada-pressure-hit")) &&
|
||||
!window.__waterNetworkMap?.isMoving()
|
||||
);
|
||||
const cameraBefore = await readCamera(page);
|
||||
expect(await handle.evaluate((element) => (element as HTMLElement).tabIndex)).toBe(-1);
|
||||
const grip = handle.locator(".agent-resize-grip");
|
||||
await expect(grip).toHaveCSS("opacity", "0");
|
||||
expect(await handle.evaluate((element) => (element as HTMLElement).tabIndex)).toBe(0);
|
||||
await expect(handle).toHaveAttribute("data-slot", "resizable-handle");
|
||||
const grip = handle.locator(".resizable-grip");
|
||||
await expect(grip).toBeVisible();
|
||||
const box = (await handle.boundingBox())!;
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await expect(grip).toHaveCSS("opacity", "1");
|
||||
await expect(handle).toHaveAttribute("data-separator", "hover");
|
||||
await page.screenshot({ path: "/tmp/nexttjwater-agent-resize-hover.png" });
|
||||
await page.mouse.down();
|
||||
await expect(handle).toHaveAttribute("data-resizing", "true");
|
||||
await expect(panel).toHaveCSS("width", "500px");
|
||||
await expect(handle).toHaveAttribute("data-separator", "active");
|
||||
await expectPanelWidth(panel, 500);
|
||||
for (const nextWidth of [540, 580, 620]) {
|
||||
await page.mouse.move(nextWidth, box.y + box.height / 2);
|
||||
await expect(panel).toHaveCSS("width", `${nextWidth}px`);
|
||||
await expectPanelWidth(panel, nextWidth);
|
||||
const panelBox = (await panel.boundingBox())!;
|
||||
const workspaceBox = (await workspace.boundingBox())!;
|
||||
expect(workspaceBox.x).toBe(panelBox.x + panelBox.width);
|
||||
}
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/desktop-resizing.png" });
|
||||
await page.mouse.up();
|
||||
await expect(handle).toHaveAttribute("data-resizing", "false");
|
||||
await expect(handle).not.toHaveAttribute("data-separator", "active");
|
||||
await expect(prompt).toHaveValue("保留尚未发送的诊断问题");
|
||||
await expect.poll(() => readCamera(page)).toEqual(cameraBefore);
|
||||
await page.mouse.move(1000, 450);
|
||||
await expect(grip).toHaveCSS("opacity", "0");
|
||||
await expect(panel).toHaveCSS("width", "620px");
|
||||
await expect(grip).toBeVisible();
|
||||
await expectPanelWidth(panel, 620);
|
||||
await page.getByRole("button", { name: "折叠 Agent 面板" }).click();
|
||||
await expect(page.getByLabel("Agent 折叠栏")).toBeVisible();
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
await expect(panel).toHaveCSS("width", "620px");
|
||||
await expectPanelWidth(panel, 620);
|
||||
await expect(prompt).toHaveValue("保留尚未发送的诊断问题");
|
||||
});
|
||||
|
||||
@@ -96,6 +136,34 @@ async function readCamera(page: Page) {
|
||||
});
|
||||
}
|
||||
|
||||
test("Agent Resizable supports keyboard limits and preserves its width across mobile", async ({ page }) => {
|
||||
await mockWorkbenchContext(page);
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openAgentPanel(page);
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const handle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
|
||||
await panel.locator("textarea").fill("键盘调整后仍保留的草稿");
|
||||
await page.waitForFunction(() => Boolean(window.__waterNetworkMap));
|
||||
await page.evaluate(() => Object.assign(window, { originalResizeMap: window.__waterNetworkMap }));
|
||||
await handle.focus();
|
||||
await expect(handle).toBeFocused();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(handle.locator(".resizable-grip")).toHaveCSS("outline-style", "solid");
|
||||
await expect.poll(async () => (await panel.boundingBox())!.width).toBeGreaterThan(500);
|
||||
await page.keyboard.press("Home");
|
||||
await expectPanelWidth(panel, 400);
|
||||
await page.keyboard.press("End");
|
||||
await expectPanelWidth(panel, 640);
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
await expect(handle).toBeHidden();
|
||||
await expect.poll(async () => (await page.locator("#main-workspace").boundingBox())!.width).toBe(375);
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await expectPanelWidth(panel, 640);
|
||||
await expect(panel.locator("textarea")).toHaveValue("键盘调整后仍保留的草稿");
|
||||
expect(await page.evaluate(() => window.__waterNetworkMap === (window as unknown as { originalResizeMap: unknown }).originalResizeMap)).toBe(true);
|
||||
});
|
||||
|
||||
test("Agent dragging stays within usable widths and adapts to the viewport", async ({ page }) => {
|
||||
await mockWorkbenchContext(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
@@ -109,18 +177,18 @@ test("Agent dragging stays within usable widths and adapts to the viewport", asy
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(targetX, box.y + box.height / 2);
|
||||
await page.mouse.up();
|
||||
await expect(panel).toHaveCSS("width", `${expectedWidth}px`);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(expectedWidth);
|
||||
await expectPanelWidth(panel, expectedWidth);
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0)).toBe(expectedWidth);
|
||||
if (expectedWidth === 400) {
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/desktop-resize-minimum.png" });
|
||||
}
|
||||
}
|
||||
await page.setViewportSize({ width: 1024, height: 768 });
|
||||
await expect(panel).toHaveCSS("width", "500px");
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(500);
|
||||
await expectPanelWidth(panel, 500);
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0)).toBe(500);
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await expect(panel).toHaveCSS("width", "620px");
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(620);
|
||||
await expectPanelWidth(panel, 620);
|
||||
await expect.poll(async () => Math.round((await workspace.boundingBox())?.x ?? 0)).toBe(620);
|
||||
});
|
||||
|
||||
test("the map stays painted while the Agent and viewport change size", async ({ page }) => {
|
||||
@@ -222,6 +290,7 @@ test("the map canvas follows workspace resizing before the next paint", async ({
|
||||
|
||||
test("the floating condition feed coordinates with the docked Agent", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await mockWorkbenchContext(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openAgentPanel(page);
|
||||
|
||||
@@ -265,6 +334,7 @@ test.describe("mobile touch layout", () => {
|
||||
test.use({ hasTouch: true, isMobile: true, viewport: { width: 375, height: 812 } });
|
||||
|
||||
test("mobile Agent toggle opens the conversation panel by touch", async ({ page }) => {
|
||||
await mockWorkbenchContext(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const openButton = page.getByRole("button", { name: "打开 Agent 面板" });
|
||||
|
||||
Reference in New Issue
Block a user