Files
next-tjwater-frontend/tests/browser/investigation-evidence-overflow.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

90 lines
5.6 KiB
TypeScript

import { expect, test } from "@playwright/test";
import type { OperationalTask } from "../../src/features/workbench/api/operational-task-client";
const now = "2026-09-23T09:00:00Z";
const sources = ["peripheral_test_data", "scada_devices", "workbench_runtime", "scada_readings", "work_orders", "other_saved_data"];
const sourceRef = (source: string) => `${source}:sha256:${"7d5852b69d8e3d4e127800882683c3319e58".repeat(2)}`;
const snapshotKey = "query_scope_with_a_long_unbroken_field_name";
const task: OperationalTask = {
project_id: "11111111-1111-4111-8111-111111111111",
task_id: "22222222-2222-4222-8222-222222222222",
task_type: "event_investigation", workflow: "water_quality_complaint",
lifecycle_status: "completed", active_run: null, status: "completed", priority: "normal",
revision: 1, execution_generation: 1, execution_request_id: null,
title: "水质投诉调查", summary: "居民反映异味",
source: { kind: "operator", external_id: null, external_revision: null, event_kind: "water_quality_complaint" },
trigger: { kind: "manual", occurred_at: now, detail: "演示小区" }, scheduled_for: null,
agent_session_id: null, agent_lease_expires_at: null, current_plan_revision: null, current_report_revision: 1,
created_at: now, updated_at: now, latest_plan: null,
latest_report: {
revision: 1, execution_generation: 1, source_session_id: "evidence-layout-session", published_at: now, published_by: "agent",
report: {
title: "调查报告", current_conclusion: "需现场核查用户端。", conclusion_boundary: "仅覆盖已有记录。",
evidence_refs: sources.slice(0, 5), recommendations: [], pending_items: [],
stage: "final", outcome: "concluded", stop_reason: null, data_gaps: []
}
},
evidence: sources.map((source) => ({
evidence_id: source, kind: "system_data", summary: `${source} 数据快照`, source_label: source,
source_ref: sourceRef(source), observed_at: now, recorded_at: now, recorded_by: "agent", has_snapshot: true
})),
actions: [], access_requests: [], work_order_snapshot: null,
permissions: {
can_publish_artifacts: false, can_propose_actions: false, can_sync_external_feedback: false,
can_decide_work_orders: false, can_execute_work_orders: false, can_decide_commands: false,
can_execute_commands: false, can_decide_access: false
}
};
test("调查证据与数据快照的长引用在桌面和窄屏完整换行", async ({ page }, testInfo) => {
await page.route("http://127.0.0.1:8787/api/v1/**", (route) => route.fulfill({ json: { items: [] } }));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.setContent('<html lang="zh-CN"><head><link rel="stylesheet" href="/src/styles.css"></head><body><main id="report" style="max-width:480px;margin:auto;padding:16px"></main></body></html>');
await page.evaluate(async ({ task, snapshotKey }) => {
const reactModule = await import("/node_modules/.vite/deps/react.js");
const React = reactModule.default ?? reactModule;
const clientModule = await import("/node_modules/.vite/deps/react-dom_client.js");
const { createRoot } = clientModule.default ?? clientModule;
const { AgentExecutionPlanPanel } = await import("/src/features/workbench/components/agent-execution-plan-panel.tsx");
const noop = async () => undefined;
createRoot(document.getElementById("report")).render(React.createElement(AgentExecutionPlanPanel, {
task, onContinue: noop, onDecision: noop, onExecute: noop, onReconcile: noop, onRefresh: noop,
onLoadEvidenceSnapshot: async (evidenceId: string) => ({
source: "workbench_runtime", origin: "system_data",
source_ref: task.evidence.find((item) => item.evidence_id === evidenceId)!.source_ref,
queried_at: task.updated_at, truncated: false, data: { [snapshotKey]: "long_snapshot_value_".repeat(8) }
})
}));
}, { task, snapshotKey });
const evidence = page.getByRole("region", { name: "主要依据", exact: true });
await expect(evidence).toBeVisible();
for (const name of ["更多引用依据 · 2 条", "其他已保存资料"]) {
const summary = evidence.locator("summary").filter({ hasText: name });
await summary.focus();
await page.keyboard.press("Enter");
await expect(summary.locator("..")).toHaveAttribute("open", "");
}
for (const source of sources) {
const card = evidence.locator("details").filter({ has: page.locator("summary", { hasText: `${source} 数据快照` }) }).last();
await card.locator("summary").first().click();
await expect(card.locator("dd.font-mono")).toHaveText(sourceRef(source));
await card.getByRole("button", { name: "查看数据快照", exact: true }).click();
await expect(card.getByRole("region", { name: "数据快照" })).toContainText(snapshotKey);
}
for (const width of [1440, 705, 375, 320]) {
await page.setViewportSize({ width, height: 900 });
const overflow = await evidence.evaluate((section) => {
return [...section.querySelectorAll("dd, dt, [aria-label='数据快照'] p")].flatMap((element) => {
const rect = element.getBoundingClientRect();
const card = element.closest("details")!.getBoundingClientRect();
return element.scrollWidth > element.clientWidth + 1 || rect.right > card.right + 1 || rect.left < card.left - 1
? [{ text: element.textContent, width: element.clientWidth, scrollWidth: element.scrollWidth, right: rect.right, cardRight: card.right }]
: [];
});
});
expect(overflow, `证据字段在 ${width}px 下不应撑出卡片`).toEqual([]);
await evidence.screenshot({ path: testInfo.outputPath(`evidence-${width}.png`) });
}
});