Midscene 做 UI 测试:把脆弱的选择器换成语义指令 aiAct()

简介: UI自动化最大痛点不是写用例,而是频繁修选择器——前端一改class或结构,用例就崩。Midscene.js用AI语义定位(如`aiTap('登录按钮')`)替代脆弱CSS/XPath,绑定用户意图而非DOM实现,显著提升改版抗性,适合高频迭代后台。代价是耗时与成本,需权衡使用。

UI 自动化里最贵的从来不是写用例,而是修用例。

一个后台系统跑了一年,用例涨了四倍,通过率却越来越难看。翻失败日志,八成不是业务真出了 bug,而是前端换了个容器的 class,或插了一行提示文案,让 div:nth-child(2) 指向了别的元素。这类失败有个共同特征:报错看着像业务故障,排查完发现是定位故障

字节开源的 Midscene.js 给了另一个思路:把「点击登录」写成自然语言指令 aiAct(),靠视觉与语义找元素,而不是靠你手写的那串选择器。这篇不讲怎么手写更稳的选择器、怎么用 data-testid 降脆性——那是入门功课,另一篇讲过;这篇假定你已经吃过选择器的亏,想知道 AI 语义定位能换来什么、代价是什么。

一、业务场景:一个两周一改版的运营后台

场景钉死:公司内部运营后台,商品、订单、活动三块,前端两个小组轮着迭代,平均两周一次改版。测试侧维护着 38 条 E2E 用例,核心链路是「登录 → 进商品列表 → 按状态筛选 → 查看详情 → 断言字段」。痛点有三个。

第一,DOM 结构不稳定,但视觉语义极稳定。登录按钮永远写着「登录」,半年里 class 换过三次,文案一个字没改——脆的是绑定定位的方式,不是页面本身。

第二,改版不由测试主导,通知链经常断。前端重构组件库,测试是在 CI 全红之后才知道的。一次改版平均牵动 17 条用例,修一轮花掉一个人两天。

第三,这条链路是回归必跑项,不能砍。登录+查询是后台入口,挂了就全挂。所以问题不是「要不要自动化」,而是「怎么让自动化少受 DOM 变动影响」。

二、硬选择器为什么最脆

选择器的脆,本质是它绑定了实现细节,而实现细节是最常被重构的东西。CSS 选择器和 XPath 绑定 DOM 路径与类名,这两样在组件库升级、设计系统替换时往往一并被改掉。你以为在定位「登录按钮」,实际在定位「第 2 层 div 的第 3 个子节点里那个带 primary 类的元素」。语义指令绑定的是用户视角的意图——描述功能而非结构,所以改版前后都成立。

