
导读
我们应用 的 E2E 测试从单页 BDD 自动化起步,如今已经长成一套同时覆盖 H5、App、小程序的跨端验收体系。随之而来的核心问题变成了:
怎么证明一条测试结果可信?
我们的做法是让 Agent 生成测试计划,再由确定性代码核对被测对象、业务断言和现场证据,把结果分成通过、失败、阻塞、不适用四种。本文结合线上真实故障,说明信任边界、跨端执行和证据留存的设计,并梳理可复用的故障模式。
当前能力面向单人本地真机验收,平台接入方案、真机结果同步机制、发布状态打通能力均不在本文讨论范围内,将另行撰文说明。
应用包含聊天、角色、好感度等业务。每次功能调整后,验收需要回答一个具体问题:用户在约定条件下,能否完成预期操作,并得到符合需求的结果。个人页的昵称是否正确、加载失败后能否恢复访问、聊天语音能否播放,都是业务目标的具体表达。
个人信息展示。用户打开「我的」页面,昵称应与当前账号一致。文中已有用例曾只检查昵称文本非空,这会把错误昵称也判为通过。验收需要核对具体信息是否符合预期。
页面加载恢复。用户遇到加载失败,恢复网络并点击重试后,应能继续访问目标页面。已有恢复样本确认了异常层消失,并取得恢复后的页面观测;要证明首页业务可用,还需要补齐具体区域和交互的检查。
聊天语音播放。用户点选语音后,需要能够听到内容。本文以语音播放异常上报用例,说明数据条件不足时如何界定验收结果。验收要有能触发目标行为的真实语音数据。文中的严格音频用例曾因没有合适语音而记为不适用;这次记录没有验证播放结果,不能当作播放功能通过。
一份验收结果需要说明:检查了哪项需求,在哪个平台和版本上执行,哪些行为已有证据,哪些部分仍未验证。业务结果不符合预期,需要定位和修复;环境未就绪,需要先解决执行条件;当前数据不适用,需要准备符合条件的数据。不同情况应分别记录,避免把尚未验证的功能算作通过。
后文的技术设计围绕这些问题展开:业务场景定义验收要求,跨端执行落实用户操作,判定规则核对业务结果,身份与证据记录限定结论适用的版本和范围。读者可以结合自己的业务,检查每项机制帮助确认了哪些要求,以及还有哪些要求需要继续验证。
要判断昵称是否正确、页面是否恢复,需要先明确业务预期,再执行操作、取得观测并核对结果。下面的架构将这些职责分开,确保每个“通过”都能追溯到具体要求和证据。
模型可以参与生成测试内容,但最终结果只由可复核的规则计算得出。

图 1:执行器负责操作和观测,内核负责核验,证据层保存本次判断的依据;发布检查位于业务验收之后。
Agent 结合业务目标、源码和设备快照产出 Issue 与 Plan;ProofKernel 校验执行范围、授权信息和被测对象,再把任务下发给执行器。执行器回报动作状态、逐条断言结果和对应证据,内核据此给出判定,证据层则完整保存判定所依赖的材料。
对已纳入校验范围的配置、身份、执行和证据异常,系统会按规则判为 blocked 或 failed;判定依据本身是否正确,仍需独立复核。
ProjectProfile 负责维护项目相关知识:源码采集、结束时的复查、测试目录管理。平台执行器负责实际跑测试,并按统一协议返回观测结果。我们应用 通过 createAipbRunnerRegistry 完成注册,登记了三类执行能力:
能力 | 执行器 | 执行方式 |
|---|---|---|
catalog | LocalRunner | 运行登记的本地测试 |
exact-case | ManagedCaseRunner 的 playwright/appium/mini 三个适配器 | 分别负责 H5、Android/iOS、小程序的固定用例 |
semantic-device | AgentDeviceRunner | 在当前设备快照中匹配目标, 执行计划中的语义步骤 |
新增测试时,要完成登记并与 case/oracle 建立关联,保证计划里的场景和判定依据能对上。新增执行器,则要实现观测协议、异常处理和资源清理。接口接通之后还得跑真实用例,确认它确实能完成对应平台的验收。
日常开发仍保留短路径反馈:先读相关源码,跑对应的单元、集成测试;只有明确要做平台回归时才调用 e2e:issue。每次小改动都拉起设备、准备 App、收集平台材料,等待成本太高。统一协议的价值是让不同路径都返回可核对的结果,完整流程只留给真正需要它的场景。
单条设备用例跑通,离发布还差几件事:测试对象必须是目标产物,判定规则必须受保护,证据必须满足对应平台的要求。
ReleaseGate 的策略签名和平台证据校验逻辑已经写完,但截至本文记录的时间尚未启用。回执接入已覆盖 Android、iOS、小程序;配置层面可以声明 H5 平台,实际接入时会被直接拒绝。installed-app 模式跑出来的功能结果,不进发布验收。

图 2:先验证发布策略的来源,再核验平台子证据和通过次数;配额不足时拒绝。签名保护发布策略,证据包另行校验。
ReleaseGate 第一步校验策略来源。策略证明用 Ed25519 签名保护,把策略摘要与签发者 issuer、来源引用 reference、签发时间、审批名称 approvals 绑定在一起。验签公钥还要和 CI 给出的固定公钥摘要比对;证明里带有效期,用来约束授权时间;预期控制面指纹用来限定允许的执行规则。
来源校验通过后,门禁读取平台子证据,核对终态、业务标志、控制面指纹、产物清单、安装回执和运行平台。只有合格记录才计入平台通过次数。平台接口连通、业务验收通过、发布条件达成,这三件事各自需要对应的证据。
这套实现仍有明确的信任边界。
只对外开放两条命令
命令面由 package.json#aipbE2E 定义成机器可读契约,并配套测试,防止 test:h5、test:android 这类旧命令重新露头;平台层面的命令则降级为 exact-case 的内部适配细节。出口越少,“绕过内核直接跑脚本、再宣称用例通过”的路子就越少,Agent 需要理解的命令集合也跟着变小。
坚持使用真实业务数据
不让模型参与结果判定。项目给 AI 划的边界是:可以浏览页面、生成待审核用例、聚类失败原因;但不能找到一个替代元素就把失败改成通过,也不能凭“置信度高”跳过检查文件。ProofKernel 只收结构化观测和证据,模型输出最多停在“计划”这一层。

图 3:测试体系的演进阶段。各阶段的能力建设与平台运行结果分别记录。
上图是完整的演进路线。看清现状和取舍之后,我们回到 2026 年 3 月,按时间顺序看这套体系怎么搭起来:从早期 BDD 试点,到跨端执行、测试对象识别、语义设备操作、证据封存;10 月完成的兼容治理和结果归因修改,对应两类更细的故障场景。
这套体系最初并不复杂。项目早期,Codex、ClaudeCode 这类工具把代码生成速度提了上来,但需求理解、环境准备、结果复核不会跟着自动解决。应用的聊天、角色、好感度等模块各有业务规则,光看页面名称,大模型推不出完整的验收要求。动手设计测试之前,仍要先把业务预期理清,再挑合适的验证手段。
测试本身也会把错误实现判成通过。大模型读源码生成用例时,很容易把代码当前的行为直接当成预期;如果实现本身就误解了需求,用例会继承这份误解。项目需要从需求规格和已确认的业务实例里提炼预期,再与实现对照。业务变更之后,还要回头检查用例是否过期、断言能否识别目标故障。
不同问题要匹配不同的验证方式。规则计算可以用单元测试隔离输入;原生容器与 H5 之间的 Bridge 接口,用契约测试校验双方约定;原生异常页与 WebView 的衔接,则要在 App 内部观测两层怎么配合。单靠堆设备用例,补不上规则层缺少边界输入的漏洞。行为驱动开发(BDD)的价值,是让团队基于业务实例对齐验收标准,再交给对应层级执行:日常开发优先用单元、集成测试拿反馈,涉及真实页面和跨层交互时才启用 E2E。
单次运行成功,只能说明这次被检查到的行为符合预期。某个页面恢复场景跑通,不代表所有页面都能恢复;浏览器里通过,也不代表原生容器同样正常。
尤其要警惕批量生成带来的批量误判:把进程正常退出当成业务通过、只凭包名和版本号认定安装包出自当前源码、页面更新后继续沿用旧连接,这些都是真实运行中出现过的问题。后面补业务断言、核对测试对象、校验证据,针对的正是这几类问题。
本章讲业务描述怎么变成可执行用例,也解释“能跑”和“断言有效”为什么必须分开确认。
2026 年 3 月,「我的」页面的用户故事(Story)已经用 Given/When/Then 写好了验收场景,分别定义前置条件、用户动作和预期结果,覆盖用户信息、按钮、空状态、客服相关行为。但当时的 TDD 流程读的是另一份验收规格,这些业务场景还没接进自动化执行。
项目引入 playwright-bdd,把业务场景转成 Playwright 原生用例,既保留可读的场景描述,又复用 Playwright 的执行和报告能力。首批 6 个场景、共 12 个 Step 全部跑通;3 月 20 日的记录显示,无头模式耗时约 7.7 秒,带界面模式约 11.1 秒。这只是早期单个 H5 页面的运行记录,App 回归的耗时后来需要单独测量。
业务描述放在.feature 文件里,步骤实现把每条描述映射到数据准备、页面操作或结果检查。FE 侧的 playwright.config.ts 通过 defineBddConfig 指定 Feature 和 Step 文件,步骤代码调用 createBdd()注册 Given/When/Then。生成器把场景和步骤实现转成 Playwrightspec,输出到.features-gen 目录;Playwright 按项目配置执行,JSONreporter 输出结构化结果,供上层汇总。

