首页
/ OpenHands Agent Canvas 测试矩阵:Install × OS × Agent 的发布验收体系与自动化测试落地

OpenHands Agent Canvas 测试矩阵:Install × OS × Agent 的发布验收体系与自动化测试落地

2026-09-04 12:09:20作者:晏闻田Solitary

本文以仓库中 docs/TESTING_MATRIX.md 定义的测试矩阵为主线,解读 OpenHands Agent Canvas 如何在"安装方式(npm / Docker)× 操作系统 × Agent 后端"的交叉维度上组织发布前的验收工作:P0/P1/P2 优先级如何落到具体检查项、四套自动化测试(vitest 单测、mock-LLM E2E、Docker mock-LLM E2E、live E2E)分别覆盖矩阵中的哪些格子、以及当前 CI 尚未覆盖的空白区域。读完后你可以直接按矩阵执行手工验收、复现各条 E2E 流水线,并理解每一项背后的工程实现。

优先级定义:P0 / P1 / P2

矩阵文档开篇给出全篇的优先级键(priority key),它决定了哪些格子必须在发布前通过:

优先级 含义
P0 任何 release 之前必须通过(must pass before any release)
P1 GA(正式发布)之前必须通过(must pass before GA)
P2 best-effort,尽力而为

这一约定贯穿后文所有矩阵:手工矩阵(Install × OS × Agent)里每个格子都是一次"冒烟测试"(smoke test),而 Automations 与 Auth Modes 矩阵则直接标注了格子对应的优先级。

Install × OS × Agent:安装冒烟矩阵

矩阵文档的第一张表定义了最基础的验收单元:每个格子 = 一次完整冒烟流程:安装 → onboarding → 发起对话 → agent 有回复(install → onboard → start conversation → agent replies)。矩阵的行是"安装方式 × Agent 后端"的组合,列是操作系统:

macOS Linux Windows
npm — OpenHands
npm — Claude Code
npm — Codex
npm — Gemini CLI
npm — Custom ACP
Docker — OpenHands
Docker — Claude Code
Docker — Codex
Docker — Gemini CLI
Docker — Custom ACP

行中的 Agent 后端对应两条接入路径:

  • OpenHands:Agent Canvas 的原生 agent-server 路径。从 config/defaults.json 可以看到,npm 与 Docker 两条安装路径共用同一份版本钉(version pin):当前仓库锁定 agentServer: 1.44.0agentCanvas: 1.16.0automation: 1.9.0,最低兼容 minimumAgentServer: 1.28.0。该文件在注释中明确说明自己是"npm 和 Docker 安装路径共享的版本、端口、路径与默认值的唯一事实来源",被 scripts/dev-safe.mjsscripts/dev-with-automation.mjsdocker/entrypoint.sh 共同读取——这保证了矩阵中 npm 与 Docker 两行验收的是同一版本语义。
  • Claude Code / Codex / Gemini CLI / Custom ACP:通过 Agent Client Protocol(ACP)接入的第三方 CLI agent。仓库中 src/constants/acp-providers.ts 及其测试 tests/constants/acp-providers.test.ts 维护了这些 provider 的定义;ACP 冒烟路径还受到 config/defaults.jsonconstraints.agentClientProtocolagent-client-protocol<0.11)这一临时上界的约束——注释解释了 acp 0.11.0 重排了 prompt() 参数会破坏 SDK 的 ACP 客户端,因此在 openhands-sdk 修复前必须钉住 <0.11

"Custom ACP"一行的验证路径在 tests/e2e/live-acp/README.md 中有更具体的说明:该 e2e 证明容器化 ACP 凭据路径通过 Canvas 自身代码走通——每个凭据先像 onboarding 一样存进 agent-server 的 secret store,buildStartConversationRequest 再以 LookupSecret 的形式引用它,由服务端在 spawn 时解析。这是对 tests/api/agent-server-adapter.test.ts 单元测试的"真实跑通"配套:单测断言请求形状,live-acp 断言真实 agent 回复。

