视觉驱动Web UI自动化:Midscene与Playwright实战解析

发布时间:2026/10/31 5:01:59
视觉驱动Web UI自动化:Midscene与Playwright实战解析

1. 从“选择器地狱”到“视觉驱动”:为什么我们需要另一种 Web UI 自动化思路

做过 Web UI 自动化的朋友,大概率都经历过这样的场景:凌晨两点,CI 流水线又红了,打开报告一看,某个按钮的id从submit-btn变成了submitBtn,或者某个元素被包进了一个新的div里,XPath 直接失效。你改完选择器,第二天前端同学重构了一下组件库,一切又回到原点。这种“选择器地狱”是传统 Web UI 自动化最让人头疼的问题,也是我这些年一直在寻找更稳定方案的根本原因。

Midscene 这个工具,第一次看到它的时候,我其实是带着怀疑态度的。它的核心卖点是“视觉驱动”——用自然语言描述你要操作什么,它通过视觉识别和 LLM 的理解能力去定位元素并执行操作。听起来很美好,但做自动化测试的人都知道,“听起来美好”和“实际能用”之间往往隔着一条鸿沟。不过,在深入使用了一段时间之后,我发现它在特定场景下确实能解决一些传统方案很难处理的问题,尤其是在页面结构频繁变动、元素定位不稳定、或者需要快速验证业务流程的场景里。

这篇文章不是一篇“官方文档翻译”,也不是“Hello World 级别的入门教程”。我想从一个实际使用者的角度,把 Midscene 在 Web UI 自动化中的定位、核心原理、与 Playwright 的配合方式、脚本化的具体实现、以及我在实际项目中踩过的坑,完整地梳理一遍。如果你正在做 Web UI 自动化,或者对“视觉驱动 + LLM”这个方向感兴趣,这篇文章应该能给你一些直接的参考。

需要提前说明的是,Midscene 并不是要替代 Playwright 或 Selenium 这类传统框架,它更像是在这些框架之上加了一层“智能定位层”。你可以把它理解成一个“翻译官”:你用自然语言告诉它要做什么,它负责把这句话翻译成具体的页面操作。这个翻译过程依赖两个核心能力——视觉识别和 LLM 推理。视觉识别负责“看”页面,LLM 负责“理解”你的意图并决定怎么操作。

2. Midscene 的核心机制拆解:它到底是怎么“看懂”页面的

2.1 视觉驱动与 DOM 驱动的本质区别

传统 Web UI 自动化的定位逻辑是“DOM 驱动”的。你写一个选择器,框架去 DOM 树里找匹配的节点,找到就操作,找不到就报错。这个过程的本质是“字符串匹配”——你的选择器和 DOM 节点的属性之间做匹配。问题在于,DOM 结构是前端实现细节,而前端实现细节是会变的。今天用class="btn-primary",明天可能就变成class="button primary",你的选择器就废了。

Midscene 的“视觉驱动”走的是另一条路。它不依赖 DOM 结构来定位元素,而是通过截图 + 视觉识别模型来“看”页面。你告诉它“点击登录按钮”,它会先截取当前页面,然后通过视觉模型识别出页面上哪些区域可能是按钮,再结合 LLM 的语义理解,判断哪个按钮最可能是“登录按钮”。这个过程更接近人类使用浏览器的方式——我们不会去检查 DOM 结构,我们就是“看”页面,然后“点”我们看到的按钮。

这两种方式的差异,在页面结构稳定的时候不明显,但在页面频繁变动的时候差异巨大。DOM 驱动的方式,页面结构一变,选择器就可能失效;视觉驱动的方式,只要按钮还在页面上、文字没变、位置没有大到离谱,它大概率还能找到。这就是 Midscene 的核心价值所在。

2.2 Midscene 与 Playwright 的协作关系

很多人第一次接触 Midscene 的时候会有一个疑问:它和 Playwright 是什么关系?是替代还是补充?答案是补充,而且是深度补充。