图 4:业务场景与步骤实现共同生成可执行用例;复用生成物后仍需重新运行测试。
场景能自动跑之后,场景描述和实际断言对不上的问题也暴露了出来。my-page.steps.ts 里有一条昵称步骤:场景要求展示固定昵称,断言却只校验去掉空白后文本长度大于 0。这样一来,页面显示了错误昵称也照样判成功,只要文本不为空。另一条身份标签步骤,也只校验了用户信息区域可见。
处理这类问题,先要明确验收的真实诉求。如果验收只要求区域里有内容,场景描述就该照实写;如果要校验特定昵称或身份,就得准备可控的账号数据,并校验具体字段值。Given/When/Then 能把团队讨论出的业务实例转成可执行场景,但 Then 里的检查够不够,取决于业务预期和步骤实现。
场景转换不必每次都重做。h5-bdd-cache.ts 读取 Feature、Step、Playwright 配置、包配置和锁文件,算出输入摘要;摘要没变且生成目录里已有 spec 文件,就直接复用,否则重新生成。业务代码改动不一定改变转换结果,所以复用生成物可以,但测试必须重跑,不能把上一轮的业务验证结论直接搬过来。
这份摘要只回答“生成输入有没有变”;生成目录里的文件有没有被其他进程改过,需要另加校验。
至此,业务验收描述已经可以自动执行。下一步要解决的是:同一个业务目标,怎么在不同运行环境里验证。
本章说明同一个业务目标怎么分配到 H5、Android、iOS 和小程序,以及哪些逻辑没法跨平台共享。
应用初期基于 uni-app 复用业务代码,但浏览器、AppWebView 和小程序运行环境本身有差异。浏览器端可以断言 DOM 节点状态;原生权限、系统交互、桥接调用,则要各平台分别实现验证逻辑。以加载失败恢复为例,异常页由 Android 原生层渲染,恢复完成后内容由 H5 加载。只跑浏览器用例,观测不到两层之间完整的交互过程。
7 月落地跨端设计,可共享的部分限定在业务意图、动作标识和后置条件。后置条件是动作完成后业务必须达成的结果,比如点击重试之后目标页面恢复。各平台围绕同一个业务目标完成验收;元素怎么定位、动作怎么执行、结果从哪里读,由各平台自己实现。
H5 继续用 Playwright;Android、iOS 接入 Appium 和对应驱动;小程序对接开发者工具的自动化能力。7 月 28 日的架构记录显示,H5 已有 16 个 BDD 场景,设备端先落地会员页一个场景,验证平台动作和业务目标能否对上。页面结构或驱动能力一变,对应平台的定位和观测逻辑就要跟着调整;共享业务目标之后,这部分维护量仍由各平台分担。
执行侧随后分成两条路径:跑提前写好的固定用例;读取当前界面节点,在计划限定的步骤内完成操作。两条路径都通过平台执行器接入同一套验收流程。

图 5:固定跨端用例与语义设备任务分别接入;指定执行器缺失时阻塞,不切换平台。
平台执行器(Runner)用四个接口接入统一流程:supports 声明能处理的任务范围,preflight 校验启动前置条件,run 执行场景,cleanup 释放资源。RunnerContext 向下传递本次授权、源码状态、尝试材料目录、剩余时间预算和取消信号。各平台仍可保留自己的准备和执行逻辑。
RunnerRegistry 按 ID 精确查找执行器,注册阶段禁止重复 ID。找不到指定实现,直接停止并返回 blocked,不会自动切到别的平台。每次 attempt 有独立材料目录,重跑生成全新的尝试记录,失败步骤的材料和后来跑通的材料可以分别留存。
不同执行器从各自的底层工具取结果:H5 的 ManagedCaseRunner 读 PlaywrightJSON 报告;Android、iOS、小程序适配器读内部执行器状态和平台证据包;AgentDeviceRunner 对接基于界面节点识别目标的设备步骤,返回每一步的观测。
环境是否就绪、动作是否完成、业务结果是否满足验收,要分开记录。统一返回格式便于上层汇总核对,但每个平台得输出足以判断后置条件的证据,上层才能汇总。最终能验到什么,受平台自身观测能力限制。
平台越接越多,前置准备也跟着变重。修局部问题的开发流程,仍要保留一条轻快的验证路径。
本章讲怎么避免微小改动每次都拉起完整设备链路,同时留一条可核对的回归入口。
8 月初,项目调整了默认开发路径:改代码时先定位对应源码,跑针对性的单元或集成测试;要看界面就直接操作当前平台窗口;只有明确需要自动化回归时才进 E2E 流程。resolveVerificationRoute 根据当前请求选一条路径,三条互斥。环境条件不达标,本次验证直接停止并返回 blocked,不让局部检查顺势扩展成装新包、切平台、跑整套回归。

图 6:本次目标只选择一条验证路径。精确用例在设备准备前解析,匹配零个或多个实例时停止。
进入 E2E 流程后,交互式回归会绑定一个稳定的用例 ID,例如 Android 主文档恢复场景对应 android-h5-main-frame-recovery。localCaseCatalogBlocker 借助 Cucumber 源码解析器展开可执行场景,强制这个 ID 只能对应一个执行实例。它只读场景定义,不加载步骤钩子,也不连设备,所以能在初始化设备之前就发现用例选错、一次匹配到多个实例这类问题。
ScenarioOutline 模板的 Examples 表格,每一行参数都会生成一条独立执行实例。标签匹配到多行就会选中多个实例,即使模板标题相同也不能合并处理。匹配不到任何用例,或者匹配到多条实例,都直接终止执行。
H5 还会校验登记的场景文件和标题,标记 @skip 的场景直接跳过。公开的精确执行固定使用 mobile-414 这套 Playwright 配置组,worker 数为 1,关闭重试,报告里只有单次执行。mobile-414 属于 Playwright 配置,稳定用例 ID 标识具体场景,两者分开校验。
缓存复用的是生成出来的用例,会话池复用的是驱动连接。DriverSessionPool 保存尚未完成的初始化 Promise,后续请求等同一个 Promise 完成,避免并发创建多套连接。驱动初始化完成后,在同一个池的生命周期内可以复用;池退出时统一关闭连接;初始化失败也要尝试清理已创建的资源。
驱动连接能复用,业务前置条件仍要逐条确认。会话池不会自动重置账号和页面,下一条场景还得自己确认前置条件,避免继承上一条用例留下的业务状态。用例范围不唯一,就在筛选阶段停下;驱动初始化失败,记为连接层面的问题。只有拿到对应的业务观测,才能判断场景的验收预期是否达成。
构建、安装、执行被拆成独立阶段。验收源码产物时,默认复用已经准备好的产物和安装记录,只有明确要求全新安装包才走独立构建安装。产物关联信息有效、设备与运行环境匹配,才允许复用;复用之后,当前用例的断言仍要照跑。
每次回归都配时间预算和清理逻辑。遇到确定性阻塞,记录原因后直接停止,避免单条用例反复重试、演变成没有边界的排查。接下来要核对两件事:业务断言是否成立;测试跑的那个包,是不是本次待验收源码编译出来的。
本章介绍结果的判定主体、判定依据,以及 passed、failed、blocked、skipped 各自的含义。
举个例子:业务要求“网络恢复之后能重新进入首页”。App 启动命令正常结束,只说明命令跑完了,首页有没有真正恢复得单独检查。旧入口曾把退出码 0 直接当成全部验收项通过,用例缺断言也能汇总成通过;V3 改造移除了这条默认通过逻辑。
验收请求(Issue)描述业务目标、执行范围和验收条件。VerificationPlan 把每条验收条件分给一条登记过的测试,同时指定判定依据,也就是代码里的 oracle。ProofKernel(下文简称内核)校验计划和执行条件,再调用平台执行器 Runner;Runner 操作页面,返回观测结果和证据材料。Agent 可以参与分析、生成计划,但用例是否通过,完全由内核按统一规则算出。

