import { expect, test } from "@playwright/test"; import type { InteractionAnswer, InteractionRequest } from "../../src/features/agent/api/interaction-requests"; import { mockAgentApi } from "./support/mock-agent-api"; for (const width of [1440, 375, 320]) { test(`question card aligns wrapped choices and accepts a text-only answer at ${width}px`, async ({ page }, testInfo) => { await page.setViewportSize({ width, height: 900 }); 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 { AgentInteractionRequestCard } = await import("/src/features/agent/components/agent-interaction-request-card.tsx"); const root = createRoot(document.getElementById("question-reader")); function Fixture({ custom }: { custom: boolean }) { const [draft, setDraft] = React.useState([] as InteractionAnswer[]); const [submitted, setSubmitted] = React.useState(false); const record: InteractionRequest = { version: "agent-interaction-request@1", interaction_id: custom ? "free-text" : "closed-choices", message_id: "m", turn_id: "t", call_id: "c", questions: [{ question_id: "target", title: "确认要定位的对象", body: custom ? "可直接填写需要定位的对象和地址线索。" : "当前记录包含公司上报地址和三组模拟管段位置。不同目标会在地图上定位到不同位置,请选择要定位的对象。", options: custom ? [] : [ { id: "company", label: "公司上报地址:安徽省滁州市凤阳县西泉镇的公司点位" }, { id: "pipe1", label: "模拟管段 Pipe19026(用户给出的约315米位置)" }, { id: "pipe2", label: "模拟管段 Pipe18751(用户给出的约516米位置)" }, { id: "pipe3", label: "模拟管段 Pipe18750(用户给出的约523米位置)" } ], mode: "single", required: true, allow_custom: custom }], status: submitted ? "answered" : "pending", revision: submitted ? 1 : 0, draft_revision: 0, draft: [], created_at: "2026-10-28T09:19:24Z", ...(submitted ? { answers: draft.map((answer: InteractionAnswer) => ({ ...answer, selected_options: [] })) } : {}) }; return React.createElement(AgentInteractionRequestCard, { entry: { record, draft, dirty: draft.length > 0, busy: false }, checking: false, onChange: (next: InteractionAnswer[]) => setDraft(next), onAction: (action: string) => { if (action === "reply") setSubmitted(true); } }); } const render = (custom: boolean) => root.render(React.createElement(Fixture, { key: String(custom), custom })); Object.assign(window, { showTextOnlyQuestion: () => render(true) }); render(false); }); const card = page.getByRole("article", { name: "需要补充的信息" }); await expect(card.getByRole("radio")).toHaveCount(4); await expect(card.getByRole("textbox")).toHaveCount(0); await expect(card.getByRole("button", { name: "提交回答", exact: true })).toBeDisabled(); const choice = card.getByRole("radio").last(); const backgroundBefore = await choice.locator("xpath=ancestor::label").evaluate((element) => getComputedStyle(element).backgroundColor); await choice.focus(); await page.keyboard.press("Space"); await expect(choice).toBeChecked(); const backgroundAfter = await choice.locator("xpath=ancestor::label").evaluate((element) => getComputedStyle(element).backgroundColor); expect(backgroundAfter).not.toBe(backgroundBefore); const offsets = await card.getByRole("radio").evaluateAll((inputs) => inputs.map((input) => { const label = input.closest("label")!; const text = label.lastElementChild!; const radioBounds = input.getBoundingClientRect(); const textBounds = text.getBoundingClientRect(); return Math.abs(radioBounds.top + radioBounds.height / 2 - textBounds.top - parseFloat(getComputedStyle(text).lineHeight) / 2); })); expect(Math.max(...offsets)).toBeLessThanOrEqual(1); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width); await card.screenshot({ path: testInfo.outputPath(`question-choices-${width}.png`) }); await page.evaluate(() => (window as unknown as { showTextOnlyQuestion: () => void }).showTextOnlyQuestion()); await expect(card.getByRole("radio")).toHaveCount(0); const custom = card.getByRole("textbox", { name: "确认要定位的对象:补充说明" }); await custom.fill("请定位公司上报地址,并保留现有管段标记。"); await expect(card.getByRole("button", { name: "提交回答", exact: true })).toBeEnabled(); await card.screenshot({ path: testInfo.outputPath(`question-custom-${width}.png`) }); await card.getByRole("button", { name: "提交回答", exact: true }).click(); const reply = page.getByRole("group", { name: "已提交的回答" }); await expect(reply.getByText("已回答", { exact: true })).toBeVisible(); await expect(card).toHaveCount(0); await expect(page.getByRole("textbox")).toHaveCount(0); await expect(reply.getByText("请定位公司上报地址,并保留现有管段标记。", { exact: true })).toBeVisible(); }); }