Midscene 本身并不直接操作浏览器,它依赖 Playwright 来完成实际的浏览器控制。你可以这样理解:Playwright 负责“动手”,Midscene 负责“动脑”。当你调用 Midscene 的ai方法时,它内部会通过 Playwright 截取当前页面截图,然后把截图和你的自然语言指令一起发给视觉模型和 LLM,模型返回操作指令(比如“点击坐标 (x, y)”或“在某个输入框输入文本”),Midscene 再通过 Playwright 执行这些操作。

这个协作模式的好处是,你不需要放弃已有的 Playwright 生态。你可以在一个 Playwright 测试用例里,混合使用传统的选择器定位和 Midscene 的视觉定位。比如,对于稳定的登录表单,你可以用传统选择器;对于频繁变动的业务页面,你可以用 Midscene。这种灵活性在实际项目中非常实用。

2.3 视觉模型与 LLM 的分工

Midscene 内部其实用了两种模型能力:视觉模型和语言模型。视觉模型负责“看”页面,识别出页面上的元素、文字、布局;语言模型负责“理解”你的指令,并决定怎么操作。

举个例子,你输入指令“在搜索框里输入‘自动化测试’并点击搜索按钮”。视觉模型会先识别出页面上的搜索框和搜索按钮的位置,语言模型则会理解你的意图是“先找到搜索框,输入文本,再找到搜索按钮,点击”。然后 Midscene 会把这两个结果结合起来,生成具体的操作序列。

这个分工很重要,因为它决定了 Midscene 的能力边界。如果页面上的元素视觉上不明显(比如两个按钮长得一模一样),视觉模型可能分不清;如果你的指令语义模糊(比如“点击那个按钮”但页面上有多个按钮),语言模型也可能判断错误。理解这个分工,有助于你在实际使用中写出更准确的指令。

3. 环境搭建与基础配置:从零开始跑通第一个 Midscene 脚本

3.1 环境准备与依赖安装

在开始之前,你需要确保本地环境满足以下条件:

  • Node.js 18 或以上版本
  • 一个可用的 Playwright 环境
  • Midscene 的 npm 包

安装步骤并不复杂,但有几个细节需要注意。首先,Playwright 的安装建议用官方推荐的方式:

npm init playwright@latest

这个命令会帮你初始化一个 Playwright 项目,包括配置文件、示例测试等。如果你已经有 Playwright 项目,可以跳过这一步。

接下来安装 Midscene:

npm install @midscene/web --save-dev

这里有一个坑需要注意:Midscene 的版本和 Playwright 的版本之间存在兼容性关系。我在早期使用的时候,因为 Playwright 版本太新,导致 Midscene 的截图功能异常。后来降级到 Playwright 1.40.x 才恢复正常。所以建议你在安装之前,先查一下 Midscene 官方文档里推荐的 Playwright 版本范围。

另外,Midscene 需要调用视觉模型和 LLM,所以你需要配置相应的 API Key。目前它支持多种模型提供商,具体配置方式会在下一节详细说明。

3.2 模型配置与 API Key 管理

Midscene 的模型配置是通过环境变量或配置文件来完成的。我推荐用.env文件来管理 API Key,这样可以避免把密钥硬编码在代码里。

一个典型的.env文件长这样:

MIDSCENE_OPENAI_API_KEY=your_api_key_here MIDSCENE_OPENAI_BASE_URL=https://api.openai.com/v1 MIDSCENE_MODEL_NAME=gpt-4o

这里有几个实操心得:

第一,模型选择很关键。我试过用gpt-4o和gpt-4-turbo,前者在视觉识别和指令理解上明显更准,但成本也更高。如果你的测试用例不多,建议直接用gpt-4o;如果用例量大,可以考虑用gpt-4-turbo做日常回归,关键用例用gpt-4o。

第二,API Key 的额度要留足。视觉模型的调用成本比纯文本模型高不少,因为每次调用都要传截图。一个中等复杂度的页面,一次操作可能消耗几千个 token。如果你的测试套件有上百个用例,费用会比较可观。

第三,建议配置一个代理地址(如果你在国内的话),否则 API 调用可能会超时。具体配置方式取决于你使用的模型提供商,这里不展开。

