Midscene.js实战:AI视觉驱动自动化测试,告别脆弱定位器
1. 项目概述:为什么我们需要AI驱动的视觉自动化测试?
在软件开发的日常里,UI自动化测试一直是个让人又爱又恨的活儿。爱的是,它能解放我们的双手,让回归测试变得高效;恨的是,它太“脆弱”了。一个按钮的># 初始化项目(如果尚未初始化) npm init -y # 安装 Midscene.js 核心库 npm install midscene # 安装 Playwright(Midscene 可以与 Playwright 集成,利用其强大的浏览器控制能力) npm install playwright # 安装测试运行器,这里以 Vitest 为例,你也可以用 Jest npm install -D vitest
注意:Midscene本身不绑定特定的测试框架,它可以独立运行,也可以集成到Vitest、Jest、Playwright Test等框架中。选择Vitest是因为它速度快、对ESM支持好,且与Vite生态结合紧密,适合现代前端项目。
3.2 模型配置:选择与接入你的“眼睛”
Midscene的强大之处在于其背后的多模态模型。你需要一个具备强大视觉理解和UI元素定位能力的模型。官方支持多种模型,主要分为两类:
- 云端API模型:如Google的
gemini-2.0-flash-exp、gemini-1.5-pro,阿里的qwen2.5-vl-72b-instruct等。这些模型能力强、开箱即用,但需要API Key,可能产生费用,且测试数据会发送到第三方。 - 开源可自托管模型:如
Qwen2-VL-7B-Instruct、UI-TARS。你可以部署在自己的GPU服务器上,数据完全私有,但需要一定的运维成本。
对于大多数团队起步,我推荐先从云端API模型开始,验证效果和流程。这里以使用Google Gemini API为例。
- 获取API Key:前往 Google AI Studio 创建项目并获取API Key。
- 配置Midscene使用Gemini:在你的测试代码或配置文件中,需要初始化Midscene并指定模型。
// test/midscene-setup.js 或你的测试准备文件 import { Midscene } from 'midscene'; // 初始化一个 Midscene 代理(agent) const agent = new Midscene({ model: { // 指定使用 Gemini 模型 name: 'gemini-2.0-flash-exp', // 也可以用 gemini-1.5-pro,精度更高但稍慢 apiKey: process.env.GEMINI_API_KEY, // 强烈建议从环境变量读取,不要硬编码 // 其他可选配置,如自定义 endpoint(如果你使用代理) // endpoint: 'https://your-proxy.com/v1beta/models/gemini-2.0-flash-exp:generateContent' }, // 日志级别,调试时可以设为 'debug' 或 'info' logLevel: 'info', }); export default agent;实操心得:API Key务必通过环境变量(如
GEMINI_API_KEY)管理。你可以在项目根目录创建.env文件(记得加入.gitignore),内容为GEMINI_API_KEY=your_key_here,然后在代码中通过process.env读取。这是安全实践的基本要求。
3.3 浏览器驱动集成:Playwright 与 Puppeteer
Midscene需要控制浏览器来截图和模拟操作。它支持与Playwright和Puppeteer无缝集成。我强烈推荐使用Playwright,因为它支持多浏览器(Chromium, Firefox, WebKit),且自动下载浏览器驱动,省心省力。
下面是如何将Midscene与Playwright结合起来的示例:
// test/visual-test.spec.js import { test, expect } from '@playwright/test'; import { Midscene } from 'midscene'; // 或者导入我们上面封装好的 agent import agent from './midscene-setup.js'; test('使用Midscene进行视觉驱动的登录测试', async ({ page }) => { // 首先,用Playwright导航到页面 await page.goto('https://your-app.com/login'); // 关键步骤:将 Playwright 的 page 对象桥接给 Midscene // 这样 Midscene 就能通过这个 page 来截图和操作了 await agent.connect(page); // 现在,可以使用自然语言进行测试了! // 示例1:让AI自动规划整个登录流程 await agent.aiAct('在用户名输入框里输入“testuser”,在密码输入框里输入“password123”,然后点击登录按钮'); // 示例2:更精细的工作流控制 // 先让AI找到用户名输入框并输入 await agent.aiTap('用户名输入框'); await page.keyboard.type('testuser'); // 这里也可以用Playwright精确输入 // 再找到密码框并输入 await agent.aiTap('密码输入框'); await page.keyboard.type('password123'); // 最后点击登录 await agent.aiTap('登录按钮'); // 使用AI进行视觉断言:检查登录后是否跳转到了仪表盘页面 const isDashboard = await agent.aiBoolean('当前页面是否是用户仪表盘?'); expect(isDashboard).toBeTruthy(); // 或者,结合Playwright的传统断言进行混合验证 await expect(page).toHaveURL(/\/dashboard/); });注意事项:
aiAct是一个“黑盒”式自动规划,适合快速编写脚本或探索。但对于稳定的核心流程,更推荐使用aiTap、aiQuery等原子操作组合成的“工作流风格”,这样每一步都可控、可调试,稳定性更高。aiAct内部可能会因为模型理解差异做出意料之外的操作。
4. 编写你的第一个AI自动化测试用例
环境配好了,我们来写一个完整的、有代表性的测试用例。假设我们要测试一个TODO List应用,覆盖创建项目、添加任务、标记完成等核心功能。
4.1 用例设计:混合自然语言与精确控制
我们不会完全依赖AI规划,而是采用“工作流风格”,将关键步骤拆解,在需要视觉定位的地方使用Midscene,在需要精确数据操作的地方使用Playwright原生API。
// test/todo-visual.spec.js import { test, expect } from '@playwright/test'; import agent from './midscene-setup.js'; test.describe('TODO List 视觉自动化测试', () => { let page; test.beforeEach(async ({ browser }) => { page = await browser.newPage(); await agent.connect(page); await page.goto('https://demo-todo-app.com'); // 可选:先让AI确认页面加载正确 const isLoaded = await agent.aiBoolean('页面中央是否显示了“我的待办事项”标题?'); expect(isLoaded).toBeTruthy(); }); test.afterEach(async () => { await page.close(); }); test('应能创建新项目并添加任务', async () => { // 1. 创建新项目:点击“新建项目”按钮(这是一个视觉操作) await agent.aiTap('新建项目按钮'); // 此时可能弹出输入框,用Playwright精确输入项目名 await page.locator('input[placeholder="输入项目名称"]').fill('Midscene测试项目'); await agent.aiTap('确定或保存按钮'); // 用AI点击模态框的确定按钮 // 2. 验证新项目创建成功(视觉断言) const projectExists = await agent.aiBoolean('在侧边栏或项目列表中,能看到名为“Midscene测试项目”的项目吗?'); expect(projectExists).toBeTruthy(); // 3. 进入该项目 await agent.aiTap('名为“Midscene测试项目”的项目'); // 4. 添加新任务 await agent.aiTap('添加新任务的输入框'); await page.keyboard.type('学习并使用Midscene.js'); await agent.aiTap('添加按钮或按回车键的图标'); // 5. 验证任务添加成功(混合断言) // 视觉上确认任务列表中有新任务 const taskVisible = await agent.aiBoolean('任务列表中是否出现了文本“学习并使用Midscene.js”?'); expect(taskVisible).toBeTruthy(); // 也可以用Playwright检查DOM中是否存在该文本 await expect(page.getByText('学习并使用Midscene.js')).toBeVisible(); // 6. 标记任务为完成 await agent.aiTap('“学习并使用Midscene.js”任务前面的复选框或完成图标'); // 7. 验证任务状态变为完成(视觉验证,如文本变灰、有删除线) const isCompleted = await agent.aiBoolean('“学习并使用Midscene.js”这个任务看起来是否被划掉或者变成了灰色?'); expect(isCompleted).toBeTruthy(); }); test('应能处理Canvas绘制的图表组件', async () => { // 假设应用有一个用Canvas画的统计图表 await page.goto('https://demo-todo-app.com/analytics'); // 传统选择器无法定位Canvas内的元素,但Midscene可以 // 让AI点击图表中“已完成任务”的图例 await agent.aiTap('图表中代表“已完成任务”的图例方块'); // 或者让AI断言图表标题 const chartTitleCorrect = await agent.aiBoolean('图表的主标题是否是“任务完成趋势”?'); expect(chartTitleCorrect).toBeTruthy(); }); });4.2 参数调优与稳定性提升技巧
直接使用默认配置,你可能会遇到AI“看不懂”或“点不准”的情况。这就需要一些调优。
- 指令描述要具体、唯一:避免使用“那个按钮”、“这里”等模糊指代。尽量结合上下文,如“页面顶部导航栏右侧的蓝色‘设置’图标按钮”。
- 利用
aiQuery获取上下文信息:在复杂操作前,可以先让AI“侦察”一下页面状态。// 获取页面上所有按钮的文本,帮助决策 const buttonTexts = await agent.aiQuery('array of text, all the button texts on current page'); console.log(buttonTexts); // 输出: ['登录', '注册', '忘记密码', ...] if (buttonTexts.includes('下一步')) { await agent.aiTap('下一步按钮'); } - 配置模型参数:在初始化Midscene时,可以调整模型的行为。
const agent = new Midscene({ model: { name: 'gemini-2.0-flash-exp', apiKey: process.env.GEMINI_API_KEY, // 增加温度值,让模型更有“创造力”,但可能降低稳定性。默认0.1比较稳定。 temperature: 0.1, // 最大输出token数,对于复杂指令可以调大 maxTokens: 1024, }, // 动作执行前的等待时间(毫秒),给页面一些反应时间 actionDelay: 500, // 截图模式:'viewport' (默认,当前视口) 或 'fullPage' (整个页面) screenshotMode: 'viewport', }); - 启用缓存:Midscene支持缓存AI的规划(Planning)和定位(Grounding)结果。这意味着对于完全相同的页面截图和指令,第二次运行时会直接使用缓存的结果,极大提升速度并保证结果一致性。在CI/CD环境中尤其有用。
const agent = new Midscene({ // ... 其他配置 cache: { enabled: true, // 开启缓存 dir: '.midscene-cache', // 缓存目录 }, });
5. 集成到CI/CD与测试报告生成
单个测试用例跑通只是第一步,我们的目标是将其融入团队的持续集成流程,让AI自动化测试成为每次代码提交的守门员。
5.1 在GitHub Actions中运行Midscene测试
以下是一个基本的GitHub Actions工作流配置文件示例:
# .github/workflows/visual-test.yml name: Visual AI Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: visual-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps chromium - name: Run Visual AI Tests env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} # 在仓库Settings/Secrets中配置 run: npm run test:visual # 假设你的package.json中定义了此脚本 - name: Upload Test Reports if: always() # 无论测试成功失败都上传报告 uses: actions/upload-artifact@v4 with: name: midscene-reports path: | test-results/ .midscene-cache/ # 也可以上传缓存,加速后续运行在你的package.json中,需要定义运行测试的脚本,并配置测试报告输出:
{ "scripts": { "test:visual": "vitest run test/ --reporter=verbose --config vitest.config.visual.js" } }5.2 生成可视化测试报告
Midscene的一个巨大优势是它自带可视化报告。每次运行aiAct或aiTap等操作,它都会记录当时的屏幕截图、AI的“思考过程”(规划)和操作结果。测试结束后,会生成一个HTML报告。
通常,Midscene会自动在运行目录下生成报告。你可以通过配置指定报告输出路径:
// 在初始化agent时配置 const agent = new Midscene({ // ... 其他配置 report: { enabled: true, dir: 'test-results/midscene-reports', // 报告输出目录 // 可以配置只记录失败用例的报告以节省空间 // recordFailedOnly: false, }, });当测试在CI中失败时,下载并打开这个HTML报告,你能像看录像一样回放AI操作的每一步,看到它当时“眼”中的屏幕是什么样子,以及它为什么做出了那样的决策。这比传统的“元素未找到”错误信息直观了无数倍,极大提升了调试效率。
避坑技巧:在CI环境中,由于可能没有图形界面,需要确保Playwright以
headless模式运行(默认就是)。同时,注意CI服务器的屏幕分辨率可能与本地不同,有时会影响AI的定位。可以在启动浏览器时固定一个常见的视口大小,如{ width: 1920, height: 1080 },来增加一致性。
6. 常见问题排查与实战经验录
在实际项目中摸爬滚打几周后,我积累了一些典型问题的解决方案和心得。
6.1 问题速查表
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
aiTap点击位置偏移 | 1. 页面缩放或CSS transform影响。 2. 模型定位精度问题。 3. 动态内容加载未完成。 | 1. 检查页面meta viewport和CSS,确保无缩放。测试时固定浏览器缩放为100%。2. 尝试更具体的指令描述(如“蓝色矩形形状的提交按钮”)。 3. 在操作前增加等待( page.waitForLoadState(‘networkidle’))或使用aiBoolean先确认元素可见。 |
aiAct执行了错误操作 | 1. 自然语言指令有歧义。 2. 模型对复杂任务规划错误。 | 1. 将复杂aiAct拆解为多个原子操作(aiTap,aiQuery等)。2. 在报告中查看AI的规划步骤,理解其逻辑,优化指令。例如,将“处理这个列表”改为“点击列表第一项的删除图标”。 |
| 测试运行速度慢 | 1. 每次调用都请求AI API,网络延迟高。 2. 截图分辨率太高。 3. 未启用缓存。 | 1. 考虑使用更快的模型(如gemini-2.0-flash-exp比gemini-1.5-pro快)。2. 评估是否可降低截图质量(Midscene配置中可能提供选项)。 3.务必开启缓存。首次运行后,重复运行的耗时将大幅下降。 |
| CI环境中失败,本地成功 | 1. CI环境屏幕分辨率/字体不同。 2. 网络或资源加载慢,AI操作时页面未就绪。 3. API Key未正确设置或额度不足。 | 1. 在CI配置中固定浏览器窗口大小。 2. 增加关键步骤后的显式等待( page.waitForTimeout)或基于状态的等待(page.waitForSelector)。3. 检查GitHub Secrets配置,并监控API使用量。 |
| 无法识别Canvas内元素 | 1. Canvas内容动态变化极快。 2. 模型对特定图形识别率低。 | 1. 在操作Canvas前,尝试用page.waitForFunction等待其渲染完成。2. 提供更详细的指令,如“点击那个红色的圆形进度条的中心”。如果Canvas内容有固定模式,可以训练专门的UI检测模型(如UI-TARS),但成本较高。 |
6.2 核心经验与进阶建议
- 始于冒烟,而非重构:不要一上来就用Midscene重写所有旧用例。挑选2-3个维护成本最高或传统方法无法测试(如Canvas)的场景作为试点。用实际效果证明其价值。
- 混合断言是王道:不要完全依赖
aiBoolean做最终断言。将其与Playwright的传统断言(expect)结合。例如,用AI找到并点击按钮,然后用Playwright断言URL变化或网络请求。这样既利用了AI的灵活性,又保证了断言的精确性。 - 管理好测试数据与状态:AI测试更容易受页面初始状态影响。确保每个测试用例都是独立的,有明确的
beforeEach和afterEach来设置和清理数据(如通过API初始化一个测试用户,测试后删除)。 - 成本意识:尤其是使用云端商业模型时,每个
aiAct、aiTap调用都计费。优化测试用例,减少不必要的AI调用。多用缓存。对于稳定不变的页面部分,考虑用传统定位器。 - 拥抱“模糊正确”:视觉自动化测试的断言有时是“模糊”的,比如“看起来像是一个错误提示弹窗”。这需要调整心态,接受这种基于视觉语义的验证,它恰恰能发现那些代码正确但UI渲染出错的问题。
- 团队协作与脚本可读性:用自然语言写的测试步骤本身就像文档。鼓励团队成员用业务语言来评审这些AI测试用例,这能促进测试、开发和产品对需求理解的一致性。
从我个人的实战体验来看,Midscene.js为代表的新一代AI视觉测试工具,正在将UI自动化测试从“前端结构的奴隶”转变为“真实用户行为的模拟者”。它确实会引入新的复杂度(模型选择、指令工程、成本),但对于应对现代Web应用日益复杂的UI和频繁的迭代,它所提供的维护性优势和覆盖能力扩展是革命性的。搭建这样一套体系并非一蹴而就,但从一个小而具体的用例开始,逐步迭代,你很快就能感受到它带来的效率红利和质量信心。