图 7:内核先校验计划,再执行任务,按逐项断言和必要证据判定结果,最后封存材料。
Issue 里不存 Shell 命令、XPath 和屏幕坐标;Plan 只能选用已登记的能力,不能把任意 Shell 命令带进执行流程。验收项写的是「首页恢复」,判定依据就必须是针对首页恢复的检查记录,光有“启动 App”这条动作记录判不了通过。
runIssueVerification 入口先解析输入格式。解析失败就不生成执行记录;解析通过才创建独立的临时目录 attempt。后面的配置、身份校验即便阻塞,审计材料照样留得下来。
内核分两轮校验计划。纯配置阶段,靠 accountingErrors 检查 Plan 内部的 issueId:每条验收条件必须恰好分给一个任务,oracle 必须和选中的能力匹配。出现未知验收项、能力未实现、unresolved 字段非空,直接阻塞本次验收。任务涉及平台或外部影响时,还要同时满足 Issue 声明范围和本次运行授权。拿到源码信息后,内核继续检查变更文件和验证任务之间的对应关系。
验收目标 | 文件约束 | 结果能说明什么 |
|---|---|---|
change-verification | 使用 complete-change-set;Plan 声明的文件集合等于当前变更集合,各任务 coveredFiles 的并集覆盖 Plan | 每个变更文件都已分配验证任务;是否验证充分仍取决于用例和判定依据 |
functional-regression | 使用 acceptance-only;Plan 与任务的 coveredFiles 均为空 | 指定验收条件是否满足,不声明源码文件覆盖范围 |
这套文件检查能发现“改动没有分配验证任务”的情况。语句覆盖率、用例发现缺陷的能力、判定依据和业务需求是否匹配,得另行评估。
每个任务依次执行 preflight→ run → validateRunnerProof →cleanup。正常路径逻辑简单,真正决定结果可信度的是异常路径:预检失败就不能继续操作页面;运行流程已中断,就不能拿残缺观测判通过;即便前面阶段失败,清理逻辑也应尽量执行。

图 8:预检不满足、运行异常或取消,都会跳过后续环节:前者跳过运行与证据核对,后者跳过证据核对。这些路径仍在尝试清理,提前阻塞则保留最小审计包。
前面的任务停止后,尚未启动的后续任务写入 skippedTaskIds。10 月 29 日又补了一类场景:某条精确用例受当前数据条件限制、本就不适用,任务直接返回 skipped。两种情况下业务都不算通过,区别在于前者完全没运行,后者做完了限定范围内的前置条件检查,并产出证据证明当前条件不适合执行。Runner 正常返回时,内核依旧分开校验环境、动作、逐条断言和证据文件。
Runner 返回的数据 | 内核如何核对 |
|---|---|
infrastructureReady、businessActionCompleted、退出码 | 分开记录环境、动作与命令状态;环境可用而动作未完成,或退出码非零,归为业务失败 |
assertions 中的 acceptanceId/oracle/met 与证据类型 | 每个验收项恰好一条观察,拒绝计划外观察;依据逐项 met 重算 businessPostconditionPassed |
evidence 中的 kind/path | 路径唯一且位于允许目录,引用的是非空普通文件,要求的证据类型齐全 |
如果在业务动作之前就发生基础设施阻塞,内核只校验现有材料,不会要求提供理论上产不出的业务截图。只要有一个任务返回 businessActionCompleted=true,流程结束时内核会复查源码上下文;源码漂移或复查失败,整轮验收优先置为 blocked。
以首页恢复用例为例:设备连接失败记为 blocked;拿到有效页面观测但首页没恢复,记为 failed;预定动作执行完成、首页断言成立、必需证据齐全,三者同时满足才标 passed。当前业务数据不足且用例允许该场景不适用,标 skipped。skipped 不等于通过;缺必需证据会降级为 blocked。汇总时,内核先处理计划错误、阻塞和源码漂移,再处理业务失败与不适用场景,最终状态以证据索引为准。
索引写完文件清单和状态后,材料整包原子封存。重跑会生成新的 attemptId,旧的失败记录完整保留。翻历史报告时,要把原因码、发生阶段和最后一条可靠观测放在一起看。
音频 APM 用例依赖当前聊天窗口里已读取或本人发送、尚未播放的语音。2026 年 9 月的一次真机运行没找到符合条件的语音,而旧逻辑把进入会话这类安全 UI 动作也算作业务动作,结果被归为 blocked。
10 月 29 日修改后,businessActionWasExecuted 只统计 external-consequence 类型动作:这类动作真正执行了,或者动作发出后业务后置条件失败,才算发生过业务动作。打开页面、进入会话这类 safe-ui-change 操作不再改变这个标记。
ManagedCaseRunner 也没有把所有夹具缺失场景一刀切成 skipped。只有 Android 的 chat-audio-playback-failure-apm 这条精确用例,同时满足固定原因码、证据策略版本>=3、探针记录完整、session 清理确认完成、未执行外部后果动作、证据包完整,才返回 CHAT_AUDIO_APM_FIXTURE_NOT_APPLICABLE。
内核复核后保留 skipped,验收项的 met 仍为 false;进程零退出也不会把结果算成 passed。
三次关键真机运行分别澄清了一个问题:9 月 20 日暴露旧归因逻辑会把安全页面动作统计成业务动作;10 月 28 日确认真机执行链路和夹具识别可用,但严格用例没有触发真实播放;10 月 29 日修正归因,同一条用例以 skipped 状态封存。这组记录验证的是安全动作边界和结果分类逻辑,音频播放业务本身是否通过,不在它们的证明范围内。关键耗时、产物版本与复核线索见附录。
准备阶段和业务执行阶段分别计时,停止与清理另有独立配额。withinBudget 超时后发送 AbortSignal,最多等 3 秒确认底层结束;清理用独立信号,预算 3 秒。runManagedProcess 向子进程组发 SIGTERM,等 250ms 后升级为 SIGKILL,再检查终止状态。底层动作能不能真的停下来,取决于执行器有没有正确实现取消和进程管理。
独立超时包装已经覆盖准备、预检、运行和清理。配置身份采集、证据核对、源码复查和封存逻辑还没做同等包装。源码复查完成后内核仍会检查截止时间,超时可以转 blocked。只在入口设一个总预算,保证不了每个阶段都能及时终止。
内核能识别断言缺失、证据为空、对应关系错乱。截图是不是对应目标页面,用例对「首页恢复」的判断逻辑是否匹配业务要求,仍依赖 Runner 和用例实现,需要独立评审。
登记用例、执行脚本、判定规则和关键配置共同决定一次验收怎么跑,本文把这套执行规则和配置统称为控制面。控制面本身也要留版本记录。AipbProjectProfile 针对 catalog/features/scripts/src、关键配置和锁文件生成按路径排序的内容摘要,拒绝符号链接和特殊文件。catalog/release-policy*.json 不纳入这份摘要,走独立签名校验;外部工具和依赖版本另需维护。
只有传入 expectedControlPlaneFingerprint,内核才比对实际摘要和预期摘要;不传就只做记录。结束阶段的源码复查不会重新计算控制面摘要。记录下来的摘要证明不了规则来源可信;预期值怎么来、规则怎么受保护、执行器和判定依据是否正确,仍要靠独立评审和 CI 管控。
本章讲怎么证明:设备上被测的 App 确实来自本次源码的构建产物,而不是旧包或错误页面。
逐条断言全过之后,还有一类常见问题:手机上装的 App,是不是刚改完代码编译出来的那版。工作区源码已经变了,设备上可能还跑着旧安装包;包名和版本号完全一致,也可能对应不同的构建输出。分别记录源码、安装包、设备安装信息和运行页面,用来确认被测对象。
产物清单(ArtifactManifest)记录声明的构建输入与输出包之间的关联;安装回执(InstallReceipt)记录本地产物与设备侧安装信息之间的关联;运行记录(RuntimeContext)描述当前测试连接的 App、WebView 和页面环境。构建、安装、执行是三个独立步骤,日常回归可以复用已准备好的安装包。