Automations × Install × Agent:需要完整栈的验收

第二张矩阵要求 automation backend 完整运行(requires full stack),并直接标注了优先级:

npm Docker
OpenHands ✅ P0 ✅ P0
Claude Code ✅ P1 ✅ P1
Codex ✅ P1 ✅ P1
Gemini CLI ✅ P2 ✅ P2

每个格子的验收动作是:创建 automation → 派发一次 run → run 到达 COMPLETED 状态 → 对话链接可用(create automation → dispatch run → run reaches COMPLETED → conversation link works)。

从源码结构看,这条链路的后端组件是 automation backend:端口 18001(见 config/defaults.jsonports.automation),SQLite 数据库位于 automation/automations.dbpaths.automationDb)。mock-LLM E2E 配置 playwright.mock-llm.config.ts 对状态目录的清理逻辑也印证了这一点:自动化 DB 存放在 $parent_of_STATE_DIR/automation/automations.db,镜像了 docker/entrypoint.sh 使用 $HOME/.openhands/automation/automations.db 的布局,STATE_DIRAUTOMATION_DB_DIR 在每次运行之间都必须清理以避免脏数据。

Auth Modes:本地自动密钥与公共模式

第三张矩阵覆盖两种会话鉴权模式:

npm Docker
Local(auto-generated key) ✅ P0 ✅ P0
Public(--public + user key) ✅ P1 ✅ P1
  • Local 模式:安装时自动生成会话 API key。mock-LLM 的 Playwright 配置正是这样工作的——playwright.mock-llm.config.ts 在未设置 MOCK_LLM_SESSION_API_KEY 时用 randomBytes(32).toString("hex") 生成随机密钥,并通过环境变量 LOCAL_BACKEND_API_KEY 注入给 bin/agent-canvas.mjs 启动的完整栈。
  • Public 模式:以 --public(对应 static-server 的 --auth-required,不烘焙 session key)启动第二个静态服务器实例,复用同一份 build/ 产物与同一后端。在 playwright.mock-llm.config.ts 中这是 webServer 数组的第 3 个进程:node scripts/static-server.mjs --dir build --port 18301 --auth-required,并把 /api/automation/api/server_info/sockets 分别路由到 18001/18000 端口。Docker 路径则由容器内 entrypoint 在设置了 PUBLIC_MODE_PORT 时启动同样的第二实例(见 playwright.mock-llm-docker.config.ts 中传入的 -e PUBLIC_MODE_PORT=…)。
  • 这两类格子在 mock-LLM 测试集中的具体验证位于 tests/e2e/mock-llm/backends/mock-llm-auth-modes.spec.ts

Feature Checklist:npm 与 Docker 的 12 项功能清单

矩阵文档为 npm 与 Docker 各列出一张功能清单(两表行项一致),这是手工验收时逐格勾选的检查单。以下完整保留原文档的 12 个功能项("LLM profiles"一行在 Claude Code / Codex / Gemini CLI 列中标注为 "—",表示该功能不适用于这些 ACP 接入路径,因为它们的模型与鉴权由各自的 CLI 管理):

npm

Feature OpenHands Claude Code Codex Gemini CLI
Onboarding
Conversation — start, resume, history
Terminal tool
File editor tool
Browser tool
LLM profiles — create / switch
Secrets — add / delete / forwarded
Automations — create, dispatch, COMPLETED
Files tab + Changes/diff tab
MCP server install
Image upload in chat
Key rotation

Docker

Feature OpenHands Claude Code Codex Gemini CLI
Onboarding
Conversation — start, resume, history
Terminal tool
File editor tool
Browser tool
LLM profiles — create / switch
Secrets — add / delete / forwarded
Automations — create, dispatch, COMPLETED
Files tab + Changes/diff tab
MCP server install
Image upload in chat
Key rotation

Automated Coverage:四套自动化如何支撑矩阵

矩阵文档的最后一节把自动化测试与矩阵的四个维度(Install / OS / Agents / Automations)对齐:

