做UI自动化测试的,谁没被元素定位坑过?我印象最深的一次,凌晨两点,监控平台突然刷出二十几条失败告警,拉出来一看,被测系统一切正常,是前端改了一个CSS类名——.submit-btn换成了.btn-primary,整套回归脚本瞬间红屏。这种“定位器焦虑”笼罩了我很多年,直到最近把Playwright和Midscene.js组合到一起,我才真正从 class、XPath 的泥潭里爬出来。Playwright 负责浏览器控制、自动等待、录制回放这些“手脚”动作,Midscene.js 提供基于大语言模型的视觉理解能力,让脚本可以用自然语言描述业务动作。这篇文章记录的是我这段时间的选型思路、最小配置、完整用例和踩过的坑,重点不是堆框架特性,而是“怎么从传统定位器思维切到 AI 驱动思维”这个转变过程。1. 传统方案的三道坎:定位器脆弱、维护滞后、录制低效1.1 换件衣服就不认识人:定位器的先天缺陷传统 UI 自动化脚本里,绝大多数时间都在跟选择器搏斗。#login-btn、.nav-item.active、//div[@class="content"]//input[@name="keyword"],这些表达式写的时候很明确,但有一个致命前提:页面结构不能乱动。前端是快速迭代的工程,某次重构改个语义化类名、某个弹窗多套了一层 div,功能上一点没变,视觉上看不出区别,但脚本里的选择器就彻底失联了。我常把这个现象总结成“换件衣服就不认识人”——你靠衣服颜色认人,人家换件衬衫,你就懵了。更麻烦的是,定位器之间还有“连环依赖”关系。比如你要定位一个表格里的第三行按钮,写的是tbody tr:nth-child(3) .btn,只要上面插入一行数据,后面所有按钮的坐标和层级全部偏移。这种选择器在真实业务系统里到处都是,今天改一个,明天炸一片,每次排查日志都像考古,而且往往要等到凌晨告警来提醒你“定位器过期了”。1.2 维护成本反客为主在传统模式里,测试脚本的维护成本往往超过编写成本。我做过一个后台系统的回归套件,大概两百多个用例,业务核心的代码改动并不频繁,但前端组件库每升一次版本,脚本里就有几十处选择器要跟着改。算下来,每次发版前真正花在“校正定位器”上的时间,比检查业务逻辑的时间还多。这带来一个连锁后果:测试团队的大部分精力不是花在“设计测试场景”上,而是花在“修脚本”上,非常本末倒置。而且选择器还有一个隐性成本——可读性极差。await page.click('#app > div.layout > main > div:nth-child(2) > form > div:nth-child(3) > button')这样一长串,写的人当时可能还记得,两周后让另一个同事接手,根本看不懂它到底在操作哪个按钮。脚本变成了只有机器能读的“天书”,团队协作效率被严重拖累,新人上手时间也被无限拉长。1.3 录制回放:看起来美好,用起来脆皮录制回放的自动化工具一直都有,但实际用下来局限很明显。录制工具生成的最常见结果是“坐标点击”和“绝对路径选择器”,它忠实记录你当时操作的那个像素位置、那个 DOM 路径,一旦页面尺寸变化、布局响应式调整,脚本就失效。而且录制出来的脚本里充满了大量无意义的步骤,比如鼠标划过某个区域的移动事件都会被录进来,后续维护时根本不敢动。我们团队早些年也用过某商业录制工具,结果三个月后脚本存活率不到一半。原因很简单:录制工具没有“理解”,它只记录动作痕迹,不理解业务意图。功能测试人员能讲清楚“用户先点这里,再填那个,最后点保存”,但录制脚本无法把这种语义沉淀下来。这也让我一直想找一种“能理解页面”的测试方案,直到 Midscene.js 出现在视野里。2. 方案选型逻辑:为什么是Playwright,又为什么加Midscene.js2.1 底盘选Playwright的三个核心理由先说为什么底层框架选 Playwright。市面上的浏览器自动化框架不少,但 Playwright 在几个关键点上做得确实到位。第一是浏览器内核覆盖,Chromium、Firefox、WebKit 三大内核统一 API,测试环境可以做到“一套脚本三端跑”,对兼容性要求高的业务场景是刚需。第二是自动等待机制,它内置了元素可操作检测,click、fill 这些操作会等到元素可见、稳定、可交互后才执行,不再需要手工在脚本里堆sleep(2000)。这一点对测试稳定性的提升是跨越式的,也是我坚定选它的直接原因。第三是调试设施完备。Playwright 的 Trace Viewer 可以记录整个执行过程的 DOM 快照、网络请求、截图、控制台日志,出了问题直接看时间轴,比看断言的报错信息高效得多。它还自带 codegen,虽然我前面提过录制有局限,但 Playwright 的 codegen 用于快速生成“交互骨架”然后再手工改写,流程上是顺畅的。选型时我也对比过其他方案,最终 Playwright 因为生态、社区和调试体验综合胜出。2.2 Midscene.js补上的关键一环如果只有 Playwright,上面说的“定位器焦虑”依然解决不了。Midscene.js 的核心价值在于:把“如何定位元素”这个问题,从“描述 DOM 结构”换成了“描述业务意图”。简单说,Midscene.js 是一个面向大语言模型的 UI 自动化框架,它用视觉截图加多模态模型理解页面,再输出可执行的动作指令。你不需要写.btn-primary,你只需要告诉它“点击页面右上角的登录按钮”,它会自己去截图、理解页面布局、找到按钮对应的位置,然后执行点击。整个交互范式从“给机器人画精确路线”变成了“告诉机器人你的目标”。这里我用一个类比:传统自动化像是你给一个陌生向导写纸条,上面写着“到第三个路口左转,再走 50 米右转”,只要路牌改了或者施工绕行,向导就迷路;Midscene.js 的方式更像是告诉向导“带我去附近最近的一家咖啡店”,向导自己会根据现场情况决定怎么走。前者依赖精确但脆弱的信息,后者依赖理解与判断,这就是两者本质上的差异。2.3 两者组合后的工作流Playwright 和 Midscene.js 不是竞争关系,是互补关系。Playwright 管浏览器生命周期、多标签页、网络拦截、并发执行、测试报告,这些是非常成熟的基础设施;Midscene.js 则管“看页面 + 决策 + 动作生成”。执行一条 AI 驱动用例时,流程大致是:Midscene.js 把当前页面截图,连同一个自然语言任务描述一起发给多模态模型,模型返回建议动作(比如“点击坐标(x, y)”或“输入文本到某个可见元素”),Midscene.js 再调用 Playwright 执行这个动作,然后继续下一轮,直到任务完成。这套组合对团队还有一个实际好处:编写用例的门槛大幅降低了。业务人员只要能把“用户做什么”描述清楚,测试脚本就基本写完,不再需要理解 CSS 选择器语法。当然这不意味着测试工程师失业——设计测试数据、分析失败原因、评估覆盖率仍然需要专业判断,只是枯燥的定位工作被大幅消解。3. 环境准备与最小可运行示例:半小时跑通第一个AI测试3.1 安装依赖与浏览器动手之前先把环境搭好。我用的版本是 Node 18+,安装命令很直接:npm init -y npm install -D playwright @midscene/playwright npx playwright install chromium@midscene/playwright会把 Playwright 相关能力一起带进来,所以上面只装它和 Playwright 本体就够了。如果你只跑核心场景,装一个 Chromium 就行;需要跨浏览器验证时再补 Firefox 和 WebKit。国内网络环境下,npx playwright install下载浏览器时如果遇到速度慢或失败(这是常见的网络问题),建议先给 npm 和 Playwright 配置镜像源再执行安装。装完之后可以用npx playwright --version验证是否成功。3.2 配置可用的多模态模型服务Midscene.js 需要接入一个支持视觉理解的多模态模型,配置方式是通过环境变量。以兼容 OpenAI 格式的接口为例:export OPENAI_API_KEY='你的密钥' export OPENAI_BASE_URL='你的模型服务地址'模型不一定只能选云端商业服务,现在很多本地多模态模型也支持 OpenAI 兼容的接口格式,可以用社区框架本地部署,好处是数据不出内网、调用成本相对可控。考虑到 UI 自动化场景对视觉理解能力要求不低,建议选在截图理解上表现较好的模型,而不是单纯追求参数规模。这步是整个方案里最需要测的一个配置,后面我会专门讲这里踩过的坑。3.3 第一个AI驱动的最小用例环境就绪后,写一个最简用例。以 Midscene.js 的 Playwright 集成写法为例(不同版本 API 命名可能微调,但思路一致):import { test, expect } from '@midscene/playwright'; test('AI驱动的最小搜索流程', async ({ page }) => { await page.ai('打开首页'); await page.ai('在搜索框中输入 自动化测试'); await page.ai('点击搜索按钮'); await page.aiAssert('页面上出现搜索结果列表'); });跑起来之后你会发现,脚本里一个选择器都没有,全是你平时描述“用户要做什么”的话。Midscene.js 会自动截取页面并判断搜索框在哪个位置。第一次跑通这个用例,我同事说了一句很直白的话:“原来测试用例可以写得像个 ChatPrompt。”这句话挺准确,整个脚本更像一份“给 AI 看的验收单”,而不是一段操作机器的指令序列。3.4 在已有Playwright项目中平滑接入老项目不用推翻重来。你只需要把测试用例的 import 源从@playwright/test换成@midscene/playwright,原来的page.locator()、expect()这些写法依然可用。这意味着可以渐进式改造:先把最容易被前端改动影响的用例切成 AI 驱动,把稳定、核心的用例继续保留传统断言方式,两种写法在同一个文件里混着用没有任何压力。实际改造中我的建议是:不要一次性把全量用例都切过去,选几条“选择器频繁失效”的用例作为试点,跑通并沉淀出写法规范后再推广。这样风险最小,团队接受度也最高。我这边第一个试点就选了一个三周内改了四次 UI 的后台查询页面,效果立竿见影。4. 核心操作拆解:SDK的核心能力与执行边界4.1 四个核心AI接口,一张表看清Midscene.js 暴露给测试同学的主要是几个自然语言接口,我按使用频率整理了一下:接口作用典型场景ai执行动作点击、输入、滑动、打开链接aiQuery提取页面数据获取列表第一行内容、读取当前金额aiAssert校验页面状态断言弹窗标题、断言成功提示aiWaitFor等待条件出现等待数据加载、等待某个状态文案出现每个接口第一条参数都是你写给模型看的自然语言指令,写法越贴近“业务意图”越好。比如await page.aiQuery('提取表格每一行的商品名称和价格'),返回结果会是结构化的数据,可以直接参与 JS 断言或数据比对。用下来我的体感是:aiQuery的价值常常被低估,它不只是 UI 操作,更像是在页面里种了一个“能读懂屏幕的查询器”。取数、汇总、状态核对,一句话就完成了。4.2 视觉定位机制到底是怎么工作的很多第一次接触 Midscene.js 的人会问:它真的能“看懂”页面吗?我拆开讲一下它的执行链路。第一步,Midscene.js 对当前页面 viewport 做截图,必要时还会进行元素级别的视觉标注;第二步,把截图和自然语言任务一起作为提示词发送给多模态模型,模型理解页面布局后,返回一个结构化动作,例如“点击位于坐标(x, y)的按钮”或“在某个输入框输入文本”;第三步,框架解析这个动作,调用底层 Playwright 在对应位置执行点击或输入;第四步,动作执行后再次截图,进入下一轮指令,直到整个任务完成。这套机制的优势,正是它不依赖 DOM 属性和层级关系。只要元素在视觉上可见且可辨识,AI 就能处理,哪怕组件库给按钮都套上了随机生成的 class,脚本也不会断。不过要强调一句:它不是魔法。遇到视觉上模棱两可的元素——比如一整屏长得一模一样的卡片,没有标题、没有图标,全靠数字区分——AI 也可能选错。所以我后面专门写了可执行边界。4.3 用自然语言写测试的边界在哪里到底哪些用例适合 AI 化,哪些不适合?根据我这段时间的实践,可以这样划分。适合的:端到端主链路回归、跨页面流程操作、定位器频繁失效的后台系统、需要理解文案和图标的页面状态校验、从复杂页面提取汇总数据。不适合的:像素级 UI 比对(那不是 AI 该干的事,建议用视觉回归工具)、超高频小粒度操作(比如对一张大表逐行点 50 次按钮,会有 token 成本压力)、依赖极端精确坐标的拖拽或 Canvas 绘制。我的核心建议是“宏观流程 AI 化,微观校验常规化”:让 AI 负责“从哪里到哪里”的路径,让传统断言负责“到了之后数值对不对”的校验。两者结合,既有灵活性又有精确性。这也是我现在的默认写法。5. 实战案例:登录查询全流程脚本逐段拆解5.1 业务场景定义用一个最常见的后台管理系统流程来演示:用户输入账号密码登录,进入工作台,在订单列表页通过状态筛选查询已完成订单,最后断言表格中出现了指定数据。这类流程在传统写法里是最让人头疼的,因为登录框、表格列、筛选下拉框这些元素在组件库改版时常常被重构。5.2 完整代码与逐段说明代码示例如下:import { test, expect } from '@midscene/playwright'; test('登录后按状态查询订单', async ({ page }) => { // 1. 打开登录页 await page.ai('打开登录页'); // 2. 账号密码登录 await page.ai('在账号输入框输入 test_user'); await page.ai('在密码输入框输入 123456'); await page.ai('勾选 记住我 复选框'); await page.ai('点击 登录 按钮'); // 3. 等待进入工作台,并校验关键信息 await page.aiWaitFor('页面左上角显示当前用户名'); const userName = await page.aiQuery('读取页面右上角展示的用户名称'); expect(userName).toContain('test_user'); // 4. 进入订单列表页 await page.ai('点击左侧菜单中的 订单管理'); await page.ai('点击子菜单 订单列表'); // 5. 按状态筛选 await page.ai('点击状态筛选下拉框,选择 已完成'); await page.ai('点击 查询 按钮'); // 6. 校验查询结果 await page.aiAssert('订单表格中每一行的状态列都显示为 已完成'); const rowCount = await page.aiQuery('统计当前表格中订单记录的行数'); expect(Number(rowCount)).toBeGreaterThan(0); });每段命令都是对业务的直接描述:输入什么、点击哪里、等待什么。作为测试设计者,你专注的是“用户会怎么操作、结果应该是什么”,而不是“DOM 里那个按钮叫啥名”。5.3 执行过程实录与调试建议我第一次跑这个用例花了大概十分钟,主要是调自然语言指令的措辞。举个例子,我想让 AI 点击“订单管理”菜单,一开始写的是“点击导航栏的订单管理”,执行到半路它去点了页面上另一处叫“订单统计”的入口卡片。改成“点击左侧菜单中的 订单管理”后一次通过。这个细节能看出,指令里包含越具体的“位置 + 名称 + 层级”描述,AI 的成功率越高。调试时我还习惯开 Playwright 的 trace 记录。Midscene.js 的每一步 AI 动作会带上当时的截图和模型返回的思考摘要,回放 trace 时能看到模型为什么做出这个动作,这比传统测试只看 click 成功与否直观太多了。排查问题时,我几乎不用猜“是不是选择器过期了”,直接看截图和指令就明白模型当时的理解是否出了偏差。5.4 混合策略:AI与传统断言怎么搭实战项目里,我现在采用的比例大约是一半 AI 动作、一半传统断言。像账号密码输入、菜单跳转、下拉筛选这些“动作型”步骤全部交给 AI,而“数值对不对”“列表条数是否大于 0”这一类必须精确的校验,保留传统 expect 写法。这样混合还有一个隐藏好处:当用例失败时,失败点通常落在精确断言上,AI 负责的路径部分基本稳。你看到报错时可以直接判断是“业务数据问题”还是“页面实现问题”,定位效率高了一截。6. 常见问题与排查技巧实录6.1 模型配置异常:报错、超时、模型不识别图片用得最多的排障场景是模型服务起不来。常见表现:用例执行时直接报超时,或者模型返回“我无法处理图片”的类似错误。前者大概率是网络不通或服务地址配置错;后者说明模型本身没有视觉理解能力,或者接口没有以多模态方式调用。排查方法:先单独把一张截图和一句话丢给模型接口做手工验证,确认它确实能理解截图内容后再怀疑框架。很多框架层面的“灵异问题”,最后都能定位到模型配置或网络链路上。另外我建议给用例执行加上整体超时和重试策略,AI 链路天然比纯脚本慢一点,不要让偶发的模型波动直接拖垮 CI。6.2 元素识别错位:AI选错了目标AI 选错目标大多和指令模糊有关。我第一次试“点击筛选按钮”时,它点了顶部的全局筛选,而不是表格上方的状态筛选。解决办法很简单:把指令改具体,带上位置、名称、可见特征,例如“点击状态筛选下拉框,选择 已完成”。如果页面有多个相似区域,可以在指令里加“在订单表格区块内”这样的限定语。这是 AI 测试的“沟通技巧”,跟向人交代需求一样,描述得越清楚,对方办得越准。另一种错位是视觉遮挡:弹窗盖住了后面的按钮,AI 截图里只看到弹窗,自然找不到目标按钮。这种情况要先用指令把弹窗关掉,或者重新组织指令顺序为“把弹窗关闭后再点击 xxx 按钮”,让 AI 按顺序把状态理顺。6.3 执行稳定性:超时、等待、重试的平衡AI 驱动的每一步都会等待模型返回,如果页面还在加载中就截图,AI 可能看到一个半渲染状态。我的处理方式是:在 AI 动作前,先用 Playwright 原生机制保证页面基本稳定——比如用page.waitForLoadState('networkidle')等待网络空闲,或对关键元素加一个传统的locator.waitFor()。稳定截图比让 AI 猜“这算不算加载完成”更可靠。重试策略上可以做两级:一级是给单个用例的 AI 任务设置合理超时时间,模型偶尔响应慢,超时设太短会无故红;另一级是 CI 层面允许失败重试一次。注意不要无限重试,否则问题会被掩盖,反而失去测试的意义。6.4 成本与频率:token消耗的账要算清每次 AI 调用都是一张截图加一次模型推理,涉及 token 消耗。如果建了很庞大的用例集天天全量跑,账单和推理时间都会上涨。我现在用的压缩策略是:能合并的动作合并成一个指令(比如“登录后点击订单管理”),减少交互轮次;回归测试只在关键版本全量跑,平时只跑核心主链路;前端未改动时,不重复跑 AI 相关用例。另外,尽量在每次任务里让 AI 一次完成多个关联操作,而不是拆成好几个独立的 ai 调用,既省 token 又减少模型上下文切换。成本控制这件事,在传统自动化里几乎不存在,但在 AI 自动化里必须提前预设预算,否则用起来心里没底。最后把这几类高频问题整理成速查表,方便遇到问题时直接对照:问题现象常见原因建议解法执行直接超时模型服务网络不通或地址配置错手工验证模型接口连通性,检查环境变量提示无法识别图片模型不具备视觉能力或接口调用模式不对换用支持图片输入的多模态模型AI 点错元素指令缺少位置限定或存在相似区域给指令加入位置、层级、可见特征描述弹窗遮挡导致找不到元素截图时页面状态异常先执行关闭弹窗指令,再执行目标动作偶发失败率偏高页面未稳定时截图在 AI 步骤前加 Playwright 原生等待账单增长过快用例拆分太碎、全量跑太频繁合并指令、分级别执行、限制回归频率这段时间用下来,我的整体判断是:Playwright + Midscene.js 这套组合真正改变了 UI 自动化的“写代码”方式,但不改变“设计测试”的本质。它把工程师从琐碎的定位器维护里解放出来,让更多人能用接近自然语言的描述去构建可靠的端到端用例。我个人在实际操作中最满意的场景,是老后台那种频繁改版的页面——之前每个版本都要跟着改脚本,现在基本都是改文字描述就能跑通。如果你也想尝试,我建议从小范围试点开始,先挑一两个高频且选择器经常失效的流程跑起来,感受一下“描述业务”和“描述 DOM”的本质差别,再决定要不要把更多用例迁过来。最后再分享一个小技巧:把每条 AI 指令当成给新同事交代任务来写——说清楚位置、对象、操作和期望结果,模型的表现会明显上一个台阶。
做UI自动化测试的,谁没被元素定位坑过?我印象最深的一次,凌晨两点,监控平台突然刷出二十几条失败告警,拉出来一看,被测系统一切正常,是前端改了一个CSS类名——.submit-btn换成了.btn-primary,整套回归脚本瞬间红屏。这种“定位器焦虑”笼罩了我很多年,直到最近把Playwright和Midscene.js组合到一起,我才真正从 class、XPath 的泥潭里爬出来。Playwright 负责浏览器控制、自动等待、录制回放这些“手脚”动作,Midscene.js 提供基于大语言模型的视觉理解能力,让脚本可以用自然语言描述业务动作。这篇文章记录的是我这段时间的选型思路、最小配置、完整用例和踩过的坑,重点不是堆框架特性,而是“怎么从传统定位器思维切到 AI 驱动思维”这个转变过程。1. 传统方案的三道坎:定位器脆弱、维护滞后、录制低效1.1 换件衣服就不认识人:定位器的先天缺陷传统 UI 自动化脚本里,绝大多数时间都在跟选择器搏斗。#login-btn、.nav-item.active、//div[@class="content"]//input[@name="keyword"],这些表达式写的时候很明确,但有一个致命前提:页面结构不能乱动。前端是快速迭代的工程,某次重构改个语义化类名、某个弹窗多套了一层 div,功能上一点没变,视觉上看不出区别,但脚本里的选择器就彻底失联了。我常把这个现象总结成“换件衣服就不认识人”——你靠衣服颜色认人,人家换件衬衫,你就懵了。更麻烦的是,定位器之间还有“连环依赖”关系。比如你要定位一个表格里的第三行按钮,写的是tbody tr:nth-child(3) .btn,只要上面插入一行数据,后面所有按钮的坐标和层级全部偏移。这种选择器在真实业务系统里到处都是,今天改一个,明天炸一片,每次排查日志都像考古,而且往往要等到凌晨告警来提醒你“定位器过期了”。1.2 维护成本反客为主在传统模式里,测试脚本的维护成本往往超过编写成本。我做过一个后台系统的回归套件,大概两百多个用例,业务核心的代码改动并不频繁,但前端组件库每升一次版本,脚本里就有几十处选择器要跟着改。算下来,每次发版前真正花在“校正定位器”上的时间,比检查业务逻辑的时间还多。这带来一个连锁后果:测试团队的大部分精力不是花在“设计测试场景”上,而是花在“修脚本”上,非常本末倒置。而且选择器还有一个隐性成本——可读性极差。await page.click('#app > div.layout > main > div:nth-child(2) > form > div:nth-child(3) > button')这样一长串,写的人当时可能还记得,两周后让另一个同事接手,根本看不懂它到底在操作哪个按钮。脚本变成了只有机器能读的“天书”,团队协作效率被严重拖累,新人上手时间也被无限拉长。1.3 录制回放:看起来美好,用起来脆皮录制回放的自动化工具一直都有,但实际用下来局限很明显。录制工具生成的最常见结果是“坐标点击”和“绝对路径选择器”,它忠实记录你当时操作的那个像素位置、那个 DOM 路径,一旦页面尺寸变化、布局响应式调整,脚本就失效。而且录制出来的脚本里充满了大量无意义的步骤,比如鼠标划过某个区域的移动事件都会被录进来,后续维护时根本不敢动。我们团队早些年也用过某商业录制工具,结果三个月后脚本存活率不到一半。原因很简单:录制工具没有“理解”,它只记录动作痕迹,不理解业务意图。功能测试人员能讲清楚“用户先点这里,再填那个,最后点保存”,但录制脚本无法把这种语义沉淀下来。这也让我一直想找一种“能理解页面”的测试方案,直到 Midscene.js 出现在视野里。2. 方案选型逻辑:为什么是Playwright,又为什么加Midscene.js2.1 底盘选Playwright的三个核心理由先说为什么底层框架选 Playwright。市面上的浏览器自动化框架不少,但 Playwright 在几个关键点上做得确实到位。第一是浏览器内核覆盖,Chromium、Firefox、WebKit 三大内核统一 API,测试环境可以做到“一套脚本三端跑”,对兼容性要求高的业务场景是刚需。第二是自动等待机制,它内置了元素可操作检测,click、fill 这些操作会等到元素可见、稳定、可交互后才执行,不再需要手工在脚本里堆sleep(2000)。这一点对测试稳定性的提升是跨越式的,也是我坚定选它的直接原因。第三是调试设施完备。Playwright 的 Trace Viewer 可以记录整个执行过程的 DOM 快照、网络请求、截图、控制台日志,出了问题直接看时间轴,比看断言的报错信息高效得多。它还自带 codegen,虽然我前面提过录制有局限,但 Playwright 的 codegen 用于快速生成“交互骨架”然后再手工改写,流程上是顺畅的。选型时我也对比过其他方案,最终 Playwright 因为生态、社区和调试体验综合胜出。2.2 Midscene.js补上的关键一环如果只有 Playwright,上面说的“定位器焦虑”依然解决不了。Midscene.js 的核心价值在于:把“如何定位元素”这个问题,从“描述 DOM 结构”换成了“描述业务意图”。简单说,Midscene.js 是一个面向大语言模型的 UI 自动化框架,它用视觉截图加多模态模型理解页面,再输出可执行的动作指令。你不需要写.btn-primary,你只需要告诉它“点击页面右上角的登录按钮”,它会自己去截图、理解页面布局、找到按钮对应的位置,然后执行点击。整个交互范式从“给机器人画精确路线”变成了“告诉机器人你的目标”。这里我用一个类比:传统自动化像是你给一个陌生向导写纸条,上面写着“到第三个路口左转,再走 50 米右转”,只要路牌改了或者施工绕行,向导就迷路;Midscene.js 的方式更像是告诉向导“带我去附近最近的一家咖啡店”,向导自己会根据现场情况决定怎么走。前者依赖精确但脆弱的信息,后者依赖理解与判断,这就是两者本质上的差异。2.3 两者组合后的工作流Playwright 和 Midscene.js 不是竞争关系,是互补关系。Playwright 管浏览器生命周期、多标签页、网络拦截、并发执行、测试报告,这些是非常成熟的基础设施;Midscene.js 则管“看页面 + 决策 + 动作生成”。执行一条 AI 驱动用例时,流程大致是:Midscene.js 把当前页面截图,连同一个自然语言任务描述一起发给多模态模型,模型返回建议动作(比如“点击坐标(x, y)”或“输入文本到某个可见元素”),Midscene.js 再调用 Playwright 执行这个动作,然后继续下一轮,直到任务完成。这套组合对团队还有一个实际好处:编写用例的门槛大幅降低了。业务人员只要能把“用户做什么”描述清楚,测试脚本就基本写完,不再需要理解 CSS 选择器语法。当然这不意味着测试工程师失业——设计测试数据、分析失败原因、评估覆盖率仍然需要专业判断,只是枯燥的定位工作被大幅消解。3. 环境准备与最小可运行示例:半小时跑通第一个AI测试3.1 安装依赖与浏览器动手之前先把环境搭好。我用的版本是 Node 18+,安装命令很直接:npm init -y npm install -D playwright @midscene/playwright npx playwright install chromium@midscene/playwright会把 Playwright 相关能力一起带进来,所以上面只装它和 Playwright 本体就够了。如果你只跑核心场景,装一个 Chromium 就行;需要跨浏览器验证时再补 Firefox 和 WebKit。国内网络环境下,npx playwright install下载浏览器时如果遇到速度慢或失败(这是常见的网络问题),建议先给 npm 和 Playwright 配置镜像源再执行安装。装完之后可以用npx playwright --version验证是否成功。3.2 配置可用的多模态模型服务Midscene.js 需要接入一个支持视觉理解的多模态模型,配置方式是通过环境变量。以兼容 OpenAI 格式的接口为例:export OPENAI_API_KEY='你的密钥' export OPENAI_BASE_URL='你的模型服务地址'模型不一定只能选云端商业服务,现在很多本地多模态模型也支持 OpenAI 兼容的接口格式,可以用社区框架本地部署,好处是数据不出内网、调用成本相对可控。考虑到 UI 自动化场景对视觉理解能力要求不低,建议选在截图理解上表现较好的模型,而不是单纯追求参数规模。这步是整个方案里最需要测的一个配置,后面我会专门讲这里踩过的坑。3.3 第一个AI驱动的最小用例环境就绪后,写一个最简用例。以 Midscene.js 的 Playwright 集成写法为例(不同版本 API 命名可能微调,但思路一致):import { test, expect } from '@midscene/playwright'; test('AI驱动的最小搜索流程', async ({ page }) => { await page.ai('打开首页'); await page.ai('在搜索框中输入 自动化测试'); await page.ai('点击搜索按钮'); await page.aiAssert('页面上出现搜索结果列表'); });跑起来之后你会发现,脚本里一个选择器都没有,全是你平时描述“用户要做什么”的话。Midscene.js 会自动截取页面并判断搜索框在哪个位置。第一次跑通这个用例,我同事说了一句很直白的话:“原来测试用例可以写得像个 ChatPrompt。”这句话挺准确,整个脚本更像一份“给 AI 看的验收单”,而不是一段操作机器的指令序列。3.4 在已有Playwright项目中平滑接入老项目不用推翻重来。你只需要把测试用例的 import 源从@playwright/test换成@midscene/playwright,原来的page.locator()、expect()这些写法依然可用。这意味着可以渐进式改造:先把最容易被前端改动影响的用例切成 AI 驱动,把稳定、核心的用例继续保留传统断言方式,两种写法在同一个文件里混着用没有任何压力。实际改造中我的建议是:不要一次性把全量用例都切过去,选几条“选择器频繁失效”的用例作为试点,跑通并沉淀出写法规范后再推广。这样风险最小,团队接受度也最高。我这边第一个试点就选了一个三周内改了四次 UI 的后台查询页面,效果立竿见影。4. 核心操作拆解:SDK的核心能力与执行边界4.1 四个核心AI接口,一张表看清Midscene.js 暴露给测试同学的主要是几个自然语言接口,我按使用频率整理了一下:接口作用典型场景ai执行动作点击、输入、滑动、打开链接aiQuery提取页面数据获取列表第一行内容、读取当前金额aiAssert校验页面状态断言弹窗标题、断言成功提示aiWaitFor等待条件出现等待数据加载、等待某个状态文案出现每个接口第一条参数都是你写给模型看的自然语言指令,写法越贴近“业务意图”越好。比如await page.aiQuery('提取表格每一行的商品名称和价格'),返回结果会是结构化的数据,可以直接参与 JS 断言或数据比对。用下来我的体感是:aiQuery的价值常常被低估,它不只是 UI 操作,更像是在页面里种了一个“能读懂屏幕的查询器”。取数、汇总、状态核对,一句话就完成了。4.2 视觉定位机制到底是怎么工作的很多第一次接触 Midscene.js 的人会问:它真的能“看懂”页面吗?我拆开讲一下它的执行链路。第一步,Midscene.js 对当前页面 viewport 做截图,必要时还会进行元素级别的视觉标注;第二步,把截图和自然语言任务一起作为提示词发送给多模态模型,模型理解页面布局后,返回一个结构化动作,例如“点击位于坐标(x, y)的按钮”或“在某个输入框输入文本”;第三步,框架解析这个动作,调用底层 Playwright 在对应位置执行点击或输入;第四步,动作执行后再次截图,进入下一轮指令,直到整个任务完成。这套机制的优势,正是它不依赖 DOM 属性和层级关系。只要元素在视觉上可见且可辨识,AI 就能处理,哪怕组件库给按钮都套上了随机生成的 class,脚本也不会断。不过要强调一句:它不是魔法。遇到视觉上模棱两可的元素——比如一整屏长得一模一样的卡片,没有标题、没有图标,全靠数字区分——AI 也可能选错。所以我后面专门写了可执行边界。4.3 用自然语言写测试的边界在哪里到底哪些用例适合 AI 化,哪些不适合?根据我这段时间的实践,可以这样划分。适合的:端到端主链路回归、跨页面流程操作、定位器频繁失效的后台系统、需要理解文案和图标的页面状态校验、从复杂页面提取汇总数据。不适合的:像素级 UI 比对(那不是 AI 该干的事,建议用视觉回归工具)、超高频小粒度操作(比如对一张大表逐行点 50 次按钮,会有 token 成本压力)、依赖极端精确坐标的拖拽或 Canvas 绘制。我的核心建议是“宏观流程 AI 化,微观校验常规化”:让 AI 负责“从哪里到哪里”的路径,让传统断言负责“到了之后数值对不对”的校验。两者结合,既有灵活性又有精确性。这也是我现在的默认写法。5. 实战案例:登录查询全流程脚本逐段拆解5.1 业务场景定义用一个最常见的后台管理系统流程来演示:用户输入账号密码登录,进入工作台,在订单列表页通过状态筛选查询已完成订单,最后断言表格中出现了指定数据。这类流程在传统写法里是最让人头疼的,因为登录框、表格列、筛选下拉框这些元素在组件库改版时常常被重构。5.2 完整代码与逐段说明代码示例如下:import { test, expect } from '@midscene/playwright'; test('登录后按状态查询订单', async ({ page }) => { // 1. 打开登录页 await page.ai('打开登录页'); // 2. 账号密码登录 await page.ai('在账号输入框输入 test_user'); await page.ai('在密码输入框输入 123456'); await page.ai('勾选 记住我 复选框'); await page.ai('点击 登录 按钮'); // 3. 等待进入工作台,并校验关键信息 await page.aiWaitFor('页面左上角显示当前用户名'); const userName = await page.aiQuery('读取页面右上角展示的用户名称'); expect(userName).toContain('test_user'); // 4. 进入订单列表页 await page.ai('点击左侧菜单中的 订单管理'); await page.ai('点击子菜单 订单列表'); // 5. 按状态筛选 await page.ai('点击状态筛选下拉框,选择 已完成'); await page.ai('点击 查询 按钮'); // 6. 校验查询结果 await page.aiAssert('订单表格中每一行的状态列都显示为 已完成'); const rowCount = await page.aiQuery('统计当前表格中订单记录的行数'); expect(Number(rowCount)).toBeGreaterThan(0); });每段命令都是对业务的直接描述:输入什么、点击哪里、等待什么。作为测试设计者,你专注的是“用户会怎么操作、结果应该是什么”,而不是“DOM 里那个按钮叫啥名”。5.3 执行过程实录与调试建议我第一次跑这个用例花了大概十分钟,主要是调自然语言指令的措辞。举个例子,我想让 AI 点击“订单管理”菜单,一开始写的是“点击导航栏的订单管理”,执行到半路它去点了页面上另一处叫“订单统计”的入口卡片。改成“点击左侧菜单中的 订单管理”后一次通过。这个细节能看出,指令里包含越具体的“位置 + 名称 + 层级”描述,AI 的成功率越高。调试时我还习惯开 Playwright 的 trace 记录。Midscene.js 的每一步 AI 动作会带上当时的截图和模型返回的思考摘要,回放 trace 时能看到模型为什么做出这个动作,这比传统测试只看 click 成功与否直观太多了。排查问题时,我几乎不用猜“是不是选择器过期了”,直接看截图和指令就明白模型当时的理解是否出了偏差。5.4 混合策略:AI与传统断言怎么搭实战项目里,我现在采用的比例大约是一半 AI 动作、一半传统断言。像账号密码输入、菜单跳转、下拉筛选这些“动作型”步骤全部交给 AI,而“数值对不对”“列表条数是否大于 0”这一类必须精确的校验,保留传统 expect 写法。这样混合还有一个隐藏好处:当用例失败时,失败点通常落在精确断言上,AI 负责的路径部分基本稳。你看到报错时可以直接判断是“业务数据问题”还是“页面实现问题”,定位效率高了一截。6. 常见问题与排查技巧实录6.1 模型配置异常:报错、超时、模型不识别图片用得最多的排障场景是模型服务起不来。常见表现:用例执行时直接报超时,或者模型返回“我无法处理图片”的类似错误。前者大概率是网络不通或服务地址配置错;后者说明模型本身没有视觉理解能力,或者接口没有以多模态方式调用。排查方法:先单独把一张截图和一句话丢给模型接口做手工验证,确认它确实能理解截图内容后再怀疑框架。很多框架层面的“灵异问题”,最后都能定位到模型配置或网络链路上。另外我建议给用例执行加上整体超时和重试策略,AI 链路天然比纯脚本慢一点,不要让偶发的模型波动直接拖垮 CI。6.2 元素识别错位:AI选错了目标AI 选错目标大多和指令模糊有关。我第一次试“点击筛选按钮”时,它点了顶部的全局筛选,而不是表格上方的状态筛选。解决办法很简单:把指令改具体,带上位置、名称、可见特征,例如“点击状态筛选下拉框,选择 已完成”。如果页面有多个相似区域,可以在指令里加“在订单表格区块内”这样的限定语。这是 AI 测试的“沟通技巧”,跟向人交代需求一样,描述得越清楚,对方办得越准。另一种错位是视觉遮挡:弹窗盖住了后面的按钮,AI 截图里只看到弹窗,自然找不到目标按钮。这种情况要先用指令把弹窗关掉,或者重新组织指令顺序为“把弹窗关闭后再点击 xxx 按钮”,让 AI 按顺序把状态理顺。6.3 执行稳定性:超时、等待、重试的平衡AI 驱动的每一步都会等待模型返回,如果页面还在加载中就截图,AI 可能看到一个半渲染状态。我的处理方式是:在 AI 动作前,先用 Playwright 原生机制保证页面基本稳定——比如用page.waitForLoadState('networkidle')等待网络空闲,或对关键元素加一个传统的locator.waitFor()。稳定截图比让 AI 猜“这算不算加载完成”更可靠。重试策略上可以做两级:一级是给单个用例的 AI 任务设置合理超时时间,模型偶尔响应慢,超时设太短会无故红;另一级是 CI 层面允许失败重试一次。注意不要无限重试,否则问题会被掩盖,反而失去测试的意义。6.4 成本与频率:token消耗的账要算清每次 AI 调用都是一张截图加一次模型推理,涉及 token 消耗。如果建了很庞大的用例集天天全量跑,账单和推理时间都会上涨。我现在用的压缩策略是:能合并的动作合并成一个指令(比如“登录后点击订单管理”),减少交互轮次;回归测试只在关键版本全量跑,平时只跑核心主链路;前端未改动时,不重复跑 AI 相关用例。另外,尽量在每次任务里让 AI 一次完成多个关联操作,而不是拆成好几个独立的 ai 调用,既省 token 又减少模型上下文切换。成本控制这件事,在传统自动化里几乎不存在,但在 AI 自动化里必须提前预设预算,否则用起来心里没底。最后把这几类高频问题整理成速查表,方便遇到问题时直接对照:问题现象常见原因建议解法执行直接超时模型服务网络不通或地址配置错手工验证模型接口连通性,检查环境变量提示无法识别图片模型不具备视觉能力或接口调用模式不对换用支持图片输入的多模态模型AI 点错元素指令缺少位置限定或存在相似区域给指令加入位置、层级、可见特征描述弹窗遮挡导致找不到元素截图时页面状态异常先执行关闭弹窗指令,再执行目标动作偶发失败率偏高页面未稳定时截图在 AI 步骤前加 Playwright 原生等待账单增长过快用例拆分太碎、全量跑太频繁合并指令、分级别执行、限制回归频率这段时间用下来,我的整体判断是:Playwright + Midscene.js 这套组合真正改变了 UI 自动化的“写代码”方式,但不改变“设计测试”的本质。它把工程师从琐碎的定位器维护里解放出来,让更多人能用接近自然语言的描述去构建可靠的端到端用例。我个人在实际操作中最满意的场景,是老后台那种频繁改版的页面——之前每个版本都要跟着改脚本,现在基本都是改文字描述就能跑通。如果你也想尝试,我建议从小范围试点开始,先挑一两个高频且选择器经常失效的流程跑起来,感受一下“描述业务”和“描述 DOM”的本质差别,再决定要不要把更多用例迁过来。最后再分享一个小技巧:把每条 AI 指令当成给新同事交代任务来写——说清楚位置、对象、操作和期望结果,模型的表现会明显上一个台阶。