BrowserSkill 回归用例体系:用 manifest 与合成 fixture 固化真实浏览器几何回归缺陷
2026/9/20 2:56:09 网站建设 项目流程

BrowserSkill 回归用例体系:用 manifest 与合成 fixture 固化真实浏览器几何回归缺陷

【免费下载链接】BrowserSkillLet AI agents use your real, logged-in browser without interrupting your work. CLI + extension for browser automation across any shell-capable AI agent.项目地址: https://gitcode.com/GitHub_Trending/br/BrowserSkill

回归用例是自动化测试体系中最难沉淀的一环:真实用户反馈的缺陷往往依赖特定的页面结构、嵌套 iframe、滚动状态与缩放组合,一旦缺少可复现的最小样例,修复便无从验证。BrowserSkill 在evals/browser/cases/regression/目录下建立了一套“一个用户反馈坏例 = 一个独立目录”的回归用例组织规范,配套 scaffold 脚手架命令、case manifest 声明式断言和合成 fixture,并以内置的真实 Chrome 几何测试与 CLI smoke 双重验证回归是否彻底修复。本文以该目录为骨架,深入剖析回归用例的目录规范、清单结构与运行方式,并以snapshot-coordinates(DOMSnapshot 布局单位回归)和oopif-scrollbars(OOPIF 占用滚动条回归)两个真实案例,展示 BrowserSkill 如何把 iframe 几何与坐标投影类缺陷固化为一套可重复执行的验证资产。

一、回归用例目录的职责与组织规范

regression/README.md 开篇即定义了该目录的核心职责:每一个用户反馈的坏例(badcase)都应拥有自己独立的目录,目录内包含三件套:

  • manifest*.case.json):描述用例的声明式清单,包括提示词、覆盖操作、断言与 smoke 步骤;
  • 合成 fixture*.fixture.mjs):一段由服务端动态渲染的最小复现页面,专门构造触发缺陷的 DOM 结构;
  • README:描述症状(symptom)、最小复现(minimal reproduction)、期望行为(expected behavior)、源码参考(source reference)与修复方式(fix)。

这种“一坏例一目录”的隔离策略,保证了每个回归缺陷都能独立复现、独立调试、独立评审,而不会相互污染。同时,README 明确划出了敏感信息红线

Never commit credentials, cookies, HAR files, production HTML, private screenshots, or proprietary assets.

即回归用例中严禁提交凭证、Cookie、HAR 抓包文件、生产环境 HTML、私有截图或专有资产。这一点对浏览器自动化项目尤为重要——fixture 必须全部由合成数据构造,绝不携带真实用户会话信息。从实际用例看,oopif-scrollbars.case.json 与 snapshot-coordinates.case.json 均通过合成 HTML 与回环主机名构造跨源拓扑,完全符合这一要求。

二、从零创建回归用例:scaffold 脚手架命令

目录规范要求每个用例必须同时具备 manifest、fixture 与 README,手工创建容易遗漏字段或写错目录结构。为此仓库提供了 scaffold 命令一键生成起点:

pnpm eval:browser scaffold <case-id> --title "..." --source "issue-or-pr"
  • <case-id>:用例 ID,必须为小写 kebab-case(如oopif-scrollbars),由 scaffold-case.mjs 中的ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/强制校验;
  • --title:用例标题;
  • --source:来源标注,通常填 issue 或 PR 引用,用于追踪缺陷出处。

从 scaffold-case.mjs 的源码可以看到脚手架的产物结构:它在<cases>/<suite>/<id>下创建目录,生成一个默认 manifest(包含$schemaschemaVersion: 1idtitleordersuitetagsfixture.startPath、中英文promptscoverageassertionssmoke.steps),并生成对应的 fixture 模板,标记默认为CASE-<ID>(大写、连字符转下划线)。若目录已存在,脚手架会直接报错,避免覆盖既有用例。

三、case manifest 清单结构剖析

case manifest 是回归用例的“声明式核心”。其字段约束由 case-loader.mjs 中的validateCaseManifest完整实现,允许的顶层字段包括:$schemaschemaVersionidtitleordersuitetagsseedsourcefixturepromptscoverageassertionssmoke,其余未知字段会被直接判为错误。

关键字段的语义与约束如下:

