CopilotKit 预构建弹窗(CopilotPopup)验收指南:以 LlamaIndex 集成为例的 QA 全流程解析
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
本文以 CopilotKit 仓库中 LlamaIndex 集成示例的 QA 验证清单 showcase/integrations/llamaindex/qa/prebuilt-popup.md 为主线,结合对应的 demo 页面、Playwright 端到端测试与后端 AG-UI Agent 实现,完整讲解预构建弹窗组件<CopilotPopup />的接入方式、关键配置项与可自动化的验收方法。读完本文,你将掌握如何验证一个浮动弹窗聊天组件是否正确挂载、默认展开、与后端 Agent 正常对话,以及如何把这一系列人工 QA 步骤改写成可重复执行的自动化测试。
一、QA 清单在项目中的定位
prebuilt-popup.md是 CopilotKit showcase 体系中按功能维度拆分的 QA 检查清单之一。在showcase/integrations/llamaindex/qa/目录下还并存着prebuilt-sidebar.md、agentic-chat.md、tool-rendering.md等同构清单,它们共同覆盖了该集成(LlamaIndex)在 CopilotKit 上支持的全部功能特性。
该清单的 4 个验收点分别为:
- 导航到
/demos/prebuilt-popup路由; - 验证浮动
<CopilotPopup />启动器(launcher)可见; - 验证弹窗默认处于打开状态;
- 发送
"Say hi from the popup!"并验证 Agent 正常响应。
其中"导航到指定路由"是前提,后三点则分别对应弹窗组件的三个核心行为:挂载与呈现、默认展开状态、消息收发链路。这三个行为也正是清单对应功能(manifest.yaml 中 features 列表里登记的prebuilt-popup)在 prebuilt-popup.spec.ts 端到端测试中被逐一验证的对象。
二、第一步:确认 Demo 路由与页面挂载
清单要求先导航到/demos/prebuilt-popup。该路由的入口组件位于 showcase/integrations/llamaindex/src/app/demos/prebuilt-popup/page.tsx:
"use client"; import React from "react"; import { CopilotKit, CopilotPopup } from "@copilotkit/react-core/v2"; import { MainContent } from "./main-content"; import { Suggestions } from "./suggestions-mount"; export default function PrebuiltPopupDemo() { return ( // @region[popup-basic-setup] <CopilotKit runtimeUrl="/api/copilotkit" agent="prebuilt-popup"> <MainContent /> <CopilotPopup agentId="prebuilt-popup" defaultOpen={true} labels={{ chatInputPlaceholder: "Ask the popup anything...", }} /> <Suggestions /> </CopilotKit> // @endregion[popup-basic-setup] ); }这段代码同时展示了预构建弹窗的最小可用接入模式,其中三个要点直接决定了 QA 清单能否通过:
<CopilotKit>根组件:通过runtimeUrl指向同仓库下的 Next.js API 路由/api/copilotkit,并通过agent指定默认 Agent。<CopilotPopup />浮层:agentId与 Provider 的agent保持一致;defaultOpen={true}决定首屏是否展开;labels.chatInputPlaceholder覆盖默认的输入占位文案。- 页面正文与弹窗解耦:
<MainContent />渲染的是普通页面内容(见 main-content.tsx),弹窗作为浮动层叠加其上——这正是"popup 浮在页面上方、原有布局保持不变"的产品形态。
页面正文在 e2e 测试中承担"路由已挂载"的断言锚点:page.getByRole("heading", { name: "Popup demo" })必须可见,其文案与 main-content.tsx 中的<h1>Popup demo</h1>逐字对应。这意味着验收第一步的核心是确认路由渲染出预期页面骨架,而非仅仅不报错。
三、第二步:验证浮动启动器(Launcher)可见
清单的第二项是验证<CopilotPopup />的浮动 launcher 气泡可见。启动器是弹窗关闭时留在页面角落的圆形悬浮按钮,用于再次唤起聊天面板。
在 prebuilt-popup.spec.ts 中,launcher 通过稳定的测试标识符定位:
await expect( page.locator('[data-testid="copilot-chat-toggle"]').first(), ).toBeVisible();copilot-chat-toggle是 CopilotPopup 内部渲染的开关按钮的data-testid,它在本仓库的整个预构建组件体系中是一致约定的定位锚点(prebuilt-sidebar等其他 demo 的 e2e 测试也采用同样的 testid 模式)。从实现结构看,启动器与弹窗内容属于同一组件的两个渲染状态:关闭时仅渲染启动器,打开时渲染启动器加聊天面板。该断言因此既验证了组件已挂载,也隐含验证了组件具备"可再次唤起"的能力。
四、第三步:验证默认展开(defaultOpen)
清单第三项要求弹窗默认打开。这一行为由页面上的defaultOpen={true}属性直接驱动。defaultOpen是<CopilotPopup />的首屏初始状态开关:为true时组件在首次渲染即展开聊天面板,为false(默认值)时则收起为角落的 launcher 气泡。
e2e 测试对该行为的验证方式非常值得借鉴——它没有直接断言组件状态,而是断言自定义占位文案可见:
// defaultOpen={true} means the popup window is open on first paint. The // demo sets a custom placeholder via labels.chatInputPlaceholder — we // assert on that literal string to prove the popup rendered AND its // labels override took effect. await expect( page.getByPlaceholder("Ask the popup anything..."), ).toBeVisible();由于输入框只存在于展开的聊天面板内部,"Ask the popup anything..."这个占位符可见,就等价于"面板已展开"。同时,这个断言还顺带验证了labels.chatInputPlaceholder自定义项生效——一举两得。这也提醒我们:QA 时选择"只在目标状态出现的唯一文案"作为断言依据,比直接断言布尔状态更稳健。
补充一点与关闭行为相关的实现细节:测试注释明确说明"关闭时 CopilotPopupView 会卸载其内容(由其内部isRendered状态跟踪)",因此弹窗关闭的可靠信号是data-testid="copilot-popup"从 DOM 中消失,而非被 CSS 隐藏。测试随后通过点击copilot-chat-toggle重新唤起弹窗并断言copilot-popup再次可见,同时验证 URL 保持不变(/demos/prebuilt-popup$),证明展开/收起是纯客户端状态切换。
五、第四步:发送消息并验证 Agent 响应
清单最后一项要求发送"Say hi from the popup!"并验证 Agent 回复。这条消息其实来自 Demo 页面上注册的"建议气泡"(suggestion pill),而非手动输入。
5.1 建议气泡的注册方式
建议由 suggestions.ts 通过useConfigureSuggestions钩子注册,并由 suggestions-mount.tsx 挂载到页面:
"use client"; import { useConfigureSuggestions } from "@copilotkit/react-core/v2"; export function usePrebuiltPopupSuggestions() { useConfigureSuggestions({ suggestions: [ { title: "Say hi", message: "Say hi from the popup!" }, { title: "Limerick", message: "Write me a quick limerick.", }, { title: "Is 17 prime?", message: "Walk me through whether 17 is prime.", }, ], available: "always", }); }suggestions数组中的每一项包含显示用的title与发送用的message;available: "always"表示这些建议气泡始终可用(不限定在空聊天等特定状态下)。QA 清单中的消息"Say hi from the popup!"正是第一条建议{ title: "Say hi", message: "Say hi from the popup!" }的message字段——人工 QA 时点击气泡即可发出该消息。
e2e 测试对建议气泡的定位是data-testid="copilot-suggestion"且文本包含"Say hi":
const sayHiPill = page .locator('[data-testid="copilot-suggestion"]') .filter({ hasText: "Say hi" }) .first(); await expect(sayHiPill).toBeVisible({ timeout: 15000 }); await sayHiPill.click();点击后,测试断言data-testid="copilot-assistant-message"的助手消息可见(超时 45 秒,为后端 LLM 往返预留余量)。该测试还覆盖了另一条手动输入路径:向输入框fill("Hello")后点击copilot-send-button,同样断言收到助手回复。
5.2 消息如何到达 LlamaIndex Agent
消息从弹窗发出的完整链路如下:
- 浏览器将消息发送到
runtimeUrl指向的 Next.js API 路由 showcase/integrations/llamaindex/src/app/api/copilotkit/route.ts; - 路由通过
createCopilotRuntimeHandler+new CopilotRuntime({ agents })建立 CopilotKit Runtime,并以 AG-UI 协议将请求代理到独立进程中的 LlamaIndex Agent 服务(默认http://localhost:8000); prebuilt-popup属于该路由注册的sharedAgentNames之一,其请求由agents表中的createAgent()(即new HttpAgent({ url: "${AGENT_URL}/run" }))转发到 Agent 服务的默认/run端点;- LlamaIndex 侧由 src/agents/agent.py 构建的
FixedAGUIChatWorkflow(使用OpenAI(model="gpt-4.1"))处理对话,回复经同一链路流式返回弹窗。
从源码结构看,prebuilt-popup与多数共享 demo 共用同一后端 Agent——per-demo 的行为差异主要由前端驱动(如本 demo 的建议气泡、labels定制、弹窗布局),而非各自独立的系统提示词。这一点在路由文件的注释中也有明确说明:// Shared-router agents — every id here resolves to the same backend + same tool set. Per-demo behavior is driven by the frontend.
Agent 的系统提示词要求"保持回复简洁(1 到 2 句话)",因此对"Say hi from the popup!"这类寒暄,预期响应是简短的自然语言文本。这也解释了为什么 QA 只验证"收到回复"而非"收到特定回复"——后端是无工具(no tools)的中性对话,重点是端到端链路贯通。
六、把人工 QA 升级为自动化测试
人工按清单逐项点击验证,是发布前的最低保障;但同样的 4 个验收点在 prebuilt-popup.spec.ts 中已被固化为 4 个 Playwright 测试用例,构成一个可重复执行的回归屏障:
| QA 清单项 | 对应测试用例 | 关键断言 |
|---|---|---|
导航到/demos/prebuilt-popup | page loads with heading and the popup open by default | heading: "Popup demo"可见 |
| launcher 可见 | 同上 | copilot-chat-toggle可见 |
| 弹窗默认打开 | 同上 | 占位符Ask the popup anything...可见(data-testid="copilot-popup"亦可见) |
| 发送消息并收到回复 | "Say hi" suggestion pill...与typing a message and clicking send... | copilot-assistant-message可见 |
| (额外)关闭/重开 | popup close button unmounts... | 关闭后copilot-popup隐藏,点击 toggle 后再次可见,URL 不变 |
测试代码中还沉淀了两条宝贵的实战经验,值得在编写类似测试时复用:
cpk-web-inspector叠加层会拦截指针事件:在 localhost 开发环境下自动启用的cpk-web-inspector会拦截 Playwright 的基于指针的click(),因此关闭按钮的点击需通过page.evaluate(() => document.querySelector(...).click())以 JS 层点击绕过(注释中说明这与共享工具_genuine-shared.ts:clickByJs是同一模式)。- 输入框回车提交不稳定:测试注释指出"textarea 上的 Enter 提交在该部署上偶发丢失",因此提交消息统一点击
copilot-send-button按钮,这是每个聊天输入区都稳定存在的提交入口。
七、排查建议
当任一 QA 步骤失败时,可按如下顺序定位(均以本仓库实际结构为依据):
- 路由 404:确认页面位于 src/app/demos/prebuilt-popup/ 目录下,Next.js App Router 会据此生成
/demos/prebuilt-popup路由。 - launcher 不可见:确认
<CopilotPopup />位于<CopilotKit>之内,且未设置会导致其隐藏的自定义样式;启动器的 testid 为copilot-chat-toggle。 - 弹窗未默认展开:检查
defaultOpen是否显式设为true(默认值为false,即收起状态)。 - Agent 无响应:先访问
/api/copilotkit的GET健康探针(其返回 JSON 中包含agent_url与agent_status,可用于确认后端http://localhost:8000是否可达、OPENAI_API_KEY是否已配置);确认 Provider 的agent与CopilotPopup的agentId一致(本 demo 均为prebuilt-popup),且该名称在 route.ts 的sharedAgentNames或specializedAgents中已注册。需要逐请求排障时,可设置环境变量SHOWCASE_ROUTE_DEBUG=1开启该路由的详细日志。
八、小结
一个只有四行的 QA 清单,背后实际上覆盖了预构建弹窗组件完整的三层链路:前端组件挂载与初始状态(defaultOpen、labels)、交互入口(launcher 气泡与 suggestion pill)、以及端到端消息收发(Next.js Runtime 代理 → AG-UI → LlamaIndex Agent)。本文对应的实现文件均可直接在仓库中对照阅读:demo 页面、建议气泡注册、后端 Agent 工作流、运行时代理路由与端到端测试。照此流程,你可以把任何一个预构建组件 demo 的人工验收,快速升级为可长期回归的自动化测试。
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考