Suite Install OS Agents Automations
vitest(unit) Linux partial
test:e2e:mock-llm npm Linux OpenHands, ACP(mock) ✅ full
test:e2e:mock-llm:docker Docker Linux OpenHands, ACP(mock) ✅ full
test:e2e:live npm Linux OpenHands

CI 尚未覆盖的部分:真实 ACP 凭据(Claude Code / Codex / Gemini)、macOS、public 鉴权模式、订阅登录路径、Windows。

说明:矩阵文档中 test:e2e:mock-llm 的 "Automations ✅ full" 与手工矩阵中 Automations 的优先级标注互为印证——自动化已经把 npm 与 Docker 两条路径上的 OpenHands 全栈(含 automation)跑满,剩下的手工格子集中在真实凭据的 ACP agent 与多 OS 组合上。

下面结合仓库源码逐个说明四套测试的实际形态。

1. vitest 单元测试:Linux 上的单元层

package.json 中的脚本为 npm testnpm run make-i18n && vitest run。Vitest 的配置内嵌在 vite.config.tstest 段:jsdom 环境、setup 文件 vitest.setup.ts、并显式 exclude: [...configDefaults.exclude, "tests"]——这解释了为什么 live E2E(位于 tests/ 下)不属于单测套件。几个对测试行为有实际影响的配置细节:

  • testTimeout / hookTimeout 提到 30s:注释说明大量 DOM 密集型测试并行时,userEvent 驱动的测试在繁忙机器上可能超过 Vitest 默认 5s 超时,提高全局超时让测试保持确定性而不改变任何生产行为;
  • coverage.include 限定为 src/**/*.{ts,tsx},报告输出到 coverage/,由 npm run test:coverage 触发;
  • vitest.setup.ts 统一桩掉了 VITE_SESSION_API_KEY"test-session-key")、canvas 的 getContextHTMLElement.scrollTo,并为 Node.js 25+ 提供了内存版 localStorage 桩(否则 zustand 的 persist 中间件无法工作)。

此外仓库还配置了变异测试:stryker.config.mjs 以 vitest 为 testRunner,对 src/**/*.{ts,tsx}(排除测试、类型声明、fixtures/mocks)执行变异,related: true 意味着 Stryker 用 vitest related 只运行与被变异文件相关的测试;入口命令是 npm run test:mutation(另有 test:mutation:diff / test:mutation:incrementalscripts/stryker-diff.mjs)。

2. test:e2e:mock-llm:npm 全栈 + 模拟 LLM

对应脚本 playwright test --config=playwright.mock-llm.config.tsplaywright.mock-llm.config.ts 的文件头注释把三进程架构写得非常清楚:

  1. Mock LLM 服务器(Python):tests/e2e/mock-llm/scripts/mock-llm-server.py 基于 openhands-sdk 的 TestLLM 提供 OpenAI 兼容的 /v1/chat/completions 接口,agent-server 的 litellm 层与之对话而无需真实 LLM 凭据。脚本内置一条单轨迹:一次 terminal 工具调用(标记 MOCK_LLM_E2E_BASH_OK)加一段文本回复(MOCK_LLM_E2E_REPLY_OK),并识别 agent-server 的 LLM profile 预检 ping(PREFLIGHT_PING_TEXT = "ping")以免污染脚本化轨迹;
  2. 完整 agent-canvas 栈:通过 bin/agent-canvas.mjs 启动(agent-server + automation backend + 静态前端 + ingress 代理),注释明确说明这镜像的是生产 npx @openhands/agent-canvas 路径;配置在启动前会清理 .tmp/mock-llm-state 与 automation DB 目录,并在 build/index.html 不存在时先 npm run build:app
  3. Public 模式静态服务器:见上文 Auth Modes 一节。