字段含义校验要点
schemaVersion清单结构版本必须等于1
id用例唯一标识小写 kebab-case,且全局不允许重复
fixture.startPathfixture 的起始 URL 路径必须以/开头,与 fixture 中注册的 route 对应
prompts驱动 Agent 的提示词必须同时提供enzh-CN两种语言
coverage覆盖的操作清单必须来自已知操作集合(如session.startsession.stoppage.navigateinspect.observe),不允许重复
assertions三层断言site(页面/站点侧)、response(Agent 回复侧)、adapter(适配器/会话侧)三类
smoke最小冒烟步骤smoke.steps必须为数组,动作需在受支持的工作流动作集合内

其中assertions的三层设计值得展开:

  • site 断言:验证页面侧状态,每个断言需要type(如geometry.readygeometry.scrollbars)、可选的where过滤器与minCount(正整数)。例如 oopif-scrollbars 用例要求data.root为真的geometry.ready事件至少出现 1 次,且data.verticaldata.horizontal同时为真的geometry.scrollbars事件至少出现 2 次;
  • response 断言:验证 Agent 最终回复文本,通过includes检查是否包含标记字符串(如OOPIF-SCROLLBARSGEOMETRY-195);
  • adapter 断言:验证适配器行为,通过key(如sessionStopped)确认浏览器会话已被正确关闭。

在 case-loader.mjs 中,加载后的 manifest 还会被normalizeCase归一化:将fixture.startPath提升为startPath、三类断言与 smoke 步骤分别挂载为siteAssertionsresponseAssertionsadapterAssertionssmokeSteps,并以Object.freeze冻结,保证后续执行阶段不会意外篡改清单。所有.case.json按 suite、order、id 排序后统一返回,任何清单的 JSON 解析失败、字段缺失或 ID 重复都会导致整个加载过程抛错,从源头拦截“带病”用例进入执行流程。