3.3 第一个 Midscene 脚本:从打开页面到点击按钮

环境配好之后,我们来写第一个脚本。这个脚本的目标很简单:打开一个页面,找到搜索框,输入关键词,点击搜索按钮。

const { chromium } = require('playwright'); const { Midscene } = require('@midscene/web'); (async () => { const browser = await chromium.launch({ headless: false }); const page = await browser.newPage(); const midscene = new Midscene(page); await page.goto('https://example.com'); await midscene.ai('在搜索框里输入“自动化测试”'); await midscene.ai('点击搜索按钮'); await page.waitForTimeout(3000); await browser.close(); })();

这段代码的核心是midscene.ai()方法。你传入一个自然语言指令,Midscene 会负责把它翻译成具体的页面操作。

这里有几个细节值得注意:

第一,headless: false是我建议在调试阶段使用的配置。因为 Midscene 的视觉识别依赖于截图,如果你用 headless 模式,截图可能和实际显示有差异,导致识别不准。调试阶段用有头模式,确认没问题后再切换到 headless。

第二,waitForTimeout是一个“笨办法”,但在 Midscene 里有时候是必要的。因为视觉识别和 LLM 推理都需要时间,如果你紧接着执行下一个操作,可能会因为页面还没更新而失败。更好的做法是用waitForLoadState或waitForSelector,但在视觉驱动的场景下,这些传统方法不一定适用。

第三,指令的写法很重要。“在搜索框里输入‘自动化测试’”比“输入自动化测试”更准确,因为它明确了操作目标。Midscene 的指令越具体,识别准确率越高。

4. 脚本化实战:用 Midscene 处理复杂业务场景

4.1 表单填写与多步骤流程

表单填写是 Web UI 自动化里最常见的场景,也是最容易出问题的场景。传统的做法是给每个输入框写选择器,但一旦表单结构变化,所有选择器都要改。用 Midscene 的话,你可以用自然语言描述整个填写过程。

比如,一个注册表单有用户名、邮箱、密码、确认密码四个字段,传统写法可能是:

await page.fill('#username', 'testuser'); await page.fill('#email', 'test@example.com'); await page.fill('#password', 'password123'); await page.fill('#confirm-password', 'password123'); await page.click('#submit-btn');

用 Midscene 的话,可以写成:

await midscene.ai('在用户名输入框里填写“testuser”'); await midscene.ai('在邮箱输入框里填写“test@example.com”'); await midscene.ai('在密码输入框里填写“password123”'); await midscene.ai('在确认密码输入框里填写“password123”'); await midscene.ai('点击提交按钮');

看起来代码变长了,但稳定性提升了。因为 Midscene 不依赖id或class,它依赖的是“用户名输入框”这个语义。只要页面上还有一个地方写着“用户名”,它就能找到。

不过这里有一个坑:如果页面上有多个“密码”相关的输入框(比如“密码”和“确认密码”),Midscene 可能会混淆。我的做法是在指令里加上更明确的描述,比如“在标有‘确认密码’的输入框里填写”,这样识别准确率会高很多。

4.2 动态内容与异步加载的处理

现代 Web 应用大量使用异步加载,页面上的内容不是一次性渲染出来的。传统的做法是用waitForSelector等待某个元素出现,但如果你不知道具体的选择器,这个方法就用不了。

Midscene 在处理异步加载时有一个天然优势:它是“看”页面的。如果页面还在加载,截图里可能没有目标元素,Midscene 会识别失败。但你可以通过重试机制来解决这个问题。

我的做法是封装一个retryAi函数:

async function retryAi(midscene, instruction, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { await midscene.ai(instruction); return; } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 2000)); } } }

这个函数会在指令执行失败时等待 2 秒后重试,最多重试 3 次。实测下来,对于大多数异步加载场景,这个重试机制已经够用了。

另外,Midscene 本身也提供了一些等待相关的配置,比如设置超时时间、设置重试次数等。具体参数可以在初始化 Midscene 时传入。

