ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Midscene.js:AI驱动的前端自动化测试,用自然语言告别脆弱选择器

Midscene.js:AI驱动的前端自动化测试,用自然语言告别脆弱选择器

很长一段时间里,一提到前端自动化测试,很多人的第一反应不是“放心”,而是“又要写一堆选择器了”。明明产品功能已经写完了,还得花半天时间对着DOM结构找class、定位xpath,结果产品经理一句“按钮文案改一下”,测试脚本当场报废一大片。这种体验,做过前端的人多少都经历过。

Midscene.js这个工具出现之后,情况开始变得不一样。它把AI直接塞进了自动化测试的链路里,让我可以用自然语言去描述操作意图,比如“点击右上角的登录按钮,输入用户名和密码,然后点击登录”,脚本自己就知道该点哪里、该输入什么、该怎么断言。换句话说,测试脚本从“告诉浏览器怎么操作”变成了“告诉AI你想做什么”。这篇文章就围绕Midscene.js聊聊它到底改变了什么,适合正在做前端测试、或者对AI辅助开发感兴趣的朋友参考。

1. 为什么传统前端自动化测试让开发者又爱又恨

1.1 传统方案的三大痛点:脆弱选择器、高维护成本、低反馈效率

传统自动化测试方案比如Selenium、Playwright、Puppeteer,核心逻辑其实没变过:先定位元素,再执行操作,最后做断言。定位元素这一步,通常靠CSS选择器、XPath,或者一些语义化的test-id。早期项目里一切还好,但一旦项目进入迭代期,问题就开始冒头了。

第一痛是选择器脆弱。前端项目用组件化开发之后,DOM结构经常被重构,class名带hash、结构层层嵌套。你今天写好的.login-form .submit-btn,下周组件库一升级,或者某个样式重构,选择器就失效了。更别说动态列表、异步加载内容这类场景,元素出现的时机稍有变化,脚本就报错。这些报错不是业务逻辑出了问题,而是测试脚本自己先“骨折”了。

第二痛是维护成本高。一个稍微像样的后台管理系统,动辄几十个页面、上百个交互场景。每个场景要写元素定位、等待逻辑、断言条件,写完还要维护。最惨的是UI调整之后,业务代码没改,测试脚本却要翻一遍。很多团队测试代码的维护工作量,其实已经超过了测试脚本最初编写的成本。

第三痛是反馈效率低。传统脚本只能告诉你“哪个步骤失败了”,但为什么失败,往往要人肉去看截图、看日志、看录屏。比如一个按钮被遮住了、元素被断言为不可见、页面加载慢导致超时,这些信息在失败报告里往往很含糊,定位问题的时间比修问题的时间还长。

1.2 Midscene.js的切入点:从“代码定位”到“意图驱动”

Midscene.js的思路是把这个链条中的一个关键环节替换掉:不再让人去描述元素在哪,而是让AI通过视觉理解页面结构,直接执行你描述的操作意图。注意,它不是一个辅助生成XPath的工具,而是真正地让测试脚本变成了“自然语言剧本”。

我举一个很直观的例子。传统Playwright脚本你要这样写:

await page.click('.submit-btn'); await page.fill('#username', 'test_user');

Midscene.js里同样的场景可以写成:

await ai('点击登录按钮,输入用户名test_user,密码123456,然后点击提交');

看完这段代码你应该已经感觉到差别了。测试编写者不再需要关心元素长什么样、在DOM哪个层级、class叫什么。AI会在运行时去“看”页面,理解“登录按钮”指的是哪个元素,然后执行点击。这个从“代码定位”到“意图驱动”的转变,是整个开发者体验重构的核心。

这个转变对谁的收益最大?我觉得是三类人。

第一类是业务前端工程师。他们最懂业务逻辑,但往往没有精力去维护一套复杂的E2E测试代码。用Midscene.js之后,一个懂业务的人用自然语言描述关键操作路径,就能变成一份可执行的自动化测试脚本,大大降低了编写门槛。

第二类是测试开发工程师。他们不需要再纠结于元素定位的稳定性问题,可以把精力花在更有价值的场景设计、数据构造和结果分析上。

第三类是刚接触自动化测试的新人。以前新人入门要先学选择器语法、等待策略、断言库,学习曲线陡峭;现在用自然语言写操作,几乎零门槛就能上手。