四、实战案例一:snapshot-coordinates(DOMSnapshot 布局单位回归 #195)

4.1 症状与根因

snapshot-coordinates/README.md 记录了该回归的来源为 PR #195,核心问题是:DOMSnapshot 的 bounds 与文档滚动偏移量保留的是 Blink 布局单位(layout units),而不是 CSS 像素。在设备缩放(device scale)或浏览器缩放(zoom)下,若直接将它们当作 CSS 像素处理,位置与尺寸都会在进入 iframe 投影(projection)或视口裁剪(clipping)之前就发生偏差——也就是说,坐标系换算的第一公里就走错了方向。

4.2 fixture 设计

snapshot-coordinates.fixture.mjs 构造了一个刻意复杂的页面拓扑:

  • 一个滚动过的根页面(body宽 2000px、高 2400px,加载后scrollTo(80, 240));
  • 一个同进程(same-process)iframe#same),自身也发生滚动(scrollTo(40, 100));
  • 一个OOPIF#cross),通过把 URL hostname 在127.0.0.1localhost之间互换来强制跨进程;
  • 每个 iframe 内再嵌套一层#nestediframe;
  • 各 owner 均带有border: 6pxpadding: 8pxtransform: scale(1.25)(nested 为scale(0.8)),同时引入边框、内边距与 CSS 变换三个干扰因素。

fixture 脚本在双requestAnimationFrame后置位data.geometryReady并上报geometry.ready事件,确保断言只在其布局完全稳定后执行。注意其 URL 默认会为 OOPIF 设置scrollbars=none,这是为了把“布局单位换算”问题与既有的“OOPIF 滚动条宽度投影”问题隔离开——后者由专门的 oopif-scrollbars 用例覆盖。若想在同一 fixture 中恢复经典滚动条形态,可附加&classic-scrollbars查询参数。

4.3 数值回归测试

几何数值断言运行在真实 Chrome 之上,命令如下:

BSK_GEOMETRY_CHROME=/path/to/chrome pnpm --filter @browser-skill/extension exec vitest run \ src/tools/__tests__/snapshot-coordinates.browser.test.ts

该测试(源码见 snapshot-coordinates.browser.test.ts)通过describe.skipIf(!process.env.BSK_GEOMETRY_CHROME)实现按需启用:普通单元测试运行时不设置BSK_GEOMETRY_CHROME,用例自动跳过;只有显式传入本地 Chrome 可执行文件路径时才真正执行,且不需要下载浏览器或引入新的包依赖。

测试用it.each覆盖5 种设备缩放/浏览器缩放组合{deviceScale: 1, zoom: 1}{deviceScale: 0.8, zoom: 1}{deviceScale: 1, zoom: 1.25}{deviceScale: 2, zoom: 1}{deviceScale: 2, zoom: 0.8},并将snapshot-coordinatesoopif-scrollbars两个 fixture 组合成参数化矩阵。其验证逻辑关键点在于:

  • 独立 oracle:测试内嵌一段独立 DOM 表达式,用getBoundingClientRect()计算各[data-geometry-probe]探针的 border box,以及各 iframe owner 的坐标与 scale(scale = rect.width / frame.offsetWidth),再用独立的clip函数做视口裁剪——期望值完全由 DOM 原生几何推导,不依赖生产代码的换算逻辑,避免“用待测实现验证待测实现”;
  • 精度阈值:生产 capture 得到的本地矩形与顶层矩形,与独立 DOM border box 相比,允许的布局舍入误差最多 2 个 CSS 像素
  • 读取次数:同时校验每个目标只读取一次布局指标(layout metrics),防止性能退化;
  • 拓扑自检:测试会显式验证请求的浏览器 zoom 与 OOPIF 拓扑是否真正生效,若环境不支持则会直接失败,而不是静默地测试了一个错误的配置。

测试自行启动并清理独立的 headless Chrome profile,保证每次运行环境一致。若想手动查看其使用方式,fixture 依赖的浏览器启动封装位于 snapshot-coordinates/chrome.mjs。

4.4 smoke 的边界

除了数值回归,该用例还提供 CLI 冒烟入口:

BSK_AUTO_UPDATE=off pnpm eval:browser smoke --case snapshot-coordinates --bsk ./target/debug/bsk

但 README 特别强调:smoke 并不能证明坐标精度——CLI 的观察结果不会暴露原始矩形数据。smoke 只负责验证 fixture 就绪与可观察语义(标记GEOMETRY-195被正确报告、会话正常关闭),数值断言必须由上述几何测试承担。这种“分层验证”的定位划分,正是该回归体系的可取之处。

五、实战案例二:oopif-scrollbars(OOPIF 占用滚动条回归)

5.1 症状与根因

oopif-scrollbars/README.md 描述的缺陷更加微妙:iframe 的 content quad(内容四边形)包含其子视口滚动条所占的空间,而 CDP 的Page.getLayoutMetrics().cssLayoutViewport却排除这部分空间。若把后者直接映射到整个 quad 上,位置与尺寸就会被拉伸。

正确的处理原则是:缩放比例必须由完整的 target-local 视口(含滚动条空间)决定;而可见视口仍须在每个 OOPIF 边界处裁剪内容,并约束 target-local 动作点。也就是说,scale 用“总空间”算,clip 用“可见区域”切,二者缺一不可。

5.2 fixture 与查询参数

oopif-scrollbars.fixture.mjs 为此构造了跨两个嵌套 OOPIF 的回环主机名交替拓扑:根页面通过127.0.0.1/localhost互换 hostname 加载外层跨源 frame,外层 frame 再以同样的方式加载内层 frame,形成两层 OOPIF。页面结构包含:

  • 滚动(不同页面不同scrollTo偏移);
  • 边框(border: 6px)、内边距(padding: 8px);
  • 缩放过的 iframe owner(外层scale(1.2)、内层scale(0.8));
  • 一个部分被裁剪的按钮#edgeposition: fixed; right: -20px; bottom: -15px);
  • 一个完全被裁剪的按钮#outside,位于视口calc(100% + 2px)之外);
  • 自定义滚动条占用不同宽高(::-webkit-scrollbar宽 17px、高 11px)。

fixture 通过scrollbars查询参数控制滚动条形态:

参数值含义
both(默认)垂直 + 水平滚动条均占位
vertical仅垂直滚动条占位(水平隐藏)
horizontal仅水平滚动条占位(垂直隐藏)
none双轴均不显示滚动条

脚本在加载后上报geometry.scrollbars(通过innerWidth > document.documentElement.clientWidth等比较判定实际滚动条是否占位)与geometry.ready,并监听[data-geometry-probe]按钮的点击事件,把点击结果回传给browserEval——这正是真实点击验证的数据通道。

5.3 浏览器测试与 smoke

oopif-scrollbars 与 snapshot-coordinates共用同一个浏览器 runnersnapshot-coordinates.browser.test.ts)。在 5 种缩放/缩放组合之外,还额外以deviceScale: 1, zoom: 1遍历verticalhorizontalnone三种单轴/无滚动条形态。该测试覆盖的能力包括:

  • 验证真实的 OOPIF 目标与滚动条占用尺寸;
  • 用独立 DOM 矩形与裁剪逻辑对比 snapshot 与 live 几何;
  • 派发真实 root-target 点击,并验证事件确实落到了预期的 frame/按钮(如#edge部分裁剪仍可点击、#outside应被拒绝);
  • 拒绝完全被裁剪的控件,并校验同一目标的 snapshot 测量复用;
  • 覆盖滚动条在垂直/水平/双向占位时的几何一致性。

runner 会创建并清理隔离的浏览器 profile,且与数值测试同样采用 opt-in 机制:未设置BSK_GEOMETRY_CHROME时在普通单元运行中自动跳过。

CLI 冒烟入口为:

BSK_AUTO_UPDATE=off pnpm eval:browser smoke --case oopif-scrollbars --bsk ./target/debug/bsk

冒烟断言仅验证两个嵌套 frame 均有占用滚动条、OOPIF-SCROLLBARS标记被观察到、会话被关闭;数值几何与真实点击断言由上面的浏览器测试承担,smoke 单独运行不能替代数值回归。

六、运行几何回归测试的环境与前提

综合两个用例,运行这套几何回归需要满足以下条件,缺一不可:

  1. Node.js 22+:数值回归测试的运行环境要求;
  2. 本地 Chrome 可执行文件:通过BSK_GEOMETRY_CHROME=/path/to/chrome指定。路径必须真实可用,因为测试会显式校验 zoom 与 OOPIF 拓扑,不支持的环境会直接失败而非悄悄跳过;
  3. 包管理器 pnpm:命令以pnpm --filter @browser-skill/extension exec vitest run ...形式在扩展包内执行测试;
  4. CLI smoke 需要已构建的二进制--bsk ./target/debug/bsk指向 Rust 侧构建产物(debug 构建即可),且以BSK_AUTO_UPDATE=off关闭自动更新,保证冒烟过程可复现。

几何模块的底层实现可参考 apps/extension/src/tools/frame-geometry.ts(节点几何解析)与 apps/extension/src/browser-driver/frame-graph.ts(CDP frame 图管理)。数值测试正是以这些模块为被测对象,用独立 oracle 校验其输出。

七、回归用例体系的最佳实践小结

regression/README.md与两个真实用例可以提炼出这套体系沉淀下来的几条关键准则:

  • 一个坏例一个目录:manifest、fixture、README 三件套齐备,缺陷可独立复现与评审;
  • 合成数据,拒绝真实资产:凭证、Cookie、HAR、生产 HTML、私有截图一律禁止入库,fixture 全部自建,跨源拓扑用回环主机名交替实现;
  • 声明式清单 + 三层断言site(页面事件)、response(Agent 回复标记)、adapter(会话关闭)分别验证不同层次,smoke提供最小工作流冒烟;
  • 数值断言与 smoke 分层:smoke 只证明“能跑通、能观察到”,坐标精度必须由真实 Chrome 几何测试证明——这是避免“看起来绿了其实坏了”的关键;
  • 问题隔离与组合覆盖:snapshot-coordinates 默认关掉 OOPIF 滚动条(scrollbars=none)以隔离布局单位换算问题,滚动条投影问题交由&classic-scrollbars与独立的 oopif-scrollbars 用例覆盖;同时用参数化矩阵把 5 种缩放组合 × 2 个 fixture × 单轴/无滚动条形态一次性铺开;
  • opt-in 运行:真实浏览器测试仅在显式设置BSK_GEOMETRY_CHROME时启用,普通 CI/单元运行不被拖慢,又不至于让回归静默漏测。

对于浏览器自动化类项目,几何与坐标换算是最容易在“真实浏览器”与“理论模型”之间产生偏差的领域。BrowserSkill 通过这套回归用例体系,把用户反馈的坏例固化为可复现、可断言的工程资产——既有声明式清单支撑自动化评估,又有真实 Chrome 数值测试兜底坐标精度,为后续 Agent 在复杂多 iframe 页面中的精准交互提供了可验证的回归防线。

【免费下载链接】BrowserSkillLet AI agents use your real, logged-in browser without interrupting your work. CLI + extension for browser automation across any shell-capable AI agent.项目地址: https://gitcode.com/GitHub_Trending/br/BrowserSkill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询