图 9:源码、产物、安装信息和运行页面分别关联。安装回执只证明元数据关联。
源码身份校验要同时采集两类输入:Git 状态指纹和选中文件的内容摘要。同一批文件加入暂存区或提交之后,git 记录会变,文件内容却可能一字未改,两者必须分开看。
catalog/source-inputs.json 按平台声明 repository/include/exclude,限定采集范围。Android 包含相关根仓配置、前端和原生壳输入;iOS 分别采集前端仓与原生仓,排除生成产物、依赖包和构建输出目录。采集会沿选中的 gitlink 递归。子模块未初始化或存在冲突时,strict 模式直接阻塞;已初始化的仓库允许记录未提交源码和 dirty 状态。
几种记录用途不同,不能互相替代。
记录 | 计算范围与用途 |
|---|---|
branch/commit/dirty | 提供 Git 复核线索,不直接进入仓级 sourceFingerprint |
仓级 sourceFingerprint | 所选路径的 HEAD 文件树、暂存与未暂存 binarydiff、非忽略 untracked 字节及规则摘要;相同内容经暂存或提交后也可能变化 |
effectiveContentSha256/sourceFiles | 记录当前路径、类型、可执行位与内容;逐文件清单解释增加、删除和修改,不替代总指纹 |
Manifest 的 source.fingerprint | 由输入 descriptor 摘要与各仓路径、仓级指纹共同计算;范围不同于 Plan 的变更集合指纹 |
输入清单要跟随构建流程维护,目前没有自动推导完整依赖图。外部工具链、忽略文件和服务端配置没有全部纳入,清单只能说明声明范围内的输入。
单独生成 Manifest,只能把当前输入快照和已有文件关联起来。受控构建会在构建前后各采集一次输入,保存构建命令和采集记录,再比对输入指纹;不匹配就拒绝生成清单。
Android 开发构建有个特例:Git 指纹变了,分支没变,文件内容完全一致,只是暂存或提交状态变了。这种情况下会继续比对文件内容,确认一致后允许更新记录;内容一旦有改动,直接丢弃新产物;写 Manifest 阶段还会再复核一次。这是 Android 开发构建的特例,不要当成通用构建规则,更不能理解成通用构建只比对文件内容。

图 10:通用构建比较输入指纹,Android 开发构建另有内容相同的状态变更特例。图中执行前的窗口核对以 Hybrid 页面为例,原生页面按 App 标识及任务要求校验。
输出包单独计算 SHA-256,避免把同名文件当成目标产物。Android 保存的 APK 副本以 SHA-256 命名;目标文件已存在时,摘要必须和待保存的 APK 完全一致。普通文件产物直接算内容摘要;目录产物按排序后的路径、类型、长度和内容计算,并拒绝符号链接。
Manifestv4 把环境、配置、严格模式和 sourceInputs 放进 build 段;源码身份放在 source 段;产物路径、SHA-256、字节数放在 artifact 段。生成 manifestId 时,会对除该字段外的全部对象算摘要,其中包含生成时间,所以同一份产物也可能得到不同 ID。自身摘要只能校验清单内容;发行者身份和签名来源另需校验。
Android 公开入口会在 source-artifact 准备阶段生成 artifact-verification.json,每次最多一份回执,把 Manifest、源码、产物摘要、验证时间和控制面指纹关联起来,减少下层重复扫描。下层优先校验这份回执的字节摘要,再核对本地产物;没有回执文件就直接校验 Manifest。它属于本次运行的校验回执,和下文说的安装回执用途不同。
这些检查仍不足以证明构建可复现。构建环境没有完全隔离,构建过程中改文件再改回来,也可能绕过前后快照比对。要进一步提高证明强度,得固定环境、锁定外部依赖。
Android 安装回执包含设备、App、Manifest、本地产物摘要、版本和 lastUpdateTime。执行前会重新读取设备安装元数据比对;靠安装更新时间,可以识别同版本覆盖安装。iOS 记录 BundleID、版本和 buildnumber,没有等价的更新时间字段,相同版本加构建号的覆盖安装仍有识别盲区。
Android、iOS 都不会读取设备上已安装的二进制文件重新算哈希。回执里的包哈希取自本地文件,证明范围明确标为 metadata-correlation-not-binary-proof,也就是只证明元数据之间存在关联。这个限制影响结论可信度,报告里不能省。
核对完安装信息,还要选定唯一的运行窗口。Android 从包名匹配的 WebView 候选集里,结合 PID、自动化 context、窗口句柄和 URL 筛选;候选唯一且观测稳定,才建立连接;连上之后重新读取 window.location.href,确认目标页面和运行环境。iOS 的枚举逻辑不同,没有 PID 等价证明,同样要校验窗口和 URL。
驱动记录保留 WebViewprovider、浏览器版本、ChromeDriver 版本和文件摘要。Chrome115 及以上比对前三段版本号,旧版本比对 major 版本号。这一步只做兼容性校验,不要求版本号完全一致。
包摘要证明不了远端 H5 有没有更新,同一个 URL 返回的内容也可能变。运行 URL 用来确认被测对象和运行环境;远端 H5 还得有额外的资源版本记录,才能对应到具体部署版本。
9 月 16 日新增了两类验证目标,各自回答不同的问题。
目标 | 执行前要求 | 结论范围 |
|---|---|---|
installed-app | 仅用于 Android/iOS 的功能回归,明确环境、设备和 App;不要求产物清单,不自动构建或安装,不声明源码覆盖,记录 installation-observation.json | 本次安装版本的指定功能表现;固定标记 sourceArtifactVerified=false,不能证明当前源码修复已生效 |
source-artifact | 默认复用有清单的产物,核对源码、Manifest、本地产物与安装信息的关联;缺少必要材料即阻塞 | 在声明的输入和安装证明边界内,验证指定验收条件;不能把材料关联扩展为设备二进制证明 |
Issue 和命令行传入的 verificationTarget 冲突时,内核停止执行。source-artifact 材料不足,也不会自动降级到 installed-app 接着跑。
测试对象身份和验收范围要分开判断。即使用 source-artifact 模式跑功能回归,也仍是 acceptance-only;要验收整套代码改动,得用 change-verification 模式,核对变更文件与验证任务之间的对应关系。
确定测试对象之后,Agent 还要拿到页面控件、找出可操作的节点,并明确本次任务允许做哪些动作。aipb-chat-device 读取界面节点树,把控件整理成带角色、标签、文本的语义快照,给每个节点分配 @ref 短引用。调用方从本次快照里挑目标,设备层在执行动作前校验引用和授权。
aipb-chat-device 是独立维护的设备操作工具,以 MIT 许可发布,通过插件接入 我们应用 E2E。对外提供 iOS、Android、Web、桌面和 TV 接口,这里只讲和本仓库直接相关的三个后端。接口能调通,不等于业务用例通过;业务是否达标,要看平台的完整执行记录。
为什么自研中间层,而不是直接用现成工具?
所以确定性回归仍交给 Appium 和 Playwright,中间层只做一件事:把“设备当前界面”转成 Agent 能消费的语义快照,同时提供统一 CLI、常驻进程和可复核的运行记录。
设备操作常常要连着执行多条指令:读页面、点控件、读执行后的结果。每条命令都重连设备,会反复重建连接和界面状态。aipb-chat-device 把 CLI 客户端和设备连接逻辑拆开:daemon 常驻进程保存连接、会话和界面状态;平台后端负责读取控件和注入输入。一条 CLI 命令结束,下一条可以复用已有连接。

