Midscene.js:基于多模态大模型的视觉驱动UI自动化测试实践
1. 项目概述当AI“看见”你的界面最近在团队里推动自动化测试落地一个老生常谈的痛点又浮出水面UI测试脚本的维护成本高得吓人。前端同事改了个class名或者重构了组件结构测试工程师就得吭哧吭哧地回去改一堆XPath或CSS Selector测试用例的“脆弱性”成了阻碍自动化效率提升的最大瓶颈。直到我深度体验了Midscene.js这个项目它提出了一种截然不同的思路——视觉驱动的UI自动化让我看到了将测试效率提升数倍的可能性。简单来说Midscene.js 是一个开源的AI驱动SDK。它的核心逻辑非常“人性化”你不再需要去分析页面的DOM树结构也不用编写复杂的选择器。你只需要像告诉一个新手测试员一样用自然语言描述你想在界面上做什么比如“点击那个蓝色的提交按钮”或者“在搜索框里输入‘测试数据’并回车”。Midscene.js 会驱动背后的多模态大模型比如GLM-4V、Qwen-VL等去“看”你提供的屏幕截图理解你的指令然后自动规划操作步骤并执行点击、输入、滑动等动作。它就像一个不知疲倦、且能精准理解视觉信息的AI测试员。这种方案带来的最直接好处就是测试脚本与页面结构的解耦。只要界面视觉元素和布局没有发生颠覆性变化比如按钮从左边移到了右边仅仅是代码层面的重构、类名更改甚至是从div换成canvas渲染你的自动化测试用例依然可以稳定运行。这对于追求快速迭代、频繁发布的前端项目来说意味着测试脚本的维护工作量将大幅下降从而实现标题所说的“5倍提升自动化测试效率”并非虚言。它覆盖了Web、移动端Android/iOS/HarmonyOS、桌面应用乃至游戏界面canvas真正实现了“所见即所得”的自动化。2. 核心原理与架构拆解为什么“看见”比“解析”更强大要理解Midscene.js为何能带来效率革命我们需要先剖析传统UI自动化测试的痛点以及视觉驱动方案是如何从根本上解决这些问题的。2.1 传统方案的“阿喀琉斯之踵”传统的UI自动化无论是基于Selenium、Appium还是Playwright其核心都是通过页面结构来定位元素。依赖DOM/视图树工具通过注入脚本或调用系统API获取当前界面的节点树结构Web的DOM移动端的Accessibility Tree。使用选择器定位测试脚本使用ID、Class、XPath等选择器像“地图坐标”一样精确定位到某个按钮或输入框。执行模拟操作工具向该坐标发送点击、输入等指令。这套流程的脆弱性显而易见强耦合与易失效前端任何微小的结构调整、属性名更改都会导致选择器失效测试脚本“断线”。“看不见”的元素对于纯图标按钮无文本、高度自定义渲染的组件如WebGL、Canvas、以及缺乏无障碍标签的元素基于结构的工具几乎无能为力。跨域与原生壁垒Web中的跨域iframe、移动端中的原生控件或WebView混合应用结构访问常常受限。缺乏视觉验证它只能验证“某个元素是否存在”无法判断“这个元素在用户看来是否正确”比如颜色是否错误、布局是否错乱、图片是否加载成功。2.2 Midscene.js的视觉驱动范式Midscene.js绕开了结构解析选择了一条更接近人类本能的路视觉感知。截图即状态将整个应用界面或指定区域截取为一张图片。这张图片就是AI认知的“当前状态”。多模态模型理解将截图和你的自然语言指令如“找到登录按钮并点击”一同提交给多模态大模型。这类模型经过海量图文对训练具备强大的视觉问答VQA和视觉定位能力。规划与坐标计算模型不仅理解指令还会在图片中框选出目标元素并计算出其相对于屏幕的坐标位置。驱动执行Midscene.js接收坐标通过底层驱动如Playwright、Appium或系统输入事件执行精确的模拟操作。这个范式的优势是颠覆性的抗重构性强只要按钮看起来还是那个按钮还在大致相同的位置无论背后的HTML标签怎么变AI都能找到它。无所不“见”只要是屏幕上人眼能看到的像素无论是Canvas绘制的图表、游戏UI、还是系统弹窗都能被识别和操作。真正的视觉校验你可以直接让AI判断“这个提示框是否显示为红色错误状态”或“列表的排序是否符合预期”实现从功能到视觉的完整断言。注意视觉驱动并非银弹。其准确性依赖于模型对UI的理解能力且执行速度可能略慢于直接的元素调用。它更适合用于业务流程验证、冒烟测试、回归测试等对稳定性要求高于极致速度的场景。对于需要高频执行、毫秒级响应的单元级交互测试传统方法仍有其价值。2.3 技术架构一览Midscene.js的架构清晰地将AI能力与自动化执行解耦[自然语言指令] [屏幕截图] | v [多模态大模型 (如 Qwen-VL, GLM-4V)] | v [元素定位与意图解析] - (坐标 操作类型) | v [平台驱动适配层 (Playwright/Appium/系统输入)] | v [目标应用程序]它通过一个统一的JavaScript SDK提供API上层应用你的测试脚本无需关心底层是浏览器还是手机。同时它支持“自动规划”和“工作流”两种风格兼顾了灵活性与可控性这部分我们将在下一章详细展开。3. 两种自动化风格与实战选型Midscene.js提供了两种编写自动化脚本的风格对应着不同的抽象层次和适用场景。理解并正确选择它们是高效利用该工具的关键。3.1 自动规划风格让AI成为测试策略师这种风格将最高级的控制权交给AI。你只需用一句自然语言描述一个相对复杂的任务AI会自行拆解步骤、寻找元素、执行操作。// 示例让AI自动完成一个Todo列表的过滤操作 await agent.aiAct(找出所有未完成的待办项将它们逐个标记为已完成);在这行代码背后AI可能会执行以下规划截取当前屏幕。识别出哪些是“待办项”可能是列表中的一行。判断每一项的“状态”通过文本“已完成”或视觉上的勾选图标。筛选出“未完成”的项。对每一项找到其“标记完成”的控件如复选框或按钮并点击。优点极其高效用最少的代码描述最复杂的任务快速原型验证和编写一次性测试脚本时优势巨大。意图驱动你关注“做什么”而不是“怎么做”更符合测试用例的业务描述。缺点与注意事项可控性较低AI的拆解路径可能不符合你的细微预期比如点击顺序、等待时机。调试成本可能较高如果任务失败你需要分析是AI的哪一步规划出了问题。稳定性依赖模型能力对于逻辑特别复杂或界面元素极其相似的场景AI可能“迷惑”。适用场景探索性测试、快速编写冒烟测试用例、测试不熟悉的老系统、执行固定且清晰的线性任务。3.2 工作流风格你掌舵AI划桨这种风格要求你将复杂任务手动拆分成明确的、原子化的步骤然后使用Midscene.js提供的原子API如aiClick,aiType,aiAssert来执行每一步。你保留了流程的控制权AI只负责每个步骤中最难的环节——视觉定位与理解。// 示例以工作流风格完成GitHub Issue创建 const issuesButton await agent.aiQuery(the Issues tab in the repository header); await agent.aiClick(issuesButton); const newIssueButton await agent.aiQuery(the green New issue button); await agent.aiClick(newIssueButton); const titleField await agent.aiQuery(the issue title input field); await agent.aiType(titleField, 发现了一个视觉自动化测试的Bug); const bodyField await agent.aiQuery(the issue body text area, maybe labeled Write or Comment); await agent.aiType(bodyField, 步骤... 预期... 实际...); const submitButton await agent.aiQuery(the green Submit new issue button at the bottom); await agent.aiClick(submitButton); // 视觉断言验证成功创建后的跳转页面或提示信息 await agent.aiAssert(a message contains Issue created successfully appears on the page, { timeout: 10000 });优点高可控性与稳定性流程完全由你定义避免了AI的“意外发挥”更适合集成到CI/CD流水线中。易于调试与维护每个步骤独立失败时能快速定位到是“点击Issues标签”还是“输入标题”出了问题。可复用性强可以将常用的原子操作如登录、导航封装成函数构建自己的测试库。缺点代码量较多需要编写更详细的步骤。仍需一定元素识别虽然不用写选择器但仍需用自然语言描述元素描述不清可能导致定位失败。适用场景核心业务流程的回归测试、CI/CD中的自动化测试套件、对稳定性和可预测性要求极高的场景。实操心得在实际项目中我推荐采用混合模式。先用“自动规划”快速跑通主流程验证AI能否理解你的界面。然后将其中稳定、核心的路径用“工作流风格”重写形成可靠的测试用例。而对于那些界面变化频繁、或本身就需要一定智能探索的测试点可以保留为自动规划任务。4. 从零开始环境搭建与第一个自动化脚本理论说得再多不如亲手跑一遍。下面我将带你从零开始搭建一个基于Midscene.js的Web端自动化测试环境并完成第一个脚本。4.1 环境准备与安装假设你已经有了Node.js (18) 和 npm/yarn 环境。创建项目并初始化mkdir midscene-demo cd midscene-demo npm init -y安装核心依赖 Midscene.js 需要与一个浏览器自动化框架协同工作这里我们选择 Playwright因为它本身功能强大且与Midscene集成良好。npm install midscene/sdk playwright同时安装Playwright的浏览器内核npx playwright install chromium配置AI模型 Midscene.js本身不提供模型你需要一个多模态大模型的API密钥。这里以智谱AI的GLM-4V为例国内访问稳定且对UI理解能力较强。前往智谱AI开放平台注册并获取API Key。在项目根目录创建.env文件填入你的密钥GLM_API_KEYyour_glm_api_key_here安装对应的Node.js客户端Midscene内部会调用但有时需要显式安装npm install zhipuai-sdk/nodejs-sdk4.2 编写第一个视觉自动化脚本我们的目标是让AI自动打开一个TodoMVC示例页面添加一个新的待办事项。创建测试文件first-test.jsrequire(dotenv).config(); // 加载环境变量 const { MidsceneAgent } require(midscene/sdk); const { chromium } require(playwright); (async () { // 1. 启动浏览器 const browser await chromium.launch({ headless: false }); // 设置为 true 可无头运行 const context await browser.newContext(); const page await context.newPage(); // 2. 创建Midscene智能体并绑定到页面 const agent new MidsceneAgent({ page: page, // 传入Playwright的page对象 model: glm-4v, // 指定使用的模型 modelConfig: { apiKey: process.env.GLM_API_KEY, }, }); try { // 3. 导航到目标页面 await page.goto(https://demo.playwright.dev/todomvc/); // 给页面一点加载时间并让AI“看到”初始界面 await page.waitForTimeout(2000); // 4. 使用自动规划风格让AI添加一个待办 console.log(开始让AI添加待办事项...); await agent.aiAct(在输入框里输入“学习Midscene.js视觉自动化”然后按回车键添加); // 5. 使用工作流风格进行一个视觉断言 console.log(验证待办是否添加成功...); const isItemAdded await agent.aiBoolean(检查列表中是否存在包含文本“学习Midscene.js视觉自动化”的项目); if (isItemAdded) { console.log(✅ 测试成功待办事项已添加。); } else { console.log(❌ 测试失败未找到新增的待办事项。); } // 6. (可选) 让AI再完成这个待办 await page.waitForTimeout(1000); // 稍作等待 await agent.aiAct(点击“学习Midscene.js视觉自动化”这一项前面的复选框将其标记为已完成); } catch (error) { console.error(自动化执行出错, error); } finally { // 7. 留出一些时间观察结果然后关闭浏览器 await page.waitForTimeout(3000); await browser.close(); } })();运行脚本node first-test.js如果一切配置正确你将看到浏览器自动打开输入框内自动填入文字并回车列表中出现新项随后该项被勾选。控制台会打印成功信息。关键配置解析headless: false在调试阶段建议关闭无头模式直观观察AI的操作过程。model: glm-4v你可以在Midscene的模型策略文档中查看所有支持的模型如qwen3-vl、gemini-2.0-flash-exp等并根据网络、成本和精度进行选择。aiBoolean这是一个非常实用的API它让AI根据视觉信息判断一个布尔性问题非常适合做断言。5. 深入核心API与高级用法掌握了基础操作后我们来深入看看Midscene.js SDK中那些让你能精细控制自动化过程的核心API。5.1 核心API三剑客aiAct(instruction: string, options?)这是“自动规划”风格的灵魂。你给它一个任务指令它负责一切。options中可以配置超时、重试次数、操作间隔等。// 示例进行一个包含条件逻辑的复杂操作 await agent.aiAct(如果页面有“接受所有Cookie”的按钮就点击它然后滚动到页面底部找到“联系我们”的链接并点击, { timeout: 30000, // 任务总超时30秒 interval: 1000, // 每个步骤间隔1秒避免操作过快 });aiQuery(instruction: string, options?)“工作流”风格的主力。它让AI在屏幕上查找并返回一个或多个元素的描述信息通常是文本内容或属性而不是直接操作。返回的是字符串或字符串数组。// 获取所有新闻标题 const headlines await agent.aiQuery(list all the news headlines on this page as an array of strings); console.log(headlines); // 输出: [标题1, 标题2, ...] // 获取特定元素的属性需要模型支持 const priceText await agent.aiQuery(the text content of the product price element, which is usually red and bold);aiClick(target: string | ElementHandle, options?)/aiType(target, text, options?)/aiAssert(condition, options?)这些是原子操作API。aiQuery找到了目标aiClick等就来执行具体动作。// 组合使用找到搜索框输入内容并搜索 const searchBox await agent.aiQuery(the main search input field at the top); await agent.aiType(searchBox, Midscene.js tutorial); const searchButton await agent.aiQuery(the magnifying glass icon button next to the search box); await agent.aiClick(searchButton); // 视觉断言验证页面是否跳转到正确结果 await agent.aiAssert(the page title or a large heading contains the text Midscene.js, { timeout: 5000, assertionMessage: 搜索后未找到相关标题 // 自定义断言失败信息 });5.2 处理动态内容与等待策略UI自动化最大的挑战之一是处理动态加载的内容。Midscene.js提供了内置的等待机制但理解其原理能让你更好地使用。隐式等待aiAct,aiQuery等API内部在发出指令后会先等待一个很短的时间如delayBeforeAction让模型有足够时间“看清”最新截图并处理可能的动画。显式等待对于已知的网络请求或元素出现应结合Playwright的原生等待。// 最佳实践混合等待 await page.goto(https://example.com/dashboard); // 先等Playwright确认导航完成 await page.waitForURL(**/dashboard); // 再等一个关键数据加载的元素出现Playwright方式 await page.waitForSelector(.data-loaded, { state: visible }); // 最后让AI开始操作 await agent.aiAct(点击刷新数据按钮); // AI操作后等待某个结果出现 await agent.aiAssert(a chart with new data is displayed, { timeout: 10000 });5.3 使用YAML编写无代码工作流对于测试工程师或不想写代码的团队成员Midscene.js支持YAML格式来定义自动化流程这大大降低了使用门槛。# test-checkout.yaml name: 电商网站下单流程测试 steps: - act: 在顶部搜索框输入“无线耳机”并回车 - wait: 2s # 等待结果加载 - query: 获取第一个商品的名称 as: firstProductName # 将结果存储为变量 - click: 第一个商品图片 - waitFor: 页面URL包含“/product” - assert: 当前页面标题包含“{{firstProductName}}” - click: 蓝色的“加入购物车”按钮 - assert: 页面顶部出现提示“已加入购物车” - click: 购物车图标 - act: 在购物车页面点击“去结算”使用Midscene CLI即可运行npx midscene run test-checkout.yaml。YAML非常适合定义标准化的、线性的业务流程测试。6. 集成到现有测试框架与CI/CDMidscene.js不是要取代你现有的测试框架如Jest, Mocha, Playwright Test而是增强它们。你可以将其作为“视觉断言”或“智能操作”模块集成进去。6.1 与Playwright Test集成示例Playwright Test是目前非常流行的E2E测试框架。集成Midscene.js后你可以编写出既稳定用选择器处理核心导航又智能用视觉处理复杂验证和动态元素的测试用例。// tests/visual-search.spec.js const { test, expect } require(playwright/test); const { MidsceneAgent } require(midscene/sdk); require(dotenv).config(); test.describe(视觉搜索功能测试, () { let agent; test.beforeEach(async ({ page }) { // 在每个测试用例中创建并绑定Agent agent new MidsceneAgent({ page, model: glm-4v, modelConfig: { apiKey: process.env.GLM_API_KEY }, }); await page.goto(https://your-app.com); }); test(应该能通过视觉识别并操作模糊搜索按钮, async ({ page }) { // 使用Playwright原生操作进行可靠导航 await page.getByRole(link, { name: Search }).click(); // 情况1搜索框的图标是纯SVG无文本传统选择器很难定位 // 使用Midscene进行视觉定位和输入 const searchInput await agent.aiQuery(the search input field with a magnifying glass icon inside it); await agent.aiType(searchInput, visual testing); // 情况2搜索结果中的“高级筛选”按钮是自定义组件类名经常变 await agent.aiClick(the button labeled Advanced Filters or with a funnel icon); // 使用视觉断言验证筛选面板弹出 const isFilterPanelOpen await agent.aiBoolean(a panel or dropdown with options like Date, Type is visible); expect(isFilterPanelOpen).toBeTruthy(); // 混合断言既用视觉也用Playwright的文本断言 await agent.aiClick(the option for Latest); await expect(page.locator(.results-container)).toContainText(Sorted by latest); }); });6.2 配置CI/CD流水线以GitHub Actions为例在CI环境中运行视觉测试关键在于处理好无头模式、模型API密钥和缓存。# .github/workflows/visual-e2e.yml name: Visual E2E Tests on: [push, pull_request] jobs: visual-test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 with: node-version: 18 - name: Install dependencies run: | npm ci npx playwright install --with-deps chromium - name: Run Visual E2E Tests env: GLM_API_KEY: ${{ secrets.GLM_API_KEY }} # 在GitHub仓库Settings/Secrets中配置 run: npm run test:visual # 假设你的package.json中配置了该脚本 - name: Upload test artifacts if: always() # 无论测试成功与否都上传 uses: actions/upload-artifactv3 with: name: midscene-reports path: | test-results/ midscene-cache/ # Midscene可以缓存模型解析结果加速后续运行CI优化技巧使用缓存Midscene支持将模型的视觉解析结果如图片特征缓存到本地。在CI中缓存midscene-cache目录可以显著减少重复调用模型的次数和耗时。设置合理的超时和重试网络或模型服务可能不稳定在CI脚本中为aiAct或aiQuery设置更长的timeout和retry次数。选择性运行视觉测试相对较慢可以配置为只在合并到主分支或发布标签时运行全套在PR时只运行核心用例。7. 常见问题、排查技巧与最佳实践在实际项目中摸爬滚打我积累了一些踩坑经验和优化技巧这些在官方文档里不一定写得那么细。7.1 常见问题速查表问题现象可能原因排查与解决方案aiAct执行错误或找不到元素1. 指令描述模糊。2. 页面未加载完全。3. 模型当前“看到”的截图区域不对。1.优化指令使用更具体、包含上下文信息的描述。如将“点击按钮”改为“点击登录表单中蓝色的提交按钮”。2.增加等待在操作前用page.waitForTimeout()或等待特定元素。3.指定区域使用agent.aiAct(instruction, { clip: { x, y, width, height } })限定AI关注的屏幕区域。测试在CI中通过本地失败或反之1. 屏幕分辨率/缩放比例不同。2. 字体渲染差异。3. CI环境缺少某些依赖如字体。1.统一环境在Playwright配置中固定浏览器窗口大小viewport: { width: 1920, height: 1080 }。2.使用容器化在Docker容器中运行测试确保环境一致。3.依赖字体在CI镜像中安装测试所需的中文字体包如fonts-wqy-zenhei。视觉断言 (aiAssert) 随机失败1. 动画或过渡效果导致截图瞬间状态不对。2. 模型对细微颜色/像素差异敏感。1.稳定断言前状态在断言前加入足够等待或使用page.waitForFunction等待动画结束。2.使用容错断言aiAssert(the button is roughly red)比aiAssert(the button is #ff0000)更健壮。或者结合传统断言如expect(await page.screenshot()).toMatchSnapshot(stable-state.png)。执行速度慢1. 模型API调用网络延迟高。2. 每一步都重新截图和调用模型。1.选择低延迟模型或部署私有化模型。2.启用缓存创建Agent时设置cache: true。首次运行后相同的指令和截图会使用缓存结果极大提速。3.减少不必要的AI调用能用page.click(selector)稳定操作的地方就不用aiClick。处理弹窗或动态覆盖层AI可能被突然出现的弹窗干扰点击了错误元素。1.主动处理已知弹窗在关键流程开始前用脚本检查并关闭常见弹窗如通知、广告。2.使用try-catch和恢复在aiAct外包裹try-catch失败后尝试让AI点击弹窗的关闭按钮再重试原操作。7.2 最佳实践心得指令描述的“黄金法则”具体、唯一、带上下文。不要说“点击它”要说“点击用户列表表格第一行‘操作’列下的红色删除图标按钮”。可以像训练新人一样把AI当成一个视力极好但毫无背景知识的助手。混合定位策略构建健壮测试套件的关键是“混合”。对于登录框、导航栏等稳定且核心的元素坚持使用Playwright的getByRole、getByTestId需开发配合添加data-testid进行定位速度极快且稳定。对于动态生成、视觉化强、第三方组件等不稳定区域再交给Midscene.js处理。建立视觉基准线对于关键页面状态如首页加载完成、订单提交成功页定期保存一张“黄金截图”。在测试中可以用aiAssert来对比当前截图与基准截图在关键区域是否一致这是回归UI视觉错误的强大手段。成本与效率的平衡模型API调用有成本。在开发调试阶段可以使用响应快、成本低的模型如gemini-2.0-flash。在CI流水线中为了稳定性可以使用精度更高的模型如glm-4v或qwen3-vl并充分利用缓存来降低成本。报告与调试Midscene.js在运行后会生成详细的HTML报告里面记录了每一步的截图、AI的指令和识别结果。测试失败时这是第一手的调试资料。务必配置好测试框架在失败时自动保存并归档这些报告。将Midscene.js引入你的测试技术栈不是一次简单的工具替换而是一次测试思维模式的升级——从“基于代码结构的探测”转向“基于视觉意图的验证”。它可能无法解决所有问题但在应对现代Web应用日益复杂的UI和频繁的迭代时它无疑是提升测试韧性、解放测试人员生产力的利器。