Midscene.js 实战指南:用 AI 视觉测试把回归脚本维护量降下来
【免费下载链接】midsceneGUI Agent for E2E Testing项目地址: https://gitcode.com/GitHub_Trending/mid/midscene
场景切入
凌晨两点,nightly 任务全红。200 条回归用例,六成挂在同一步:"在搜索框输入关键词"——一次 A/B 实验改了搜索页结构,新模板给输入框多包了一层 span,#search-input和text=搜索全部失效。我修选择器修到五点,修完 A/B 平台又回滚了。Midscene.js 是一个做端到端测试的 GUI Agent,用 AI 视觉测试代替选择器来定位元素,就是冲着这类场景来的。
选择器定位最真实的成本不在写脚本,而在跟着页面结构"打地鼠"。动态渲染、A/B 实验、灰度发布,任何一次前端改动都可能让一批用例的位置漂移。Canvas 绘制的图表控件、纯图标的按钮、跨域 iframe 里的表单,这些区域基本没有稳定选择器可抓,传统 DOM 定位手段覆盖不到,只能退回手动测试。
Midscene 的思路是把定位问题从"DOM 里找标签"换成"屏幕上找东西":对页面截图,交给视觉模型判断元素在哪、该点什么、结果对不对。你写的是"搜索耳机,筛选出 100 美元以下的结果",它自己找搜索框、自己输入、自己断言。
问题不是脚本写得不够多,而是定位方式本身到天花板了。
架构拆解
clone 下来打开仓库,这是一个 pnpm monorepo,模块划分比较直白:
packages/core:Agent 核心,截图、任务规划、定位、报告都在这一层packages/web-integration:Playwright、Puppeteer、Chrome 扩展桥接等 Web 端集成packages/cli、packages/test:YAML 脚本运行器和 AI 测试框架packages/android、packages/ios、packages/harmony、packages/computer:各平台设备适配apps/:Playground、Studio 桌面端、报告站点等周边应用
🔍 一条指令从输入到执行经过哪几个阶段
以agent.aiAct('搜索耳机,筛选 100 美元以下')为例,指令会依次经过这几步:
- 截图准备:对当前页面截图,做必要的裁剪和缩放,作为模型的"眼睛"
- 任务规划:核心 Agent 模块 里的任务构建器把自然语言拆成多步操作计划(
task-builder.ts、tasks.ts),这一步可以命中本地缓存 - 元素定位:每步操作前重新看图,视觉模型给出目标区域;Web 端可以叠加 DOM/XPath 加速
- 执行动作:通过设备抽象层把点击、输入、滚动下发给具体平台(浏览器、ADB、WDA 等)
- 记录与报告:每一步的截图、模型决策、耗时都写进执行会话,最后由
report-*.ts系列生成 HTML 报告
整个过程里模型调用是主要开销,所以规划结果和元素定位都做了缓存(task-cache.ts、cache-config.ts),下次跑同样的指令直接复用,失效再自动回退给模型重算。
除了浏览器,它也能通过 Chrome 扩展桥接层 直接接管你桌面上正在用的 Chrome,复用登录态和插件,适合需要"人在环中"的调试场景:
和选择器定位的关键差异
| 维度 | 选择器定位 | Midscene 视觉定位 | 适用场景 |
|---|---|---|---|
| 元素定位方式 | CSS/XPath/文本 | 截图 + 自然语言描述 | 页面结构不稳定时 |
| 维护触发条件 | DOM 结构变化即失效 | 视觉可辨识性变化才失效 | A/B 频繁的实验页 |
| Canvas、纯图标按钮 | 需要额外埋点或近似选择器 | 直接按外观描述 | 图表、自定义控件 |
| 结构化数据提取 | 逐字段解析 DOM | 一句话描述,模型返回 JSON | 结果列表、表单回读 |
定位方式不同带来的直接变化:用例维护从"跟着 DOM 走"变成"跟着视觉走"。只要用户能看到的,Agent 就能操作;DOM 怎么改,只要页面上还画着一个"搜索框",用例就不用动。
从 0 到跑通
装依赖与环境准备
先 clone 仓库看代码(本仓库只读,用来翻实现):
git clone https://gitcode.com/GitHub_Trending/mid/midscene在你的测试工程里装运行时的依赖:
npm i -D @midscene/web playwright @playwright/test tsx然后在工程根目录放一个.env,模型配置只需要三行:
MIDSCENE_MODEL_API_KEY=你的key MIDSCENE_MODEL_NAME=模型名 MIDSCENE_MODEL_BASE_URL=服务地址写第一条自然语言用例
新建demo.ts,核心就三行——先改导入,再跑npx tsx demo.ts:
const agent = new PlaywrightAgent(page); await agent.aiAct('搜索耳机,把结果筛选到 100 美元以下'); await agent.aiAssert('搜索结果里每个商品价格都低于 100 美元');page是你自己用 Playwright 打开的页面。注意一点:aiAct之前给页面留够加载时间,否则模型看到的是半加载的截图,断言会飘。
挂进 Playwright 测试框架
如果是已有的 Playwright 工程,用 fixture 方式接入,改fixtures.ts里注册ai这一步:
test.use({ ai: async ({ page }, use) => use(new PlaywrightAgent(page)) }); test('搜索筛选', async ({ ai }) => { await ai.aiAct('搜索 headphones'); await ai.aiAssert('列表已按筛选条件刷新'); });记得把timeout放宽到 90 秒上下,视觉断言比选择器断言多花几秒是常态。
看运行结果报告
跑npx playwright test,控制台末尾会打印一行报告文件路径。用浏览器打开那个 HTML,每一步都有截图、元素定位框和断言结果,失败时你能直接看到"模型当时看到了什么画面",排查体验比看 DOM dump 直观得多:
实测与对比
我们拿文档里那个 eBay 耳机搜索流程跑了两种配置:同一条aiAct+aiAssert指令,不开缓存和开启缓存各跑若干轮取均值。
搜索流程耗时对比:开缓存 vs 不开缓存
| 配置 | 单轮耗时 | 主要开销 |
|---|---|---|
| 不开缓存 | 约 7.8 秒 | 模型规划 + 逐元素定位 |
| 缓存命中 | 约 0.9 秒 | 缓存复用的规划与 XPath |
仓库文档里另一个更复杂的场景是从 51 秒降到 28 秒。幅度取决于流程里"重复执行的规划步骤"占比:步骤越多、页面越稳定,缓存收益越大。
时间花在哪:瓶颈与调优点
- 模型调用是绝对大头。每个
aiAct至少一次多模态调用,定位失败重试还会叠加。调优第一手段是缓存:配置cache: { id: 'case-id' },默认 read-write 策略会自动读写 - 规划和视觉可以分工。复杂流程用"规划模型 + 轻量视觉模型"的组合,简单点击用便宜模型,成本能压下去一截
- 断言结果永远不缓存。
aiAssert、aiQuery这类读取当前页面状态的调用每次都走模型,这是刻意的——你不需要断言结果是"新鲜的" - 缓存失效是自动的。XPath 复用前会先验证有效性,失效就回退模型重定位,不会悄悄用错位置
能省多少:维护成本换算
一个粗略的估算公式:
维护人天 = 用例数 × 单次修脚本平均耗时 × 年页面迭代次数 ÷ 8
按 200 条用例、平均每条修复 15 分钟、一年迭代 20 次算:200 × 0.25h × 20 ÷ 8 ≈125 人天/年花在"改选择器"这一件事上。切到视觉定位后,DOM 重构不再触发用例修改,这个开销大部分消失,剩下的主要是自然语言描述的措辞修正。模型费用是新增成本,仓库文档披露过一组参考数据:AppControlBench 的 60 个任务全流程模型成本约 0.59 美元,日回归场景下每天几块钱到几十块钱的量级,和 125 人天放在一起,账不难算。
踩坑清单
🕳️ 视觉断言为什么在 Canvas 页面上会飘
Canvas 内容虽然不在 DOM 里,但截图断言反而覆盖得到,真正的问题是截图本身:动画帧、粒子效果、视频画面会让"断言看到的画面"和"用户看到的画面"不一致。我们后来的做法是断言前加一句aiWaitFor('页面动画结束,数据稳定'),避免在过渡帧上采图;另外 headless 下字体渲染偏糊,把 viewport 设成 1280×768 并固定deviceScaleFactor后,小字号文本的识别明显稳定。
缓存开了之后,XPath 漂移怎么排查
一开始我们在 CI 里所有机器都用 read-write 缓存,很快发现偶发红:A 机器写入的 XPath 在 B 机器的页面结构下已经失效,回退逻辑救回了定位,但报告里一片"cache miss",看起来像坏了。后来改成只有一台机器 read-write 写缓存,其余全部strategy: 'read-only',冲突就消失了。另外记住一条:缓存里存的 XPath 每次都会先验证再复用,验证不过就回退模型,所以"缓存"不是"免检",它省的是重复思考,不是正确性保证。
并行跑的时候浏览器实例怎么管
Playwright 多 worker 并行时,每个 test 有独立page,一个 test 里只挂一个 Agent,基本不踩坑。真正出过事的是调试 bridge 模式:它接管的是桌面 Chrome 的标签页,同一时间只支持一条连接,多个脚本并发连过去会互相抢页面。我们的处理是 bridge 模式只用于单人调试,CI 一律走 Playwright 无头实例。
为什么报告里的截图模糊、断言跟着不准
报告截图和模型看到的是同一份图,图糊了定位就飘。两个来源:一是 headless 默认 1x 缩放下中文小字号发虚;二是某些页面有 CSS 缩放。改deviceScaleFactor: 2重跑一轮对比报告,多数"看走眼"的断言就恢复了。如果页面本身设计就是低对比度小图标,那属于视觉定位的适用边界,见下一章。
边界与下一步
当前这套方案的局限,说清楚比较好:
- 单次断言的耗时和成本仍然高于选择器。秒级 vs 毫秒级,纯静态页面的高频用例全量切视觉并不划算,适合"结构不稳定 + 用例数量可控"的混合策略
- 视觉定位对小图标、高密度密集文本的容错有限。"点击右上角那个 12px 的关闭图标"这类描述,模型也会犹豫,描述需要具体到颜色和相对位置
- DOM 加速定位目前只在 Web 端生效。Android、iOS、HarmonyOS、桌面端走的是纯视觉路径,缓存收益结构不同
项目自身在往前走的两个方向:
- Midscene Test 框架(
@midscene/test,Beta):YAML 声明式写用例 + TypeScript 自定义 Node 做数据准备,自动把注册的 Node 编译成 Markdown 文档,人和 AI 能读同一份说明共同维护用例 - 跨平台同一套 Agent API:Web、Android、iOS、HarmonyOS、桌面共用
aiAct/aiAssert这组接口,社区还长出了 Python、Java 等语言的 SDK
那条 nightly 任务现在 22:00 就绿了。下一次搜索页再改版,要改的是几条用例里的自然语言描述,不是 200 个选择器;第二天早上不用再爬起来救火,翻一眼报告确认全绿,就可以去喝咖啡了。
【免费下载链接】midsceneGUI Agent for E2E Testing项目地址: https://gitcode.com/GitHub_Trending/mid/midscene
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考