4.3 断言与结果验证

自动化测试的核心不只是“操作”,还有“验证”。Midscene 提供了一些断言相关的能力,但和传统的断言方式不太一样。

传统的断言是“检查某个元素是否存在”或“检查某个元素的文本是否等于预期值”。Midscene 的断言更偏向于“视觉验证”——你可以让它检查页面上是否出现了某个内容,或者某个区域的状态是否符合预期。

比如,你可以这样写:

const result = await midscene.ai('检查页面上是否显示“注册成功”'); console.log(result);

Midscene 会返回一个结果,告诉你它“看到”了什么。这个结果可能是一个布尔值,也可能是一段描述性的文本,具体取决于你的配置。

这里有一个实操心得:Midscene 的断言结果不如传统断言那么精确。传统断言是“是/否”的二元结果,Midscene 的断言可能会返回“我看到了‘注册成功’这几个字,但不确定是不是你要找的”。所以,如果你的测试需要严格的断言,建议还是用传统方式;如果只是做“大致验证”,Midscene 的断言够用了。

5. 常见问题与排查技巧实录

5.1 识别失败:为什么它找不到我要的元素

识别失败是使用 Midscene 时最常见的问题。原因通常有以下几种:

第一,页面截图不完整。如果页面有滚动条,截图可能只截到了可视区域,目标元素在可视区域之外。解决办法是在截图前先滚动到目标位置,或者配置 Midscene 截取整个页面。

第二,元素视觉特征不明显。如果两个按钮长得一模一样,视觉模型可能分不清。解决办法是在指令里加上更多上下文,比如“点击页面底部的提交按钮”而不是“点击提交按钮”。

第三,指令语义模糊。如果你说“点击那个按钮”,但页面上有多个按钮,LLM 也不知道你指的是哪个。解决办法是把指令写具体,比如“点击标有‘登录’的按钮”。

第四,模型能力不足。如果你用的是比较弱的视觉模型,识别准确率会下降。解决办法是换用更强的模型,比如gpt-4o。

5.2 性能与成本优化

Midscene 的每次操作都需要调用视觉模型和 LLM,这会带来两个问题:速度慢、成本高。

速度方面,一次操作可能需要 2-5 秒,比传统选择器定位慢很多。如果你的测试套件有几百个用例,整体执行时间会很长。我的优化建议是:对于稳定的页面和元素,用传统选择器;对于频繁变动的页面,用 Midscene。混合使用可以在稳定性和速度之间取得平衡。

成本方面,视觉模型的调用成本比纯文本模型高不少。我的优化建议是:第一,尽量合并指令,比如把“在用户名输入框输入 testuser”和“在密码输入框输入 password123”合并成一条指令“在用户名输入框输入 testuser,在密码输入框输入 password123”;第二,对于不需要视觉识别的操作(比如导航到某个 URL),直接用 Playwright 的 API;第三,设置合理的重试次数,避免因为一次失败就反复调用模型。

5.3 与传统测试框架的集成

Midscene 可以和 pytest、Jest、Mocha 等测试框架集成。集成的关键在于把 Midscene 的操作封装成测试步骤,然后用测试框架来组织这些步骤。

以 Jest 为例,你可以这样写:

const { chromium } = require('playwright'); const { Midscene } = require('@midscene/web'); describe('登录流程', () => { let browser; let page; let midscene; beforeAll(async () => { browser = await chromium.launch(); page = await browser.newPage(); midscene = new Midscene(page); }); afterAll(async () => { await browser.close(); }); test('用户可以用正确的凭据登录', async () => { await page.goto('https://example.com/login'); await midscene.ai('在用户名输入框里填写“testuser”'); await midscene.ai('在密码输入框里填写“password123”'); await midscene.ai('点击登录按钮'); const result = await midscene.ai('检查页面上是否显示“欢迎回来”'); expect(result).toBeTruthy(); }); });

这个集成方式的好处是,你可以用 Jest 的断言和报告功能,同时享受 Midscene 的视觉驱动能力。

5.4 常见问题速查表

