🌑

Mocha's Blog

目录
  1. 1. 三者保持独立,在项目验收层组合
  2. 2. 先把业务 slice 变成可验证契约
  3. 3. Journey runtime 的实际能力
    1. 原稿与当前版本
    2. 运行与选择
    3. 不把字段名当成验证强度
  4. 4. browser-harness 提供环境,不替换 Journey 执行器
    1. 两种登录态不可假定互通
    2. 环境准备也有迁移成本
  5. 5. admin 的后端测试作为另一条证明链
  6. 6. 项目全场景矩阵
  7. 7. 隔离与清理是测试设计的一部分
  8. 8. 一个可落地的组合执行方案
  9. 9. CI 与日常开发的执行范围
  10. 实现核对位置

项目全场景自动化测试套件:从用户旅程到可复现验收

发布时间:2026年9月29日

在基于业务切片的 Journey Cases里,我们把任务和验收绑在同一个业务 slice 上:任务拆出来时,验收也一起拆出来。它解决了前端 Agent “写完之后怎么知道做完了”的问题。

但用户旅程只是完整测试体系中的一层。一个保存按钮表现正常,不能证明权限中间件拒绝了越权请求;一次 API 调用成功,也不能证明数据库在并发冲突时正确回滚。

这篇文章把三份已有实践放在一起:Journey 测试套件负责业务验收,browser-harness 负责验收环境和浏览器采证,admin 的分层测试负责规则、接口和真实数据库。目标是让每个业务 slice 都有明确证据,而不是把所有检查都塞进浏览器。

本文根据 2026-09-29 核对的 testing-suite、browser-harness 和 enterprise admin 代码整理。已有实现与新增设计分开说明;综合调度和跨层报告是本文提出的方案,不代表这些项目已经完成统一接入。

1. 三者保持独立,在项目验收层组合

能力 主要问题 输出
testing-suite 用户如何完成一个 slice,主路径和分支是否符合预期 task / journey 结果、请求断言、失败诊断
browser-harness 环境是否准备好,怎样登录、操作、截图和收集网络证据 实际 APP_URL、浏览器证据、资源状态
admin 分层测试 规则、权限、数据映射、迁移、事务是否正确 类型检查和各层测试结果

Journey 套件值得单独分享:它包含任务选择、声明式用例、定位、动作执行、请求检查和诊断,已经具有独立主题。browser-harness 也保持通用能力,不在技能中重新实现项目测试。本文作为综合篇,负责说明两者如何与后端测试组成交付链路。

集成的最小公共接口是 APP_URL、测试身份、结果与证据位置、资源所有权。这不要求所有测试使用同一个浏览器执行器。

2. 先把业务 slice 变成可验证契约

slice 表达用户目标,例如“管理员修改某个角色的权限,合法修改生效,越权修改不写入,已撤销权限的会话不能继续操作”。页面、接口和数据层围绕同一目标承担不同证明责任。

检查点 所在层 必须观察的结果
非法表单提交 Journey 字段错误出现,变更请求没有发出
合法保存 Journey 请求参数正确、结果反馈出现、重新查询显示新值
越权访问 tRPC 真实权限中间件拒绝,下游写入未发生
状态和字段映射 unit / model 输入产生正确结果,失败被准确传播
事务失败 database integration 写入回滚,不留下部分状态
并发修改 database integration 冲突结果、版本或唯一约束符合约定
历史升级 migration integration 旧数据保留,迁移后行为与最终模型一致

给每个检查点稳定 ID,用来连接 task、测试和报告。ownerGlobs 可以帮助挑选受影响用例,但不能作为业务覆盖证明:共享权限中间件变更可能影响很多页面,路径匹配无法代替风险判断。

3. Journey runtime 的实际能力

原稿与当前版本

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 是否可见。两者均不证明数据真的刷新。完整保存旅程还要验证查询请求、返回结果及最终业务字段。
  • 请求捕获记录 URL、method、payload,不等于已经验证响应成功或数据库提交。浏览器结果与后端契约各自承担证据。
  • noRequest 只能证明观察窗口内未看到匹配请求;需要覆盖防抖、异步校验和延迟提交的实际时序。
  • 无选中 journey 会返回 pending;但部分显式 journey 或 slice 缺失时,当前实现仅记录 notes,如果仍有其他用例选中并通过,最终可能仍为 passed。完整验收入口应把这种覆盖缺口视为未通过。
  • 普通报告写入 testing/results/<iteration>/<taskId>.json,重复执行会覆盖。用于 CI 和交付时,需要在外层按 run ID 或提交版本归档。

这些是当前实现与“项目全场景验收”之间的差距,本文没有暗中修改套件来掩盖它们。

4. browser-harness 提供环境,不替换 Journey 执行器

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,不接管日常浏览器。