关键端口分配(均可用环境变量覆盖):mock LLM 在 9999,ingress 在 18300,public 模式在 18301——刻意与 dev / live E2E 错开避免端口冲突。值得注意的是 webServer 的就绪探测 URL 是 http://localhost:18300/api/automation/v1 而不是 ingress 根路径:注释解释了 automation backend 经 uvx 启动最后才完成、可能耗时 30–60 秒,只探测 ingress 根或 /server_info 会让测试在栈未完全就绪时开始,因此要探测"最后启动"的服务。另外 gracefulShutdown: { signal: "SIGTERM", timeout: 15_000 } 也是刻意为之:Playwright 默认的 SIGKILL 组杀无法被 scripts/dev-process-utils.mjs 派生的 detached 服务捕获,会造成孤儿进程占着端口,改用 SIGTERM 才能触发 scripts/dev-with-automation.mjs 中的关停处理器。

运行约束:workers: 1fullyParallel: false、单用例超时 60s、CI 下 10 分钟全局硬上限;仅 chromium。测试规格按功能域组织在 tests/e2e/mock-llm/onboarding/conversations/(含 image upload)、automations/backends/(auth modes、cross-connect、partial stack)、files/(files & git)、mcp/settings/(ACP agent、模型切换、profile 管理)、skills/home/(folder workspace)、canvas-extensions/regressions/

3. test:e2e:mock-llm:docker:同一套规格打 Docker 镜像

playwright test --config=playwright.mock-llm-docker.config.ts 与上一节复用同一批 test specstestDir: "./tests/e2e/mock-llm"),只是把宿主上的 bin/agent-canvas.mjs + uvx 换成 all-in-one Docker 镜像(默认 ghcr.io/openhands/agent-canvas:latest,可用 MOCK_LLM_DOCKER_IMAGE 覆盖,与 config/defaults.jsonimages.agentCanvas 一致)。playwright.mock-llm-docker.config.ts 的几个工程细节值得注意:

  • 网络:Linux 上用 --network host 使容器共享宿主网络栈,容器内 agent-server 可以像 npm 路径一样直接访问 127.0.0.1:9999 的 mock LLM;macOS/Windows 的 Docker Desktop(bridge 网络)则需设置 MOCK_LLM_AGENT_URL=http://host.docker.internal:<port>
  • 卷挂载:把宿主上的 mock ACP 脚本(tests/e2e/mock-llm/scripts/mock-acp-server.py → 容器内 /opt/mock-acp-server.py)、技能仓库、用户技能目录(/home/openhands/.openhands/skills)和 folder-workspace 测试目录挂进容器,保证与 npm 路径可测同一批场景;
  • 环境注入:容器以 -e PORT=18300 -e SESSION_API_KEY=… -e OH_SESSION_API_KEYS_0=… -e PUBLIC_MODE_PORT=18301 启动,容器内还会额外起一个 --auth-required 的 public 模式 static-server(见 docker/entrypoint.shPUBLIC_MODE_PORT 的处理);
  • 清理:容器使用唯一随机名 + docker rm -f 前置清理 + --rm,Playwright 退出时 webServer 命令收到 SIGTERM,docker run --rm 自动收尾。

运行前提:已构建的 Docker 镜像和正在运行的 Docker daemon。CI 下全局超时默认 20 分钟(MOCK_LLM_DOCKER_GLOBAL_TIMEOUT_MS 可覆盖)。

4. test:e2e:live:真实 LLM 的 npm 路径

