纯视觉UI自动化:Midscene 1.0 定位架构拆解与 Playwright 落地实践
UI 自动化的核心矛盾从来不是"驱动浏览器",而是"元素定位"。CSS/XPath/TestID 这类 selector 方案,前端每次重构就批量失效;DOM/无障碍树方案又对 canvas、CSS background-image 图标按钮、跨域 iframe、无 a11y 标注的自定义控件集体失灵,而且这类失败是间歇性的——同一个用例有时过有时挂,排查成本极高。Midscene.js(字节跳动 web-infra 团队,GitHub 14.4k stars,MIT)给出的答案是纯视觉:只把截图交给多模态模型,靠模型的 visual grounding 能力直接定位元素、规划动作、执行断言。1.0 起它彻底移除了 DOM 提取兼容模式,本文拆解其 Locate/Planning/Insight 三模型分工架构、与 Playwright 的 fixture 级集成方式,以及落地时最影响成功率的模型选型与稳定性调优。
两种定位路线的取舍
传统做法是 DOM + 标注截图:先注入 JS 提取 DOM 树,把节点元数据标注到截图上,再让模型"挑"节点。流程大致是:
截图 ──┐
DOM树 ──┼──► 标注合成 ──► 多模态模型 ──► "点击 #btn-submit"
问题在于这条路线的上限被 DOM 质量锁死:渲染在 canvas 里的内容、用 CSS background-image 画的图标、跨域 iframe 内部的节点、纯 div 拼出来的控件,DOM 里要么不存在要么没有语义。Midscene 团队在几十个版本、上百个项目里对比后得出的结论是:DOM 路线的间歇性失败会把团队拖进"调 prompt 碰运气"的循环。
纯视觉路线的优势是结构性的,不只是精度问题:
| 维度 | DOM + 标注截图 | 纯视觉 |
|---|---|---|
| 定位依据 | DOM 树 + 截图标注 | 仅截图 |
| canvas / 背景图 / 跨域 iframe | 经常失效 | 不受影响 |
| token 消耗 | 整棵 DOM 序列化 | 压缩后的截图,省约 80% |
| 跨端复用 | 每端都要适配 DOM | 同一套视觉引擎 |
| 视觉态断言(颜色/高亮/布局) | 做不到 | 天然支持 |
这也是 1.0 敢直接删掉 DOM 兼容模式的原因:维护两套定位引擎的成本远大于收益。数据抽取(aiQuery/aiAsk)场景仍可通过 domIncluded: true 按需附加 DOM,但动作执行和元素定位是纯视觉一条路。
三模型分工架构
一次 UI 操作被拆成三类意图,每类可以独立指定模型:
- Default:元素定位(Locate),以及未指定意图的兜底
- Planning(可选):任务规划,
aiAct/ai的步骤分解 - Insight(可选):数据抽取与断言(
aiQuery/aiAsk/aiAssert),依赖 VQA 能力
配置示例:
export MIDSCENE_MODEL_BASE_URL="https://openrouter.ai/api/v1"
export MIDSCENE_MODEL_API_KEY="your-openrouter-api-key"
export MIDSCENE_MODEL_NAME="qwen/qwen3.7-plus"
export MIDSCENE_MODEL_FAMILY="qwen3"
# 可选:复杂任务把 Planning / Insight 分给更强的模型
export MIDSCENE_PLANNING_MODEL_NAME="gpt-5.4"
export MIDSCENE_INSIGHT_MODEL_NAME="gpt-5.4"
执行流程示意:
aiAct("填写GitHub注册表单,地区选United States,确保每个字段通过校验")
│
├─► [Planning模型] 分解动作序列
│ 定位用户名框 → 输入 → 定位密码框 → 输入 → 展开地区选择器 → ...
│
├─► [Default模型] 逐动作定位(截图 → 视觉grounding → 坐标)
│ 执行点击/输入 → 截图 → 校验上一步是否生效
│
└─► 全部完成 → 返回执行结果
两个关键开关直接影响成功率:
- deepThink(
aiAct的planningStrategy相关选项):默认情况下"规划下一步 + 定位元素"在同一次模型调用里完成;deepThink: true时拆成两次独立调用,先规划后定位,复杂任务更稳,代价是延迟和 token。任务简单就别开。 - 模型原生思考:Midscene 默认强制关闭模型的 native thinking(
MIDSCENE_MODEL_REASONING_ENABLED=false),换取执行速度和稳定性。各厂商参数被统一映射——Qwen 的enable_thinking、Doubao/GLM 的thinking.type、GPT-5 的reasoning_effort、Gemini 的thinking_config.thinking_level——需要推理质量时再开,并用MIDSCENE_MODEL_REASONING_BUDGET/EFFORT限流。
与 Playwright 的 fixture 级集成
先封装 fixture,把 AI 能力注入 test:
// e2e/fixture.ts
import { test as base } from '@playwright/test';
import type { PlayWrightAiFixtureType } from '@midscene/web/playwright';
import { PlaywrightAiFixture } from '@midscene/web/playwright';
export const test = base.extend<PlayWrightAiFixtureType>(
PlaywrightAiFixture({
waitForNetworkIdleTimeout: 2000, // 每次动作后的网络空闲等待
replanningCycleLimit: 30, // aiAct 重新规划上限,防止死循环
}),
);
测试用例完全用自然语言写:
// e2e/ebay-search.spec.ts
import { expect } from '@playwright/test';
import { test } from './fixture';
test('ebay 搜索耳机并提取价格', async ({ ai, aiQuery, aiAssert, page }) => {
await page.goto('https://www.ebay.com');
await page.waitForLoadState('networkidle');
await ai('在搜索框输入 "Headphones" 并回车');
await aiAssert('列表页出现了至少一个耳机商品');
const items = await aiQuery<{ title: string; price: number }[]>(
'提取列表中所有耳机的标题和价格',
);
expect(items.length).toBeGreaterThan(0);
// 底层能力:aiTap / aiInput / aiBoolean / aiNumber / aiString / aiLocate
});
reporter 配置生成可回放的报告:
export default defineConfig({
testDir: './e2e',
timeout: 90 * 1000,
reporter: [
['list'],
['@midscene/web/playwright-reporter', { type: 'merged' }],
],
});
跑完输出 midscene_run/report/<id>.html,逐步回放每次动作的截图、Prompt、模型输出——排查"AI 为什么走偏"基本靠它。报告太大时用 outputFormat: 'html-and-external-assets' 把截图拆成独立文件(注意该模式必须起 HTTP 服务访问,file:// 会被 CORS 拦住)。
模型选型与踩坑记录
官方推荐与实测排序(2026 年中生态):
| 场景 | 推荐模型 | 说明 |
|---|---|---|
| 默认起步 | Qwen3-VL(qwen3 family) | 性价比高,OpenRouter 可直连 |
| 复杂任务规划/洞察 | gpt-5.4 | Planning/Insight 意图指定 |
| 国产云端 | Doubao-Seed-2.1 / GLM-4.6V | 国内网络与计费友好 |
| 自托管(成本敏感) | Qwen3-VL 8B/30B | 数据不出内网 |
| 自托管(开源UI agent) | UI-TARS-1.5-7B(ByteDance-Seed) | 专为 GUI 操作训练 |
踩坑清单,按杀伤力排序:
MIDSCENE_MODEL_FAMILY必须设置。漏了会报 "MIDSCENE_MODEL_FAMILY is not set to a multimodal model with UI localization",即使不报错,元素定位也会明显漂移——family 是 Midscene 做厂商参数映射的依据,不同厂商的视觉输入格式和 thinking 参数完全不同。- 模型版本差距极大。Qwen3-VL 明显强于 Qwen2.5-VL,72B 比 30B 定位准。成功率不达标时先换更大更新的模型,再谈调 prompt——这是性价比最高的调优手段。
- headless 模式先别上。部分页面 headless 渲染结果与有头模式有差异,先
headless: false跑通用例再进 CI。 - viewport 必须固定。布局一变,视觉定位的坐标和语义都会漂,用例里显式
page.setViewportSize(...)。 - 复杂任务失败先开 deepThink,再考虑配独立 Planning 模型;还不行就把一个大
aiAct拆成多个小aiAct,每个只干一件事。 - 原生思考默认关闭是特性不是缺陷。追求吞吐保持默认;要推理质量时
MIDSCENE_MODEL_REASONING_ENABLED=true并设置 budget,别裸开。
收益与成本
收益侧:前端重构不再炸测试(不追 selector);能覆盖 canvas、桌面端、移动端,一套 API 通吃;断言的是"用户真实看到的东西"(颜色、高亮、布局),而不是 DOM 节点是否存在;非前端背景的同学也能上手写 E2E。
成本侧要认清:每次动作都是一次模型调用,单步延迟秒级、按 token 计费,全链路跑下来比传统 E2E 慢一个数量级;模型输出非确定,同样的指令可能走不同路径,必须靠 replanningCycleLimit 和超时兜底;纯视觉对模型本身要求高,弱模型直接拉垮成功率——这不是框架能弥补的。
两个省钱/提效的细节:Midscene 提供 planning 与 locate 的结果缓存,同一页面同一意图的重复执行直接命中缓存,回归测试里第二次跑的成本断崖式下降;需要底层控制时用 fixture 里的 agentForPage 拿到 PageAgent 实例,直接调 recordToReport()、aiLocate() 这类原始 API,把 AI 步骤和传统 Playwright 步骤混排在同一个用例里。
总结与进阶方向
Midscene 的技术判断很干脆:与其维护一层脆弱的语义映射(selector / DOM / a11y),不如直接让模型看图。1.0 砍 DOM 模式、默认关 native thinking、三模型按意图分工,都是在为"稳定 + 可控成本"两个目标服务。把它定位成"传统 E2E 的补充层"而不是替代品:高频回归用 selector 快跑,视觉态、复杂流程、跨端场景交给纯视觉,是目前性价比最高的组合。
进阶方向:YAML workflow 把自动化写成声明式脚本;Gherkin 风格的 BDD 脚本;Midscene Skills 接入 OpenClaw 让 AI agent 自主执行测试;Android/iOS/HarmonyOS/桌面端复用同一套视觉引擎;自托管 Qwen3-VL 或 UI-TARS 实现数据不出内网(注意纯视觉路线对 GPU 显存和推理延迟的要求)。
更多推荐



所有评论(0)