## 修复清单
### 后端路由修复
1. — 新增 GET 路由与现有 POST 并存
- 前端 widget.html 原使用 GET 调用但路由仅注册 POST,导致 404
- 修复: 注册 GET + POST 双路由,handler 支持 query param + JSON body
### 前端修复
2. — Config 调用方式修正
- GET /api/v1/widget/config?website_token= → POST /api/v1/widget/config
- 发送 JSON body: {"website_token":"..."}
- 后端 Config handler 已同时支持两种调用方式
### 文档更新
3. 修正测试计划 §13.7 已知未实现端点状态
- 标记 4 项已验证正常(copilot/config、agent_bot_inboxes/、reports/overview)
- 标记 2 项已修复(widget/config GET+POST)
- 保留 1 项 P3 未实现(conversation_workflows 需完整 CRUD handler)
### 已验证非 BUG 项
- 仪表盘会话计数的0问题:实际显示 1/13/14 ✅(之前为 stale snapshot)
- 会话列表为空:filter status=open 过滤掉 snoozed 会话 ✅(设计如此)
- Reports API:正确路由 /reports?metric=&since=&until= ✅
1052 lines
56 KiB
Markdown
1052 lines
56 KiB
Markdown
# CDP 用户功能全量测试计划
|
||
|
||
> 创建:2026-07-15
|
||
> 最后更新:2026-07-22
|
||
> 目标:连接已启动的 GoChat 本地服务,用 Chrome DevTools Protocol 按真实用户路径覆盖 dashboard / widget / settings / Captain / Copilot / public surfaces。
|
||
> 当前服务由人工启动,不由测试脚本托管。
|
||
|
||
## 0. 当前前提
|
||
|
||
已启动:
|
||
|
||
```bash
|
||
pnpm dev:backend
|
||
pnpm dev:frontend
|
||
GOCHAT_WEBHOOK_URL=http://127.0.0.1:3000/webhooks/fake/fake_01 FAKE_AUTO_REPLY=true pnpm fake:start
|
||
```
|
||
|
||
本轮计划默认以上述三条人工启动命令为权威运行基线;后续 CDP 验收、数据补齐清单、以及 `fake:ai` 设计都以这套本地端口和进程拓扑为前提。
|
||
|
||
### 0.1 当前推荐的 CDP 连接方式(Sunday, July 19, 2026)
|
||
|
||
这轮计划默认优先复用已经打开的 Chrome,会比重新拉起 headed 浏览器更稳,也更符合“接现有人工会话继续点测”的目标。
|
||
|
||
建议默认使用:
|
||
|
||
| 项 | 值 | 说明 |
|
||
|---|---|---|
|
||
| CDP debug port | `127.0.0.1:9222` | 优先 attach 已存在的 Chrome 会话 |
|
||
| version probe | `http://127.0.0.1:9222/json/version` | 读取 `webSocketDebuggerUrl` |
|
||
| 当前可复用 CLI | `/home/rogee/.npm/_npx/15c61037b1978c83/node_modules/chrome-devtools-mcp/build/src/bin/chrome-devtools.js` | 已在当前 QA 流程中实际使用过 |
|
||
|
||
建议连接顺序:
|
||
|
||
1. 先请求 `http://127.0.0.1:9222/json/version`,确认能拿到 `webSocketDebuggerUrl`。
|
||
2. 成功后直接 attach,不重新登录、不重开新浏览器。
|
||
3. 进入测试前先做一次 snapshot,确认当前 tab、当前账号、当前 account id。
|
||
4. 若 `9222` 不可用,再退回新开 Chrome,并固定 `--remote-debugging-port=9222`,避免同一轮报告里混入多套浏览器状态。
|
||
|
||
连接成功标准:
|
||
|
||
- `json/version` 返回 200;
|
||
- 能对当前 tab 成功 snapshot;
|
||
- 能读取 console / network;
|
||
- 能通过真实点击让页面发生路由变化。
|
||
|
||
### 0.2 测试环境异常时的重置策略(Wednesday, July 22, 2026)
|
||
|
||
这轮点测里已经确认过:测试环境一旦进入“持续重连 / `/cable` 抖动 / 页面大量 `429` / fake 平台残留旧消息”的脏状态,继续硬点只会把环境噪音和真实缺陷混在一起。因此后续执行时,把“允许重置并继续”写成正式策略,而不是临场救火。
|
||
|
||
重置优先级:
|
||
|
||
1. 先重置 fake 平台内存态,不动前后端。
|
||
2. 若 dashboard 已出现连续 `429`、`正在重连...` 挡点击、或 `/cable` 无法恢复,再重启 backend。
|
||
3. frontend 只在 Vite 自身白屏、热更新异常、或静态资源 5xx 时才重启。
|
||
4. 浏览器 tab 状态明显污染时,可以保留现有 Chrome/CDP 会话,但需要重新从 `/app/login` 走一遍点击链路。
|
||
|
||
推荐重置动作:
|
||
|
||
```bash
|
||
curl -fsS -X POST http://127.0.0.1:9100/api/reset
|
||
curl -fsS http://127.0.0.1:3000/health
|
||
curl -fsS http://127.0.0.1:9100/health
|
||
```
|
||
|
||
若 backend 已进入脏状态,直接重新执行:
|
||
|
||
```bash
|
||
pnpm dev:backend
|
||
```
|
||
|
||
若 fake 进程已退出或 webhook 指向失效,重新执行:
|
||
|
||
```bash
|
||
GOCHAT_WEBHOOK_URL=http://127.0.0.1:3000/webhooks/fake/fake_01 FAKE_AUTO_REPLY=true pnpm fake:start
|
||
```
|
||
|
||
重置后的恢复标准:
|
||
|
||
- `GET /health` 返回 200。
|
||
- `GET http://127.0.0.1:9100/health` 返回 `{"status":"ok"}`。
|
||
- 登录后 `/cable` 不再持续 401/403/断开重连。
|
||
- dashboard 底部不再常驻 `正在重连...` 浮层。
|
||
- 同一页面不再连续出现大批量 `429`。
|
||
|
||
报告要求:
|
||
|
||
- 一旦发生重置,必须在 QA report 中记下“重置原因 / 重置动作 / 重置后恢复结果”。
|
||
- 重置后恢复通过的页面,要和“真实功能缺陷”分开归类,避免把环境污染误记成产品 bug。
|
||
|
||
测试入口:
|
||
|
||
| 服务 | URL | 用途 |
|
||
|---|---|---|
|
||
| backend | `http://127.0.0.1:3000` | API、webhook、WebSocket |
|
||
| frontend | `http://127.0.0.1:3036` | dashboard / widget 前端 |
|
||
| fake channel | `http://127.0.0.1:9100` | 外部客户消息、出站消息断言 |
|
||
| CDP | `http://127.0.0.1:<debug-port>` | 连接现有 Chrome,优先不新开 headed browser |
|
||
|
||
## 1. 测试目标
|
||
|
||
1. 用 CDP 模拟真实用户点击、输入、上传、保存、导航、退出登录。
|
||
2. 每个页面至少验证:可打开、核心数据加载、主要操作可执行、错误态可见、无异常 API/console、刷新后状态仍正确。
|
||
3. 消息链路必须验证:客户入站 → dashboard 实时出现 → 客服回复 → fake 收到出站 → auto reply 回流 → dashboard 无刷新更新。
|
||
4. Chatwoot parity 相关页面以“前端实际请求成功 + UI 可用”为准,不只看路由存在。
|
||
5. AI/Copilot/Captain 测试不依赖真实 LLM Key;先补 `fake:ai`,让页面功能和后端调用链可自动断言。
|
||
6. 除登录页、widget/public 入口页外,dashboard 内部页面一律通过真实 UI 点击进入,不直接 `open` 深层内部 URL,避免把路由可达误判成用户可达。
|
||
|
||
## 2. CDP 执行协议
|
||
|
||
每个页面统一记录:
|
||
|
||
- `page.url`
|
||
- `document.title`
|
||
- `#app` 是否挂载
|
||
- console `error` / `warning`
|
||
- `Network.responseReceived` 中所有 `/api`、`/platform`、`/public`、`/cable`、`/webhooks` 的状态码
|
||
- `Runtime.exceptionThrown`
|
||
- 关键 DOM 文案或按钮存在性
|
||
- 操作前后截图
|
||
- 操作产生的 API 请求和响应摘要
|
||
|
||
导航约束:
|
||
|
||
- 允许直接打开:`/app/login`、widget/public 根入口、必要的外部 fake/fake:ai 观察接口。
|
||
- 不允许直接打开:`/app/accounts/:id/...` 下的深层功能页作为“通过”依据。
|
||
- dashboard 内导航必须由登录后侧边栏、列表项、按钮、tab、面包屑、弹窗入口逐步点击完成。
|
||
- 若页面只能通过手输 URL 才能访问,记录为信息架构或入口缺失问题,而不是直接算页面通过。
|
||
|
||
失败分级:
|
||
|
||
| 等级 | 标准 |
|
||
|---|---|
|
||
| P0 | 登录失败、dashboard 不可用、消息收发断、权限泄露、数据保存丢失 |
|
||
| P1 | 页面主要 CRUD 不可用、关键 API 4xx/5xx、实时事件错误 |
|
||
| P2 | 局部功能不可用、空态错误、表单校验不清晰 |
|
||
| P3 | 文案、布局、轻微 console warning、非阻断体验问题 |
|
||
|
||
最小 CDP harness 只需要:
|
||
|
||
1. 连接 `http://127.0.0.1:<debug-port>/json/version` 拿 `webSocketDebuggerUrl`。
|
||
2. `Page.enable`、`Runtime.enable`、`Network.enable`。
|
||
3. 注入 `window.__gochatQa` 记录 console、fetch、XHR、resource timing。
|
||
4. 用 `Runtime.evaluate` 点击和输入;必要时用 `Input.dispatchKeyEvent`。
|
||
5. 每个页面结束调用 `assertNoFailedBackendRequests()`。
|
||
|
||
跳过:新测试框架、复杂 Page Object、视觉 diff。等第一轮人工可读报告稳定后再加。
|
||
|
||
## 3. 预检查
|
||
|
||
| 检查 | 命令/动作 | 通过标准 |
|
||
|---|---|---|
|
||
| backend health | `GET /health` | 200 |
|
||
| frontend mount | 打开 `http://127.0.0.1:3036/app/login` | login 页面可见 |
|
||
| fake health | `GET http://127.0.0.1:9100/health` | `status=ok` |
|
||
| CDP tooling | `command -v npx && node --version && npm --version` | `npx` 可用,Node/npm 正常 |
|
||
| fake 配置 | `POST /api/config` | webhook 指向 `fake_01`,auto reply 开启 |
|
||
| 登录账号 | seed 或 DB 查询 | 管理员、客服、普通 agent 可登录 |
|
||
| WebSocket | 登录后监听 `/cable` | 不循环 401/403/重连 |
|
||
| route parity 基线 | 参考 `docs/parity/route-parity.md` | 页面请求不应出现缺失路由 |
|
||
|
||
## 4. 基础测试数据缺口
|
||
|
||
需要补齐这些数据,否则“全量实际功能测试”会退化成空态浏览:
|
||
|
||
| 数据 | 最低数量 | 用途 |
|
||
|---|---:|---|
|
||
| Account | 1 | 主测试租户 |
|
||
| Administrator | 1 | 设置、成员、平台配置 |
|
||
| Agent | 2 | 分配、团队、在线状态、跨坐席实时 |
|
||
| Custom role 用户 | 1 | 权限边界 |
|
||
| Fake inbox `fake_01` | 1 | 消息 E2E |
|
||
| Website inbox | 1 | widget、pre-chat、campaign |
|
||
| API inbox | 1 | inbox 类型覆盖 |
|
||
| Email inbox | 1 | 邮件配置、SMTP/IMAP 页面 |
|
||
| Voice/Twilio inbox | 1 | voice 设置页和降级态 |
|
||
| Contact | 8+ | 列表、搜索、合并、标签、公司 |
|
||
| Company | 3+ | 公司详情、联系人关联 |
|
||
| Conversation | 12+ | open/resolved/pending/snoozed、assignee、team、label、priority |
|
||
| Messages | 每会话 3+ | incoming/outgoing/private/note/attachment/email |
|
||
| Labels | 5 | 会话/联系人标签、报表 |
|
||
| Teams | 2 | 团队分配、团队报表 |
|
||
| Canned responses | 3 | 回复框插入、CRUD |
|
||
| Macros | 3 | 宏执行、条件动作 |
|
||
| Automation rules | 3 | create/edit/clone/delete、条件校验 |
|
||
| Custom attributes | contact/conversation/company 各 2 | 表单渲染、筛选 |
|
||
| SLA policies / applied SLA | 2 | SLA 报表 |
|
||
| CSAT responses | 5 | CSAT 报表、公开页 |
|
||
| Dashboard apps | 1 | 侧边栏 iframe/app surface |
|
||
| Webhook subscriptions | 2 | 集成 webhook CRUD |
|
||
| Help center portal | 1 | portal、locale、category、article |
|
||
| Campaigns | live chat / sms / whatsapp 各 1 | campaign 页面 |
|
||
| Notifications | 5 | 通知列表、已读 |
|
||
| Audit logs | 5 | audit 页面 |
|
||
| Agent capacity policies | 2 | assignment policy |
|
||
| Captain assistant | 1 | Captain 页面根对象 |
|
||
| Captain document | 3 | 文档列表、上传/同步状态 |
|
||
| Captain response/FAQ | 5 | responses、pending |
|
||
| Captain scenario | 2 | scenario 页面 |
|
||
| Captain custom tool | 1 | tools 页面 |
|
||
| Copilot config | 1 fake provider | AI 功能不打真实外网 |
|
||
|
||
### 4.1 首轮必须先补的数据(否则会大面积 blocked)
|
||
|
||
| 优先级 | 数据 | 最低要求 | 影响范围 |
|
||
|---|---|---|---|
|
||
| P0 | Administrator | 1 个可登录管理员 | 所有 settings / Captain / reports |
|
||
| P0 | Agent | 2 个可登录 agent | 分配、协作、在线状态、mentions |
|
||
| P0 | Fake inbox `fake_01` | 已绑定 webhook 且可收发 | 会话主链路、实时消息 |
|
||
| P0 | Website inbox | 1 个带 `website_token` 的 live chat inbox | widget、pre-chat、campaign |
|
||
| P0 | 基础 conversations/messages | 至少 6 个会话、每个 3 条消息 | dashboard 列表、详情、筛选、报表 |
|
||
| P1 | Labels / Teams | labels 5 个、teams 2 个 | 标签、团队过滤、自动化、报表 |
|
||
| P1 | Contacts / Companies | contacts 8+、companies 3+ | CRM、搜索、合并、关联 |
|
||
| P1 | Custom attributes | 三类对象各 2 个 | 筛选器、详情表单、自动化 |
|
||
| P1 | Canned responses / Macros | 各 3 条 | 回复提效、设置页 CRUD |
|
||
| P1 | Automation rules | 3 条可编辑规则 | 自动化列表、编辑、校验 |
|
||
| P1 | Copilot fake provider | 1 套假配置 | Copilot/Captain 页面进入与联调 |
|
||
| P2 | Help center portal | 1 portal + category + article | portal/public/help center |
|
||
| P2 | Campaigns | live chat 至少 1 条 | campaign 页面、widget 触发 |
|
||
| P2 | CSAT / SLA 数据 | CSAT 5 条、SLA 2 条 | 报表、公开页、inbox csat |
|
||
| P2 | Audit logs / notifications | 各 5 条 | 列表页、跳转、筛选 |
|
||
|
||
建议策略:
|
||
|
||
1. 能由 UI 自举创建的,优先在首轮 CDP 中顺手创建并复用。
|
||
2. 会阻断主链路的种子数据(管理员、agent、fake/website inbox、基础 conversations)应在执行前一次性准备好。
|
||
3. Captain/Copilot 相关不要等真实第三方 Key,直接用 `fake:ai` 打通请求与错误态。
|
||
|
||
### 4.2 数据准备方式建议
|
||
|
||
把“缺数据”再分成三类,执行时更省时间:
|
||
|
||
| 类别 | 数据 | 建议来源 | 是否需要执行前准备 | 备注 |
|
||
|---|---|---|---|---|
|
||
| A | Administrator / Agent / Custom role 用户 | seed + 后台 Settings 手工补齐 | 是 | 登录、权限、分配依赖它们 |
|
||
| A | Fake inbox `fake_01` | 当前 fake channel + inbox 配置 | 是 | 主消息链路阻断项 |
|
||
| A | Website inbox | Settings 新建或 seed | 是 | widget / campaign / pre-chat 依赖 |
|
||
| A | 基础 conversations / messages | fake channel 批量造数 | 是 | 推荐至少覆盖 open / pending / resolved / snoozed |
|
||
| B | Contacts / Companies / Labels / Teams | 优先走 UI 创建,缺口再补 seed | 否 | 同时可顺手验证 CRUD |
|
||
| B | Canned responses / Macros / Automation rules | 走 UI 创建 | 否 | 适合首轮 CDP 过程中创建并复用 |
|
||
| B | Custom attributes | 走 UI 创建 | 否 | 可直接覆盖表单和筛选能力 |
|
||
| B | Help center portal / locale / article | 走 UI 创建 | 否 | 既补数据又验证 portal 后台 |
|
||
| C | CSAT / SLA / Audit logs / Notifications | 定向 seed 或接口回灌 | 视页面而定 | 纯空态也能先验渲染,但无法完成“全量功能”断言 |
|
||
| C | Captain documents / responses / scenarios / tools | `fake:ai` + Captain 后台创建 | 是(若要做 AI 主链路) | 没有 `fake:ai` 时只能做页面渲染检查 |
|
||
| C | Campaigns(live chat / sms / whatsapp) | Settings / Campaign UI 创建 | 否 | 需要 website inbox 与 portal 先到位 |
|
||
|
||
建议最小准备顺序:
|
||
|
||
1. 先准备管理员、2 个 agent、`fake_01`、website inbox。
|
||
2. 用 fake channel 批量灌入基础会话和消息。
|
||
3. 再通过 UI 顺手创建 labels / teams / macros / canned responses / custom attributes。
|
||
4. AI 相关最后统一切到 `fake:ai`,避免前面主链路被外部依赖拖住。
|
||
|
||
### 4.3 当前仓库已具备的数据准备能力 vs 仍需补齐项
|
||
|
||
为了避免把“已有 smoke seed 能力”和“真正缺失的数据/能力”混为一谈,这里按当前仓库实际情况再拆一次。
|
||
|
||
当前仓库里已经存在可直接复用的 smoke seed 基线,入口是:
|
||
|
||
```bash
|
||
cd backend
|
||
go run ./cmd/gochat seed
|
||
```
|
||
|
||
按当前 `cmd/gochat seed` 的实现,已经能稳定准备出这些基础对象:
|
||
|
||
| 类别 | 当前 seed 覆盖情况 | 备注 |
|
||
|---|---|---|
|
||
| Administrator | 已覆盖 1 个 | 默认 `admin@gochat.local / changeme`,可通过环境变量覆盖 |
|
||
| Account | 已覆盖 1 个 | 默认 `Test Account` |
|
||
| Website inbox | 已覆盖 1 个 | `web_widget` inbox,带 `website_token` |
|
||
| Voice/Twilio-like inbox | 已覆盖 1 个 | 当前是 `twilio_sms` 型 smoke inbox,适合页面和降级态验证 |
|
||
| Contact | 已覆盖 1 个 | `Smoke Customer` |
|
||
| Company | 已覆盖 1 个 | `Smoke Company` |
|
||
| Conversation | 已覆盖 1 个 | 已绑定 contact / inbox / assignee |
|
||
| Messages | 已覆盖 3 条 | incoming / outgoing / CSAT template 各 1 条 |
|
||
| Help Center portal/category/article | 已覆盖 1 套 | 适合 articles / preview / public help center 基线 |
|
||
| CSAT | 已覆盖 1 条模板消息 | 够做基础渲染,不够做分布/列表型报表 |
|
||
| SLA policy | 已覆盖 1 条 | 够做基础页面进入 |
|
||
| Custom role | 已覆盖 1 条 | 可做权限页基线 |
|
||
| Capacity policy | 已覆盖 1 条 | 可做 assignment policy 基线 |
|
||
| Captain assistant | 已覆盖 1 条 | 只够列表/详情基线,不够真实 AI 回路 |
|
||
| Captain message | 已覆盖 1 条 | 适合 conversation 内 Captain message 渲染 |
|
||
| Agent bot | 已覆盖 1 条 | 适合 agent bot 页面基线 |
|
||
|
||
基于当前代码和脚本,仍然明确缺失、会影响“全量实际功能测试”的项如下:
|
||
|
||
| 优先级 | 缺失项 | 为什么缺 | 直接影响 |
|
||
|---|---|---|---|
|
||
| P0 | 第二个可登录 agent | 当前 smoke seed 只准备管理员,没有双坐席协作基线 | 分配、在线状态、mentions、团队协作、跨坐席实时 |
|
||
| P0 | `fake_01` 对应 inbox 基线 | 当前 seed 侧重 `web_widget` smoke inbox,不是 fake channel inbox | fake webhook 主链路、会话实时回流、回复 E2E |
|
||
| P0 | 6+ 条不同状态会话 | 当前 seed 只有 1 条 open conversation | dashboard 列表、筛选、报表、批量操作、状态流转 |
|
||
| P0 | 更丰富的消息类型 | 当前仅 text + CSAT template | private note、attachment、email、AI 消息、系统事件渲染 |
|
||
| P1 | Teams 2 条以上 | 当前未见 smoke team 基线 | team 过滤、team assign、team report |
|
||
| P1 | Labels 5 条以上 | 当前会话只有字符串标签,不是完整标签数据集 | 标签设置页、筛选器、报表 |
|
||
| P1 | Contacts 8+ / Companies 3+ | 当前各只有 1 条 | CRM 列表、搜索、合并、关联、空态外真实分页 |
|
||
| P1 | Canned responses / Macros / Automations | 当前 seed 未覆盖 | 设置 CRUD、回复提效、自动化规则 |
|
||
| P1 | Custom attributes 三类对象各 2 条 | 当前仅对象上有少量属性值,不是完整属性定义 | 表单、筛选、详情编辑 |
|
||
| P1 | Notifications / Audit logs 有内容 | 当前 seed 未覆盖可见事件流 | 通知页、审计日志页、跳转链路 |
|
||
| P1 | Copilot fake provider | 当前仓库只有真实配置入口,没有本地 fake provider 基线 | Copilot/Captain 成功/失败/超时/429 验证 |
|
||
| P2 | Email inbox / API inbox | 当前 smoke seed 未覆盖 | channel create/edit、provider 配置、空态/降级态之外的实际流程 |
|
||
| P2 | Campaign 样本 | 当前未覆盖 live chat / sms / whatsapp 实例 | campaign 列表、编辑、启停、触发条件 |
|
||
| P2 | Captain documents / responses / scenarios / tools | 当前仅 assistant/message 基线 | Captain 子页 CRUD、embedding、playground、tool 调用 |
|
||
| P2 | 多 locale Help Center 数据 | 当前 seed 基本是单 portal、单 locale | locales / categories / settings 多语言验证 |
|
||
|
||
建议把数据准备再拆成三条线并行推进:
|
||
|
||
1. `cmd/gochat seed` 继续承担“可登录 + 可进入页面”的 smoke 基线。
|
||
2. `channels/fake` 负责批量制造真实消息、会话状态变化、typing、agent online/offline。
|
||
3. 新增 `fake:ai` 后,再把 Copilot / Captain 的成功、失败、超时、429 路径补齐成可回放证据。
|
||
|
||
### 4.4 按页面分组看数据阻断关系
|
||
|
||
为了执行时不把“页面 bug”和“数据没准备好”混为一谈,建议按页面分组提前标记它们依赖的最小数据集:
|
||
|
||
| 页面组 | 最小依赖数据 | 缺失时会怎么 blocked |
|
||
|---|---|---|
|
||
| 登录 / 账号切换 / Profile | administrator、至少 1 个 account、至少 1 个可登录 agent | 无法进入主应用、无法验证 account switch / profile 保存 |
|
||
| Dashboard / Conversations / Mentions / Unattended | `fake_01` inbox、6+ conversations、3+ message types、2 个 agent | 列表空、实时链路无证据、分配/协作不可测 |
|
||
| Contacts / Companies | 8+ contacts、3+ companies、custom attributes | 只能看到空态,搜索/合并/关联无法完成 |
|
||
| Reports | conversations、labels、teams、CSAT、SLA、audit/event 数据 | 图表和表格只剩空态,无法验证筛选/导出/维度切换 |
|
||
| Settings - Agents / Teams / Labels | 2 个 agent、2 个 teams、5 个 labels、1 个 custom role | 只能验渲染,无法验成员绑定、权限边界、筛选联动 |
|
||
| Settings - Inboxes / Channel create | website inbox、fake inbox、api/email/voice 至少部分样本 | 只能看入口,无法验证编辑页、collaborators、business hours、channel 特有字段 |
|
||
| Settings - Automation / Macros / Canned Responses | 3 automation、3 macros、3 canned responses | 列表可打开但无法验证 CRUD 与执行结果 |
|
||
| Help Center | 1 portal、1 locale、2 categories、3 articles | 只能做最浅页面进入,发布/预览/分类/多语言不完整 |
|
||
| Campaigns | website inbox、portal、至少 1 live chat campaign 样本 | 只能看空态,无法验证创建/启停/触发条件 |
|
||
| Notifications / Audit logs | 5+ notifications、5+ audit logs | 只能确认页面壳存在,跳转和已读不可测 |
|
||
| Copilot / Captain | fake provider、assistant、documents、responses、scenarios、tools | 只能验页面渲染,无法做 AI 成功/失败/超时全链路 |
|
||
| Widget / public | website inbox、public help center、CSAT 样本、真实站内入口 | 即使内部后台健康,也无法完成 public/user 侧闭环验收 |
|
||
|
||
推荐执行时先给每个页面组打一个前置标签:
|
||
|
||
- `ready`:数据和入口都齐,可以做完整功能断言
|
||
- `render-only`:只能做渲染/空态/入口断言
|
||
- `blocked-by-data`:缺数据,先补数据再测
|
||
- `blocked-by-entrypoint`:入口未接通,不应靠手输内部 URL 绕过
|
||
|
||
## 5. `fake:ai` 最小方案
|
||
|
||
先加一个本地 fake AI 服务,目标是“可测”,不是模拟完整 LLM。
|
||
|
||
建议命令:
|
||
|
||
```bash
|
||
pnpm fake:ai
|
||
```
|
||
|
||
建议端口:`9200`。
|
||
|
||
最小 API:
|
||
|
||
| 方法 | 路径 | 用途 | 响应 |
|
||
|---|---|---|---|
|
||
| GET | `/health` | 健康检查 | `{ "status": "ok" }` |
|
||
| GET | `/v1/models` | OpenAI-compatible 模型列表 | `fake-chat`, `fake-embedding` |
|
||
| POST | `/v1/chat/completions` | Copilot / Captain 回复 | 回显 prompt 摘要,支持固定 delay/error |
|
||
| POST | `/v1/embeddings` | 文档 embedding | 固定维度向量 |
|
||
| POST | `/api/config` | 切换模式 | `ok/error/slow/rate_limit` |
|
||
| GET | `/api/requests` | 测试断言 | 最近请求列表,脱敏 Authorization |
|
||
| POST | `/api/reset` | 清空状态 | `{ "status": "ok" }` |
|
||
|
||
Provider 配置建议:
|
||
|
||
| 字段 | 值 |
|
||
|---|---|
|
||
| provider | `openai_compatible` |
|
||
| base_url | `http://127.0.0.1:9200/v1` |
|
||
| api_key | `fake-ai-key` |
|
||
| chat model | `fake-chat` |
|
||
| embedding model | `fake-embedding` |
|
||
| embedding dimensions | `1536` |
|
||
|
||
必须覆盖的 AI 场景:
|
||
|
||
1. Copilot 配置页保存 fake provider。
|
||
2. 测试连接成功、失败、超时、429 四种状态。
|
||
3. Captain Playground 输入问题,收到 fake answer。
|
||
4. Captain document embedding 调用 fake embeddings。
|
||
5. Agent 回复框 AI 改写/建议调用 fake chat。
|
||
6. 错误态不泄露 API key。
|
||
|
||
跳过:真实 OpenAI/Anthropic/DeepSeek 兼容性;等 fake 链路稳定再做外部 provider smoke。
|
||
|
||
### 5.1 `fake:ai` 实现方式建议(直接复用 fake channel 骨架)
|
||
|
||
为了少造轮子,`fake:ai` 建议直接按 `channels/fake` 的组织方式复制一套最小骨架:
|
||
|
||
| 项 | 建议 |
|
||
|---|---|
|
||
| 目录 | `channels/fake-ai/` |
|
||
| 启动脚本 | 根 `package.json` 增加 `fake:ai` / `fake:ai:dev` / `fake:ai:test` |
|
||
| 运行时 | `tsx src/index.ts` |
|
||
| HTTP 框架 | 继续用 Express,和 fake channel 保持一致 |
|
||
| 状态存储 | 先用内存 store,记录最近请求、模式、延迟、错误注入 |
|
||
| 模式切换 | `/api/config` 支持 `ok` / `error` / `slow` / `rate_limit` |
|
||
| 测试断言 | `/api/requests` 返回最近 chat / embeddings 请求摘要 |
|
||
| 脱敏 | 所有请求日志都隐藏 Authorization / api_key |
|
||
|
||
建议脚本:
|
||
|
||
```json
|
||
{
|
||
"fake:ai": "cd channels/fake-ai && tsx src/index.ts",
|
||
"fake:ai:dev": "cd channels/fake-ai && tsx watch src/index.ts",
|
||
"fake:ai:test": "pnpm --dir channels/fake-ai test"
|
||
}
|
||
```
|
||
|
||
这样后续维护会和 `fake:start` 基本同构,排查成本也低。
|
||
|
||
补充说明:当前仓库根 `package.json` 里已经有 `fake:start / fake:dev / fake:test`,并且 `pnpm-workspace.yaml` 已纳入 `channels/fake`;但还没有现成的 `channels/fake-ai` 目录和 `fake:ai` 脚本,所以这部分目前仍属于待新增测试支撑能力,而不是现成可执行项。
|
||
|
||
### 5.2 `fake:ai` 断言矩阵
|
||
|
||
`fake:ai` 不只是“让页面不报错”,还要能作为 CDP 回放时的稳定证据源。建议第一版就固定支持下面这几类断言:
|
||
|
||
| 场景 | CDP 页面动作 | `fake:ai` 需要返回 | 测试证据 |
|
||
|---|---|---|---|
|
||
| Provider 连通性测试 | Settings → Copilot → Test connection | 200 + 固定模型列表 | 页面成功提示 + `/api/requests` 有 `GET /v1/models` |
|
||
| Provider 保存后首次使用 | 保存 fake provider 后进入 Copilot/Captain | `GET /v1/models` 或首次 chat 请求成功 | 配置持久化成功 + 后续 AI 页面可继续使用 |
|
||
| Copilot 发问 | 会话侧边栏输入问题并发送 | `POST /v1/chat/completions` 返回固定答复 | 气泡渲染 + `/api/requests` 记录 prompt 摘要 |
|
||
| Captain Playground | Playground 输入问题 | `POST /v1/chat/completions` 返回固定答复 | 页面回复 + 请求日志 |
|
||
| Reply suggestion / rewrite / summarize | 回复框触发 AI 建议 | `POST /v1/chat/completions` 返回不同 action 标记 | 建议文本正确落入 UI,对应 action 可区分 |
|
||
| Document embedding | 上传 URL/PDF 文档后触发 embedding | `POST /v1/embeddings` 返回固定维度向量 | 文档状态变化 + `/api/requests` 有 embedding 记录 |
|
||
| Slow provider | 切到 `slow` 模式后重试 | 延迟 3~8 秒再返回 200 | 页面 loading、可取消/可恢复、无死锁 |
|
||
| Error provider | 切到 `error` 模式后重试 | 500 或结构化错误 | toast / inline error 正确展示,不泄露 key |
|
||
| Rate limit | 切到 `rate_limit` 模式 | 429 | 页面可见限流反馈、不会假成功 |
|
||
| 敏感信息脱敏 | 任意 AI 请求 | `fake:ai` 仅记录掩码后的鉴权信息 | `/api/requests` 不出现明文 `Authorization` / `api_key` |
|
||
|
||
建议 `fake:ai` 每条请求至少记录这些字段,便于后续 report 复用:
|
||
|
||
- `ts`
|
||
- `method`
|
||
- `path`
|
||
- `mode`
|
||
- `model`
|
||
- `account_id`(若请求链路可带出)
|
||
- `request_summary`(截断后的 prompt/输入摘要)
|
||
- `response_status`
|
||
- `latency_ms`
|
||
- `auth_masked`
|
||
|
||
### 5.3 当前仓库核对结果(2026-07-19)
|
||
|
||
这部分是为了把“计划建议”和“当前仓库现实”分开:
|
||
|
||
| 项 | 当前状态 | 结论 |
|
||
|---|---|---|
|
||
| 根脚本 `fake:start / fake:dev / fake:test` | 已存在 | fake channel 现成可用,可直接作为消息 E2E 基线 |
|
||
| 根脚本 `fake:ai` | 不存在 | 需要新增 |
|
||
| workspace 包 | 当前只纳入 `frontend`、`channels/fake` | `channels/fake-ai` 需要加入 workspace |
|
||
| `channels/fake-ai/` 目录 | 不存在 | 需要新建最小服务骨架 |
|
||
| 现有 `channels/fake` 结构 | 已具备独立 package + tsconfig | 适合直接镜像出 `fake-ai` 的最小实现 |
|
||
|
||
因此,“AI 相关测试可参考 fakechannel 搞一个 fake:ai 来支持对接”在当前仓库里应拆成明确的补齐任务:
|
||
|
||
1. 新建 `channels/fake-ai/`,提供独立 `package.json`、`tsconfig.json`、`src/index.ts`。
|
||
2. 根 `package.json` 增加 `fake:ai`、`fake:ai:dev`、`fake:ai:test`。
|
||
3. `pnpm-workspace.yaml` 纳入 `channels/fake-ai`。
|
||
4. 提供最小 `/health`、`/v1/models`、`/v1/chat/completions`、`/v1/embeddings`、`/api/config`、`/api/requests`、`/api/reset`。
|
||
5. GoChat 内新增一套本地 fake provider 配置模板,方便 Copilot / Captain 直接切过去联调。
|
||
|
||
### 5.4 当前 `channels/fake` 已可直接复用的测试能力(2026-07-19 核对)
|
||
|
||
为了避免把 `fake channel` 和未来的 `fake:ai` 混在一起,这里把当前已经现成可用的 fake 消息平台能力单独列出来:
|
||
|
||
| 能力 | 当前端点 | 可直接支撑的测试 |
|
||
|---|---|---|
|
||
| 健康检查 | `GET /health` | 执行前确认 fake 服务在线 |
|
||
| 运行时改配置 | `POST /api/config` | 动态切换 webhook、token、auto reply、delay |
|
||
| 客户入站消息 | `POST /api/send` | 造新会话、造入站消息、验证 dashboard 实时出现 |
|
||
| 客户回复消息 | `POST /api/reply` | 验证 reply_to / 同会话追加消息 |
|
||
| 会话结束事件 | `POST /api/close` | 验证 session.end、关闭链路、状态流转 |
|
||
| typing 事件 | `POST /api/typing` | 验证 typing.start / typing.stop UI 提示 |
|
||
| agent 在线状态观测 | `POST /api/agent/online`、`POST /api/agent/offline` | 验证 presence / availability 展示 |
|
||
| 出站消息留痕 | `GET /api/messages`、`GET /api/messages/:id` | 断言客服回复有没有真正发回 fake 平台 |
|
||
| 平台状态观测 | `GET /api/status` | 观察 auto reply、消息计数、agent 状态 |
|
||
| 内存态重置 | `POST /api/reset` | 每轮 CDP 测试前清理平台状态 |
|
||
|
||
基于当前实现,`channels/fake` 已经足够支撑这几类真实验收:
|
||
|
||
1. fake 客户发消息 → GoChat 创建或追加会话。
|
||
2. 客服在 dashboard 回复 → fake 平台能收到 outbound。
|
||
3. auto reply 回流 → dashboard 无刷新出现下一条 incoming。
|
||
4. typing / session.end / agent online/offline 等外围实时事件可单独回放。
|
||
|
||
但它还不是“批量数据工厂”,当前仍缺这些会明显影响全量页面验收的能力:
|
||
|
||
- 批量制造多状态会话(open / pending / resolved / snoozed);
|
||
- 批量制造更多消息类型(private note、attachment、email-like、system event);
|
||
- 一次性造多联系人、多公司、多标签、多 team 的数据集;
|
||
- AI provider 兼容接口(这部分应由独立的 `fake:ai` 负责,而不是继续堆进 `channels/fake`)。
|
||
|
||
## 6. 页面功能矩阵
|
||
|
||
### 6.1 Auth / account lifecycle
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| 登录 | `/app/login` | 正确登录、错误密码、空表单校验、无注册链接、回车提交 |
|
||
| SSO 登录 | `/app/login/sso` | 无配置时错误态,配置后跳转态 |
|
||
| 重置密码 | `/app/auth/reset/password` | 表单校验、提交反馈 |
|
||
| 邮箱确认 | `/app/auth/confirmation` | token 缺失错误态 |
|
||
| 无账号 | `/app/no-accounts` | 空账号用户展示 |
|
||
| onboarding | `/app/accounts/:id/onboarding` | 首次账号信息表单 |
|
||
| suspended | `/app/accounts/:id/suspended` | 账号暂停页 |
|
||
|
||
### 6.2 Inbox / conversation
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Dashboard | `/app/accounts/:id/dashboard` | 会话列表、筛选、排序、在线状态、未读数 |
|
||
| 会话详情 | `/conversations/:conversation_id` | 消息渲染、发送、私密备注、附件、emoji、引用、草稿 |
|
||
| Inbox 会话 | `/inbox/:inbox_id` | inbox 筛选、列表一致性 |
|
||
| Label 会话 | `/label/:label` | 标签过滤、标签增删 |
|
||
| Team 会话 | `/team/:teamId` | 团队过滤、团队分配 |
|
||
| Custom view | `/custom_view/:id` | 自定义视图过滤、缺失视图重定向 |
|
||
| Mentions | `/mentions/conversations` | @ 提及列表 |
|
||
| Unattended | `/unattended/conversations` | 未处理会话 |
|
||
| Conversation search | 搜索入口 | 搜索结果、跳转详情 |
|
||
| Inbox view | `/inbox-view/:type/:id` | 聚合视图、详情页 |
|
||
|
||
核心操作:
|
||
|
||
1. fake 客户发消息,dashboard 不刷新出现新会话。
|
||
2. 客服回复,fake `/api/messages` 能查到 outbound。
|
||
3. auto reply 回流后同一会话追加 incoming。
|
||
4. 切换 open/resolved/pending/snoozed。
|
||
5. 分配 agent/team,刷新后仍保持。
|
||
6. 添加/移除 label、priority。
|
||
7. 上传图片/文件,检查预览和下载。
|
||
8. private note 不发送到 fake。
|
||
9. typing.start/typing.stop 有 UI 指示。
|
||
10. 多标签页实时同步。
|
||
|
||
说明:
|
||
|
||
- 当前 dashboard 登录后的默认会话请求是 `assignee_type=me`,即“我的”视图。
|
||
- fake 入站若创建的是未分配会话,验证会话可见性时应继续点击切到 `未分配的` 或 `所有的`,不要把“我的”视图下不可见误判成实时失败。
|
||
|
||
### 6.3 CRM
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Contacts | `/contacts` | 列表、搜索、分段、标签过滤、新建 |
|
||
| Contact detail | `/contacts/:contactId` | 编辑资料、custom attributes、会话历史、备注 |
|
||
| Companies | `/companies` | 列表、搜索、新建 |
|
||
| Company detail | `/companies/:companyId` | 编辑、关联联系人、历史记录 |
|
||
|
||
### 6.4 Reports
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Overview | `/reports/overview` | 指标卡、日期范围、图表 |
|
||
| Conversations | `/reports/conversations` | 表格、导出、筛选 |
|
||
| Agents | `/reports/agents` | agent 维度指标 |
|
||
| Labels | `/reports/labels` | label 维度指标 |
|
||
| Inboxes | `/reports/inboxes` | inbox 维度指标 |
|
||
| Teams | `/reports/teams` | team 维度指标 |
|
||
| CSAT | `/reports/csat` | 评分分布、评价列表 |
|
||
| SLA | `/reports/sla` | SLA 命中/违约 |
|
||
| Bot | `/reports/bot` | bot/assistant 指标 |
|
||
| Live reports | `/reports/live` | 实时数据刷新 |
|
||
|
||
### 6.5 Settings
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Account | `/settings/account` | 名称、语言、auto-resolve、删除保护 |
|
||
| Agents | `/settings/agents/list` | 创建 agent、临时密码、编辑、禁用、重置密码 |
|
||
| Teams | `/settings/teams/list` | 创建、成员、编辑、删除 |
|
||
| Inboxes list | `/settings/inboxes/list` | channel 列表、创建入口、fake/website 可见 |
|
||
| Inbox configuration | `/settings/inboxes/:id` | 名称、欢迎语、允许域名、sender name、保存 |
|
||
| Inbox collaborators | `/settings/inboxes/:id/collaborators` | agent 绑定 |
|
||
| Pre-chat form | `/settings/inboxes/:id/pre-chat-form` | 字段开关、必填、保存 |
|
||
| CSAT inbox | `/settings/inboxes/:id/csat` | 开关、消息模板 |
|
||
| Business hours | inbox 子页 | 每周时间、时区 |
|
||
| Channel create pages | `/settings/inboxes/new/*` | website/api/email/fake/twilio/line/tiktok/facebook 等表单和错误态 |
|
||
| Labels | `/settings/labels/list` | CRUD、颜色 |
|
||
| Custom attributes | `/settings/custom-attributes/list` | contact/conversation/company 属性 CRUD |
|
||
| Automation | `/settings/automation/list` | CRUD、条件/动作校验 |
|
||
| Macros | `/settings/macros` | CRUD、执行宏 |
|
||
| Canned responses | `/settings/canned-response/list` | CRUD、插入回复框 |
|
||
| Agent bots | `/settings/agent-bots` | bot 列表、绑定 inbox |
|
||
| Integrations | `/settings/integrations` | Slack/Linear/Notion/Webhook/Dashboard app 卡片和配置 |
|
||
| Webhooks | integration 子页 | CRUD、事件选择、签名字段 |
|
||
| Conversation workflow | `/settings/conversation-workflows` | 开关、保存 |
|
||
| Assignment policy | `/settings/assignment-policy/*` | assignment、capacity 创建/编辑 |
|
||
| Custom roles | `/settings/custom-roles/list` | 权限勾选、角色用户 |
|
||
| Audit logs | `/settings/audit-logs/list` | 列表、筛选 |
|
||
| Security/SAML | `/settings/security` | 无配置错误态、字段校验 |
|
||
| Billing | `/settings/billing` | subscription/limits 降级态 |
|
||
| Profile | `/profile/settings` | 资料、密码、消息签名、通知偏好、access token、MFA |
|
||
| Notifications | `/notifications` | 列表、已读、跳转 |
|
||
| Copilot 配置 | `/settings/copilot` 或当前实际入口 | fake provider 保存、测试连接、账户功能开关 |
|
||
|
||
### 6.6 Help Center
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Portals | `/portals/:navigationPath` | portal 列表、新建 |
|
||
| Articles | `/portals/:slug/:locale/articles` | 列表、草稿/发布 tab |
|
||
| Article editor | `articles/new` / `edit/:slug` | 标题、正文、保存、发布 |
|
||
| Categories | `/categories` | CRUD、排序 |
|
||
| Locales | `/locales` | locale 增删 |
|
||
| Portal settings | `/settings` | 域名、主题、SEO |
|
||
| Article preview | `/articles/preview/:articleSlug` | 公开预览 |
|
||
|
||
### 6.7 Campaigns
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Live chat campaigns | `/campaigns/live_chat` | 新建、编辑、启停、触发条件 |
|
||
| SMS campaigns | `/campaigns/sms` | 空配置降级、表单校验 |
|
||
| WhatsApp campaigns | `/campaigns/whatsapp` | provider 缺失提示、模板字段 |
|
||
|
||
### 6.8 Captain / AI
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Assistants | `/captain/:navigationPath` | assistant 列表、新建、切换 |
|
||
| FAQs / Responses | `/captain/:assistantId/faqs` | CRUD、搜索、批量删除 |
|
||
| Pending responses | `/faqs/pending` | approve/reject |
|
||
| Documents | `/documents` | 新建文档、上传、embedding 状态 |
|
||
| Tools | `/tools` | custom tool CRUD、参数、鉴权 |
|
||
| Scenarios | `/scenarios` | CRUD、启停 |
|
||
| Playground | `/playground` | 输入问题、fake AI 回复、错误态 |
|
||
| Inboxes | `/inboxes` | assistant 绑定 inbox |
|
||
| Settings | `/settings` | 名称、描述、开关 |
|
||
| Guardrails | `/settings/guardrails` | 规则保存 |
|
||
| Guidelines | `/settings/guidelines` | response guideline 保存 |
|
||
|
||
### 6.9 Widget / public
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| Widget home | `/widget?website_token=...#/home` | 可用性、欢迎语、campaign |
|
||
| Widget messages | `/widget?website_token=...#/messages` | 客户发消息、附件、emoji、历史 |
|
||
| Pre-chat widget | widget pre-chat | 表单字段、必填校验 |
|
||
| Article viewer | widget article route | 文章搜索、打开 |
|
||
| CSAT public | public CSAT route | 评分、评价提交 |
|
||
| Help center public | public portal route | 文章浏览、搜索 |
|
||
|
||
### 6.10 Global shell / personal / super admin
|
||
|
||
| 页面 | 路径 | 功能点 |
|
||
|---|---|---|
|
||
| 侧边栏与全局壳层 | dashboard 任意已登录页 | logo、主导航、收起/展开、未读徽标、当前激活态 |
|
||
| 用户菜单 | 侧边栏头像菜单 | 键盘快捷键弹层、更改外观、个人设置入口、退出登录 |
|
||
| 账号切换 | 用户菜单中的 account switcher | 不同 account 间切换、URL/accountId 同步、权限不足账号不应泄露 |
|
||
| 通知中心 | `/notifications` | 列表、已读、全部已读、跳转回原会话/对象 |
|
||
| Super admin 入口 | `/super_admin` | 仅授权用户可见、入口可打开、能返回主应用 |
|
||
| Super admin dashboard | super admin 当前实际默认页 | 概览卡片、列表、降级态 |
|
||
| Super admin playground | super admin playground | 可进入、表单可操作、无权限用户不可见 |
|
||
|
||
### 6.11 页面统一验收模板
|
||
|
||
为了避免“有些页面只看打开,有些页面又测到了保存”,后续 CDP 执行时建议所有页面按页面类型套同一套断言模板。
|
||
|
||
#### A. 列表页
|
||
|
||
适用:Dashboard、Contacts、Companies、Teams、Labels、Inboxes、Articles、Documents、Responses、Notifications。
|
||
|
||
统一断言:
|
||
|
||
1. 页面可通过真实点击进入。
|
||
2. 列表主表格/卡片区有数据或空态文案,不允许白屏。
|
||
3. 首屏加载请求全部成功,分页/排序/筛选请求状态码正确。
|
||
4. 搜索输入可操作,URL/query 参数与结果同步。
|
||
5. 点击一条记录可进入详情或编辑页。
|
||
6. 返回列表后筛选条件、滚动位置、tab 状态按产品预期保持。
|
||
7. 空态、无结果态、加载态可见。
|
||
8. console 无新的未捕获异常。
|
||
|
||
#### B. 详情页
|
||
|
||
适用:Conversation、Contact detail、Company detail、Inbox detail、Assistant detail、Portal detail。
|
||
|
||
统一断言:
|
||
|
||
1. 必须从列表/入口点击进入,不直接手输内部 URL。
|
||
2. 详情页标题、主信息区、侧栏信息区都已渲染。
|
||
3. 至少执行 1 个读操作和 1 个写操作(如编辑、切状态、加标签、保存备注)。
|
||
4. 保存后 toast/提示正确,刷新后状态仍在。
|
||
5. 若详情页包含关联对象(联系人/会话/文档/工具),至少点进 1 个二级对象。
|
||
6. 404/已删除/无权限的降级态可见且不崩溃。
|
||
|
||
#### C. 表单页
|
||
|
||
适用:创建 Inbox、创建 Agent、Automation、Macro、Custom Attribute、Portal/Article、Campaign、Custom Tool。
|
||
|
||
统一断言:
|
||
|
||
1. 必填项校验正确,错误文案清晰。
|
||
2. 合法数据可提交,非法数据被前端或后端拒绝。
|
||
3. 保存按钮 loading 态和防重复提交正确。
|
||
4. 成功后跳转、返回列表或停留当前页的行为符合预期。
|
||
5. 编辑已有对象时,默认值回填完整。
|
||
6. 离开未保存表单时,如产品定义有提醒则必须触发。
|
||
|
||
#### D. 报表页
|
||
|
||
适用:Overview、Agents、Labels、Inboxes、Teams、CSAT、SLA、Bot、Live Reports。
|
||
|
||
统一断言:
|
||
|
||
1. 默认时间范围有数据或空态,不白屏。
|
||
2. 切换日期范围会重新拉取数据,图表/表格同步变化。
|
||
3. 导出、下载、切换维度时请求参数正确。
|
||
4. 空数据时仍有结构化占位,而不是 `main` 空白。
|
||
5. 指标卡、图表 legend、表格列头与接口字段一致。
|
||
|
||
#### E. AI / Captain / Copilot 页
|
||
|
||
适用:Copilot 配置、Assistants、Responses、Documents、Tools、Scenarios、Playground。
|
||
|
||
统一断言:
|
||
|
||
1. 页面渲染与列表/表单交互正常。
|
||
2. 在 `fake:ai` `ok` 模式下,核心 AI 请求必须可成功走通。
|
||
3. 在 `error` / `slow` / `rate_limit` 模式下,错误态必须可见且不假成功。
|
||
4. 页面不能泄露明文 API key、Authorization、provider secret。
|
||
5. 所有 AI 页面都要同时保留 UI 证据和 `fake:ai` 请求证据。
|
||
|
||
## 7. Fake channel E2E 脚本化步骤
|
||
|
||
1. 重置 fake:
|
||
|
||
```bash
|
||
curl -fsS -X POST http://127.0.0.1:9100/api/reset
|
||
```
|
||
|
||
2. 确认配置:
|
||
|
||
```bash
|
||
curl -fsS -X POST http://127.0.0.1:9100/api/config \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"webhook_url":"http://127.0.0.1:3000/webhooks/fake/fake_01","auto_reply":true}'
|
||
```
|
||
|
||
3. 入站消息:
|
||
|
||
```bash
|
||
curl -fsS -X POST http://127.0.0.1:9100/api/send \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"inbox_identifier":"fake_01","sender_id":"cdp_customer_01","sender_name":"CDP测试客户","content":"CDP 全量测试入站消息"}'
|
||
```
|
||
|
||
4. CDP 断言 dashboard 出现 `CDP测试客户` 和消息内容。
|
||
5. CDP 在回复框发送 `收到,正在测试。`
|
||
6. fake 断言:
|
||
|
||
```bash
|
||
curl -fsS http://127.0.0.1:9100/api/messages
|
||
```
|
||
|
||
7. CDP 断言 auto reply 回流后同一会话增加客户消息。
|
||
|
||
## 8. 报告格式
|
||
|
||
生成到:
|
||
|
||
```text
|
||
docs/qa/reports/2026-07-15-cdp-user-function-report.md
|
||
.tmp/cdp-qa/2026-07-15/
|
||
```
|
||
|
||
报告必须包含:
|
||
|
||
- 服务基线:health、登录账号、测试 account id、fake config。
|
||
- 页面矩阵:pass/fail/blocked/skipped。
|
||
- 每个失败:复现路径、请求、响应、console、截图、影响等级。
|
||
- 数据缺口:缺哪条 seed 导致 blocked。
|
||
- fake channel 证据:`/api/messages` 摘要。
|
||
- fake:ai 证据:`/api/requests` 摘要。
|
||
|
||
## 9. 第一轮执行顺序
|
||
|
||
1. 登录和 WebSocket。
|
||
2. fake channel 消息 E2E。
|
||
3. Dashboard / conversation 核心路径。
|
||
4. Settings 中会影响后续数据的页面:agents、teams、inboxes、labels、attributes。
|
||
5. Reports。
|
||
6. Help Center / Campaigns / Widget。
|
||
7. Captain / Copilot,先用 `fake:ai`。
|
||
8. 权限用户回归:agent、custom role。
|
||
|
||
这样排是为了先证明链路活着,再铺开页面。最省事,也最不容易把数据缺口误判成产品 bug。
|
||
|
||
## 10. 当前已知高风险 / 预期阻断
|
||
|
||
以下问题已经在点击驱动验收中出现,后续 CDP 全量测试时应直接按“产品缺陷”记录,不要误归类成数据缺口:
|
||
|
||
| 项 | 当前现象 | 建议分类 |
|
||
|---|---|---|
|
||
| fake auto reply 回流 | 最新观测表明平台侧 echo 已回流,但 GoChat 侧会出现“新会话分叉”或“同会话重复/顺序异常渲染”两类缺陷,均应按链路问题记录 | P1 链路缺陷 |
|
||
| widget / public 入口 | Website inbox `脚本 / CodePen / 预览 / Chat mode` 已基本穷举,但仍未发现可作为“真实 public/widget 用户入口”验收的稳定点击路径;`CodePen` 外跳也不能替代站内入口 | P1 入口未接通 |
|
||
| Help Center 后台三页 | `设置 / 类别 / 语言` 三页在 fresh session 中仍稳定落入空白主区,而 `文章` 列表、编辑、预览链路健康,说明是局部后台渲染缺陷,不是整套帮助中心都坏 | P1 页面主内容未渲染 |
|
||
| Super admin 入口 | 从用户菜单点击 `超级管理员控制台` 后,当前 tab 未进入 `/super_admin`;额外新 tab 实为此前 CodePen 的迟到外跳 | P1 入口失效 |
|
||
| 通知入口识别 | 顶部无文案按钮当前确认打开的是“新消息/全局发消息”弹层,不是通知中心;真正通知入口在当前健康链路里仍待明确 | P2 验收前置澄清 |
|
||
| Captain AI 主链路 | `FAQ / 文档 / Scenarios / 试验场 / 收件箱 / 工具 / 设置 / Guardrails / Response guidelines` 已在健康 click-only 链路下证明可渲染;其中 `试验场` 等真实 AI 效果仍需 `fake:ai` 才能做成功/失败/超时/429 全链路断言 | P1 测试依赖未就绪 |
|
||
| Campaign SMS / WhatsApp | 这两页在后续 fresh session 中已能正常渲染内容与空态,不再作为稳定阻断项;后续只需继续做创建/编辑/触发层面的功能验收 | 已从阻断项移除 |
|
||
|
||
执行原则:
|
||
|
||
1. 这类问题一旦复现,不再继续用补数据方式兜底。
|
||
2. 报告里要保留“点击路径 + 最终 URL + main 区域状态 + 关键请求/快照”四类证据;如果不是空白而是错误态,也要按真实渲染结果记录。
|
||
3. widget / public 相关在真正站内入口修好前,只保留入口级验证,不做功能通过判定。
|
||
4. Captain 当前不要再按“稳定白屏”预设处理;除了真实 AI 依赖项外,应继续按正常页面矩阵做列表/表单/子页验收。
|
||
|
||
## 11. 执行前补齐清单(可直接转实施)
|
||
|
||
这一节把“测试计划”“缺数据”“缺 fake:ai 支撑”压成执行清单,避免后续再来回翻全文。
|
||
|
||
### 11.1 P0:不补就没法做全量功能验收
|
||
|
||
| 项 | 需要补什么 | 建议落地方式 | 完成标准 |
|
||
|---|---|---|---|
|
||
| 登录与权限基线 | 1 个管理员、2 个可登录 agent、1 个 custom role 用户 | `cmd/gochat seed` + 后台补齐 | 三类账号都能真实登录,菜单和权限差异可见 |
|
||
| fake 会话主链路 | `fake_01` inbox、fake webhook 正常、会话能进入正确列表 | inbox 配置 + `pnpm fake:start` | 入站、客服回复、auto reply 回流三段都能留证据 |
|
||
| 基础会话池 | 至少 6 条不同状态会话,每条 3+ 消息 | fake channel 批量灌数 | open / pending / resolved / snoozed 都能在 UI 中找到 |
|
||
| 页面不再只剩空态 | contacts 8+、companies 3+、labels 5、teams 2 | seed 或 UI 创建 | CRM、筛选、报表、分配页面不再被空数据阻断 |
|
||
| AI 本地假服务 | `channels/fake-ai` + 根脚本 `fake:ai` | 复用 fake channel 骨架 | Copilot / Captain 能走本地假 provider 成功/失败/超时/429 |
|
||
|
||
### 11.2 P1:不补会导致“大量页面只能做 render-only”
|
||
|
||
| 项 | 需要补什么 | 影响页面 |
|
||
|---|---|---|
|
||
| 回复提效数据 | canned responses 3、macros 3、automation rules 3 | 回复框、设置 CRUD、自动化 |
|
||
| 属性与筛选数据 | contact / conversation / company custom attributes 各 2 | CRM、筛选器、自动化条件 |
|
||
| 通知与审计数据 | notifications 5、audit logs 5 | 通知中心、审计日志、跳转链路 |
|
||
| Help Center 丰富数据 | 1 portal、2 categories、3 articles、2 locales | 后台 portal、public help center、多语言 |
|
||
| Campaign 样本 | live chat / sms / whatsapp 至少各 1 个样本 | Campaigns 列表、编辑、启停 |
|
||
| Captain 子资源 | documents 3、responses 5、scenarios 2、tools 1 | Captain 子页 CRUD、Playground、文档链路 |
|
||
|
||
### 11.3 `fake:ai` 的最小验收标准
|
||
|
||
`fake:ai` 只要做到下面这些,就足够支撑本轮 CDP 页面级功能验收:
|
||
|
||
| 能力 | 最小要求 | 为什么必须有 |
|
||
|---|---|---|
|
||
| OpenAI-compatible chat | `POST /v1/chat/completions` 返回固定答复 | Copilot / Captain / reply suggestion 要能成功走通 |
|
||
| OpenAI-compatible embeddings | `POST /v1/embeddings` 返回固定维度向量 | Captain documents / embedding 状态要能推进 |
|
||
| 模式切换 | `ok / error / slow / rate_limit` | 页面要验证成功、失败、超时、429,而不是只测 happy path |
|
||
| 请求留痕 | `GET /api/requests` 可查最近请求摘要 | 报告要拿得到 AI 调用证据,不只截图 |
|
||
| 脱敏 | 不记录明文 `Authorization` / `api_key` | 测试日志不能泄露敏感配置 |
|
||
|
||
### 11.4 推荐实施顺序
|
||
|
||
1. 先补账号、`fake_01` inbox、基础会话池。
|
||
2. 再补 contacts / companies / labels / teams,让 dashboard、CRM、reports 能进入“非空态测试”。
|
||
3. 然后补 canned responses / macros / automation / custom attributes,打通 settings 主体 CRUD。
|
||
4. 再补 Help Center / campaigns / notifications / audit logs 这些页面群的数据。
|
||
5. 最后实现 `fake:ai`,把 Copilot / Captain 从 render-only 升级成真实功能验收。
|
||
|
||
### 11.5 本轮文档产出对应关系
|
||
|
||
为避免后续拆任务时语义漂移,这份计划文档里的三类产出边界如下:
|
||
|
||
| 产出 | 本文档里对应内容 | 后续动作 |
|
||
|---|---|---|
|
||
| CDP 测试执行规则 | 第 1、2、3、6、7、8、9、10 节 | 后续按此直接做 click-only QA 和 report 追加 |
|
||
| 影响全量测试的数据缺口 | 第 4 节 + 本节 11.1 / 11.2 | 后续可拆成 seed、fake、后台初始化任务 |
|
||
| AI 测试支撑方案 | 第 5 节 + 本节 11.3 | 后续单独实现 `fake:ai` 并接入 Copilot / Captain |
|
||
|
||
### 11.6 建议直接拆出的实施任务
|
||
|
||
为了把“计划文档”顺滑转成“落地任务”,建议按下面 4 个实施包推进:
|
||
|
||
| 任务包 | 目标 | 建议落地位置 | 完成标志 |
|
||
|---|---|---|---|
|
||
| A. smoke seed 扩容 | 从单管理员/单会话扩到可支撑大部分页面验收 | `backend/cmd/gochat seed` | 能一次性产出 2 agents、更多会话状态、更多 contacts/companies/labels/teams |
|
||
| B. fake channel 批量造数 | 把会话、消息类型、实时事件做成可重复回放 | `channels/fake` | 能按脚本批量制造 open/pending/resolved/snoozed、typing、reply、close |
|
||
| C. fake inbox/bootstrap | 让 `fake_01` 不再靠手工散配置维持 | seed + inbox/bootstrap script | fresh 环境里一条命令后即可直接收发 fake webhook |
|
||
| D. fake:ai | 给 Copilot/Captain 提供本地稳定 AI 依赖 | `channels/fake-ai` | chat/completions、embeddings、mode switch、request log 全部可用 |
|
||
|
||
推荐先后顺序:
|
||
|
||
1. 先做 A + C,保证 dashboard 主链路可测。
|
||
2. 再做 B,把 reports / CRM / settings 从“空态浏览”升级成“真实数据验证”。
|
||
3. 最后做 D,把 Captain / Copilot 从 render-only 升级成完整 E2E。
|
||
|
||
## 12. 执行前一页纸清单
|
||
|
||
这一节是给真正开跑 CDP 全量验收时直接照着做的,避免再从前文抽命令。
|
||
|
||
### 12.1 环境与服务核对
|
||
|
||
```bash
|
||
curl -fsS http://127.0.0.1:3000/health
|
||
curl -fsS http://127.0.0.1:9100/health
|
||
curl -fsS http://127.0.0.1:9222/json/version
|
||
```
|
||
|
||
通过标准:
|
||
|
||
1. backend 返回 200。
|
||
2. fake channel 返回 `{"status":"ok","service":"fake-message-platform"}`。
|
||
3. CDP debug 口能返回 `webSocketDebuggerUrl`。
|
||
|
||
### 12.2 现有仓库能力 vs 需要新增的能力
|
||
|
||
| 类别 | 当前已具备 | 仍需新增 |
|
||
|---|---|---|
|
||
| 本地消息 fake | 根脚本 `fake:start` / `fake:dev` / `fake:test`,workspace 已纳入 `channels/fake` | 批量造多状态会话、多消息类型、批量造数接口 |
|
||
| AI fake | 无 | `channels/fake-ai`、根脚本 `fake:ai*`、workspace 纳入 `channels/fake-ai` |
|
||
| smoke seed | `go run ./cmd/gochat seed` 可产出管理员、website inbox、portal/article、1 contact/company/conversation、Captain assistant 基线 | 第二个 agent、fake inbox、更多会话状态、labels/teams、通知/审计、campaign 样本 |
|
||
| 页面级执行规则 | 本文档已覆盖 click-only + report sink + fake/fake:ai 证据要求 | 后续按本文档直接执行 |
|
||
|
||
### 12.3 数据准备责任分层
|
||
|
||
为了避免后续把所有“补数据”都塞进一个入口,建议这样拆:
|
||
|
||
| 层 | 负责内容 | 最适合落地位置 |
|
||
|---|---|---|
|
||
| smoke seed | 可登录账号、website inbox、portal/article、最小 smoke records | `backend/cmd/gochat seed` |
|
||
| fake channel | 批量制造会话、消息、typing、close、online/offline | `channels/fake` |
|
||
| 后台 UI 自举 | labels、teams、macros、canned responses、custom attributes、部分 portal/campaign | CDP 执行过程中顺手创建 |
|
||
| fake AI | chat/completions、embeddings、error/slow/429 模式、请求留痕 | `channels/fake-ai` |
|
||
|
||
### 12.4 建议先补的 5 个最小 blocker
|
||
|
||
只要还没补齐下面 5 项,就不要把结果叫“全量实际功能验收完成”:
|
||
|
||
1. 第二个可登录 agent。
|
||
2. `fake_01` 对应 inbox 和 webhook 主链路。
|
||
3. 至少 6 条不同状态会话。
|
||
4. contacts / companies / labels / teams 的非空数据集。
|
||
5. `fake:ai` 本地假 provider。
|
||
|
||
### 12.5 后续实施拆单建议
|
||
|
||
如果接下来按实现任务推进,建议就按下面 4 张单拆,不要混成一张“大而全”任务:
|
||
|
||
1. 扩 `cmd/gochat seed`:补双 agent、更多 CRM / reports / settings 基线数据。
|
||
2. 扩 `channels/fake`:补批量造数和多状态回放能力。
|
||
3. 加 fake inbox/bootstrap:fresh 环境一条命令后可直接收发 `fake_01`。
|
||
4. 新建 `channels/fake-ai`:给 Copilot / Captain 提供本地稳定 AI 依赖。
|
||
|
||
---
|
||
|
||
## 13. 第二轮回归补充测试用例
|
||
|
||
> 更新日期: 2026-07-28
|
||
> 基于 §6 页面功能矩阵执行第二轮回归,补充遗漏的功能测试点。
|
||
|
||
### 13.1 会话(Conversation)操作补充
|
||
|
||
| # | 操作 | API 端点 | 验证结果 | 说明 |
|
||
|---|------|----------|---------|------|
|
||
| C1 | 客服发送消息 | POST /conversations/:id/messages (outgoing) | ✅ | 回复消息持久化到 DB |
|
||
| C2 | 私密备注 | POST /conversations/:id/messages (private_note) | ✅ | private flag 正确处理 |
|
||
| C3 | 会话详情 | GET /conversations/:id | ✅ | 含消息列表 |
|
||
| C4 | 切换优先级 | POST /conversations/:id/toggle_priority | ✅ | 无请求体 |
|
||
| C5 | 设置优先级 | PATCH /conversations/:id/priority | ✅ | body: {"priority":"high"} |
|
||
| C6 | 状态流转: resolve | POST /conversations/:id/toggle_status | ✅ | status=resolved |
|
||
| C7 | 状态流转: reopen | POST /conversations/:id/toggle_status | ✅ | status=open |
|
||
| C8 | 状态流转: snooze | POST /conversations/:id/toggle_status | ✅ | status=snoozed |
|
||
| C9 | 消息已读 | POST /conversations/:id/update_last_seen | ✅ | |
|
||
| C10 | 静音/取消静音 | POST /conversations/:id/mute & /unmute | ✅ | 两次调用均成功 |
|
||
| C11 | 标签增删 | POST /conversations/:id/labels | ✅ | body: {"labels":["tag1"]} |
|
||
| C12 | 附件列表 | GET /conversations/:id/attachments | ✅ | 返回空列表 meta |
|
||
| C13 | 输入状态(API) | POST /toggle_typing_status | ✅ | typing_status=on/off |
|
||
| C14 | 文件上传 | POST multipart /messages + file | ✅ | 见下方 §13.6 |
|
||
|
||
### 13.2 静态 Auto-Reply(新增功能点)
|
||
|
||
| # | 操作 | 端点 | 验证结果 | 说明 |
|
||
|---|------|------|---------|------|
|
||
| AR1 | 创建规则 | POST /captain/auto_reply_rules (mode=static) | ✅ | 返回 id, status=draft |
|
||
| AR2 | 激活规则 | PUT /:rule_id (status=active) | ✅ | |
|
||
| AR3 | 条件匹配 | content contains "hello" | ✅ | 自动回复生效 |
|
||
| AR4 | 多规则匹配 | 按 priority 降序执行首条匹配 | ✅ | |
|
||
| AR5 | 回复内容 | 静态回复文本 | ✅ | "Hello! How can we help..." |
|
||
| AR6 | Sender 身份 | AgentBot (sender_type=AgentBot) | ✅ | 通过 botInboxRepo 解析 |
|
||
|
||
### 13.3 LLM Auto-Reply(新增功能点)
|
||
|
||
| # | 操作 | 条件 | 验证结果 | 说明 |
|
||
|---|------|------|---------|------|
|
||
| LR1 | Provider 配置 | COPILOT_PROVIDER_CONFIG (installation_configs) | ✅ | openai_compatible + deepseek-v4-flash |
|
||
| LR2 | 聊天 API | ChatCompletion | ✅ | 10.58.144.6:2014/v1 可达 |
|
||
| LR3 | 创建 LLM 规则 | mode=llm | ✅ | 无 response_text |
|
||
| LR4 | LLM 生成回复 | content matches -> LLM call | ✅ | deepseek 返回自然语言 |
|
||
|
||
### 13.4 AgentBot(新增功能点)
|
||
|
||
| # | 操作 | 端点 | 验证结果 |
|
||
|---|------|------|---------|
|
||
| AB1 | 创建 | POST /agent_bots | ✅ |
|
||
| AB2 | 查询列表 | GET /agent_bots | ✅ |
|
||
| AB3 | 详情 | GET /agent_bots/:id | ✅ |
|
||
| AB4 | 更新 | PATCH /agent_bots/:id | ✅ |
|
||
| AB5 | 绑定到 Inbox | POST /inboxes/:id/set_agent_bot | ✅ |
|
||
| AB6 | Inbox 查询绑定 | GET /agent_bot_inboxes/by_bot?agent_bot_id=X | ❌ 404 路由待注册 |
|
||
|
||
### 13.5 前端渲染时序(已知 BUG 验证)
|
||
|
||
| # | 场景 | 状态 | 说明 |
|
||
|---|------|------|------|
|
||
| F1 | localStorage auth token 同步 | ✅ | BUG-W2 修复验证通过 |
|
||
| F2 | Dashboard 自动加载会话 | ❌ | ChatList 组件 onMounted 有时不发请求 |
|
||
| F3 | API 代理可达 | ✅ | 通过 Vite proxy `/api` → :3000 正常 |
|
||
| F4 | 手动 dispatch 加载 | ✅ | `store.dispatch('fetchAllConversations')` 成功后渲染正常 |
|
||
|
||
### 13.6 文件上传(Round 7 后修复验证)
|
||
|
||
| # | 步骤 | 证据 | 状态 |
|
||
|---|------|------|------|
|
||
| U1 | POST multipart + file → 200 | msg_id=126 attachments=1 | ✅ |
|
||
| U2 | Metadata jsonb 修复 | attachment.metadata = "{}" 非空字符串 | ✅ |
|
||
| U3 | 搜索结果含附件消息 | GET search 返回正确 | ✅ |
|
||
|
||
### 13.7 已知后端未实现端点
|
||
|
||
| 端点 | 方法 | 说明 | 状态 |
|
||
|------|------|------|------|
|
||
| /auth/login | POST | Chatwoot 使用 /auth/sign_in | 差异(非缺陷) |
|
||
| /api/v1/accounts/:id/conversation_workflows | GET | 路由未注册 | 未实现 (P3), 需完整 CRUD handler |
|
||
| /api/v1/accounts/:id/reports/overview | GET | 前端仅 Vue Router 路径,API 使用 /reports?metric=... | 非缺陷 |
|
||
|
||
### 13.7b 已修复端点
|
||
|
||
| 端点 | 方法 | 修复内容 | 状态 |
|
||
|------|------|---------|------|
|
||
| /api/v1/widget/config | GET | 新增 GET 路由(原仅 POST) | ✅ |
|
||
| /api/v1/widget/config | POST | widget.html 将 GET→POST 调用,发送 JSON body | ✅ |
|
||
| /api/v1/accounts/:id/copilot/config | GET | 确认正确路径为 /copilot/config 非 /copilot_config | ✅ 已验证 |
|
||
| /api/v1/accounts/:id/agent_bot_inboxes/ | GET | 正确路径为 ?inbox_id=X(非 /by_inbox) | ✅ 已验证 |
|
||
|
||
### 13.8 补充关键断言模板
|
||
|
||
#### D. Sender 身份断言模板
|
||
|
||
适用:所有 auto-reply / bot / Captain 发出的消息。
|
||
|
||
统一断言:
|
||
1. 消息 `sender_type = "AgentBot"`(非 "user" 或 "contact")
|
||
2. `sender_id` 指向有效的 `agent_bots` 记录
|
||
3. inbox 必须在 `agent_bot_inboxes` 表中有 active 绑定
|
||
4. 前端 UI 中消息头像/名称显示为 bot 名称而非 agent 头像
|
||
|
||
#### E. AI Provider 连通性断言模板
|
||
|
||
适用:Copilot、Captain、LLM auto-reply 等调用外部 AI API 的功能。
|
||
|
||
统一断言:
|
||
1. Provider 配置在 `installation_configs` 表中完整
|
||
2. ChatCompletion 调用返回 200 + choices > 0
|
||
3. 失败时有明确错误日志(非静默吞掉)
|
||
4. 网络延迟/超时时有重试机制和退避
|
||
5. 生产环境 API key 不写入代码或 YAML 配置
|