2. 核心原理拆解:Midscene.js到底在做什么不一样的事

2.1 自然语言指令的执行链路:从文本到浏览器操作

Midscene.js的执行链路可以拆成四步:指令解析、页面感知、动作规划、执行反馈。它不像传统脚本那样是一条“死命令”,而是一个“感知-决策-执行”的循环。

第一步是指令解析。你写的自然语言字符串会被发给一个AI模型,模型会把一句话拆解成多个原子操作。比如“点击登录按钮,输入用户名test_user,密码123456,然后点击提交”,模型会理解成三步操作:点击登录按钮;在用户名输入框输入test_user;在密码输入框输入123456;点击提交按钮。这一步的关键在于模型具备语义理解能力,它知道“输入密码123456”隐含了要先找到密码输入框。

第二步是页面感知。Midscene.js会把当前页面的可见状态做成一个“快照”,包括可见的文本、按钮、输入框、链接等,这个快照保留了元素的视觉位置信息,但又不需要依赖底层的DOM结构细节。AI基于这张快照来“看懂”页面当前长什么样。

第三步是动作规划。模型结合你的自然语言指令和页面快照,决定要执行哪些操作、按什么顺序执行、每个操作作用在哪个元素上。这一步其实就相当于传统测试里的“调page.click()”,只不过调用的目标选择器变成了“模型在快照里匹配到的元素”。

第四步是执行反馈。Midscene.js去浏览器里执行真实操作,操作完成后,再次获取页面快照,把新的状态带回给模型,以便支撑下一步决策。比如你点击了“登录”按钮,页面弹出了错误提示,AI看到这个提示,可能就会返回一个“登录失败”的结果给断言逻辑。

这套链路里最难的部分在于“页面感知”,因为页面不是静态文档,而是动态的、可交互的、有状态的UI。Midscene.js在这里做的事情,实际上是把“看页面”这件事,从依赖DOM结构转移到了依赖AI的视觉理解能力。

2.2 不用CSS选择器,AI到底怎么定位元素

你可能想知道,AI到底是怎么“找”到那个按钮的。这里有一层很容易被误解的地方:Midscene.js不是完全抛弃DOM,而是把DOM变成了一种AI可以理解的中间表示。

在运行过程中,它会提取页面上的可见元素信息,比如元素在屏幕上的坐标、元素的类型(按钮、输入框、链接)、元素的可见文本、元素的ARIA标签等。这些信息被组织成结构化的上下文,再交给模型处理。模型就像一个有眼睛的测试工程师,它看着页面截图,又读着元素的语义标签,理解“右上角那个写着‘登录’的按钮”到底对应哪个元素。

这个设计的好处非常明显。它不再因为一个class名变化就挂掉,因为模型理解的依据是“语义”而不是“样式”。也不在意元素嵌套多深,因为模型看的是页面呈现出来的样子,而不是代码结构的层级。这种定位方式实际上更接近人类测试者的思维方式:我看到一个按钮,按钮上写的是“登录”,我就去点它。

不过这里也要说实话,这种方式并不是银弹。如果页面上有十个长得一模一样的按钮,文本内容也一样,AI也会困惑——这其实和人类一样,人眼也要找一下才知道是哪个。针对这类情况,Midscene.js支持增加额外的约束描述,比如“点击第一行列表里的编辑按钮”,或者“点击弹窗底部的确定按钮”。描述得越具体,模型定位越精准。

2.3 视觉断言:让测试结果真正反映“用户看到的”

除了交互操作,断言也是自动化测试的重头戏。传统断言通常是针对DOM属性、JS变量、接口返回值做判断。比如断言某个toast文本出现,你得定位到toast的元素再判断文本内容。Midscene.js带来了一个新的断言范式:视觉断言。

视觉断言的核心是“我不管DOM里面存了什么,我只管页面上看起来是什么”。我以前测过一个数据可视化的页面,图表是用canvas绘制的。传统方案想断言“图表里面是否显示了预期的曲线数据”,几乎无从下手,因为canvas内部的内容DOM根本拿不到。用Midscene.js后,我可以直接写:

const result = await aiQuery('页面上显示的销售总额是否大于10000?');

AI会去看canvas渲染出来的图像内容,结合坐标轴、数字标签等信息给出判断。这个能力在传统测试框架里,基本是做不到的。