图 11:aipb-chat-device 完整结构:命令投影、统一传输、daemon 路由与常驻状态、授权和 refframe 安全面,以及本地平台与云 provider 执行分派。
CLI 解析参数,提交请求,输出 JSON。和 daemon 默认走 127.0.0.1TCP,传输换行分隔的 JSON;也支持 HTTP 入口 POST/rpc,遵循 JSON-RPC2.0。RequestRouter 校验连接 token;服务靠会话锁和设备锁管控请求,换个会话名也绕不开单台设备的并发限制。
命令和执行规则通过 CommandDescriptor 注册,路由和请求策略从声明推导。获取快照、点击、输入这些操作,交给 Interactor 或专用处理器,再由对应后端完成。
后端 | 界面采集与输入 | 实际连接 |
|---|---|---|
Android | 自带 snapshot-helperAPK,经 Instrumentation、UiAutomation 读取 AccessibilityNodeInfo;普通输入走 ADB,复杂手势走 helper | 持久 helper 经 adbforward 建 TCP;采集失败可退到同一 helper 的一次性 Instrumentation |
iOS | 自有 SwiftXCTest Runner,用 XCUIApplication、XCUIElement 读取控件并执行动作 | daemon 发送 POST/command JSON;模拟器走本机,真机由 usbmux 或 CoreDevice 网络隧道承载 HTTP |
Web | WebProvider 管理 agent-browser 子进程,以 --json--session 和标准输出交换结果 | 复用浏览器会话;agent-browser 内部协议由外部后端实现 |
Android、iOS 的原生路径不依赖 Appium;只有启用了 WebDriverprovider 才会用到 Appium/WebDriver。iOS 的 POST/command 和 CLI 的/rpc 是两段不同协议,不能混为一谈。
各后端保留平台原生的采集与输入逻辑,异常处理区分读取失败和输入失败。Android 快照采集失败时,可以退回 helper 的一次性读取;手势一旦下发就不会自动重放,因为上一轮可能已经执行了一部分事件。
连接 token 只能确认请求来源,不能授权任意业务操作。设备层还得知道本次任务被测的 App、允许的动作集合和授权到期时间,这些约束来自项目策略 ProjectPolicy,再叠加本次任务的 RunSpec。

图 12:请求准入先于设备发现;WebURL 与动作引用在处理器中校验,原生不逐动作读取前台 App 身份。
ProjectPolicy 声明允许的平台、App、环境和预算;RunSpec 进一步限定本次目标、动作目的(Intent)和有效期,不能放大项目授予的权限。请求准入阶段校验字段、目标 App、有效期和 Intent;打开会话时校验 appId,把授权摘要绑到会话上。后续命令不能删除或修改授权,也不能把无保护会话临时提权;预算检查发生在调用命令处理器之前。后续兼容迭代补齐了业务 Intent 授权和到期清理边界,相关验证在后文展开。
本仓库 E2E 内部的 executeDeviceAction 会为每个任务生成独立会话、project-policy.json 和 run-spec.json。平台、App、环境取自控制面已校验完成的授权;Intent 取自步骤的 id/action/target/purpose。适配器不会直接读仓库全局策略,控制面策略文件的保护由接入方自行保障。
处理受保护动作时,设备层核对当前引用、节点和 Intent 是否匹配,不会降级到坐标或选择器定位。Web 分支还会读取 window.location.href,校验来源、路径和环境。
原生后端有明确局限:不会每执行一个动作就重读前台包名或 BundleID。会话打开时锁定目标 App,但保证不了每一步动作执行时前台仍是它。任务可以配置 expectedEnv 或校验 URL,可原生后端拿不到可信 URL,于是受保护的修改操作直接停止。
本节解释两件事:为什么全量回归全绿,安全关键字段仍可能丢;以及如何靠跨层契约测试补上这个缺口。
2026 年 10 月 14 日,aipb-chat-device 完成了一次大规模 Agent 层代码更新,目标是在保留 AIPB 策略边界的前提下,把多平台设备能力、语义解析和安装源安全检查并入同一套执行模型。本次改动涉及 1239 个文件,新增 68481 行,删除 20566 行。
更新带来三点直接收益:设备层增加 HarmonyOS 支持;多节点匹配返回 ambiguous,不再默认选第一个;安装源网络入口拒绝 URL 凭据、本机地址、非公网地址和异常 DNS 结果。三者分别加强了跨平台覆盖、语义动作确定性和安装链路安全。
AIPB 守卫信息要穿过多层边界:rawCLI 解析参数,commandgrammar 控制允许的 flag,commandschema 约束结构化输入,commonToClientOptions 组装 client 参数,daemon 再做请求准入、会话绑定和预算校验。大规模改动只要漏掉其中一层,CLI 参数显示正常,守卫却已经失效。
这次更新的验证回执记录了静态检查、构建和 TypeScript 校验,750 个测试文件共 6291 项测试全部通过。可后续兼容复核仍发现安全字段在跨层传递时被漏掉。这说明:全量回归通过只代表当次回归范围内没发现失败;安全关键字段从输入到 daemon 准入的逐层契约测试,是另一项替代不了的工作。
兼容复核发现 commonToClientOptions 不再透传 runSpec、projectPolicy 和 intentId,commonProperties 也没有声明这三个字段。前者导致已经读到的守卫信息在组装 clientoptions 时直接丢失;后者配合 additionalProperties:false,会把携带这几个字段的结构化输入直接拒掉。
10 月 15 日完成兼容修复,恢复了 client 字段投影和结构化 schema,新增 72 行回归测试,覆盖 press、click、fill、longpress 四条 CLI 命令的字段投影与 schema 校验,同时验证无守卫兼容和拒绝非字符串字段,共生成 12 个 case。10 月 29 日定向复跑,12 项全部通过。
这个缺陷不是守卫算法算错,而是安全字段跨层传递少了一段逻辑。它也改变了回归策略:关键安全字段不能只验证某一层读到了参数,要从命令输入、schema、clientoptions 一路校验到 daemon 准入,避免界面参数看着正常、实际约束已经失效。
后续兼容迭代继续收紧守卫语义:不再靠关键词统一拦截消息发送、删除、下单、支付、隐私同意类操作;只有 RunSpec 显式声明了对应业务 Intent,动作才允许执行。完整用例授权本身已包含用例需要的业务动作,不需要每一步再确认一次。
取消关键词拦截之后,边界仍完整保留:动作要匹配当前 Intent 和语义节点;ref 必须来自当前 refframe;会话、App、环境、有效期和预算全部继续校验。RunSpec 没声明的动作和目标,照样不能执行。
同一批改动还处理了 RunSpec 到期的资源清理。expiresAt 是绝对截止时间,不会因为排队或 open 操作延迟而顺延;只有原 RunSpec 绑定的现存 session,可以在到期后最多 2 秒执行 close。宽限期内禁止 app-onlyclose、shutdown、save-script 和脚本发布;拿到会话锁后还要再校验一次身份和时间边界。
这批工作区兼容改动已完成定向验证:字段投影、请求执行范围、业务 Intent 授权、到期 close、租约清理、脚本发布清理和 daemonclient,共 7 个文件、201 项用例全部通过。单元和契约边界已经锁住授权逻辑与到期清理逻辑;但代码有没有跟随目标版本发布,要和验证结果分开看。
点击可能触发页面跳转,而响应返回超时。调用方不能仅凭超时就断定动作没执行,更不能继续用上一页的节点引用,否则会误点新页面的其他控件。

