import { expect, test } from "@playwright/test"; import { mockAgentApi } from "./support/mock-agent-api"; for (const width of [1440, 375]) { test(`retains failed investigation paths through updates and new rounds at ${width}px`, async ({ page }, testInfo) => { await page.setViewportSize({ width, height: 1000 }); await page.emulateMedia({ reducedMotion: "reduce" }); await mockAgentApi(page); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.setContent('
'); await page.evaluate(async () => { const entry = await (await fetch("/src/main.tsx")).text(); const reactUrl = entry.match(/from "([^"\n]*\/react\.js[^"\n]*)"/)?.[1]; const clientUrl = entry.match(/from "([^"\n]*\/react-dom_client\.js[^"\n]*)"/)?.[1]; if (!reactUrl || !clientUrl) throw new Error("Vite React module paths unavailable"); const reactModule = await import(reactUrl); const React = reactModule.default ?? reactModule; const clientModule = await import(clientUrl); const { createRoot } = clientModule.default ?? clientModule; const { InvestigationTaskReport } = await import("/src/features/workbench/components/investigation-task-report.tsx"); const { investigationDiagramSchema } = await import("/src/features/workbench/api/investigation-report.ts"); const now = "2026-11-10T01:00:00Z"; const initial = investigationDiagramSchema.parse({ title: "SCADA 累计调查脉络", source_mermaid: null, mermaid: 'flowchart TD\nread["读取全量 SCADA"]\nlocate_1["第一次定位(失败)"]\nread --> locate_1', node_refs: [ { node_id: "read", label: "读取全量 SCADA", status: "recorded" }, { node_id: "locate_1", label: "第一次定位", status: "failed", detail: "计算超时,已保留本次失败记录。" } ], edges: [{ source: "read", target: "locate_1" }] }); const report = { revision: 1, execution_generation: 1, published_at: now, source_session_id: "session-1", report: { title: "SCADA 调查", current_conclusion: "首次定位超时,继续核查。", conclusion_boundary: "现有资料", stage: "final", outcome: "concluded", data_gaps: [], evidence_refs: [], recommendations: [], pending_items: [], diagram: initial } }; let task = { task_id: "cumulative-graph", title: "SCADA 数据问题调查", summary: "保留失败路径后继续调查", execution_generation: 1, status: "running", lifecycle_status: "open", latest_report: report, latest_plan: null, actions: [], evidence: [], questions: [], active_run: { run_id: "run-1", status: "running" }, latest_progress: { revision: 1, run_id: "run-1", execution_generation: 1, published_at: now, progress: { summary: "首次定位失败", diagram: initial } } }; const root = createRoot(document.getElementById("reader")); const render = () => root.render(React.createElement(InvestigationTaskReport, { task, onContinue: () => undefined, renderAction: () => null, renderEvidence: () => null, onLoadReports: async () => ({ items: [report], next_offset: null }) })); Object.assign(window, { advanceInvestigationGraph: () => { const diagram = investigationDiagramSchema.parse({ ...initial, mermaid: initial.mermaid + '\nlocate_2["调整窗口重试"]\nlocate_1 -->|"重试"| locate_2', node_refs: [...initial.node_refs, { node_id: "locate_2", label: "调整窗口重试", status: "recorded" }], edges: [...initial.edges!, { source: "locate_1", target: "locate_2", label: "重试" }] }); task = { ...task, execution_generation: 2, active_run: { run_id: "run-2", status: "running" }, latest_progress: { ...task.latest_progress, execution_generation: 2, run_id: "run-2", revision: 1, progress: { summary: "重试成功,原失败记录保留", diagram } } }; render(); }, startNextGraphRound: () => { task = { ...task, execution_generation: 3 }; render(); }, stopGraphRun: () => { Object.assign(task, { active_run: null, status: "completed", lifecycle_status: "completed" }); render(); } }); render(); }); const graph = page.getByRole("region", { name: "调查脉络", exact: true }); await expect(graph.locator("svg g.node")).toHaveCount(2); await expect(graph.locator("svg g.node.tjFailed")).toHaveCount(1); await graph.getByRole("button", { name: /^第一次定位 ·/ }).click(); await expect(graph.getByLabel("节点详情 第一次定位")).toContainText("计算超时"); await page.evaluate(() => (window as unknown as { advanceInvestigationGraph: () => void }).advanceInvestigationGraph()); await expect(graph.locator("svg g.node")).toHaveCount(3); await expect(graph.locator("svg g.node.tjFailed")).toHaveCount(1); await expect(graph.locator("svg g.node.tjRecorded")).toHaveCount(2); await expect(graph.locator("svg g.node").filter({ hasText: "第一次定位(失败)" })).toHaveCount(1); await expect(graph.getByLabel("节点详情 第一次定位")).toContainText("计算超时"); await page.evaluate(() => (window as unknown as { startNextGraphRound: () => void }).startNextGraphRound()); await expect(graph.locator("svg g.node")).toHaveCount(3); await expect(graph.locator("svg g.node.tjFailed > rect")).toHaveCSS("fill", "rgb(254, 242, 242)"); await page.evaluate(() => (window as unknown as { stopGraphRun: () => void }).stopGraphRun()); await expect(graph.locator("svg g.node.tjRecorded")).toHaveCount(2); await expect(graph.locator("svg g.node.tjFailed")).toHaveCount(1); await expect(graph).toContainText("不实时同步执行状态"); await graph.screenshot({ path: testInfo.outputPath(`cumulative-failed-path-${width}.png`) }); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width); await page.getByRole("button", { name: "展开报告版本", exact: true }).click(); await page.getByRole("dialog", { name: "报告版本", exact: true }).getByRole("button", { name: /第 1 轮 · 报告第 1 版/ }).click(); await expect(graph.locator("svg g.node")).toHaveCount(2); await page.getByRole("button", { name: "返回实时内容", exact: true }).click(); await expect(graph.locator("svg g.node")).toHaveCount(3); }); }