视觉断言还有另一个好处:能够发现“代码正常但展现异常”的问题。比如一段文本被样式遮挡了,或者接口数据已经更新但图表渲染没有刷新,这些情况DOM层面的断言往往发现不了,但视觉断言可以通过AI理解页面的实际呈现效果,抓到这类“看起来不对”的异常。注意,这不是要替代传统断言,而是提供了一种更高维度的兜底检查。

3. 实操落地:从安装到跑通第一个AI测试脚本

3.1 环境准备与项目接入:三分钟跑起来

说再多原理,不如直接上手。Midscene.js目前的接入方式主要是在Node.js项目里安装npm包,结合你已有的浏览器自动化工具一起使用。

npm install midscene-js --save-dev

如果你是Playwright用户,本身就熟悉浏览器实例管理,相当于只多加了一个AI执行的入口。基础代码结构是这样:

import { Midscene } from 'midscene-js'; import { chromium } from 'playwright'; const browser = await chromium.launch(); const page = await browser.newPage(); const midscene = new Midscene({ page, apiKey: '你的模型APIKey', model: 'gpt-4o' }); await page.goto('http://localhost:3000/login'); await midscene.ai('输入用户名admin,密码admin123,点击登录按钮');

这段代码里,Midscene.js和你的Playwright浏览器实例被绑定在一起。它有两个核心方法,一个是ai()用来执行自然语言交互指令,一个是aiQuery()用来从页面提取信息做判断。模型APIKey来自大模型服务商,具体用哪个模型取决于你的预算和对延迟的容忍度。

有几点环境上的建议。第一,建议Node.js版本不低于18,因为Midscene.js依赖一些较新的API接口,老版本容易出兼容问题。第二,如果你在公司内网环境,需要确认模型API的访问是否通畅。第三,如果你是刚上手,建议先在自己本地的开发环境跑通,不要直接对着生产环境写测试脚本,页面的复杂度和数据环境都会影响AI的理解效果。

3.2 用自然语言编写交互与断言:写“剧本”而不是写“代码”

一旦跑通基础环境,你就会发现编写测试脚本的思维模式变了。过去写脚本,满脑子想的是“我要选择哪个元素”“我要等待什么条件”,现在写脚本更像是在写一份“用户操作手册”。

比如一个典型的登录流程,你可以这样写:

await midscene.ai('在用户名输入框中输入tester,在密码输入框中输入pass123,点击登录按钮'); await midscene.ai('等待页面跳转,确认页面上显示"欢迎回来,tester"');

这里每一步都不需要你去指定元素,AI会自动理解“用户名输入框”在哪里。“等待页面跳转”这种隐含等待,AI也会处理好,不用自己去写显式的waitForSelector。

更复杂一点的场景,比如需要先判断页面状态再决定操作,可以用aiQuery配合条件语句:

const hasError = await midscene.aiQuery('页面上是否出现了红色的错误提示?'); if (hasError) { await midscene.ai('点击错误提示上的关闭按钮'); } else { await midscene.ai('点击下一步按钮'); }

这种感觉确实和传统自动化测试很不一样,它把很多底层细节吞掉了,让你更专注于“用户会怎么操作、系统该怎么响应”本身。我实测下来,一个中等复杂度的表单页面,从零到跑通一条完整主流程脚本,大概只需要传统方案三分之一的编写时间。

不过要提醒一下:Midscene.js的自然语言脚本不需要精确的控制流逻辑,但需要清晰的业务场景描述。“点击按钮A然后检查提示”这种描述方式和“如果出现弹窗就点击确定否则点击取消”这种分支逻辑,表达能力完全不同。前一种AI可以应对,后一种建议用代码逻辑来控制分支,让AI只负责执行每一步的具体操作。简单说,AI擅长的是“怎么做”,程序擅长的是“该走哪条路”,两者配合才是最优解。

3.3 在CI流水线里跑AI测试:排队、超时与失败重试

本地跑通只是第一步,自动化测试真正的战场在CI流水线。Midscene.js在CI环境里跑,有一些需要额外注意的地方。