图 13:引用在副作用发送前失效;响应成功或超时都不会恢复旧引用,后续动作需重新观测。
实现上,最新观测树 session.snapshot 和动作引用集合 refframe 分开存储。refframe 保存生成引用那一刻的节点树、代次、有效状态和本次返回给调用方的节点范围。内部只读采集可以更新观测树,但不会自动恢复旧引用的授权。
完整快照可以授权快照里的全部节点引用;find 查询和动作后的差异观测只返回部分节点,那就只授权这一部分。例如 @e12~s42 表示 s42 代观测生成的节点引用,e12 不能当成跨页面稳定的元素 ID。admitRefMutation 校验引用集合的有效性、代次匹配以及节点是否属于本次返回范围;局部结果必须带上带代次的引用。
expireRefFrame 在设备副作用发出前同步把引用标记为失效,然后才等待设备响应。哪怕点击超时,旧引用也不会恢复。要继续执行需要节点引用的受保护动作,必须重新观测,拿新快照对应的引用。
这套逻辑会增加读取次数,但把动作的执行范围限定在刚刚观测过的界面节点上,避免页面跳转或请求超时后误用过期节点。
Agent 可以交互式读快照、挑下一步,用来做页面探索。本仓库的 E2E 适配器只执行 Plan 预先写好的 deviceSteps,循环过程不交给模型决定下一步;固定跨端用例仍由 Playwright/Appium 这类执行器承担。
语义适配器默认加载 Frontend/aipb-chat-device/bin/aipb-chat-device.mjs,也可以从 AIPB_CHAT_DEVICE_BIN 或 PATH 环境变量读取;找不到 CLI 直接返回 blocked。每一步都会重新采集界面:动作类命令用-i 交互快照,把节点引用 refsGeneration 一起提交;断言读普通快照,由执行器在本地判断。Plan 里的动作名 long-press 由适配器映射为设备 CLI 的 longpress。

图 14:固定步骤循环分为动作和断言两类。正常执行后先尝试关闭会话,再整理证据并关联验收项;阻塞异常单独返回,不组装验收断言。
分支 | 当前实现的判断 | 不满足时 |
|---|---|---|
press、fill、long-press | 目标唯一且有引用;enabled/hittable 明确为 false 时拒绝,缺失时此处不拒绝;fill 从指定环境变量取值并在日志脱敏,长按核对返回时长 | 零匹配、多匹配、不可操作、缺输入或无法证明长按时长均阻塞 |
assert-visible | 匹配节点恰好一个 | 零匹配记为未满足,多匹配阻塞 |
assert-text | 唯一节点的 text/value/label 拼接后包含预期文本 | 无节点或文本不符记为未满足,多匹配阻塞 |
这些断言没有内置的自动轮询等待。匹配到一个节点,不等于像素级可见;文本出现,也不等于后端业务已经处理完。动作之后的 verify/settle 只观测界面变化,界面稳定和业务验收通过要分开判断。
执行步骤和验收项靠 acceptanceId→ oracle.ref → postconditionId 建立映射。执行器产出步骤后置条件的观测,Runner 按标识组装验收断言;缺少指定观测,默认判不通过。这条映射能核对观测对应的是哪条验收需求,但断言本身够不够证明业务正确,仍需评审。探索中跑通的操作,必须明确动作约束、后置条件和证据要求之后,才能纳入回归计划。
执行器区分正常完成和执行阻塞两条路径。步骤阻塞时,executeDeviceAction 抛出异常,finally 块只尝试关闭会话,不再汇总证据引用,也不返回 postconditions。步骤正常完成,先尝试关闭会话,再整理证据、映射验收项;关闭会话失败同样抛出异常。已经产生的阻塞异常不会被后来的关闭错误覆盖。
任务预算 120 秒,最多 8 次动作、24 次观测,不配重试;RunSpec10 分钟后过期。每条 CLI 命令同时受 ProofKernel 的剩余预算和取消信号管控。材料保存在<taskId>.agent-device/,包含前后截图、driver.log、策略和 RunSpec;快照 JSON 写入日志。RunSummary 会话摘要记录耗时、动作次数、观测次数、重试、停止原因和未验证项。completed 字段只代表会话执行结束;E2E 最终是否通过,仍要看断言和证据要求。
前面讲了各项机制,下面用一次真实运行把整套逻辑串起来。Android 断网恢复用例同时覆盖网页和原生两层:H5 主文档加载失败,App 展示原生异常页;离线点击重试,仍会触发重试;网络恢复后再点,异常层消失,H5 页面正常加载。这个用例能同时检验执行器的故障注入能力、两层界面的观测能力,以及页面重建之后的证据获取能力。

图 15:恢复后重新发现目标页面并建立连接,取得 DOM 断言;两次尝试分别保留。
用例只让目标 WebView 进入离线,整机网络保持连通;借助网页调试协议 CDP 关闭缓存、绕过 ServiceWorker,并在 URL 上追加本次恢复标记。这里不模拟整机断网,也不注入全部资源类型的故障。
图片加载失败不等于主文档加载失败。观测器要在导航发起前注册,从 Network.requestWillBeSent 里筛出目标 DocumentURL 并记录 requestId,只接受同一个 requestId 对应的 Network.loadingFailed 事件。首次导航和离线重试各存一份这样的关联记录,同时校验原生异常标题和重试按钮。这样才能确认异常页来自本次主文档失败,而不是某个子资源报错。
9 月 17 日那次运行,前四步全部成功:网络恢复后点击重试,异常层消失,截图和原生页面树里都能看到 H5 首页。但测试流程调用 Appium 重新绑定 WebView 时超时了。证据确认页面已经渲染,自动化却拿不到恢复后的 DOM 断言;缺少最后一步断言,本次结果标为 failed。
排查方向锁定在一个假设:页面完整重建后,旧的 Appium/ChromeDriver 连接仍指向旧的 DevTools 窗口。修改后的步骤逻辑是:异常层隐藏后,重新从目标 App 的 DevTools 端口查找页面;候选页面必须和初始页面同源、路径一致、环境匹配,且候选唯一,才建立全新的 CDP 会话,读取实际 URL 和正文容器 uni-page-body。
9 月 18 日的新尝试,1 个场景、5 个步骤全部通过,总耗时约 54 秒。证据包里保存了两次主文档 net::ERR_INTERNET_DISCONNECTED 的失败记录;恢复完成后,正文容器包含 4 个直接子元素。新的通过记录不覆盖旧的失败记录:失败尝试 60a7f0e1、通过尝试 30a95002,两份材料都在.aqe-output/e2e/目录下,可以对照确认修改点在测试连接和取证逻辑。
历史记录和当前规则要分开看。9 月 17 日的绑定超时保留当时的 failed 结果;按现在的规则,恢复步骤找不到或连不上目标页面会归为 blocked。只看最终状态定位不了责任,得结合原因码、失败阶段和最后一次有效观测,区分“产品没恢复”和“测试链路断了”。
唯一候选检查和真实 URL 比对,避免了读到错误页面;正文容器子元素非空,排除了完全空白页。这些条件满足了本次加载恢复的验收要求。但 4 个子元素也可能只是骨架屏或错误内容,要完整验收首页业务可用,还得补上对应区域和交互逻辑的断言。
当前观测能确认:重试前异常层可见,重试后异常层隐藏且能读到 DOM。要证明“正文先就绪、异常层后隐藏”,需要把同一次加载的就绪事件和隐藏事件关联起来,光靠两张截图推不出事件顺序。
用例结束后,把 WebView 恢复在线,尽量还原缓存和 ServiceWorker 设置,关闭 CDP 会话,移除端口转发。部分设置只能尽力恢复,清理残留单独记录,避免下一条用例继承离线状态。
本章解释为什么同一次验收的失败记录和通过记录都要保留,以及怎么核验一份证据包。
恢复案例要同时保存失败和通过记录,其他验收场景同理。源码、安装版本或观测逻辑一变,重跑得到的就是新条件下的判定。只留最后一次通过记录,复盘时说不清上一轮失败在哪一步、后来改了什么。框架把每轮执行记为独立尝试,用 runId 和 attemptId 关联整套材料。