脚本为 node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs,配合 playwright.live.config.ts。与 mock-LLM 路径不同,live 路径需要真实 LLM 凭据:tests/e2e/live/scripts/run-live-e2e.mjsLIVE_E2E_LLM_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / LLM_API_KEY 中取第一个可用者,未配置时回落到内置的 LLM proxy 默认值(模型默认 openhands/claude-haiku-4-5-20251001)。webServer 命令用 npm run dev:minimal 起最小编译的前端(端口 3101)加本地 agent-server(默认 http://127.0.0.1:18100,由 OH_CANVAS_SAFE_BACKEND_PORT 控制),并在启动前清理 .tmp/live-e2e-statenode_modules/.vite。矩阵表中该套件对 Automations 一栏标 ❌——live 路径不覆盖 automation 全栈,这与 mock-LLM 路径"✅ full"形成互补。

受影响测试选择:变更驱动的 E2E 收窄

在 CI 中跑 E2E 成本高,仓库用一个映射文件实现"改了哪些源码就只跑哪一组测试"。tests/e2e/mock-llm/test-mapping.json 把源码 glob 映射到 mock-LLM 测试子目录,例如:

  • src/components/features/settings/**src/routes/llm-settings*.tsx 等 → settings 测试组;
  • src/components/features/chat/**src/routes/conversation.tsx 等 → conversations
  • src/components/features/automations/**src/manifests/** 等 → automations
  • alwaysRun 固定包含 regressions 组;
  • runAllSources 列出"触碰即跑全量"的高风险文件,如 src/api/agent-server-adapter.tssrc/root.tsxplaywright.mock-llm.config.tspackage.json 以及 CI workflow 文件本身。

tests/e2e/mock-llm/scripts/resolve-affected-tests.mjs 读取该映射,根据变更文件集合决定本次运行哪几个测试目录;其自身行为由 tests/e2e/resolve-affected-tests.test.ts 覆盖。这对维护矩阵很有意义:手工矩阵关注"发布前各格子绿",而这份映射保证日常 PR 阶段的自动化成本与风险成正比。

覆盖缺口与矩阵维护建议

矩阵文档明确列出当前 CI 未覆盖的区域,这也是一份现成的手工验收 TODO:

  • 真实 ACP 凭据(Claude Code / Codex / Gemini):mock 路径只覆盖 OpenHands 与 mock ACP;真实凭据路径可参考 tests/e2e/live-acp/README.md 的手工 e2e——它要求 agent-server:1.25.0-python 或更新镜像(旧镜像会在首轮 ACP 冷启动死锁),凭据取自宿主(Codex 的 ~/.codex/auth.json、Claude Code 的 macOS keychain OAuth token、Gemini 的 gcloud ADC),凭据缺失的 provider 自动跳过,且凭据绝不打印;
  • macOS / Windows:所有自动化均跑在 Linux,跨 OS 只能靠 Install × OS × Agent 手工矩阵;
  • public 鉴权模式在 CI 中的覆盖依赖 mock-LLM 的 18301 public 实例,矩阵中该模式整体仍是 P1;
  • 订阅登录路径(subscription login paths)尚无自动化。

维护该矩阵时的实践要点(均可从源码直接验证):

  1. 新增功能后同步更新 tests/e2e/mock-llm/test-mapping.json,否则变更驱动的测试选择可能漏跑相关 E2E;
  2. 修改 config/defaults.json 的端口/版本钉时,注意它同时被 npm 启动器、Docker entrypoint 与 CI workflow 消费,属于矩阵表中 npm 与 Docker 两行共同的"地基";
  3. 任何触碰 runAllSources 中文件的改动,CI 会退化为跑全量 mock-LLM 测试组,这是设计好的保守策略而非故障。

常用命令速查

目的 命令
单元测试 npm testvitest run,Linux,jsdom)
单测 + 覆盖率 npm run test:coverage
变异测试 npm run test:mutation / npm run test:mutation:diff
mock-LLM E2E(npm 全栈) npm run test:e2e:mock-llm
mock-LLM E2E(Docker 镜像) npm run test:e2e:mock-llm:docker(需先构建/拉取镜像,Linux 建议 host 网络)
live E2E(真实 LLM) npm run test:e2e:live(配置 LIVE_E2E_LLM_API_KEY 等凭据)
MSW 驱动的开发服务器(基础 e2e 默认 webServer) npm run dev:mockplaywright.config.ts 的 webServer 即调用 npm run dev:mock -- --port 3001

以上命令均可在 package.jsonscripts 段与四个 Playwright 配置文件(playwright.config.tsplaywright.mock-llm.config.tsplaywright.mock-llm-docker.config.tsplaywright.live.config.ts)中逐条对照。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341