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.
139 lines
7.8 KiB
TypeScript
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" });
|
|
});
|