图 16:材料先隔离写入,再核验、生成索引并整包发布;读取时重新校验。封存完成不等于业务通过。
createEvidenceAttempt 生成 runId,对应一次验收;每次重试生成新的 attemptId,临时目录再加随机后缀。截图、日志这类运行中产生的材料,和正式发布记录隔离开。运行过程中可以更新 run-state.json,已发布的历史记录不会被改动;重跑生成全新 attempt。
Issue 入口的目录结构如下,执行器原始材料和任务子目录省略。核验一次尝试时,要确认结果文件和证据索引属于同一组 runId/attemptId。
<issueId>/
.tmp/<runId>-<attemptId>-<nonce>/
<runId>/<attemptId>/
issue-contract.json
run-authorization.json
source-context.json
control-plane.json
verification-plan.json
task-results.json
blocker-resolution.json
run-result.json
evidence-index.json
#Runner 原始材料与任务子目录省略JSON 文件、结果和索引都用 wx 模式创建:只新建文件,同名文件已存在就报错,防止旧记录被覆盖重写。wx 不改变目录权限,也不提供写完之后防篡改的保护。
sealEvidenceAttempt 第一步校验执行结果与材料属于同一次验收:核对运行、尝试身份、用例、验证目标和证据策略。必需文件必须存在且内容非空;设备子包额外校验 RunSpec、前后观测和执行状态;场景数量、唯一键和观测顺序要保持一致。判 passed 之前,还要确认业务动作已完成、场景后置条件已满足。
封存过程有两轮材料校验,三个状态字段分别保存各阶段的结论。