5. admin 的后端测试作为另一条证明链

当前 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。索引帮助定位证据,真正的覆盖仍来自错误实现会导致失败的断言。不能把“入口出现在表里”当成“权限、异常和成功路径都被验证”。

6. 项目全场景矩阵

全场景并不意味着所有状态的无限笛卡尔积。按业务风险选择重要组合,让缺口可见:

维度 典型场景 主要证据
身份与权限 未登录、只读、可写、撤权、会话失效 浏览器真实登录 + tRPC 权限 + 必要数据库状态
查询 空数据、分页筛选、无结果、请求失败 请求参数、结果集、错误反馈
新建编辑 必填校验、合法提交、重复提交、冲突、回显 noRequest / request、幂等、刷新后字段
删除 取消确认、权限不足、依赖约束、成功删除 不写入断言、拒绝契约、结果查询
导入导出 类型大小限制、部分失败、撤权后下载 页面反馈、API 契约、对象访问边界
外部依赖 超时、限流、不可用、结果未知 可控 Mock / 故障注入与可恢复反馈
状态与并发 重试、重复请求、部分写入、竞态 真实数据库事务和唯一约束
迁移 空库安装、历史升级、连续重复执行 数据与迁移记录保留、最终模型一致
交互 小屏、键盘、弹窗焦点、长列表 实际浏览器结果与截图

权限验收必须关闭开发鉴权旁路。打开旁路的页面走查只能说明开发交互正常,不能用于证明真实权限。

7. 隔离与清理是测试设计的一部分

admin 的真实数据库夹具要求显式 DATABASE_TEST_URL,连接专用测试 PostgreSQL 的维护库,创建随机的隔离测试库。维护账号需要创建数据库权限;缺连接或权限不足都是未通过,不能 skip 后宣布成功。

迁移专项命令 db:verify-admin-rbac 使用的却是 DATABASE_URL,且不在 test:acceptance 内。应用、迁移和测试入口必须分别核实加载规则,不能假定 .env.local 会覆盖所有脚本。连接只注入目标进程,报告中不输出凭据。

可以清理的是有明确创建记录、隔离且可丢弃、已完成诊断和交接的一次性测试资源。开发数据库、历史迁移记录和有复现价值的失败现场默认保留;工作区回收不等于获得清库授权。当前夹具会清理自己的随机库,若排障需要保留数据,必须在运行前设计好保留或一致性快照流程,不能等 teardown 之后再要求复现。

同一套原则也适用于 Redis、对象存储和浏览器登录态。测试结束释放服务进程,不代表必须删除数据;报告分别说明进程释放与数据处置。

8. 一个可落地的组合执行方案

先复用现有命令,在项目层补薄的聚合入口,不重写三套执行器:

  1. 校验本轮目标、slice 检查清单、身份和隔离资源。没有用例、用例缺失、登录态缺失不能转成通过。
  2. 运行受影响的规则、模型和接口测试;涉及数据库语义时执行真实数据库层。
  3. 使用 browser-harness 准备或借用环境,取得 APP_URL。
  4. Journey runtime 在自己的 Playwright context 执行业务链路。补充浏览器走查由独立 session 完成,各自产生原始证据。
  5. 报告按 slice 聚合,而不是只记录工具退出码。失败时区分业务错误、测试资产错误、环境阻塞和证据缺失。
  6. 修复后复验受影响范围,归档该次结果,再精确关闭自有进程。

建议的聚合结果可以很小:

{
  "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 非空意味着证据可能是兜底占位,也不能凭文件存在判定采证成功。

9. CI 与日常开发的执行范围

admin 现有 CI 分别运行类型与契约检查、PostgreSQL 集成测试。浏览器任务需要另行接入环境、测试身份和产物归档,不能把既有 CI 绿色解释成完整用户旅程已经通过。

日常改动优先运行直接相关的已有检查;PR 按风险扩大到跨层链路;发布验收覆盖关键业务旅程、权限、迁移和外部失败路径。按相同版本归档结果,并在报告中标明哪些能力使用 Mock、哪些真实连接、哪些没有执行。

完整交付的判断最终落在业务检查点上:用户能完成目标,权限和数据契约有独立证据,失败能被准确识别,资源归属与结果可以复查。Journey、browser-harness 和后端套件各自提供这条证明链的一段。

实现核对位置

  • testing-suite: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。
  • admin:package.json、tests/README.md、各层 COVERAGE.md、vitest.database.config.ts、tests/database/fixtures/postgresTestDatabase.ts、.github/workflows/test.yml。
  • browser-harness:SKILL.md 及登录、交互采证、运行时和资源清理参考。

Powered By Hexo.js Hexo and Minima. Support By Oracle & Docker-Compose.