import { expect, test, type Page } from "@playwright/test"; test("submits a custom answer with the actionable id after a later tool placeholder", async ({ page }) => { const replies: Array<{ pathname: string; body: unknown }> = []; await mockQuestionApi(page, replies); await page.goto("/", { waitUntil: "domcontentloaded" }); const prompt = page.getByPlaceholder(/输入调度问题/).filter({ visible: true }).first(); await prompt.fill("测试 question 自定义回答"); await page .getByRole("button", { name: "发送 Agent 指令" }) .filter({ visible: true }) .first() .click(); const panel = page.locator('aside[aria-label="Agent 命令面板"]').filter({ visible: true }); await panel.getByRole("radio", { name: "自定义回答" }).click(); await panel.getByPlaceholder("输入自定义回答").fill("分析高峰时段压力"); await panel.getByRole("button", { name: "提交回答" }).click(); await expect.poll(() => replies).toEqual([ { pathname: "/api/v1/agent/chat/question/question-1/reply", body: { session_id: "question-session", answers: [["分析高峰时段压力"]] } } ]); await expect(panel.getByText("已回答", { exact: true })).toBeVisible(); await expect(panel.getByRole("button", { name: "提交回答" })).toHaveCount(0); }); test("keeps the custom answer control within a 375px Agent sheet", async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }); await mockQuestionApi(page, []); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "打开 Agent 面板" }).click(); await page.getByRole("button", { name: /Agent 工作台抽屉高度/ }).click(); const panel = page.locator('aside[aria-label="Agent 命令面板"]').filter({ visible: true }); const prompt = panel.getByPlaceholder(/输入调度问题/); await prompt.fill("测试窄屏 question"); await panel.getByRole("button", { name: "发送 Agent 指令" }).click(); await panel.getByRole("radio", { name: "自定义回答" }).click(); const textarea = panel.getByPlaceholder("输入自定义回答"); await expect(textarea).toBeVisible(); const box = await textarea.boundingBox(); expect(box).not.toBeNull(); expect(box!.x).toBeGreaterThanOrEqual(0); expect(box!.x + box!.width).toBeLessThanOrEqual(375); }); async function mockQuestionApi( page: Page, replies: Array<{ pathname: string; body: unknown }> ) { await page.route("**/api/v1/agent/chat/**", async (route) => { const request = route.request(); const pathname = new URL(request.url()).pathname; if (pathname.endsWith("/stream")) { await route.fulfill({ status: 200, headers: { "Cache-Control": "no-cache", "Content-Type": "text/event-stream", "X-Vercel-AI-UI-Message-Stream": "v1" }, body: createQuestionStream() }); return; } if (pathname.endsWith("/question/question-1/reply")) { replies.push({ pathname, body: request.postDataJSON() }); await route.fulfill({ json: {} }); return; } if (pathname.endsWith("/models")) { await route.fulfill({ json: { default_model: "test/model", models: [ { id: "test/model", label: "测试模型", description: "Question 交互测试", icon: "bolt" } ] } }); return; } if (pathname.endsWith("/sessions")) { await route.fulfill({ json: { sessions: [] } }); return; } await route.fulfill({ json: {} }); }); } function createQuestionStream() { const question = { session_id: "question-session", questions: [ { header: "分析范围", question: "你想分析哪一部分?", options: [ { label: "雨污混接分析", description: "检查混接问题" } ] } ], tool: { messageID: "assistant-question", callID: "call-1" } }; const parts = [ { type: "start", messageId: "assistant-question" }, { type: "text-start", id: "answer" }, { type: "text-delta", id: "answer", delta: "请选择分析范围。" }, { type: "data-question_request", id: "question-1", data: { ...question, request_id: "question-1", created_at: 100 } }, { type: "data-question_request", id: "call-1", data: { ...question, request_id: "call-1", created_at: 120 } }, { type: "text-end", id: "answer" }, { type: "finish", finishReason: "stop" } ]; return parts.map((part) => `data: ${JSON.stringify(part)}\n\n`).join(""); }