Files
next-tjwater-frontend/tests/browser/palette.e2e.ts
T
jiang ec23d44b02 feat(agent): support Responses steering and project-aware tools
Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running
turns active while users steer or queue follow-ups and edit pending delivery.
Refresh the model catalog before generation and preserve approval mode selection.

Integrate scoped investigation messaging/presentation, geographic place selection,
current Agent composer styles and supporting workbench contracts.

Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and
production build. Preserve direct Server/Agent routing and project authentication.
2026-10-19 18:46:58 +08:00

139 lines
7.8 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { expectThemeColor, readColorAlpha, readRenderedContrast, readThemeColor } from "./support/theme-colors";
test.beforeEach(async ({ page }) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
await mockAgentApi(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
});
test("gem-blue actions and semantic labels remain readable on the new surfaces", async ({ page }) => {
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await expectThemeColor(panel, "background-color", "--agent-dock-surface");
const tint = await panel.evaluate((element) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
context.fillStyle = getComputedStyle(element).backgroundColor;
context.fillRect(0, 0, 1, 1);
return Array.from(context.getImageData(0, 0, 1, 1).data);
});
// Low-chroma material interpolation must stay in the cool silver/blue family.
expect(tint[2]).toBeGreaterThan(tint[1]);
expect(tint[2]).toBeGreaterThan(tint[0]);
await expectThemeColor(panel.locator("form.agent-panel-control"), "background-color", "--surface-reading");
expect(await readRenderedContrast(panel.getByText("对话主题"))).toBeGreaterThanOrEqual(4.5);
await page.getByPlaceholder("询问供水问题,或描述需要处理的任务").fill("检查压力变化");
const send = panel.getByRole("button", { name: "发送 Agent 指令", exact: true });
await expect(send).toBeEnabled();
await expectThemeColor(send, "background-color", "--action-blue");
expect(await readRenderedContrast(send)).toBeGreaterThanOrEqual(4.5);
await send.hover();
await expectThemeColor(send, "background-color", "--action-blue-hover");
expect(await readRenderedContrast(send)).toBeGreaterThanOrEqual(4.5);
// Exercise every shared semantic tone after CSS variables and color-mix are resolved.
await panel.evaluate((element) => {
const samples = document.createElement("div");
samples.dataset.paletteSamples = "true";
for (const tone of ["neutral", "info", "success", "warning", "danger"]) {
const label = document.createElement("span");
label.className = `status-badge status-tone-${tone}`;
label.dataset.paletteTone = tone;
label.textContent = tone;
samples.append(label);
}
element.append(samples);
});
for (const tone of ["neutral", "info", "success", "warning", "danger"]) {
expect(await readRenderedContrast(panel.locator(`[data-palette-tone="${tone}"]`)), tone)
.toBeGreaterThanOrEqual(4.5);
}
});
test("selected controls preserve readable hover, pressed and keyboard focus states", async ({ page }) => {
const control = page.getByRole("button", { name: /任务浮条/ });
await expect(control).toHaveAttribute("aria-pressed", "true");
expect(await readRenderedContrast(control)).toBeGreaterThanOrEqual(4.5);
await control.hover();
await expectThemeColor(control, "background-color", "--action-selection-soft-hover");
expect(await readRenderedContrast(control)).toBeGreaterThanOrEqual(4.5);
const box = (await control.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await expectThemeColor(control, "background-color", "--action-selection-soft-pressed");
expect(await readRenderedContrast(control)).toBeGreaterThanOrEqual(4.5);
await page.mouse.move(720, 460);
await page.mouse.up();
await page.keyboard.press("Tab");
await control.focus();
expect(await control.evaluate((element) => element.matches(":focus-visible"))).toBe(true);
expect(await control.evaluate((element) => getComputedStyle(element).boxShadow)).not.toBe("none");
});
test("floating panel text stays readable over light and dark map backgrounds", async ({ page }) => {
// A solid underlay gives the transparent material a known actual backdrop.
await page.addStyleTag({ content: "#workspace-map .maplibregl-canvas { visibility: hidden; } .acrylic-panel { background-image: none; }" });
await page.getByRole("button", { name: /图层:管理地图图层/ }).click();
const layers = page.getByRole("region", { name: "图层", exact: true });
await expect(layers).toBeVisible();
const title = layers.getByText("图层", { exact: true }).first();
for (const tone of ["light", "satellite"]) {
await page.locator("main").evaluate((element, value) => {
element.setAttribute("data-basemap-tone", value);
(element as HTMLElement).style.backgroundColor = value === "light" ? "#f5f7fa" : "#172b3a";
}, tone);
await expectThemeColor(layers, "background-color", "--acrylic-panel");
expect(await readRenderedContrast(title), tone).toBeGreaterThanOrEqual(4.5);
const color = await layers.evaluate((element) => getComputedStyle(element).backgroundColor);
expect(readColorAlpha(color)).toBeCloseTo(tone === "light" ? 0.56 : 0.72, 2);
await expect(layers).toHaveCSS("backdrop-filter", "blur(24px) saturate(" + (tone === "light" ? "1.08" : "1.02") + ")");
}
});
for (const width of [320, 375, 390]) {
test(`new palette keeps Agent input and controls usable at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 844 });
await page.getByRole("button", { name: "打开 Agent 面板" }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
const composer = panel.locator("form.agent-panel-control");
await expect(composer).toBeVisible();
const material = panel.locator(".agent-panel-composer");
await expectThemeColor(material, "background-color", "--agent-white-acrylic-surface");
expect(readColorAlpha(await material.evaluate((element) => getComputedStyle(element).backgroundColor))).toBeCloseTo(0.86, 2);
await expect(material).toHaveCSS("backdrop-filter", "blur(18px) saturate(1.08)");
expect(await readRenderedContrast(panel.getByText("对话主题"))).toBeGreaterThanOrEqual(4.5);
for (const element of [panel, composer]) {
const box = (await element.boundingBox())!;
expect(box.x).toBeGreaterThanOrEqual(0);
expect(box.x + box.width).toBeLessThanOrEqual(width);
}
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
await page.screenshot({ path: `test-results/palette/agent-${width}.png` });
});
}
test("desktop palette captures dock, history, conditions and map tools", async ({ page }) => {
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await page.screenshot({ path: "test-results/palette/desktop-1440.png" });
await page.setViewportSize({ width: 1920, height: 1080 });
await page.screenshot({ path: "test-results/palette/desktop-1920.png" });
await panel.getByRole("button", { name: "打开 Agent 历史记录" }).click();
const history = panel.getByRole("region", { name: "Agent 历史记录" });
await expect(history).toBeVisible();
expect(await readThemeColor(history, "--agent-dock-overlay-surface"))
.toBe(await history.evaluate((element) => getComputedStyle(element).backgroundColor));
await page.screenshot({ path: "test-results/palette/desktop-history.png" });
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "工况任务", exact: true }).click();
const conditions = page.getByRole("region", { name: "工况任务", exact: true });
await expect(conditions).toBeVisible();
await conditions.getByRole("button", { name: "展开工况任务" }).click();
await page.screenshot({ path: "test-results/palette/desktop-report.png" });
});