维度 硬选择器(CSS / XPath) 语义指令(aiAct
绑定对象 DOM 结构、class、层级 可见文本、位置、功能语义
改版抗性 低:换 class / 插容器 / 改组件库即失效 高:文案与视觉角色不变即命中
失败表现 element not found,像业务故障 带截图与推理过程,可回放
耗时与成本 毫秒级,近乎零成本 秒级 + token 费用,需核算
适合环节 稳定页面、高频冒烟、性能敏感断言 频繁改版的业务链路、跨版本回归

最容易被读错的是最后一行。语义指令不是选择器的替代品,是补充品。稳定页面和冒烟集,硬选择器依然最优;把全部用例改写成 aiAct,你只会得到一份跑得慢、花钱多、更难调试的资产。

三、六个 ai 方法的分工

Midscene 在 Web 侧的核心是六个方法,分工搞混是最常见的误用。aiAct()多步规划入口:给它一句自然语言,它自己拆成若干步逐步执行,强在承接完整意图,代价是慢且贵。aiTap / aiInput / aiQuery / aiAssert / aiWaitFor单步动作,目标明确时直接用,跳过规划环节。

方法 干什么 典型写法 什么时候用
aiAct 规划并执行多步操作 aiAct('登录后台并进入商品列表') 完整意图、步骤会变
aiTap 点击语义描述的元素 aiTap('右上角「新建活动」') 只点一下,不付规划成本
aiInput 向语义输入框写文本 aiInput('测试商品A', '顶部搜索框') 框的 class 不稳
aiQuery 提取页面结构化数据 aiQuery('前 5 行的名称与状态') 页面内容要变成可断言数据
aiAssert 自然语言断言页面状态 aiAssert('顶部显示「登录成功」') 断言视觉/文案而非 DOM
aiWaitFor 等待语义条件成立 aiWaitFor('列表已加载出一行') 替代固定 sleep

一条经验规则:能用单步就别用 aiAct。这一步永远是「点登录」,aiTap('登录按钮') 更快更省也更可预测;反过来,一段流程每次改版都要重排步骤,硬拆成一串 aiTap 等于把维护成本从选择器搬到行号上。而 aiQueryaiAssert 的真正价值,是把断言从「某节点的 text 等于某串字符」换成「页面呈现了登录成功这件事」。

06-配图1.png

四、可运行代码:PlaywrightAgent + aiAct 最小登录用例

Web 侧通过 PlaywrightAgent(包名 @midscene/web/playwright)接入,它在 Playwright 的 Page 之上挂了一层语义能力。下面是一份能直接跑的最小登录+查询用例。

依赖与环境(Node 18+):

npm init -y
npm i -D @midscene/web playwright typescript ts-node @types/node
npx playwright install chromium

# 模型接入:Midscene 需要一个多模态模型来做视觉/语义定位,
# 按官方文档配置你的 API Key 与模型名,这里只给环境变量占位。
export OPENAI_API_KEY="sk-xxxx"
export MIDSCENE_MODEL_NAME="你的多模态模型名"
// tests/login.spec.ts
// 运行:npx ts-node tests/login.spec.ts
import {
    chromium } from "playwright";
import {
    PlaywrightAgent } from "@midscene/web/playwright";

const BASE_URL = process.env.BASE_URL || "https://admin.example.internal";

async function main() {
   
  const browser = await chromium.launch({
    headless: false });
  const page = await browser.newPage();

  // cacheId:同一条流程多次跑时复用规划结果,是压耗时的关键开关。
  // 参数写法以官方文档当日版本为准;页面大改后应换 id 或清缓存重跑。
  const agent = new PlaywrightAgent(page, {
    cacheId: "admin-login-query" });

  try {
   
    await page.goto(`${
     BASE_URL}/login`, {
    waitUntil: "domcontentloaded" });

    // 1) 多步意图交给 aiAct:登录这一步里包含填账号、填密码、点登录
    await agent.aiAct(
      `在登录页输入账号 ${
     process.env.TEST_USER}、密码 ${
     process.env.TEST_PASS},然后点击「登录」按钮`
    );

    // 2) 等语义条件成立,而不是 sleep 一个固定秒数
    await agent.aiWaitFor("页面左侧出现了「商品管理」菜单,说明已进入后台首页");

    // 3) 单步动作:目标明确就别再付规划成本
    await agent.aiTap("左侧菜单里的「商品管理」");
    await agent.aiInput("测试商品A", "页面顶部的商品名称搜索框");
    await agent.aiTap("搜索框右侧的「查询」按钮");

    // 4) 把页面读成结构化数据,便于做精确断言
    const rows = await agent.aiQuery(
      "{name:string, status:string, price:number}[],返回商品列表表格里所有行的名称、状态与价格"
    );
    console.log("查询结果:", rows);

    // 5) 两类断言并用:语义断言管视觉结果,普通断言管数据正确性
    await agent.aiAssert("表格中出现了名称为「测试商品A」的一行,且列表不为空");
    if (!rows?.length) throw new Error("查询结果为空,链路不通");
  } catch (e) {
   
    // Midscene 会生成带截图与推理过程的执行报告,失败时先看报告再看 DOM
    console.error("用例失败:", e);
    await page.screenshot({
    path: "failure.png", fullPage: true });
    throw e;
  } finally {
   
    await browser.close();
  }
}

main();

几处关键写法说明。

aiAct 里塞了三个动作,这是刻意的。 登录框改版最常变的是字段顺序、验证码提示、按钮文案;整段交给 aiAct,一次改版你只改一句自然语言,而不是三个选择器。

aiWaitFor 替代 page.waitForTimeout(3000) 固定等待是自动化里第二贵的债:等短了 flaky,等长了拖慢流水线。

aiQuery 拿到结构化数据后,精确断言交回普通代码。 数值比较、行数校验在 JS 里判断更可靠,也更省 token。

账号密码走环境变量,不要写进自然语言指令。 aiAct 的指令会被写进执行报告和缓存,明文密码留在报告里是安全事故。

五、缓存:把重复规划从 51s 降到 28s

引入 AI 语义定位,最先撞上的墙是慢:每一步都要截图、送模型、拿回定位结果,一条十步的用例轻松破分钟。Midscene 官方的解法是缓存——同一条流程重复执行时复用已完成的规划结果,跳过重复的模型规划。官方数据是耗时从 51s 降到 28s(来源:midscenejs.com 官方基准,Midscene 1.9.5 + Gemini-3.5-Flash 口径)。

接近腰斩,但落地时有三条必须自己守住:

第一,缓存要绑 cacheId,一条流程一个 id。 所有用例共用一个 id,A 的规划结果会被套到 B 上——症状是「本地能跑,CI 上乱点」,非常难查。

第二,页面大改后要主动失效缓存。 缓存存的是「这条指令对应这些步骤与位置」,页面重排后旧缓存就是错的;改版合入时同步清缓存或换 id,这条要进前端-测试的联动清单。

第三,缓存只改耗时结构,不改成本结构。 首跑仍付全额规划开销,省的是重复跑——它在「同一条流程每天跑几十遍」的 CI 里收益最大,在一次性脚本里几乎没有。

六、成本口径:$0.59 / 60 任务该怎么读

第二个墙是钱。官方给的成本参照是60 个任务花费 $0.59(来源同上,midscenejs.com 官方基准),折算一个任务不到一美分——但这个数字必须读对:它是官方基准任务集上的口径,不是你 Web 后台的口径,任务复杂度、截图分辨率、模型单价都不同。你要做的不是引用它,而是用它验证数量级——单任务成本落在「美分」而非「美元」量级,这条路线在经济上就成立。真正的放大器是用例数量与执行频次:400 条用例、每天 CI 跑 6 轮,就是 2400 次任务/天。落地前先算一遍:

口径 怎么估 控制手段
单任务成本 3—5 条真实用例跑一遍,账单除以任务数 优先单步方法,少用 aiAct 做长规划
日执行次数 用例数 × CI 触发轮次 分层:语义用例只进回归集,冒烟留硬选择器
重复与重试开销 复跑占比 + flaky 率 × 重试次数 cacheId;用 aiWaitFor 等语义,别固定 sleep

最后一行最容易被忽略:flaky 是成本放大器,一条 30% 概率重试的用例,账单是 1.3 倍还不止。

再说能力边界。官方基准显示 Midscene 在 AndroidWorld 上 Pass@1 为 93.10%(Midscene 1.9.5 + Gemini-3.5-Flash)、MobileWorld 为 78.63%(92/117)、AppControlBench 为 96.7%(58/60)。三个分数分属三个不同榜单,不能混读也不能取平均;且都是移动端 / GUI Agent 场景的成绩,只能说明「语义定位这条路跑得通」,不能换算成你 Web 后台用例的通过率。

七、什么时候不要用 Midscene

以下四种情况,我建议继续用硬选择器。

页面已经稳定、且打了 data-testid 选择器一年不动一次,语义定位带来的只有耗时和账单。

性能与并发敏感的压测脚本。 几百并发打同一个流程时,每步多一次模型往返不可接受。

断言对象是数值与状态机,不是视觉。 「金额等于 99.00」「状态从待支付到已支付」,走接口或数据库比看页面可靠。

团队还没能力维护执行报告与缓存。 排查依赖报告与截图回放,缓存失效依赖流程约定;没人管,你会得到一套「跑得慢、还查不清」的用例。

反过来,它最该上的位置很清楚:改版频繁、DOM 不稳、但视觉语义稳定的业务链路回归。节奏上先挑 1—2 条最痛的链路试点两周,看维护工时和账单再决定铺不铺开——别一次性把 38 条全改。

八、写在最后

选择器绑的是实现,语义指令绑的是意图——实现天天变,意图很少变。

UI 自动化的维护成本,大头不在写用例,而在前端改了一个我们并不关心的实现细节之后。把定位从「第 2 层 div 的第 3 个子节点」换成「点击登录按钮」,本质是让用例和前端重构解耦:他们改组件库,我们改一句中文。代价是秒级耗时和 token 账单——这笔账在频繁改版的后台系统上算得过来,在稳定页面上算不过来。先算账,再改造,别反过来。

我们在整理 GUI Agent 在测试侧的落地做法,如果你们的 UI 用例也在被前端改版反复打红、想评估语义定位值不值得引入,留言区聊聊你们的改版频率和维护工时。

相关文章
|
6天前
|
人工智能 API 内存技术
刚刚 DeepSeek V4.1 Flash 开启内测,1 分钟教你用上!
刚刚 DeepSeek 内测群发布了 DeepSeek V4.1 Flash 中间版本内测的消息,这次的模型采用了新的结构,原生支持多模态、能力更强、速度更快、且成本更低。
1749 9
|
10天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1637 2
|
11天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)
|
7天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
770 2
|
5天前
|
缓存 测试技术 API
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
DeepSeek V4.1 Flash 内测不用申请,base_url 不变、改个模型名就能调,9/10 到期。本文讲清接入、计费限流与多模态注意点。
765 0
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
|
19天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3934 5
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
10天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1150 0
|
12天前
|
缓存 数据可视化 开发工具
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
DeepSeek Harness 的更新分两层:本体更新(npx 自动最新、npm update -g、源码 git pull)与插件更新(插件市场点更新、命令行覆盖安装)。本文按「准备 → 更新本体 → 更新插件 → 更新后检查」四步走,覆盖新手常见疑问。
1399 1
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式