Midscene + Playwright:视觉驱动 Web UI 自动化测试实战

发布时间:2026/10/31 16:33:16
Midscene + Playwright:视觉驱动 Web UI 自动化测试实战 Web UI 自动化测试这块我踩过的坑比写过的用例还多。早些年用 Selenium 的时候最头疼的就是页面一改版满屏的NoSuchElementException定位器维护成本高得离谱。后来 Playwright 出来了自动等待、多浏览器支持确实舒服了不少但本质上还是人告诉机器点哪里——选择器写错了照样跑不通。直到我开始接触 Midscene 这个工具才真正感觉到一种范式上的变化它不再依赖你手写 CSS 选择器或 XPath而是让模型看页面截图理解界面上的元素和语义然后自己决定该点哪里、该填什么。这篇文章就来聊聊怎么把 Midscene 和 Playwright 结合起来做视觉驱动的 Web UI 自动化脚本化测试包括它的工作原理、实操步骤、我踩过的坑以及一些能直接抄作业的配置。1. 为什么选择视觉驱动而不是传统选择器1.1 传统选择器方案的三个死穴做 Web UI 自动化的人都知道选择器是绕不过去的坎。不管你是用 Playwright 的page.locator()还是 Selenium 的find_element本质上都是在告诉机器去 DOM 树里找 id 为 submit-btn 的那个元素然后点它。这套逻辑在页面稳定的时候没问题但一旦遇到下面几种情况就开始难受了。第一个死穴是动态类名和属性。现在前端框架越来越流行 CSS-in-JS 和原子化 CSS类名可能是css-1x2y3z这种编译时生成的哈希值每次构建都可能变。你昨天写的.btn-primary-abc123今天部署完就失效了。第二个死穴是频繁改版。产品经理说把这个按钮挪到右边DOM 结构一变你的选择器路径就断了。第三个死穴是跨端适配。同一个功能在 PC 端和移动端可能用完全不同的组件库选择器没法复用。我印象最深的一次是做一个电商后台的回归测试一个确认发货的按钮因为前端把button换成了div rolebutton整个用例挂了三天才发现。这种问题不是技术难度高而是维护成本太磨人。1.2 视觉驱动到底改变了什么Midscene 的思路和传统方案完全不同。它不关心 DOM 结构而是把当前页面截一张图交给视觉语言模型去理解这个页面上有哪些可交互的元素它们分别是什么然后你只需要用自然语言描述你的意图比如点击登录按钮、在搜索框里输入 iPhone 15模型会根据截图找到对应的坐标再通过 Playwright 的底层能力去执行点击或输入。这背后的核心变化是定位逻辑从结构化查询变成了语义理解。你不再需要知道按钮的 id 是什么、在第几层 div 里你只需要知道它长什么样、叫什么名字。这就像你让一个真人去操作网页你不会告诉他点击 DOM 树第三个子节点的第二个按钮你会说点那个蓝色的登录按钮。当然视觉驱动也不是银弹。它的响应速度比选择器慢因为每次都要截图、推理、定位。而且模型也有看错的时候特别是页面上有多个相似元素时。但在页面频繁变动、选择器维护成本极高的场景下它的优势非常明显。1.3 Midscene 和 Playwright 的分工关系很多人第一次听到 Midscene 会误以为它是 Playwright 的替代品其实不是。Midscene 更像是一个智能定位层它负责把自然语言指令翻译成具体的页面操作而真正的浏览器控制、网络拦截、多标签页管理这些底层能力还是交给 Playwright 来做。你可以这样理解Playwright 是手和脚负责执行动作Midscene 是眼睛和大脑负责看页面、做决策。两者结合之后你写测试用例的方式就变成了描述意图 执行动作而不是写选择器 执行动作。这个分工模式让代码的可读性提升了很多非技术背景的测试人员也能看懂用例在干什么。2. Midscene 被 Playwright 调用的底层原理2.1 从一次点击看完整的调用链路要理解 Midscene 怎么被 Playwright 调用最好的方式是跟着一次点击登录按钮的操作走一遍完整链路。当你在代码里写下await midscene.aiAction(点击登录按钮)时背后发生的事情大致是这样的首先Midscene 通过 Playwright 提供的page.screenshot()接口获取当前页面的截图同时可能还会拿到页面的 DOM 快照或者可访问性树作为辅助信息。然后它把截图和你的自然语言指令一起打包发送给配置好的视觉语言模型。模型返回的结果通常是一个坐标或者一个元素描述比如{ x: 320, y: 480 }或者{ selector: button:has-text(登录) }。接下来Midscene 把这个结果转换成 Playwright 能执行的动作。如果是坐标就用page.mouse.click(x, y)如果是选择器就用page.locator(selector).click()。执行完之后Midscene 可能还会再截一张图确认操作是否生效形成一个感知-决策-执行-验证的闭环。这个链路里最关键的是截图和指令的绑定。模型看到的必须是操作前的页面状态否则它定位的就是过期信息。Midscene 在这方面做了不少优化比如在截图前等待页面稳定、过滤掉动画中的中间态等等。2.2 视觉模型返回的定位信息怎么用模型返回的定位信息一般有两种形式坐标和选择器。坐标的好处是通用性强不管页面用什么技术栈只要有像素就能点。但坐标的缺点是脆弱页面一滚动、一缩放坐标就偏了。所以 Midscene 在实际执行时通常会结合 Playwright 的滚动和视口管理能力确保坐标对应的元素真的在可点击区域。选择器形式则更稳定一些但需要模型能准确理解 DOM 结构。有些实现会让模型同时输出坐标和候选选择器然后优先用选择器执行失败时回退到坐标。这种双保险策略在实际项目中很实用我在配置的时候也会特意打开这个选项。还有一个细节是元素边界框。模型有时候返回的不是一个点而是一个矩形区域比如{ x: 100, y: 200, width: 80, height: 40 }。这时候 Midscene 会计算中心点或者用 Playwright 的boundingBox()方法去校验这个区域是否真的对应一个可交互元素。这个校验步骤能过滤掉不少误识别。2.3 缓存机制对执行速度的影响纯视觉驱动的方案有个天然劣势慢。每次操作都要截图、调模型、等返回一轮下来可能好几秒。如果用例里有几十个步骤整体执行时间会非常可观。Midscene 在这方面引入了缓存机制来优化。缓存的逻辑大概是这样的第一次执行某个操作时模型返回的定位信息会被记录下来和当前页面的某种指纹绑定。下次再执行同样的操作如果页面指纹匹配就直接用缓存里的定位信息跳过模型调用。这个指纹可能是页面 URL、关键元素的哈希、或者截图的感知哈希。我在实际使用中发现缓存对稳定页面的提速非常明显首次执行可能要 3-5 秒命中缓存后能降到几百毫秒。但缓存也有风险如果页面变了但指纹没变就可能用到过期的定位信息。所以我的建议是在开发调试阶段关掉缓存确保每次都是实时推理在稳定的回归测试阶段再打开缓存提升执行效率。3. 环境搭建与项目初始化实操3.1 Playwright 环境准备中的常见坑虽然 Midscene 是主角但 Playwright 的环境必须先跑通。安装 Playwright 本身不复杂npm init playwrightlatest一路回车就行但有几个坑我得提前说。第一个坑是浏览器二进制下载。npx playwright install这个命令在国内网络环境下经常卡住或者失败因为要下载 Chromium、Firefox、WebKit 三个浏览器的二进制包加起来好几百兆。我的做法是只装需要的浏览器比如npx playwright install chromium能省不少时间。如果还是慢可以配置镜像源具体方法 Playwright 官方文档里有说明。第二个坑是Node 版本。Midscene 对 Node 版本有要求太低会报语法错误太高又可能遇到依赖不兼容。我实测下来 Node 18 LTS 和 Node 20 LTS 都比较稳建议用 nvm 管理版本别用系统自带的。第三个坑是项目结构。Playwright 默认生成的目录结构是tests/放用例、playwright.config.ts放配置。如果你要集成 Midscene建议单独建一个midscene/目录放相关配置和辅助函数别和原生用例混在一起不然维护起来很乱。3.2 Midscene 的安装与模型配置Midscene 的安装方式取决于你用哪种集成模式。如果是和 Playwright 配合一般是装midscene/web这个包。安装命令很简单npm install midscene/web --save-dev装完之后核心的配置是模型接入。Midscene 需要调用视觉语言模型你得提供一个能访问的模型服务。配置通常在项目根目录建一个.env文件把模型相关的参数写进去MIDSCENE_MODEL_BASE_URLhttps://your-model-endpoint/v1 MIDSCENE_MODEL_API_KEYyour-api-key MIDSCENE_MODEL_NAMEyour-vision-model MIDSCENE_MODEL_FAMILYgpt-4o这里有几个点要注意。MODEL_FAMILY这个参数告诉 Midscene 用哪种模型的调用协议不同模型的请求格式和返回格式不一样填错了会一直报解析错误。MODEL_NAME必须是支持视觉输入的模型纯文本模型没法处理截图。另外 API Key 千万别提交到代码仓库用.gitignore把.env排除掉。提示模型服务的响应速度直接决定测试执行效率。如果条件允许选一个网络延迟低的端点能明显改善体验。3.3 第一个可运行的视觉驱动用例配置好之后写一个最简单的用例验证链路是否通。下面这段代码是我常用的冒烟测试模板import { chromium } from playwright; import { MidsceneAgent } from midscene/web; async function smokeTest() { const browser await chromium.launch({ headless: false }); const page await browser.newPage(); await page.goto(https://example.com); const agent new MidsceneAgent(page); await agent.aiAction(在搜索框中输入 自动化测试); await agent.aiAction(点击搜索按钮); await agent.aiAssert(页面中出现了搜索结果列表); await browser.close(); } smokeTest();这段代码里aiAction负责执行操作aiAssert负责断言。注意headless: false调试阶段一定要开着浏览器能看到模型到底在点什么不然出了问题你都不知道是定位错了还是页面没加载完。跑通这个用例之后你会对 Midscene 的工作方式有个直观感受。我第一次跑的时候模型把搜索按钮定位到了页面顶部的导航搜索图标上而不是页面中间的搜索框旁边的按钮。这就是视觉驱动的典型问题页面上有多个语义相近的元素时模型可能选错。解决办法后面会讲。4. 视觉驱动脚本化的核心写法与技巧4.1 用自然语言描述意图的正确姿势Midscene 的指令是自然语言但自然不等于随意。我总结了几条写指令的经验能显著提升定位准确率。第一指令要包含足够的区分信息。别说点击按钮要说点击页面右下角的蓝色提交按钮。页面上按钮那么多信息越具体模型越不容易选错。第二避免歧义词汇。比如确认这个词页面上可能既有确认订单又有确认收货你直接说点击确认模型就懵了。第三用页面上的可见文本。如果按钮上写着立即购买你就说点击立即购买按钮这比描述颜色和位置更可靠。还有一个技巧是分步描述复杂操作。比如登录并进入个人中心这种复合指令模型可能理解成先登录再跳转也可能理解成直接找个人中心入口。拆成两步写每步一个明确动作稳定性会高很多。4.2 aiAction、aiAssert、aiQuery 的分工Midscene 提供的几个核心 API 各有用途用对了能省很多事。aiAction是执行操作点击、输入、滚动、拖拽都靠它。它的返回值通常是操作是否成功的布尔值但别太依赖这个返回值做断言因为操作执行了和操作生效了是两回事。aiAssert是做断言你给它一个自然语言的判断条件它返回真或假。比如aiAssert(购物车图标上显示的数字是 3)。这个 API 特别适合做视觉层面的校验比如元素是否可见、文本是否正确、布局是否合理。aiQuery是从页面提取信息返回结构化的数据。比如你想知道页面上所有商品的价格可以用aiQuery(提取所有商品的价格返回数组)。这个在数据校验场景下很有用比一个个选择器去抓要方便。我的使用习惯是操作类用aiAction校验类用aiAssert数据提取用aiQuery。三者配合基本能覆盖大部分测试场景。4.3 处理模型定位失败的兜底策略模型不是万能的定位失败是常态。关键是要有兜底策略别让一个失败卡死整个用例。我的做法是重试 降级。首先给每个aiAction包一层重试逻辑失败后等一秒再试一次有时候只是页面还没渲染完。如果重试还失败就降级到传统选择器方案用 Playwright 的page.locator()去兜底。虽然这样代码会啰嗦一点但稳定性提升明显。另一个策略是截图留证。每次aiAction失败时自动保存当前页面截图和模型的返回结果方便事后分析。我在项目里封装了一个safeAiAction函数失败时会把截图存到debug/目录文件名带上时间戳和操作描述。这个习惯帮我定位过好几次模型到底看到了什么的问题。async function safeAiAction(agent, instruction, fallbackSelector?) { try { await agent.aiAction(instruction); } catch (e) { await agent.page.screenshot({ path: debug/${Date.now()}.png }); if (fallbackSelector) { await agent.page.locator(fallbackSelector).click(); } else { throw e; } } }这段代码虽然简单但在实际项目里救过我好几次。5. 实测中暴露的问题与排查过程5.1 页面有多个相似元素时的误定位这是视觉驱动最常见的问题。我做一个后台管理系统的测试时页面上有编辑和删除两个按钮图标长得很像只是颜色不同。模型好几次把点击编辑按钮定位到了删除按钮上差点造成数据误删。排查过程是这样的我先打开调试模式让 Midscene 输出每次操作的截图和模型返回的坐标。对比之后发现模型确实看到了两个按钮但在判断哪个是编辑时主要依赖图标形状而这两个图标形状太接近了。解决办法是在指令里加上颜色或位置信息比如点击蓝色的编辑按钮或者点击表格第一行的编辑按钮。加上之后准确率从 70% 左右提升到了 95% 以上。这个案例给我的教训是视觉驱动的准确率高度依赖指令的信息量。你给的信息越独特模型越不容易混淆。5.2 动态加载内容导致的时序问题现代前端应用大量使用懒加载和异步渲染页面截图的时候目标元素可能还没出现。这时候模型看到的是一个不完整的页面自然定位不到。我遇到过一个典型场景点击加载更多之后列表会异步追加新数据。如果紧接着就执行点击最后一条记录模型截到的图里可能还没有新数据。解决办法是在操作之间加等待但不是死等而是等某个条件满足。Midscene 本身有一些等待机制但不够灵活我一般会结合 Playwright 的waitForSelector或者waitForFunction来做。await agent.aiAction(点击加载更多按钮); await page.waitForFunction(() { return document.querySelectorAll(.list-item).length 10; }); await agent.aiAction(点击最后一条记录);这种视觉操作 结构化等待的混合模式在动态页面上特别管用。5.3 模型响应超时与重试机制模型服务不是永远可用的网络抖动、服务限流、推理排队都可能导致超时。如果每个操作都因为超时失败整个测试就没法跑了。我的处理方式是设置合理的超时时间并配合指数退避重试。超时时间不能太短视觉推理本身就需要时间一般设 30 秒比较稳妥。重试次数设 2-3 次每次间隔翻倍。如果连续失败就跳过这个操作并记录日志继续执行后面的步骤最后统一报告哪些步骤失败了。这里有个细节重试之前要确认页面状态。如果第一次操作其实已经生效了只是响应没返回重试就会造成重复操作。比如点击提交重试两次可能提交了两遍订单。所以对于有副作用的操作重试要格外谨慎最好先检查操作结果再决定是否重试。6. 把视觉驱动融入现有测试体系的思路6.1 和 pytest 或 Jest 的集成方式Midscene 本身不限制测试框架你可以把它嵌到 pytest、Jest、Mocha 任何框架里。我用得比较多的是 Playwright Test 和 pytest 两种。在 Playwright Test 里集成比较自然因为 Midscene 本来就是基于 Playwright 的。你可以写一个 fixture把 MidsceneAgent 注入到每个测试用例里import { test as base } from playwright/test; import { MidsceneAgent } from midscene/web; export const test base.extend({ agent: async ({ page }, use) { const agent new MidsceneAgent(page); await use(agent); }, });这样每个用例里直接test(xxx, async ({ page, agent }) {...})就能用。如果用 pytest思路类似写一个 fixture 返回 agent 对象。不过 Python 生态里 Midscene 的支持不如 TypeScript 完善可能需要通过子进程调用 Node 脚本或者用 HTTP 接口的方式桥接。这个方案我没深入实践过如果团队以 Python 为主建议先评估一下集成成本。6.2 视觉用例和传统用例的边界划分不是所有用例都适合视觉驱动。我的划分原则是页面稳定、选择器清晰的场景用传统方案页面频繁变动、选择器难维护的场景用视觉驱动。具体来说登录、注册这种流程固定、元素稳定的页面用 Playwright 原生选择器就够了没必要上视觉模型又快又稳。而像后台管理系统、数据看板这种经常改版、组件动态生成的页面视觉驱动的优势就体现出来了。还有一类是跨端测试同一套用例要在 PC 和移动端跑视觉驱动能复用指令省去写两套选择器的麻烦。混合使用的时候要注意状态传递。视觉操作和传统操作可能交替进行确保页面状态在两者之间是一致的。比如视觉操作点击了某个 tab传统选择器要能感知到这个状态变化别还在找旧 tab 里的元素。6.3 执行报告里怎么体现视觉步骤视觉驱动的步骤在报告里怎么展示是个容易被忽略但很重要的问题。传统用例失败时报告里能看到具体是哪个选择器没找到。视觉用例失败时你得让报告能说明模型看到了什么、返回了什么、为什么失败。我的做法是在每个视觉步骤里附加截图和模型返回的原始数据。Playwright Test 支持testInfo.attach()可以把截图和 JSON 数据挂到报告里。这样失败的时候打开报告就能看到当时的页面长什么样模型返回的坐标在哪里一目了然。另外我还会在报告里标注每个步骤是视觉驱动还是传统选择器方便统计两种方式的成功率和耗时。跑一段时间之后你就能用数据说话知道哪些场景适合视觉驱动哪些还是传统方案更划算。7. 一些实战中攒下来的经验7.1 指令库的沉淀与复用视觉驱动的指令是自然语言但好的指令是可以复用的。我在项目里建了一个instructions/目录把常用的操作指令抽成常量或者函数。比如登录这个操作封装成login(agent, username, password)内部实现是在用户名框输入 xxx、在密码框输入 xxx、点击登录按钮。这样用例里只需要调函数不用每次重写指令。指令库的另一个好处是统一维护。如果某个页面的按钮文案变了你只需要改指令库里的一个地方所有用例都跟着更新。这比散落在各个用例里的选择器好维护多了。7.2 模型选型对成本和效果的影响视觉模型的选型直接影响两个指标准确率和成本。我试过几种不同的模型差异还挺明显的。有的模型对中文界面理解好有的对图标识别准有的响应快但准确率一般。我的建议是先用效果好的模型跑通流程再根据成本考虑替换。开发阶段别太在意成本把用例写对、跑稳最重要。到了 CI 阶段如果调用量大再评估是不是换成更便宜的模型或者对稳定页面开启缓存减少调用次数。还有一个省钱的技巧是只在关键步骤用视觉驱动。比如一个 20 步的用例可能只有 3-4 步涉及动态元素其他步骤用传统选择器就行。这样既保证了稳定性又控制了模型调用量。7.3 团队协作中的注意事项如果要把视觉驱动测试推广到团队有几个事得提前做好。首先是文档把环境配置、模型接入、指令写法这些写成文档别让每个人都来问你。其次是规范约定好指令的写法风格、失败处理方式、报告格式不然每个人写出来的用例风格差异很大维护起来头疼。最后是预期管理。视觉驱动不是银弹它解决的是选择器维护成本高的问题但引入了模型调用成本和不确定性。跟团队沟通的时候要客观说明它的适用场景和局限别让大家觉得上了视觉驱动就一劳永逸了。我在团队里推的时候先选了一个改版最频繁的模块做试点跑了一个月用数据证明了维护成本确实下降了大家才愿意跟进。视觉驱动这条路我觉得是 Web UI 自动化测试的一个有意思的方向。它不完美慢、有不确定性、依赖外部服务但在页面越来越动态、前端技术栈越来越复杂的今天它提供了一种新的解题思路。Midscene 作为这个方向的工具成熟度还在演进中但已经能解决一些传统方案很头疼的问题。我的态度是把它当成工具箱里的一把新工具在合适的场景用合适的方案别迷信也别排斥。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询