Midscene.js:基于自然语言与视觉AI的跨平台UI自动化测试实践
1. 项目概述:当UI测试遇上自然语言
如果你做过UI自动化测试,大概率经历过这样的痛苦:花半天时间写一个登录测试脚本,结果前端改了个按钮的># 在你的项目目录下初始化并安装Midscene npm init -y npm install midscene
如果你需要测试Web端,强烈建议同时安装Playwright,因为Midscene的Web驱动可以与其无缝集成,复用其浏览器实例,功能更强大。
# 安装Playwright及其浏览器 npm install playwright npx playwright install对于移动端(Android/iOS)或桌面端测试,则需要额外准备相应的平台环境:
- Android:需要安装Android SDK并配置好ADB,确保设备或模拟器可连接。
- iOS:需要Xcode和
appium-WebDriverAgent,配置相对复杂。 - Desktop:需要确保被测应用已启动,并且操作系统允许辅助功能控制(如macOS需在“安全性与隐私”中授权)。
3.2 编写你的第一个自然语言测试用例
让我们从一个最简单的Web测试开始:打开Midscene官网,并断言页面标题包含“Midscene”。
首先,创建一个测试文件first-test.js。
const { launch, aiAct, aiAssert } = require('midscene'); (async () => { // 1. 启动一个浏览器会话,这里使用Playwright驱动 const driver = await launch({ platform: 'web', headless: false // 设为true则不显示浏览器窗口 }); try { // 2. 导航到目标网址 - 这里用的是传统API,因为导航不需要视觉识别 await driver.goto('https://midscene.com'); // 3. 使用自然语言进行断言:检查页面标题 // aiAssert会截图,让AI“看”页面,并根据你的描述判断断言是否成立 await aiAssert(driver, '页面标题中包含Midscene这个词'); // 4. 使用自然语言进行操作:点击导航栏的“文档”链接 await aiAct(driver, '点击顶部导航栏中的“文档”链接'); // 5. 再次断言,确认已跳转到文档页 await aiAssert(driver, '当前页面是文档页面'); console.log('测试通过!'); } catch (error) { console.error('测试失败:', error); } finally { // 6. 关闭驱动,释放资源 await driver.close(); } })();运行这个脚本:
node first-test.js你会看到浏览器自动打开,访问官网,然后AI会自动找到并点击“文档”链接。整个过程,你没有写任何选择器。
注意事项:第一次运行时,Midscene可能会提示你配置AI模型API密钥(如OpenAI, DeepSeek等)。你需要根据官方文档,在环境变量或配置文件中设置
MIDSCENE_API_KEY或对应模型的密钥。如果使用其自带的豆包Seed模型,则可能无需额外配置,但会有调用次数限制。
3.3 核心API详解
上面的例子引用了两个最常用的API,我们来详细拆解一下Midscene.js的核心API设计:
aiAct(driver, instruction, options?):执行动作。这是最常用的指令。instruction是你用自然语言描述的操作,如“在搜索框输入‘自动化测试’并回车”、“点击蓝色的提交按钮”、“向下滚动直到看到加载更多”。options可以指定超时时间、使用的模型、操作前延迟等。aiAssert(driver, instruction, options?):进行断言。用于验证某个状态。instruction是描述预期状态的句子,如“登录成功的提示信息应该显示”、“当前页面URL包含‘dashboard’”、“按钮处于禁用状态”。AI会分析截图,判断该描述是否成立。aiLocate(driver, instruction, options?):定位元素。当你不需要立即操作,只是想获取某个元素的位置信息时使用。它返回一个BoundingBox对象(包含x, y, width, height),你可以用这个坐标进行更精细的操作(比如用传统自动化库去点击)。launch(config):启动驱动。这是入口函数,根据config.platform(‘web’, ‘android’, ‘ios’, ‘desktop’)创建并返回对应的驱动实例。配置项决定了使用哪种浏览器、连接哪台设备、指向哪个桌面应用等。
这些API的设计哲学是“声明式”的。你关注做什么(What),而不是怎么做(How)。这极大地降低了编写测试用例的心智负担和技能门槛,产品经理甚至业务人员都有可能参与编写测试场景描述。
4. 进阶实战:复杂场景与跨平台用例设计
4.1 处理复杂交互与条件逻辑
真实的测试场景很少是线性的。Midscene.js提供了组合API和编程逻辑来处理复杂情况。
场景:测试一个购物车功能。添加商品后,如果出现“库存不足”的弹窗,则点击“知道了”;如果添加成功,则断言购物车角标数字增加。
const { launch, aiAct, aiAssert, aiLocate } = require('midscene'); (async () => { const driver = await launch({ platform: 'web', headless: false }); await driver.goto('https://your-ecom-site.com/product/123'); try { // 1. 点击“加入购物车”按钮 await aiAct(driver, '点击“加入购物车”按钮'); // 2. 等待一小段时间,让页面反应(如弹窗出现) await driver.waitForTimeout(2000); // 3. 尝试定位“库存不足”的弹窗。这里使用aiLocate并设置短超时和静默模式。 const outOfStockPopup = await aiLocate(driver, '找到显示“库存不足”的提示弹窗', { timeout: 3000, silent: true // 静默模式,定位失败不抛异常,返回null }).catch(() => null); // 进一步捕获可能的错误,确保流程继续 if (outOfStockPopup) { // 情况A:出现了库存不足弹窗 console.log('检测到库存不足弹窗,执行关闭操作。'); await aiAct(driver, '点击弹窗上的“知道了”或“关闭”按钮'); await aiAssert(driver, '弹窗已经消失'); } else { // 情况B:没有弹窗,假设添加成功 console.log('未检测到异常弹窗,假设添加成功。'); // 断言购物车图标上的数量角标从0变成了1 // 注意:这里需要更精确的指令。假设初始数量是0。 await aiAssert(driver, '页面右上角购物车图标旁边的数字是1'); } } catch (error) { console.error('测试流程出错:', error); } finally { await driver.close(); } })();这个例子展示了如何将传统的编程逻辑(if-else)与Midscene的AI指令结合。aiLocate的silent: true选项在这里是关键,它让定位尝试变成一种“探测”行为,而非必定成功的断言。
4.2 实现真正的跨平台测试用例
Midscene.js的威力在于用同一套业务逻辑测试不同平台。假设我们有一个“记事本”应用,有Web版、桌面版和移动版,我们想测试其核心的“创建-编辑-保存”流程。
我们可以抽象出一个通用的测试函数:
// test-notes-core.js async function testCreateEditSaveNote(driver, platform) { console.log(`开始在${platform}平台测试...`); // 1. 创建新笔记 await aiAct(driver, '找到并点击“新建笔记”或“+”按钮'); await aiAssert(driver, '出现了空白的编辑区域或新笔记标题'); // 2. 输入内容 await aiAct(driver, '将焦点定位到笔记正文编辑区域'); // 这里我们使用driver.type进行精确输入,因为aiAct输入长文本可能不稳定 // 先让AI定位到输入框,然后直接用底层API输入 const editorBox = await aiLocate(driver, '找到主要的文本编辑区域'); await driver.click(editorBox.center.x, editorBox.center.y); // 点击定位到的中心点 await driver.type('这是一条由Midscene.js自动创建的测试笔记。时间:' + new Date().toLocaleString()); // 3. 保存笔记 await aiAct(driver, '点击“保存”或“完成”按钮'); await aiAssert(driver, '界面显示保存成功的提示,或者编辑区域失去焦点'); // 4. 验证笔记列表 await aiAct(driver, '返回或导航到笔记列表页面'); // 断言列表中存在包含我们输入特征文字的笔记条目 await aiAssert(driver, '笔记列表中有一条包含“自动创建”字样的笔记'); console.log(`${platform}平台核心流程测试通过!`); }然后,为不同平台编写启动脚本:
// test-web.js - 测试Web版 const { launch } = require('midscene'); const { testCreateEditSaveNote } = require('./test-notes-core'); (async () => { const driver = await launch({ platform: 'web', browserType: 'chromium', // 使用chromium浏览器 headless: false }); await driver.goto('https://notes-app-web.com'); await testCreateEditSaveNote(driver, 'Web'); await driver.close(); })();// test-desktop-mac.js - 测试Mac桌面版 const { launch } = require('midscene'); const { testCreateEditSaveNote } = require('./test-notes-core'); (async () => { // 启动驱动,连接到正在运行的“Notes”桌面应用 const driver = await launch({ platform: 'desktop', desktopApp: 'Notes', // 应用名称,或使用bundleId os: 'macos' }); // 假设应用已经打开在前台 await testCreateEditSaveNote(driver, 'macOS Desktop'); await driver.close(); // 关闭驱动连接,不会关闭应用本身 })();// test-mobile-android.js - 测试Android移动版 const { launch } = require('midscene'); const { testCreateEditSaveNote } = require('./test-notes-core'); (async () => { // 通过ADB连接已开启USB调试的Android设备或模拟器 const driver = await launch({ platform: 'android', deviceName: 'emulator-5554', // 你的设备序列号 appPackage: 'com.example.notesapp', // 被测App包名 appActivity: '.MainActivity' // 启动Activity }); // 驱动会自动启动App await testCreateEditSaveNote(driver, 'Android'); await driver.close(); // 关闭会话 })();可以看到,核心业务逻辑testCreateEditSaveNote是完全一致的,使用的都是自然语言指令。变化的只是启动驱动时的平台配置。这就是“一套用例,多端运行”的雏形。
实操心得:为了实现最佳跨平台兼容性,给AI的指令应尽量语义化、与UI文案相关,而不是描述样式。例如,用“点击‘保存’按钮”而不是“点击蓝色的圆形按钮”。因为不同平台的UI风格差异巨大,但功能文案(如“保存”、“确定”、“搜索”)通常是相同或相似的。这能极大提高指令在不同平台上的可复用性。
5. 集成与CI/CD:让AI测试跑起来
5.1 与现有测试框架集成
Midscene.js本身不绑定任何测试运行器,你可以轻松地将其集成到Jest、Mocha、Playwright Test、Cypress等主流测试框架中。
以下是一个使用Jest测试框架的示例:
// notes-app.spec.js const { launch } = require('midscene'); describe('跨平台记事本应用核心功能', () => { let driver; // 每个测试用例开始前启动驱动 beforeEach(async () => { driver = await launch({ platform: 'web', headless: true // CI环境下通常无头运行 }); await driver.goto('https://notes-app-test.com'); }); // 每个测试用例结束后关闭驱动 afterEach(async () => { if (driver) { await driver.close(); } }); test('应能成功创建并保存一条新笔记', async () => { const { aiAct, aiAssert } = require('midscene'); await aiAct(driver, '点击“新建笔记”按钮'); await aiAssert(driver, '标题输入框获得焦点'); await aiAct(driver, '在正文区域输入“Jest集成测试内容”'); await aiAct(driver, '点击“保存”按钮'); await aiAssert(driver, '页面显示“保存成功”的提示'); }); test('应能搜索到已存在的笔记', async () => { const { aiAct, aiAssert } = require('midscene'); await aiAct(driver, '点击顶部的搜索框'); await aiAct(driver, '输入“Jest集成测试”并按下回车'); await aiAssert(driver, '搜索结果列表中存在包含该文字的笔记'); }); });运行npm test或jest命令,这些基于自然语言的测试用例就会像普通单元测试一样执行并生成报告。
5.2 在CI/CD流水线中运行
在GitHub Actions、GitLab CI、Jenkins等CI/CD环境中运行Midscene测试,需要注意以下几点:
环境准备:CI机器需要安装Node.js、浏览器(或无头浏览器)、以及移动端测试所需的SDK和模拟器。对于桌面端测试,CI环境通常难以支持,可以考虑将其放在专门的测试机集群中执行。
模型API密钥管理:将如
OPENAI_API_KEY、DEEPSEEK_API_KEY等敏感信息存储在CI/CD系统的Secrets中,并通过环境变量注入。截图与报告归档:测试失败时,截图和详细的AI推理日志是排查问题的关键。Midscene可以与测试框架的报告功能结合,将每次操作的截图附加到测试报告中。你也可以配置在失败时自动上传截图到云存储或归档到构建产物中。
GitHub Actions 示例配置 (.github/workflows/midscene-test.yml):
name: Midscene UI Automation Tests on: [push, pull_request] jobs: test-web: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps chromium - name: Run Midscene Tests env: # 从GitHub Secrets中读取API密钥 MIDSCENE_API_KEY: ${{ secrets.MIDSCENE_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: npm test -- --testPathPattern=web - name: Upload test artifacts on failure if: failure() uses: actions/upload-artifact@v4 with: name: midscene-screenshots path: | test-results/ **/*.png5.3 性能优化与稳定性提升策略
在CI中大规模运行视觉AI测试,成本和稳定性是两大挑战。
策略一:分层测试,精准使用AI。不要所有测试都用AI。将测试用例分层:
- 单元测试/接口测试:覆盖核心逻辑和数据流。
- 传统E2E测试:使用Playwright/Cypress处理稳定的、选择器明确的流程。
- AI视觉测试:专门用于测试视觉交互、Canvas、动态内容、跨平台UI一致性等传统方法难以覆盖的场景。这样能大幅减少对AI的调用次数。
策略二:模型分级与缓存。
- 分级:为不同重要级别的测试用例配置不同成本的模型。核心冒烟用例用高精度商用模型,大量回归用例用开源模型。
- 缓存:Midscene支持对视觉模型的定位结果进行缓存。对于UI长期不变的稳定页面,首次成功定位后,可以将“指令-截图-坐标”的结果缓存起来。下次遇到相同的指令和高度相似的截图时,直接使用缓存结果,跳过模型调用,速度极快且零成本。
策略三:设置合理的超时与重试。网络波动、AI服务暂时不可用、页面加载慢都可能导致单次失败。在测试框架或Midscene配置中设置合理的超时和自动重试机制(如
retries: 2),可以显著提升流水线的稳定性,避免因偶发问题导致的构建失败。
6. 避坑指南与常见问题排查
在实际项目中踩过不少坑后,我总结了一份问题排查清单,希望能帮你节省时间。
6.1 定位失败:AI“找不到”元素
这是最常见的问题。指令发出后,AI返回“无法定位元素”。
检查指令清晰度:
- 坏指令:“点击那个按钮”(“那个”是模糊指代)。
- 好指令:“点击登录表单中文字为‘提交’的蓝色按钮”或“点击‘登录’按钮”。
- 技巧:在指令中包含元素的文本内容和其上下文关系。例如,“在用户名输入框下方找到密码输入框”。
检查屏幕状态:
- 在执行指令前,页面是否已经完全加载?动态内容(如弹窗、下拉列表)是否已经出现?必要时使用
driver.waitForTimeout()或基于传统选择器的等待条件。 - 实操技巧:在关键步骤前后手动调用
driver.screenshot()并保存图片,查看AI“看到”的到底是什么画面。很多时候问题出在页面状态未就绪。
- 在执行指令前,页面是否已经完全加载?动态内容(如弹窗、下拉列表)是否已经出现?必要时使用
调整模型或参数:
- 尝试更换更强大的视觉模型(如从Qwen-VL切换到豆包Seed)。
- 调整
confidenceThreshold(置信度阈值)。默认值可能过高,适当调低(如从0.9调到0.7)可以让AI在不太确定时也尝试操作,但可能增加误操作风险。 - 使用
options.region参数限定搜索区域,减少干扰。例如,你知道按钮只在屏幕下半部分,就可以指定一个区域框。
6.2 操作错误:AI“点错了”或“输错了”
AI定位到了元素,但执行的操作不符合预期。
- 输入文本错误:
aiAct用于输入长文本时,可能会漏字或错字。对于关键的文本输入,更可靠的做法是:先用aiLocate精确定位输入框,然后使用驱动的driver.type(text)或driver.fill(selector, text)方法进行输入。 - 点击位置偏差:AI返回的是一个边界框,默认会点击该框的中心。如果元素是可点击区域不规则(如图标只有一部分可点),可能导致点击无效。可以尝试:
- 使用
options.clickPoint参数指定相对偏移量,如{ x: 5, y: 5 }点击左上角。 - 如果元素是固定的,首次成功后,将其坐标缓存下来,后续直接使用缓存坐标点击,又快又准。
- 使用
6.3 跨平台指令的适配问题
同一句指令在Web上工作,在移动端却失败了。
- 平台差异:移动端和Web端的UI布局、文案、交互方式可能不同。例如,Web端叫“提交”,移动端可能是一个“箭头图标”或“完成”。
- 解决方案:
- 使用更通用的指令:“点击进行登录操作的按钮”。
- 为不同平台编写不同的指令,但封装在同一个业务函数里。这需要你对各端UI有一定了解。
async function clickLoginButton(driver, platform) { if (platform.includes('mobile')) { await aiAct(driver, '点击屏幕上的“登录”或“Sign In”按钮,或者是一个向右的箭头图标'); } else { await aiAct(driver, '点击表单下方的“登录”按钮'); } }- 利用Midscene的“Skills”或自定义Agent:将针对特定平台的操作逻辑封装成更高级的指令,如
loginWithCredentials(username, password),在这个Skill内部处理平台差异。
6.4 成本与速度控制
AI调用是按Token或次数计费的,速度也比本地操作慢。
- 启用缓存:如前所述,这是降本增效的最有效手段。确保在测试配置中开启缓存功能。
- 批量操作:对于一连串操作(如填写表单),可以考虑用一个复杂的指令完成,而不是分多个
aiAct调用。例如:“在注册表单中,依次在‘用户名’框输入‘testuser’,在‘邮箱’框输入‘test@example.com’,在‘密码’框输入‘Password123’,然后点击‘注册’按钮”。但这需要模型有较强的规划能力,成功率可能低于分步操作,需要权衡。 - 监控与预算:定期查看AI服务商的控制台,监控Token消耗,设置预算警报。在非关键路径或大量回归测试中,优先使用性价比高的开源模型。
视觉驱动的UI自动化测试是一个正在快速发展的领域,Midscene.js提供了一个非常前沿且实用的实现。它并非要完全取代传统的基于选择器的测试,而是作为一种强大的补充,专门攻克那些传统方法难以应对的“硬骨头”。将两者结合,构建一个分层、智能、可维护的自动化测试体系,或许是未来高质量UI测试的发展方向。
