发布时间:2026年9月29日
在基于业务切片的 Journey Cases里,我们把任务和验收绑在同一个业务 slice 上:任务拆出来时,验收也一起拆出来。它解决了前端 Agent “写完之后怎么知道做完了”的问题。
但用户旅程只是完整测试体系中的一层。一个保存按钮表现正常,不能证明权限中间件拒绝了越权请求;一次 API 调用成功,也不能证明数据库在并发冲突时正确回滚。
这篇文章把三份已有实践放在一起:Journey 测试套件负责业务验收,browser-harness 负责验收环境和浏览器采证,admin 的分层测试负责规则、接口和真实数据库。目标是让每个业务 slice 都有明确证据,而不是把所有检查都塞进浏览器。
本文根据 2026-09-29 核对的 testing-suite、browser-harness 和 enterprise admin 代码整理。已有实现与新增设计分开说明;综合调度和跨层报告是本文提出的方案,不代表这些项目已经完成统一接入。
| 能力 | 主要问题 | 输出 |
|---|---|---|
| testing-suite | 用户如何完成一个 slice,主路径和分支是否符合预期 | task / journey 结果、请求断言、失败诊断 |
| browser-harness | 环境是否准备好,怎样登录、操作、截图和收集网络证据 | 实际 APP_URL、浏览器证据、资源状态 |
| admin 分层测试 | 规则、权限、数据映射、迁移、事务是否正确 | 类型检查和各层测试结果 |
Journey 套件值得单独分享:它包含任务选择、声明式用例、定位、动作执行、请求检查和诊断,已经具有独立主题。browser-harness 也保持通用能力,不在技能中重新实现项目测试。本文作为综合篇,负责说明两者如何与后端测试组成交付链路。
集成的最小公共接口是 APP_URL、测试身份、结果与证据位置、资源所有权。这不要求所有测试使用同一个浏览器执行器。
slice 表达用户目标,例如“管理员修改某个角色的权限,合法修改生效,越权修改不写入,已撤销权限的会话不能继续操作”。页面、接口和数据层围绕同一目标承担不同证明责任。
| 检查点 | 所在层 | 必须观察的结果 |
|---|---|---|
| 非法表单提交 | Journey | 字段错误出现,变更请求没有发出 |
| 合法保存 | Journey | 请求参数正确、结果反馈出现、重新查询显示新值 |
| 越权访问 | tRPC | 真实权限中间件拒绝,下游写入未发生 |
| 状态和字段映射 | unit / model | 输入产生正确结果,失败被准确传播 |
| 事务失败 | database integration | 写入回滚,不留下部分状态 |
| 并发修改 | database integration | 冲突结果、版本或唯一约束符合约定 |
| 历史升级 | migration integration | 旧数据保留,迁移后行为与最终模型一致 |
给每个检查点稳定 ID,用来连接 task、测试和报告。ownerGlobs 可以帮助挑选受影响用例,但不能作为业务覆盖证明:共享权限中间件变更可能影响很多页面,路径匹配无法代替风险判断。
4 月原稿展示了 v1 风格的 action / intent 与独立页面 semantic 文件。本次提供的 runtime 已使用下面的模型:
| 项目 | 当前代码 |
|---|---|
| 格式 | schemaVersion: 2 |
| 页面入口 | start.page、start.route |
| 定位定义 | 用例内的 targets,带 kind、scope、role、label 或 selector |
| 操作 | 节点的 actions 数组 |
| 分支 | 节点及分支下的 branchFlows |
| 请求 | request / requests、noRequest / noRequests、次数与 payload 检查 |
| 执行器 | Playwright provider |
| 认证 | none / open / use,保存与加载 storageState |
因此,不能直接把原稿 JSON 当成当前 runtime 的有效输入。迁移应保留 slice 与验收意图,再按 v2 schema 转换定位、动作和分支。
下面是基于当前字段的示意用例,路由、文案和 API 映射需要按目标项目填写:
{
"schemaVersion": 2,
"id": "role-create-validation",
"title": "角色创建表单校验",
"sliceRefs": ["ROLE-CREATE"],
"ownerGlobs": ["src/features/roles/**"],
"start": { "page": "roles", "route": "/roles" },
"targets": {
"create": { "kind": "button", "scope": "main", "role": "button", "text": "新建" },
"submit": { "kind": "dialogSubmit", "scope": "dialog", "text": "确定" }
},
"acceptanceFlow": [
{
"id": "open",
"intent": "打开新建角色弹窗",
"actions": [{ "type": "click", "target": "create" }],
"acceptance": { "dialogTitle": "新建角色" }
},
{
"id": "invalid-submit",
"intent": "空表单不得触发创建",
"actions": [{ "type": "submit", "target": "submit" }],
"acceptance": {
"requiredErrors": ["请输入名称"],
"noRequest": { "api": "role.create", "method": "POST" },
"result": { "dialogStillOpen": true }
}
}
]
}
manifest 的 apiUrlKeywords 必须将 role.create 映射到真实请求 URL 特征。当前请求匹配器遇到未知 API 会报错,不会凭名字自动发现接口。
现有入口是 scripts/test-journey.ts。运行环境必须先具备项目依赖、迭代任务和 manifest;单独复制 runtime 目录并不构成一个完整可运行项目。它会读取:
iterations/<iteration>/tasks.json
testing/iterations/<iteration>/manifest.yaml
testing/iterations/<iteration>/journeys/*.json
在已经安装该套件、注册 test:journey 脚本的项目里,命令可写为:
# APP_URL 来自成功的环境准备;包管理器沿用项目约定
bun run test:journey -- \
--iteration example-iteration \
--task ROLE-003 \
--app-url "$APP_URL" \
--browser-provider playwright \
--auth use \
--output json
省略 --task 会按任务文件顺序运行整个迭代;--changed-file 可重复传入。本次提供的快照没有 package.json,admin 也没有注册 test:journey,因此上面是接入后的调用形式,不是在任意目录可直接执行的现有命令。
planner 以显式 journey、sliceRefs、变更文件匹配的并集选择用例。默认变更范围来自最近两个提交,可能漏掉未提交修改或完整 PR 范围;项目集成时应显式传入实际变更集合。全量回归也不能只靠这个集合筛选。
当前实现有几个值得明确的边界:
listReloaded 实际只检查 table 是否可见;detailReloaded 只检查 main 是否可见。两者均不证明数据真的刷新。完整保存旅程还要验证查询请求、返回结果及最终业务字段。noRequest 只能证明观察窗口内未看到匹配请求;需要覆盖防抖、异步校验和延迟提交的实际时序。testing/results/<iteration>/<taskId>.json,重复执行会覆盖。用于 CI 和交付时,需要在外层按 run ID 或提交版本归档。这些是当前实现与“项目全场景验收”之间的差距,本文没有暗中修改套件来掩盖它们。
browser-harness 的流程是 prepare、登录、交互、collect-evidence、cleanup。项目 prepare 成功后给出 APP_URL、服务 PID 和日志路径;项目测试消费这个 URL,不再从框架日志猜端口。
下面只展示环境生命周期。BH_DIR 应取实际安装技能的 scripts 目录,任务 session/profile 和自带 Chromium 路径应按技能准备:
if PREPARE_ENV="$(bun "$BH_DIR/bh.ts" prepare "$PROJECT_ROOT")"; then
eval "$PREPARE_ENV"
else
bun "$BH_DIR/bh.ts" cleanup "$PROJECT_ROOT"
exit 1
fi
# 在此运行项目自己的 Journey / E2E,并传入 APP_URL。
# 无论通过还是失败,保留结果后关闭测试自有浏览器,清理自有服务。
已有用户服务时直接借用 URL,不对同一项目再次 prepare;这会涉及停止已有服务。cleanup 也只能用于本次拥有清理权的 target。
Journey runtime 通过 Playwright 创建浏览器 context,以 storageState 保存登录态;browser-harness 使用命名的 agent-browser session 与持久 profile。二者不是同一种存储,也不会因为 APP_URL 相同就自动共享身份。
最小接入方案是复用 APP_URL,各自维护同角色的独立测试登录态。Journey 自己的瞬时失败截图由自己的 session 保存;不能让 browser-harness 重新打开首页,再把那张图当作 Journey 失败现场。
若未来统一执行器,应通过已定义的 BrowserProvider / BrowserSession 接口实现适配,并验证定位、请求捕获和登录语义。这个适配在当前快照中尚不存在,不应提前声明支持 agent-browser provider。
现有套件的 ensurePlaywrightRuntime 会尝试用 tnpm 安装依赖和 Chromium,入口还保留了原项目的默认 APP_URL。通用化时应明确提供 APP_URL,沿用目标项目的包管理器、锁文件和浏览器准备流程,避免测试过程中隐式改变依赖。
已完成依赖与浏览器准备的受控环境,可使用现有 INSPECS_SKIP_PLAYWRIGHT_INSTALL=1 跳过安装步骤;这不代表可以跳过依赖核对。browser-harness 也要求显式使用已核实的工具自带 Chromium,不接管日常浏览器。
当前 admin 的测试已收口在 tests/:
| 命令 | 范围 | 证明什么 |
|---|---|---|
bun run type-check |
TypeScript | 类型与模块接线 |
bun run test:unit |
tests/unit |
可计算规则、安全边界、服务编排 |
bun run test:models |
database contracts / models / schemas / migrations | 模型输入输出、映射、错误契约 |
bun run test:trpc |
tests/trpc |
真实 router / caller 与权限中间件 |
bun run test:db |
database integration | 隔离 PostgreSQL 上的约束、迁移、事务、并发 |
bun run test 只运行 unit。bun run test:acceptance 依次运行以上五层,不包含浏览器验收。
模型和 tRPC 测试保留真实业务逻辑,在数据库或外部服务边界 Mock。Admin 自有数据库的事务与约束则使用真实 PostgreSQL;LobeHub DB、License、Market、对象存储等外部能力在这些后端套件中保持 Mock,不能为追求“真实”去操作共享外部数据。
覆盖索引位于 tests/database/COVERAGE.md 和 tests/trpc/COVERAGE.md。索引帮助定位证据,真正的覆盖仍来自错误实现会导致失败的断言。不能把“入口出现在表里”当成“权限、异常和成功路径都被验证”。
全场景并不意味着所有状态的无限笛卡尔积。按业务风险选择重要组合,让缺口可见:
| 维度 | 典型场景 | 主要证据 |
|---|---|---|
| 身份与权限 | 未登录、只读、可写、撤权、会话失效 | 浏览器真实登录 + tRPC 权限 + 必要数据库状态 |
| 查询 | 空数据、分页筛选、无结果、请求失败 | 请求参数、结果集、错误反馈 |
| 新建编辑 | 必填校验、合法提交、重复提交、冲突、回显 | noRequest / request、幂等、刷新后字段 |
| 删除 | 取消确认、权限不足、依赖约束、成功删除 | 不写入断言、拒绝契约、结果查询 |
| 导入导出 | 类型大小限制、部分失败、撤权后下载 | 页面反馈、API 契约、对象访问边界 |
| 外部依赖 | 超时、限流、不可用、结果未知 | 可控 Mock / 故障注入与可恢复反馈 |
| 状态与并发 | 重试、重复请求、部分写入、竞态 | 真实数据库事务和唯一约束 |
| 迁移 | 空库安装、历史升级、连续重复执行 | 数据与迁移记录保留、最终模型一致 |
| 交互 | 小屏、键盘、弹窗焦点、长列表 | 实际浏览器结果与截图 |
权限验收必须关闭开发鉴权旁路。打开旁路的页面走查只能说明开发交互正常,不能用于证明真实权限。
admin 的真实数据库夹具要求显式 DATABASE_TEST_URL,连接专用测试 PostgreSQL 的维护库,创建随机的隔离测试库。维护账号需要创建数据库权限;缺连接或权限不足都是未通过,不能 skip 后宣布成功。
迁移专项命令 db:verify-admin-rbac 使用的却是 DATABASE_URL,且不在 test:acceptance 内。应用、迁移和测试入口必须分别核实加载规则,不能假定 .env.local 会覆盖所有脚本。连接只注入目标进程,报告中不输出凭据。
可以清理的是有明确创建记录、隔离且可丢弃、已完成诊断和交接的一次性测试资源。开发数据库、历史迁移记录和有复现价值的失败现场默认保留;工作区回收不等于获得清库授权。当前夹具会清理自己的随机库,若排障需要保留数据,必须在运行前设计好保留或一致性快照流程,不能等 teardown 之后再要求复现。
同一套原则也适用于 Redis、对象存储和浏览器登录态。测试结束释放服务进程,不代表必须删除数据;报告分别说明进程释放与数据处置。
先复用现有命令,在项目层补薄的聚合入口,不重写三套执行器:
建议的聚合结果可以很小:
{
"runId": "unique-run-id",
"revision": "tested-git-revision",
"sliceId": "ROLE-CREATE",
"status": "failed",
"checks": [
{ "id": "access-denied", "layer": "trpc", "status": "passed" },
{ "id": "invalid-submit", "layer": "journey", "status": "passed" },
{ "id": "saved-value-visible", "layer": "journey", "status": "failed" }
],
"artifacts": ["journey-report.json", "failed-step.png"],
"cleanup": { "ownedProcessesReleased": true, "persistentDataRetained": true }
}
这个格式是新增设计,现有 runtime 的 task JSON 尚不包含所有字段。聚合器应要求所有必需检查都有结果且通过;pending、blocked、缺证据、未执行都不能自动折算成 passed。browser-harness 的 artifact_errors 非空意味着证据可能是兜底占位,也不能凭文件存在判定采证成功。
admin 现有 CI 分别运行类型与契约检查、PostgreSQL 集成测试。浏览器任务需要另行接入环境、测试身份和产物归档,不能把既有 CI 绿色解释成完整用户旅程已经通过。
日常改动优先运行直接相关的已有检查;PR 按风险扩大到跨层链路;发布验收覆盖关键业务旅程、权限、迁移和外部失败路径。按相同版本归档结果,并在报告中标明哪些能力使用 Mock、哪些真实连接、哪些没有执行。
完整交付的判断最终落在业务检查点上:用户能完成目标,权限和数据契约有独立证据,失败能被准确识别,资源归属与结果可以复查。Journey、browser-harness 和后端套件各自提供这条证明链的一段。
scripts/test-journey.ts、runtime/testJourney.ts、runtime/iteration/planner.ts、runtime/journey/definition.ts、acceptance-runner.ts、request-capture.ts、diagnostics.ts。runtime/browser/{types,resolver,playwright,playwright-runtime}.ts。package.json、tests/README.md、各层 COVERAGE.md、vitest.database.config.ts、tests/database/fixtures/postgresTestDatabase.ts、.github/workflows/test.yml。SKILL.md 及登录、交互采证、运行时和资源清理参考。