问题现象可能原因解决办法
识别不到元素截图不完整滚动到目标位置后再截图
识别到错误元素指令语义模糊在指令里加上更多上下文
操作超时模型响应慢增加超时时间或换用更快的模型
成本过高调用次数太多合并指令,混合使用传统选择器
断言不准确视觉验证的局限性关键断言用传统方式,辅助断言用 Midscene
页面加载慢导致失败异步加载封装重试机制,等待页面稳定后再操作

6. 视觉驱动自动化的边界与适用场景

6.1 什么场景适合用 Midscene

根据我的实际使用经验,Midscene 在以下几类场景里表现最好:

第一,页面结构频繁变动的项目。如果你的前端团队经常重构组件、改类名、调布局,传统选择器会频繁失效,Midscene 的视觉驱动方式能大幅降低维护成本。

第二,需要快速验证业务流程的场景。比如产品经理说“帮我验证一下从注册到下单的完整流程”,你用 Midscene 可以快速写一个脚本跑通,不需要花时间研究每个元素的选择器。

第三,元素定位困难的场景。有些元素没有稳定的id或class,或者被动态生成,传统方式很难定位,Midscene 可以通过视觉特征来识别。

第四,跨平台或跨设备的测试。Midscene 的视觉驱动方式不依赖具体的 DOM 结构,理论上可以在不同平台之间复用同一套指令。

6.2 什么场景不适合用 Midscene

Midscene 也不是万能的,以下场景建议谨慎使用:

第一,对执行速度要求极高的场景。Midscene 的每次操作都需要调用模型,速度比传统选择器慢很多。如果你的测试套件需要在几分钟内跑完,Midscene 可能不适合。

第二,对断言精度要求极高的场景。Midscene 的断言是“视觉验证”,不如传统断言精确。如果测试结果需要严格的是/否判断,建议用传统方式。

第三,预算有限的场景。视觉模型的调用成本不低,如果测试量大,费用会比较可观。

第四,页面元素视觉特征不明显的场景。如果页面上有大量长得一样的元素,视觉模型很难区分,这时候传统选择器反而更可靠。

6.3 混合策略:传统选择器 + 视觉驱动

在实际项目中,我通常采用混合策略:对于稳定的、不常变动的页面(比如登录页、导航栏),用传统选择器;对于频繁变动的、结构复杂的页面(比如业务表单、动态列表),用 Midscene。这样既能保证稳定性,又能控制成本和速度。

具体来说,我会在 Playwright 的测试用例里,根据页面模块的稳定性来决定用哪种方式。比如:

// 稳定的登录页,用传统选择器 await page.fill('#username', 'testuser'); await page.fill('#password', 'password123'); await page.click('#login-btn'); // 频繁变动的业务页面,用 Midscene await midscene.ai('在搜索框里输入“订单编号”'); await midscene.ai('点击搜索按钮'); await midscene.ai('在结果列表里找到第一条记录并点击查看详情');

这种混合方式在实际项目中非常实用,既能享受传统选择器的速度和精度,又能享受 Midscene 的灵活性和稳定性。

7. 我踩过的坑与实操心得

7.1 截图质量对识别率的影响

Midscene 的识别准确率高度依赖截图质量。我遇到过几次因为截图分辨率太低导致识别失败的情况。后来发现,Playwright 默认的视口大小是 1280x720,这个分辨率下有些小字体的按钮识别起来比较吃力。把视口调大到 1920x1080 之后,识别率明显提升。

另外,如果你的页面有动画效果,截图可能会截到动画中间状态,导致识别失败。我的做法是在截图前先等待动画结束,或者禁用页面上的动画效果。

7.2 指令写法的经验总结

写 Midscene 指令是一门“手艺”。我总结了几条经验:

第一,指令要具体。“点击按钮”不如“点击标有‘提交’的按钮”。

第二,指令要唯一。“在输入框里输入”不如“在用户名输入框里输入”。

第三,指令要分步。复杂的操作拆成多条指令,比一条长指令更可靠。

第四,指令要符合页面上的实际文字。如果页面上写的是“登 录”(中间有空格),你写“登录”可能识别不到。