第一个问题是模型API的调用延迟。传统E2E测试每一步操作都是毫秒级响应,但AI的推理通常需要几秒。一条包含十个步骤的测试用例,光是AI推理时间可能就要几十秒,整个测试执行时间会比传统脚本长不少。我的做法是,在CI机器上把模型调用超时时间放宽到60秒以上,并且对单条用例设置合理的整体超时,避免因为慢而误报超时失败。

第二个问题是模型访问的稳定性。公网模型服务偶尔会有波动,这个时候测试脚本会跟着失败。可以给关键步骤加一个“失败重试”机制。Midscene.js本身支持在实例化的时候配置重试参数,我建议配置为重试2~3次,每次间隔2秒以上,能过滤掉大部分偶发的模型调用异常。

第三个问题是多任务并发。如果你有多个CI任务同时执行,注意看模型服务是否有并发配额限制。我这里遇到过并发一多,部分AI调用直接返回限流错误的情况。建议在CI的流水线配置里,对涉及Midscene.js的任务做并发控制,或者错峰执行。

还有一个CI环境的坑:无头浏览器模式下,页面渲染如果太慢,AI看到的页面快照可能是空白或者不完整的。传统脚本遇到这种情况是等待某个元素出现,Midscene.js则需要你主动加一句等待描述:

await midscene.ai('等待页面完全加载完成,出现"仪表盘"标题');

这句话会让AI先确认页面状态,再执行后续操作。本质上是用自然语言代替了显式的等待条件。

4. 实测中的常见问题与排查实录

4.1 模型响应慢或者超时,怎么判断是模型问题还是脚本问题

AI测试最大的变量就是模型响应时间。遇到超时,不要急着怀疑Midscene.js本身,先判断是哪一段卡住了。

我有一个比较简单的方法:在代码里把ai()调用和普通自动化操作分开计时。如果普通操作几步之后才慢,说明AI推理环节耗时长;如果一执行ai()就卡住,大概率是模型服务连接的问题。

排查的第一件事是看模型服务商的状态页,确认当前是否有故障。第二件事是检查你的APIKey是否有效、配额是否充足。第三件事是看请求日志里的具体耗时。Midscene.js在做AI请求的时候会打印日志,里面有请求发送时间和响应接收时间,可以直观看到延迟到底有多高。

如果模型本身响应快,但整个测试用例还是很慢,那就要看看是不是脚本里的自然语言描述过于冗长。模型推理速度和输入内容长度正相关,你一句话写了几百个字符,推理时间自然会上来。把长描述拆成几个短步骤,既能提升性能,也能让失败定位更准确。

还有一个常见的坑是:本机能跑通,CI上却超时。这往往是因为本机直接连模型服务延迟低,CI机器走了内网代理或者公司防火墙,连接不稳定。排查方法是先在CI机器上最小化复现:只调用一次ai('点击首页的登录入口'),看是否正常。如果最小化场景都超时,说明是网络链路的问题,需要和运维协调网络访问策略。

4.2 断言不稳定、偶发失败,问题出在谁身上

我刚用Midscene.js的时候,遇到最头疼的问题就是“上一次能过,这一次过不了”。这种偶发失败,排查起来比稳定失败麻烦得多。后来总结经验,偶发失败通常来自三个层面。

第一层是页面本身的异步渲染。页面加载了,但某些模块是后置懒加载的,AI快照生成的时候,关键内容还没渲染出来。针对这种情况,我习惯在操作前加一句“等待XX内容出现”的描述,给AI一个明确的等待信号。

第二层是AI视觉理解的随机性。大模型本身带有采样性质的推理,不同时刻对同一张页面快照的理解可能会有细微差异。比如有些时候AI认为某个元素是“按钮”,有些时候认为是“链接”。缓解方法是描述得更加具体,减少二义性,比如不要只说“点击确定”,而是说“点击弹窗底部的蓝色确定按钮”,把位置和视觉特征都带上。

第三层是断言标准本身太模糊。aiQuery('页面是否正常显示了数据?')这句话就太模糊了,“正常”是一个主观判断。更好的写法是具体描述预期结果,比如aiQuery('页面上的用户列表中是否包含名为张三的记录?')。断言描述越具体,AI返回结果的稳定性越高。

如果偶发失败还是频繁出现,我建议做分步定位:先用aiQuery('描述当前页面的状态')把页面快照引述出来,人工看一眼AI认为页面是什么样子,再决定是调整脚本描述,还是要给页面渲染加稳定等待机制。