图 17:第一轮核验后写结果,二次核验后写索引;最终以索引状态为准。恢复只接受唯一且为 blocked 的链末端。
字段 | 产生时机与含义 |
|---|---|
requestedStatus | 内核根据执行观察传入的请求状态,输入保持不变 |
provisionalStatus | 第一轮材料检查后的临时状态;缺材料或结构不符时降为 blocked,随后写入结果文件 |
finalStatus | 结果文件写完,二次遍历并复查必需文件后写入索引的最终状态 |
二次遍历读取文件内容,计算 SHA-256 和字节大小,记录步骤与工具版本,补充材料一并纳入摘要集合。全部完成后输出 evidence-index.json,外部消费方采信索引里的 finalStatus。证据错误单独记录;即便最后因材料不全变成 blocked,已经观测到的业务失败和原因也会保留。
执行前置阶段就阻塞的情况不会产生设备截图和业务观测。框架会生成最小审计包,保存 Issue、授权、源码、控制面、Plan、任务结果、阻塞处理和结果文件。显式配置选项时,设备子包也可以封存不完整的 blocked 材料,但这类记录不满足业务通过或业务失败的完整证据要求。
结果、材料、状态都就绪后,靠文件系统 rename 原子发布,外部消费方不会看到文件分批移入正式目录的中间状态。
读取证据包时,validateEvidenceBundle 重新枚举全部文件,要求磁盘实际文件集合与索引完全匹配,复算 SHA-256 并核对字节数。新增、删除、改内容、用符号链接,一律拒绝。随后核对结果、索引的 runId/attemptId、状态和策略版本,并基于代码重建通用与当前用例的必需文件策略。删掉索引条目,也绕不开必需材料校验。
这套检查仍有边界:封存没有文件系统快照隔离;二次枚举不能保证目录全程没有并发写入;证据包本身没有独立签名;目录不是只读;也没有 fsync 保障断电持久化。如果主体能同时改写文件和索引,校验基准照样被篡改。要更强的留存保障,得额外保护存储权限和校验基准,不能只靠一句“已封存”。
resume 时先校验同一 runId 下已封存的证据包,再用 resumedFromAttemptId 找到没被后续恢复引用的链路末端。末端必须唯一且状态为 blocked;出现分叉、审计记录异常或末端状态不匹配,直接拒绝,也不看文件修改时间。
恢复复用 runId,生成全新 attemptId,重新读取 Issue、Plan 和本次授权,从头完整执行;不会从上次的设备步骤断点接着跑,旧证据包完整保留。阻塞记录保存发生阶段、原因码、责任方和处理动作,用于复盘当时为什么阻塞、修复后执行了哪些逻辑。
2026 年 8 月 13 日的发布加固验收,记录了实现层面的改动。改造之后,源码、产物、安装、运行分别留存证据,验收也有了明确的拒绝条件。
维度 | 改造前 | 改造后 |
|---|---|---|
源码身份 | 仓库列表和笼统的 dirty 状态 | 声明构建输入,已提交、暂存、未暂存、未跟踪内容分别摘要 |
产物身份 | 产物、设备与驱动信息混合 | v4Artifact Manifest,只含构建输入与产物摘要 |
安装身份 | 安装与运行混在会话中 | 分平台 InstallReceipt,关联目标产物与设备元数据 |
运行身份 | 上下文名称或旧设备状态 | 独立 RuntimeContext |
结果 | 脚本退出可能被当作通过 | passed/failed/blocked,通过须动作、后置条件和证据齐全 |
证据 | 共享目录、散落文件 | runId/attemptId、SHA-256、原子封存 |
发布规则 | 本地配置或 CLI 可自报可信 | Ed25519 签名策略与 CI 固定公钥摘要 |
本次验收基于 catalog/fault-injection-cases.json 登记的 13 类故障,逐条比对预期状态和实际运行状态:业务后置条件失败输出 failed;源码、产物、驱动、证据等前置问题输出 blocked。该样本中没有观测到假通过,结论也仅限于登记的故障清单和本次验收。同期 Android/iOS 还没有大规模真机运行;小程序做了 6 次调试和验收尝试,均未通过。
其余阶段样本各自验证不同能力,需要分开解读。
时间 | 验证对象 | 历史结果 |
|---|---|---|
2026 年 3 月 | 早期 H5 页面 | 6 个 BDD 场景通过,无头执行约 7.7 秒 |
2026 年 9 月 3 日 | Issue 驱动控制面 | 单测 188/188,发布、证据与驱动故障注入专项 37/37 |
2026 年 9 月 10 日 | 跨端契约 | 179/179 契约测试通过,由测试替身验证平台衔接 |
2026 年 9 月 18 日 | Android 真机恢复 | 1 个场景、5 个步骤通过,约 54 秒,40 个文件进入证据索引 |
2026 年 10 月 14 日 | aipb-chat-device 中间层 Agent 代码更新 | 静态检查、构建与中间层全量回归均通过;回执记录 750 个测试文件、6291 项测试,后续字段投影兼容问题通过定向回归锁定 |
2026 年 10 月 15—29 日 | 字段投影、业务 Intent 与到期清理兼容 | 10 月 15 日字段投影 12 项全部通过;后续工作区兼容改动的 7 文件定向套件为 201/201,验证与发布状态分开记录 |
2026 年 10 月 28—29 日 | E2E 四态结果与 Android 音频真机样本 | 本轮单测 78+ 206 = 284/284,类型检查通过,跨平台契约专项 270/270;10 月 28 日真机语音发送链通过,严格 APM 用例在 10 月 29 日因无可复用夹具封存为 skipped,总耗时 21.411 秒,证据完整 |
各阶段回答的问题不同,不能合并成一个笼统的总通过率;规模统计只代表验证范围,不等于测试覆盖率。
汇总起来,机制层面已经得到验证:进程零退出但业务实际不通过,会判 failed;安装包和源码对不上,不计入本次验收;环境阻塞、数据不适用、业务失败各有独立状态,都不算 passed;13 类登记故障注入场景里,没有观测到假通过。
成本口径也补一句:整套体系由单人维护,没有设备农场,也没有 CI 集群;本地 catalog 类验收是秒级,真机涉及安装和执行,是分钟级。还有几项指标暂无数据:缺陷拦截率、长期假通过率、用例维护成本对发布质量的量化影响,都要靠更长周期的样本积累,见后续改进计划。
runtime-summary.ts 只用于内部复核:同一个逻辑请求区分首次尝试和后续重试分别统计;skipped 从各通过率的分母里剔除。环境阻塞修好后跑通的用例仍会被统计,所以这些指标不能直接当成产品的随机失败率。
耗时记录保存样本数量、最小值、P50、P95 和最大值;没有样本时,比例字段等于零并不代表系统稳定。
falsePasses 只检查内部一致性:终态标成 passed,但基础设施、动作、后置条件或证据标志不满足通过条件。它识别不出 oracle 和业务逻辑同时出错的情况,也不能凭一次零假通过推导长期假通过率,仍需独立复核、故障注入和线上缺陷反馈。
一次请求包含 2037 条不匹配改动,优化后耗时约 14.6 秒,标准输出约 968token;优化前同类路径超过 30 秒、约 87142token。完整文件清单写入证据包,控制面只接收摘要。这一轮没有执行业务任务,这组数据代表的是入口处理和输出开销,不能证明平台用例执行变快了。
后续重点推进以下工作,都还不算已完成的结论。
业务验收:补齐断网恢复的业务断言,准备可重复、可清理的真实语音数据,验证音频 APM 的播放失败与旧 APK 兼容上报链路。
运行度量:积累首次结果、阻塞原因和分阶段耗时,评估用例维护成本,补齐入口全阶段的硬时限。
发布门禁:补齐目标平台的真机样本,校验产物与受保护策略,接入 H5 发布证据,修复证据配额重复计数,并在目标版本复核设备授权与清理。
证据留存:持续维护构建输入与远端 H5 身份;按需增强签名、只读存储和持久化,并用反例验证消费端确实会拒绝错误记录。签名替代不了 CI 的权限隔离。
这些经验不用照搬现有的工具和目录结构。它们回答的是同一个问题:AI 参与测试时,怎么让结果可核对、可追责、可复现。
如果只能选一个起步点,不必先搭完整平台。第一步,挑一条高价值的真实业务链路,把“通过”定义为动作完成、业务断言成立、证据齐全三者同时满足,环境阻塞和数据不适用各自输出独立状态。这个可信闭环,少数几个开发者就能落地。第二步,为被测对象搭“源码→产物→安装→运行”的分段身份证明,让证据有完整可审计的生命周期。第三步,按需扩展平台执行器,再考虑发布门禁。
也要认清反向边界:如果目标是多团队共享设备、并发调度、CI 高频触发,本文这套单人真机模式就不够用了,需要另搭设备池、调度和隔离系统,属于另一套架构,本文没有覆盖。
主题 | 原始依据与复核入口 |
|---|---|
早期场景与耗时 | FE 提交 87e672070,记录于 2026 年 3 月 20 日 |
跨端设计 | 根仓 9450ce4 |
开发快路径 | 根仓 adeab8b |
App 验证目标分层 | 根仓 aab0246 |
Issue 驱动设计与控制面验收 | Test/e2e/docs/issue-driven-e2e-v3.md;ai-docs/versions/v1.4.10/dev-task/e2e-001-local-issue-slice/ |
发布加固及小程序真实 attempt | Test/e2e/docs/release-hardening-acceptance-2026-08-13.md |
公开入口处理成本 | Test/e2e/docs/issue-driven-e2e-v3.md,对应「统计口径怎样避免误读」一节中的输入检查与输出成本样本 |
规模快照 | Test/e2e/docs/architecture.md:28 子模块、约 633 单元测试声明、16H5 场景 |
语义设备层 | 独立项目 Frontend/aipb-chat-device,docs/aipb/architecture.md;E2E 侧为 Test/e2e/src/runners/agent-device-runner.ts |
内核、证据与发布门禁 | Test/e2e/src/harness/proof-kernel.ts、Test/e2e/src/evidence/run-evidence.ts、Test/e2e/src/release/release-gate.ts |
有效授权和引用新鲜度 | Frontend/aipb-chat-device/src/daemon/aipb/run-guard.ts、Frontend/aipb-chat-device/src/daemon/ref-frame.ts |
设备架构基线 | Frontend/aipb-chat-devicedev@43ad41e |
请求路由 | 设备仓 src/daemon/request-router.ts、src/daemon/request-execution-scope.ts |
本地平台后端 | 设备仓 src/platforms/android/snapshot-helper-session.ts、src/platforms/apple/core/runner/runner-command-route.ts、src/platforms/web/agent-browser-provider.ts |
语义步骤与本地断言 | Test/e2e/src/change-coverage/executor.ts |
BDD 缓存、用例预检和会话池 | Test/e2e/src/execution/h5-bdd-cache.ts、Test/e2e/src/execution/local-case-preflight.ts、Test/e2e/src/execution/session-pool.ts |
产物与安装身份 | Test/e2e/src/artifacts/source-inputs.ts、manifest.ts、android-installation.ts、ios-installation.ts、verification-receipt.ts,后 4 个文件与前者同目录 |
阻塞恢复与统计 | Test/e2e/src/recovery/blocker-resolver.ts、Test/e2e/src/metrics/runtime-summary.ts |
Android 恢复 | Test/e2e/features/android-h5-load-recovery.feature、对应 steps、artifacts/handoffs/android-h5-main-frame-recovery.json;在.aqe-output/e2e/按失败 60a7f0e1、通过 30a95002 查结果与索引 |
设备中间层代码更新与字段投影 | Frontend/aipb-chat-device 提交 08b290e、88e9298;src/commands/command-input.ts;src/commands/tests/aipb-guard-projection.test.ts |
守卫字段跨层投影 | 设备仓 src/commands/interaction/index.ts、src/commands/cli-grammar/flag-groups.ts、src/cli/parser/args.ts;根仓 Test/e2e/src/change-coverage/executor.ts |
授权语义与到期清理兼容 | 设备仓工作区业务 Intent 与到期清理改动:src/daemon/aipb/run-guard.ts、src/aipb/run-spec-loader.ts、src/daemon/request-execution-scope.ts 及 cleanup 测试;定向验证 7 文件、201/201 |
业务动作与 skipped 归因 | 根仓 56458719;Test/e2e/src/evidence/run-state.ts、src/runners/managed-case-runner.ts、src/harness/proof-kernel.ts、src/metrics/runtime-summary.ts |
Android 音频 APM 真机样本 | outputs/gray-installed-chat-audio-playback-failure-apm-20261029-102819/.../run-result.json(中间目录省略);installed-app1.4.16(156),终态 skipped,总耗时 21.411 秒,evidenceComplete=true |
术语表供阅读时查阅,无需预先记忆。
术语 | 含义 |
|---|---|
IssueContract | 本次验收要回答的问题:验收项、验证意图、覆盖声明 |
VerificationPlan | Agent 生成的执行计划:任务、步骤、后置条件、证据要求 |
ProofKernel | 唯一可信的判定者:校验计划与授权,核对 Runner 观察,计算最终状态 |
RunnerObservation | Runner 上报的结构化观察;v2 契约要求观察、证据、状态相互对应 |
catalog/ exact-case / semantic-device | 三类执行器族:本地命令、固定 case、语义设备操作 |
aipb-chat-device | 自研设备中间层:CLI、常驻 daemon、各平台后端 |
ref/ ref frame | 语义快照中节点的临时标识及其所属快照代次,动作后旧 ref 立即失效 |
RunSpec/ ProjectPolicy / Intent | 本次运行声明、本次授权、可触达的设备动作边界 |
AipbProjectProfile | 项目知识对象:业务术语、源码与设备控制面的身份 |
runId/ attemptId | 一次验收/一次尝试;同一 runId 恢复后产生新的 attemptId |
四态结果 | passed、failed、blocked、skipped;skipped 不计入通过,也不算业务失败 |
evidence-index | 证据索引,外部读取一次验收最终状态的口径 |
ReleaseGate | 发布判定入口:校验策略签名、产物身份与证据完整性,已实现待启用 |
oracle | 判定依据:验收项对应的检查逻辑,登记在代码中,供计划选用 |
wx 模式 | 文件创建标志:仅当目标文件不存在时创建,已存在即报错,防止旧记录被覆盖 |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。