7.3 模型选择的权衡

我试过多种模型组合,最终发现gpt-4o在视觉识别和指令理解上表现最好,但成本也最高。对于日常回归测试,我会用gpt-4-turbo;对于关键业务流程的验证,我会用gpt-4o。这个组合在成本和准确性之间取得了不错的平衡。

另外,模型的选择也会影响执行速度。gpt-4o的响应速度比gpt-4-turbo快一些,但差距不大。如果你对速度有要求,可以考虑用更小的模型,但识别准确率会下降。

7.4 调试技巧:如何快速定位问题

当 Midscene 操作失败时,快速定位问题很重要。我的调试流程是这样的:

第一步,检查截图。Midscene 在每次操作时都会截图,你可以把截图保存下来,看看页面上到底显示了什么。

第二步,检查指令。看看你的指令是否足够具体、是否和页面上的文字一致。

第三步,检查模型返回。Midscene 会返回模型的推理结果,你可以看看模型“理解”了什么,是不是和你的意图一致。

第四步,简化场景。如果复杂指令失败,试着拆成简单指令,看看是哪一步出了问题。

这套流程帮我解决了很多问题,也让我对 Midscene 的能力边界有了更清晰的认识。

8. 从脚本化到工程化:Midscene 在团队协作中的落地建议

8.1 脚本组织与复用

在团队协作中,Midscene 脚本的组织方式很重要。我的建议是把常用的操作封装成函数,比如“登录”、“搜索”、“填写表单”等,然后在不同的测试用例里复用。

async function login(midscene, username, password) { await midscene.ai(`在用户名输入框里填写“${username}”`); await midscene.ai(`在密码输入框里填写“${password}”`); await midscene.ai('点击登录按钮'); } async function search(midscene, keyword) { await midscene.ai(`在搜索框里输入“${keyword}”`); await midscene.ai('点击搜索按钮'); }

这样组织的好处是,当页面变化时,你只需要修改封装函数,不需要修改每个测试用例。

8.2 与 CI/CD 的集成

Midscene 可以集成到 CI/CD 流水线中,但需要注意几点:

第一,API Key 的管理。不要把 API Key 硬编码在代码里,用环境变量或密钥管理服务。

第二,超时设置。Midscene 的操作比传统方式慢,CI 的超时时间要相应调大。

第三,失败重试。网络波动或模型响应慢可能导致偶发失败,建议配置重试机制。

第四,报告生成。Midscene 的操作日志和截图可以集成到测试报告里,方便排查问题。

8.3 团队协作中的注意事项

在团队中使用 Midscene,有几个问题需要提前考虑:

第一,成本分摊。视觉模型的调用成本不低,需要明确谁来承担这笔费用。

第二,指令规范。团队需要统一指令的写法,避免因为个人风格不同导致识别率波动。

第三,模型版本管理。不同版本的模型识别效果可能不同,需要锁定版本或定期评估。

第四,与传统自动化的分工。明确哪些场景用 Midscene,哪些场景用传统方式,避免混用导致维护混乱。

9. 视觉驱动自动化的未来可能性

Midscene 目前还处于比较早期的阶段,但它的方向很有意思。随着视觉模型和 LLM 能力的提升,视觉驱动自动化的准确率和速度都会改善。我比较期待的几个方向是:第一,更精准的元素定位,减少对指令具体性的依赖;第二,更快的响应速度,让视觉驱动接近传统选择器的效率;第三,更好的断言能力,让视觉验证也能做到精确判断。

从工程实践的角度来看,Midscene 目前最适合的场景是“辅助”而不是“替代”。它可以帮你处理那些传统方式很难搞定的场景,但不需要完全替代现有的自动化体系。混合使用、各取所长,是我目前认为最务实的做法。

如果你正在做 Web UI 自动化,并且被选择器维护问题困扰,我建议你花半天时间试试 Midscene。它可能不会解决你所有的问题,但它提供了一种新的思路,而这种思路在某些场景下确实能带来实实在在的效率提升。