4.3 与传统自动化脚本共存:要不要推倒重来

很多团队在做技术选型的时候都会问一个问题:我已经有Playwright或Cypress的测试资产了,要迁移到Midscene.js是不是等于推倒重来?

我的看法是:不需要推倒,反而应该共存互补。你应该把Midscene.js用在那些传统方案成本高、效果差的环节,而不是全面替换。

哪些环节适合用Midscene.js?第一类是UI频繁变化的业务主流程,用自然语言脚本能大幅降低维护成本。第二类是canvas、WebGL这类传统DOM断言覆盖不到的场景,视觉断言是刚需。第三类是回归测试中的冒烟场景,编写速度快,迭代效率高。

哪些环节保留传统脚本?第一类是对执行速度敏感的接口级测试、性能测试,不需要也不应该引入AI推理延迟。第二类是纯数据逻辑验证,比如校验接口返回值的字段、类型、格式,这类断言用传统方式更加精确稳定。第三类是高频执行的细粒度单元测试,也没必要用AI。

我在实际项目中的做法是:核心主流程用Midscene.js写“AI冒烟用例”,具体到业务数据校验和边界条件的断言,仍然用Playwright的传统方式。两者通过同一个浏览器实例协作,Midscene.js负责操作交互,Playwright负责数据级断言,各干各擅长的事情。这样既拿到了AI的体验红利,又不牺牲原有测试体系的精确性。

5. 经验心得与边界思考

5.1 我踩过的最深的坑:把AI当成“万能测试框架”

接触Midscene.js一段时间后,我发现一个值得警惕的倾向:很多人会过度信任AI的“智能”,什么场景都想用自然语言一把梭。有一个真实案例,我项目里有个复杂的权限系统,包含几十种角色和数据权限组合。同事用Midscene.js写了很长的自然语言脚本去覆盖各种权限场景,结果跑起来成功率忽高忽低。

后来定位发现,权限场景里很多判断是逻辑判断——某个用户对某个资源有没有操作权限,本质上取决于后端返回的数据结构,而不是页面长什么样。这种场景用AI视觉理解,本身就跑偏了。正确做法是用传统脚本去校验接口返回数据,用Midscene.js去校验“数据变化之后页面呈现是否正确”。

这个经历给我最大的教训是:AI自动化测试不是把“人的工作量”变成“AI的工作量”,而是把人的精力从低价值的元素定位中释放出来,投入到更高价值的场景设计上。你仍然需要理解业务逻辑、理解测试目标的本质,只是实现手段变了。

另一个坑是不要忽略脚本的可读性和可维护性。自然语言脚本表面上看着好读,但写多了之后,长句子、模糊表述会让脚本变成一座“语言泥潭”。我的习惯是给每个ai()调用加上清晰的中文注释,说明这个步骤的业务意图。代码是会演化的,自然语言脚本也是代码,同样需要版本管理和评审流程。

5.2 AI自动化测试的边界与合适的切入点

聊到边界,需要承认Midscene.js目前还不适合替代所有测试场景。它的定位更适合交互验证,而不是状态穷举。它的优势在UI层理解,这在传统测试里是最难啃的骨头;但在数据校验、逻辑断言这些领域,传统脚本依然更精准、更快速。

不过从整个前端研发链路的视角看,AI测试工具的兴起正在改变一个长期以来被低估的问题:开发者体验。工具决定效率,效率决定质量。当一个工具能让开发者在五分钟内写出一条原本需要三十分钟才能完成的E2E用例,这个工具的进化方向就是对的。Midscene.js不一定最终能占领所有自动化测试场景,但它确实打开了一条新的道路:让测试从“代码”的范畴走向“意图”的范畴。

最后聊一个实际操作中的小经验:我建议所有第一次接触Midscene.js的人,不要一上来就把它接到复杂的业务项目里。先拿一个静态页面、一个简单的登录框练手,感受一下自然语言指令在不同模型版本下的表现,积累一些“AI的理解偏好”。比如某些模型对“点击”这个词更敏感,某些模型对“填写”更敏感。摸透这些细微差别之后,再把它投入到真实的项目里,会顺利很多。测试本身是个需要耐心的工作,用AI工具也一样,需要你花时间和它磨合,才能获得最顺手的体验。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进