import { expect, test } from "@playwright/test"; import { mockAgentApi } from "./support/mock-agent-api"; import { TEST_PROJECT_ID } from "./support/mock-scada-api"; import { mockWorkbenchContext } from "./support/mock-workbench-context"; for (const { scope, width } of [ { scope: "project", width: 1440 }, { scope: "personal", width: 1440 }, { scope: "personal", width: 375 } ] as const) { test(`authenticated ${scope} chat sends the matching context at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 900 }); await mockWorkbenchContext(page); await mockAgentApi(page); if (scope === "personal") { await page.route("**/api/v1/projects?*", (route) => route.fulfill({ headers: { "Access-Control-Allow-Origin": "*" }, json: { items: [], total: 0, limit: 1000, offset: 0 } })); } await page.route("**/runtime-config.js", (route) => route.fulfill({ contentType: "application/javascript", body: `globalThis.__TJWATER_CONFIG__ = ${JSON.stringify({ TJWATER_AUTH_MODE: "required", TJWATER_KEYCLOAK_ISSUER: "https://authentication.invalid/realms/test", TJWATER_KEYCLOAK_CLIENT_ID: "browser-test", TJWATER_SERVER_API_BASE_URL: "https://tjwater-api.invalid", TJWATER_AGENT_URL: "http://127.0.0.1:8787", TJWATER_MAP_URL: "https://workbench-map.invalid/geoserver", TJWATER_GEOSERVER_WORKSPACE: "tjwater_next", TJWATER_ENABLE_MSW: "false" })};` })); // Exercise the real authentication provider and chat transport without // credentials, external authentication, or model inference. await page.route("**/keycloak-js.js*", (route) => route.fulfill({ contentType: "application/javascript", body: `export default class Keycloak { token = "BROWSER_CONTEXT_TEST_ONLY"; idTokenParsed = { name: "Browser context test" }; async init() { return true; } async updateToken() { return false; } async login() {} async logout() {} }` })); const streamHeaders: Array> = []; const expectedProject = scope === "project" ? TEST_PROJECT_ID : undefined; await page.route("**/api/v1/responses", async (route) => { if (route.request().method() !== "POST") { await route.fallback(); return; } const headers = await route.request().allHeaders(); streamHeaders.push(headers); if (headers.authorization !== "Bearer BROWSER_CONTEXT_TEST_ONLY" || headers["x-project-id"] !== expectedProject) { await route.fulfill({ status: 401, headers: { "Access-Control-Allow-Origin": "*" }, json: { code: "PRODUCT_CONTEXT_UNAVAILABLE", message: "valid Bearer authorization and X-Project-Id are required" } }); return; } await route.fallback(); }); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click(); const panel = page.locator('aside[aria-label="Agent 命令面板"]').filter({ visible: true }); if (scope === "personal") { await panel.getByRole("button", { name: "选择对话归属" }).click(); await page.getByRole("menuitem", { name: "个人会话", exact: true }).click(); await expect(panel.getByRole("button", { name: "选择对话归属" })).toHaveText("个人会话"); } await expect(panel.getByRole("button", { name: "选择 Agent 模型" })).toContainText("测试模型"); await panel.getByRole("textbox").fill("检查会话项目上下文"); await panel.getByRole("button", { name: "发送 Agent 指令" }).click(); await expect.poll(() => streamHeaders.length).toBe(1); expect(streamHeaders[0].authorization).toBe("Bearer BROWSER_CONTEXT_TEST_ONLY"); expect(streamHeaders[0]["x-project-id"]).toBe(expectedProject); await expect(panel.getByText("已收到工况汇总请求。", { exact: true })).toBeVisible(); await expect(page.getByText("Agent 请求失败", { exact: true })).toHaveCount(0); const box = await panel.boundingBox(); expect(box).not.toBeNull(); expect(box!.x).toBeGreaterThanOrEqual(0); expect(box!.x + box!.width).toBeLessThanOrEqual(width); }); }