1. 项目概述:为什么我们需要 Midscene.js?
最近在跟几个测试团队的朋友聊天,发现大家普遍有个痛点:传统的自动化测试脚本,维护成本太高了。页面元素一变,脚本就得跟着改,一个回归测试跑下来,光是处理各种定位失败、等待超时的问题,就能耗掉大半天。更别提那些复杂的业务流,写脚本的逻辑比开发业务代码还绕。
这时候,AI 驱动的自动化测试工具开始进入视野。它们号称能“看懂”界面,自动适应变化,听起来很美,但上手一试,问题也不少。要么是云端服务,数据安全心里没底;要么是集成复杂,本地环境搞不定;再或者就是生成的脚本可读性差,出了问题都不知道从哪查起。
直到我深度折腾了Midscene.js,才感觉找到了一个比较理想的平衡点。它不是一个庞大的平台,而是一个开源的 Node.js 库,核心思路很清晰:利用多模态大模型(比如 GPT-4V)的视觉理解能力,将自然语言指令直接转化为对浏览器页面的自动化操作。你可以告诉它“点击那个蓝色的登录按钮”,或者“在搜索框里输入‘手机’然后按回车”,它就能尝试去理解和执行,而不是依赖脆弱的 XPath 或 CSS Selector。
这带来的改变是根本性的。测试用例可以用更接近人类思维的自然语言来描述,脚本的编写和维护门槛大大降低。对于需要快速验证的探索性测试、或者页面结构频繁变动的早期项目,Midscene.js 提供了一种全新的、更灵活的自动化可能性。它特别适合前端开发者自测、测试工程师构建智能测试辅助工具,或者任何想尝试将 AI 能力低成本、高可控地集成到本地工作流中的团队。
2. 核心设计思路:Midscene.js 是如何工作的?
要玩转 Midscene.js,不能只停留在调用 API 的层面,得先理解它底层的运作机制。这样当遇到执行偏差或者性能问题时,你才知道该从哪个环节去排查和优化。
2.1 架构拆解:从指令到动作的旅程
Midscene.js 的核心流程可以概括为“观察-思考-执行”的循环,这非常像一个人工测试员在操作电脑。
观察(Screenshot & Context):当你下达一个指令(如
click “提交” button)后,Midscene.js 首先会命令 Puppeteer 或 Playwright 对当前浏览器页面进行截图。但这还不够,它还会收集当前页面的文本上下文。这个上下文通常是通过提取页面的可访问性树(Accessibility Tree)或者主要文本节点来获得的,包含了按钮文字、链接文本、标签等所有可见文本信息。截图提供了视觉布局,文本上下文提供了语义信息,两者结合,构成了 AI 理解页面的“眼睛”。思考(LLM Reasoning):截图和文本上下文会被一起打包,发送给你配置的多模态大模型(例如 OpenAI 的 GPT-4 with vision)。你的自然语言指令也会一并发送。模型的任务是进行“视觉问答”(VQA):基于看到的画面和文字,理解指令的意图,并精准定位到需要操作的元素。它的输出不是一个简单的坐标,而是一个结构化的操作计划,通常包括:
- 操作类型:
click,type,hover,select等。 - 元素定位描述:一个基于画面和文本的自然语言描述,例如“那个位于‘用户名’输入框下方、带有‘登录’文字的蓝色矩形按钮”。
- 操作参数:对于
type操作,就是需要输入的文本。
注意:这里的一个关键点是,模型并不直接输出浏览器可执行的 DOM 选择器。它输出的是人类可读的描述。这意味着 Midscene.js 本身不依赖于任何具体的页面实现技术(React, Vue 等),只要模型能“看”懂,它就能操作。
- 操作类型:
执行(Action Execution):Midscene.js 拿到模型返回的操作计划后,需要将其“翻译”成浏览器能执行的动作。这一步通常通过计算机视觉(CV)或文本匹配来实现。例如,对于“点击‘登录’按钮”,它可能会在截图或文本上下文中寻找与“登录”文本最匹配的元素,然后计算出该元素在屏幕上的坐标,最后通过 Puppeteer/Playwright 的 API 模拟鼠标点击该坐标。
2.2 方案选型考量:为什么是 Midscene.js 而不是其他?
市面上 AI 测试方案不少,为什么我会花时间研究这个库?主要是基于以下几点权衡:
- 可控性 vs. 易用性:许多成熟的 AI 测试平台(如某些云测平台提供的 AI 功能)开箱即用,但成了黑盒。测试数据、业务逻辑都要上传到云端,对于金融、医疗等对数据敏感的行业,这是不可接受的。Midscene.js 运行在本地,你可以自己掌控从截图到 API 调用的整个链条,数据不出私域,心里踏实。
- 成本与灵活性:作为开源库,Midscene.js 本身免费。成本主要来自调用大模型 API(如 GPT-4V)的费用。这看似是缺点,实则是优点。你可以自由选择不同模型供应商(OpenAI, Anthropic, 国内合规的模型服务商等),根据任务复杂度在效果和成本间做权衡(例如,简单任务用便宜的模型,复杂任务用强模型)。这种按需付费、自主选择的模式,比订阅一个固定功能的 SaaS 平台通常更灵活、长期看可能更经济。
- 集成与扩展:它是一个 Node.js 库,可以无缝集成到现有的 CI/CD 流水线(Jenkins, GitLab CI, GitHub Actions)中,也能和你用 Mocha、Jest、Playwright Test 写的传统用例共存。你可以用它处理那些变化频繁、难以定位的模块,而稳定的核心流程仍用传统脚本,形成“AI+传统”的混合自动化策略,性价比最高。
- 技术栈亲和度:对于前端和 Node.js 技术栈的团队来说,Midscene.js 基于 Puppeteer/Playwright,学习曲线平缓,调试工具链(Chrome DevTools)也是大家熟悉的,出了问题更容易排查。
3. 环境搭建与核心配置实战
理论讲完,我们动手搭一个真正能跑起来的、高效的环境。这里我会分享一个我优化过的配置方案,兼顾了执行速度、稳定性和成本。
3.1 基础环境准备
首先,确保你的系统已经安装 Node.js (建议 v18 或以上) 和 npm/yarn。然后创建一个新的项目目录。
mkdir midscene-test-project && cd midscene-test-project npm init -y接下来,安装核心依赖。Midscene.js 默认使用 Playwright 作为浏览器驱动,因为它对现代 Web 技术的支持更好,且自带浏览器内核,无需单独安装 Chrome。
npm install midscene playwright # Playwright 需要安装其自带的浏览器 npx playwright install chromium3.2 大模型 API 配置(关键步骤)
这是 Midscene.js 的“大脑”配置,直接决定其智能程度。我们以 OpenAI GPT-4 Turbo with vision 为例,因为它目前在多模态理解和指令跟随上表现最均衡。
获取 API Key:前往 OpenAI 平台创建 API Key。妥善保管,不要提交到代码仓库。
创建环境变量文件:在项目根目录创建
.env文件。OPENAI_API_KEY=sk-your-actual-api-key-here # 可选:如果你需要通过代理访问(请注意,此处仅指企业内网或合规的网络代理服务,用于访问国际互联网服务,绝对不涉及任何违规翻墙行为) # HTTPS_PROXY=http://your-corporate-proxy:port配置 Midscene Client:创建一个配置文件,例如
config/midscene.config.js。这里我会加入一些优化参数。
import { Midscene } from 'midscene'; import { chromium } from 'playwright'; import dotenv from 'dotenv'; dotenv.config(); // 加载 .env 文件中的环境变量 export async function createMidsceneClient() { // 1. 启动浏览器,推荐使用无头模式(headless: true)在CI中运行,调试时可设为 false const browser = await chromium.launch({ headless: true, args: ['--disable-dev-shm-usage', '--no-sandbox'] // 这些参数有助于在Docker或内存有限的环境中稳定运行 }); const page = await browser.newPage(); // 2. 设置页面视口,让AI看到的画面和人类一致 await page.setViewportSize({ width: 1280, height: 720 }); // 3. 创建 Midscene 客户端 const client = new Midscene({ page, // 传入 Playwright 的 page 对象 apiKey: process.env.OPENAI_API_KEY, model: "gpt-4-turbo", // 指定使用支持视觉的模型 // 以下为优化参数 maxTokens: 500, // 限制响应长度,控制成本 temperature: 0.1, // 低温度值,使模型输出更确定、更稳定,减少“胡思乱想” reasoningEffort: "low", // 如果使用 o1 系列模型可设置,平衡速度与成本 // 超时和重试配置 actionTimeout: 30000, // 单次动作执行超时时间(毫秒) maxRetries: 2, // 操作失败时重试次数 }); return { client, browser, page }; }实操心得:
temperature参数对测试自动化至关重要。设为较低值(如0.1-0.3)可以大幅提高模型执行相同指令时输出操作计划的一致性,避免因为模型的随机性导致脚本时而过、时不过,这是保证测试稳定性的一个关键技巧。
3.3 编写你的第一个 AI 自动化测试脚本
让我们写一个简单的测试用例:打开百度首页,搜索“Midscene.js”。
创建文件test/first-ai-test.js。
import { createMidsceneClient } from '../config/midscene.config.js'; (async () => { const { client, browser, page } = await createMidsceneClient(); try { console.log('🎬 开始 AI 自动化测试...'); // 步骤 1: 导航到目标页面 await page.goto('https://www.baidu.com'); // 给页面一点加载时间,AI也需要“看”清楚 await page.waitForTimeout(2000); // 步骤 2: 使用自然语言命令 AI 在搜索框输入内容 console.log('输入搜索关键词...'); await client.action('在搜索框里输入“Midscene.js”'); // 等待一下,让输入完成 await page.waitForTimeout(1000); // 步骤 3: 使用自然语言命令 AI 点击“百度一下”按钮 console.log('点击搜索按钮...'); await client.action('点击“百度一下”按钮'); // 步骤 4: 等待搜索结果页面加载 await page.waitForSelector('#content_left', { timeout: 10000 }); await page.waitForTimeout(3000); // 等待结果渲染 // 步骤 5: (可选)让AI验证结果。这是一个更复杂的指令。 console.log('验证搜索结果...'); const result = await client.action('当前页面是否出现了与“自动化测试”相关的文字?如果有,请告诉我第一处出现的完整句子。'); console.log('AI 验证结果:', result); console.log('✅ 测试流程执行完毕!'); } catch (error) { console.error('❌ 测试执行失败:', error); } finally { // 步骤 6: 关闭浏览器,释放资源 await browser.close(); } })();运行这个脚本:
node test/first-ai-test.js如果一切配置正确,你将看到浏览器自动打开(或无头运行),完成搜索操作,并在控制台输出 AI 对结果的判断。第一次成功运行的那一刻,你会真切感受到 AI 驱动自动化的魔力。
4. 进阶实战:构建健壮的 AI 测试用例与工作流
基础的跑通只是第一步。要把 Midscene.js 用于实际项目,我们需要解决稳定性、可维护性和集成性问题。
4.1 编写稳定可靠的 AI 测试用例
直接使用client.action(‘描述’)虽然简单,但在复杂场景下容易失败。我们需要更精细的控制。
策略一:混合定位,提升稳定性完全依赖 AI 识别在元素密集或动态加载区域可能不准。我们可以结合传统定位方式。
// 不好的方式:完全依赖AI在复杂表格中找“编辑”按钮 // await client.action('点击第一行产品的编辑按钮'); // 更好的方式:先用Playwright定位到表格行,再让AI在该区域操作 const firstRow = await page.locator('.product-table tbody tr').first(); // 假设我们让AI在 firstRow 这个元素代表的截图区域内操作 // 注:Midscene.js可能需要扩展或使用其高级API来支持指定操作区域,这是一个重要的实践方向。 // 当前思路:可以先对特定区域截图,然后结合上下文发送给AI。这需要更底层的调用。策略二:分步引导,降低AI理解难度不要给AI一个复杂的长指令。拆分成原子操作。
// 复杂指令,容易出错 // await client.action('在顶部的导航栏找到“用户管理”,点击它,然后在打开的新页面中找到搜索框,输入“张三”,点击搜索'); // 拆解后的稳定指令 await client.action('点击页面上方的“用户管理”菜单'); await page.waitForURL('**/user-management'); // 等待页面跳转 await client.action('在页面上找到搜索框'); await client.action('在搜索框里输入“张三”'); await client.action('点击搜索按钮');策略三:加入明确验证点自动化测试的灵魂是验证。除了让AI执行,更要让AI判断。
// 执行登录操作 await client.action('在用户名输入框输入“testuser”'); await client.action('在密码输入框输入“password123”'); await client.action('点击登录按钮'); await page.waitForNavigation(); // 等待跳转 // 验证登录成功 - 让AI“看”页面并做出判断 const loginCheck = await client.action('当前页面是否显示“欢迎回来,testuser”或“登录成功”之类的提示?请只回答“是”或“否”。'); if (loginCheck.toLowerCase().includes('是')) { console.log('✅ 登录成功验证通过'); } else { throw new Error('❌ 登录成功提示未出现,可能登录失败'); }
4.2 集成到现有测试框架(以 Jest 为例)
为了让 AI 测试用例能被项目管理、生成报告,需要集成进测试框架。
安装 Jest:
npm install --save-dev jest jest-playwright-preset配置 Jest:创建
jest.config.jsmodule.exports = { preset: 'jest-playwright-preset', testEnvironment: './config/custom-test-environment.js', // 自定义环境,用于初始化Midscene setupFilesAfterEnv: ['./config/jest-setup.js'], testMatch: ['**/__tests__/**/*.test.js'], };创建自定义测试环境:
config/custom-test-environment.jsconst NodeEnvironment = require('jest-environment-node').TestEnvironment; const { createMidsceneClient } = require('./midscene.config.js'); class CustomTestEnvironment extends NodeEnvironment { async setup() { await super.setup(); // 在每个测试文件执行前,创建浏览器和Midscene客户端 const { client, browser, page } = await createMidsceneClient(); this.global.browser = browser; this.global.page = page; this.global.client = client; } async teardown() { // 在每个测试文件执行后,关闭浏览器 if (this.global.browser) { await this.global.browser.close(); } await super.teardown(); } } module.exports = CustomTestEnvironment;编写 Jest 测试用例:
__tests__/ai-search.test.jsdescribe('百度搜索 AI 自动化测试', () => { beforeAll(async () => { // 环境已由 CustomTestEnvironment 设置好 this.client = global.client; this.page = global.page; }); it('应该能通过AI指令完成搜索并看到结果', async () => { await this.page.goto('https://www.baidu.com'); await this.page.waitForTimeout(2000); await this.client.action('在搜索框输入“Jest自动化测试”'); await this.page.waitForTimeout(1000); await this.client.action('点击“百度一下”按钮'); await this.page.waitForSelector('#content_left', { timeout: 10000 }); // 使用Jest的expect进行断言(结合AI的判断) const searchResult = await this.client.action('页面主体内容区域是否包含了“Jest”这个词?请只回答“包含”或“不包含”。'); expect(searchResult).toContain('包含'); // 断言AI的反馈 }, 60000); // 设置较长的超时时间,因为AI操作可能较慢 });运行测试:
npx jest __tests__/ai-search.test.js这样,你的 AI 测试用例就能像普通单元测试一样运行,并生成标准的测试报告了。
4.3 搭建 CI/CD 流水线示例(GitHub Actions)
将 AI 自动化测试接入持续集成,确保每次代码变更都得到验证。
创建.github/workflows/ai-test.yml:
name: AI 自动化测试 on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest timeout-minutes: 30 # AI测试可能较慢,设置长超时 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install chromium - name: Run AI Automation Tests env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # 在GitHub仓库Settings/Secrets中配置 run: npx jest __tests__/ --ci --maxWorkers=2 --testTimeout=120000 # 增加单个测试超时 - name: Upload Playwright trace (on failure) if: failure() uses: actions/upload-artifact@v3 with: name: playwright-traces path: test-results/5. 常见问题、性能优化与避坑指南
在实际使用中,你肯定会遇到各种问题。下面是我踩过坑后总结的实战经验。
5.1 常见问题与排查技巧
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| AI 执行动作失败(如点击错位置) | 1. 页面未完全加载/动态内容未稳定。 2. 指令描述模糊,存在歧义。 3. 模型“看”到的截图分辨率或内容不对。 | 1. 在action前增加page.waitForTimeout或等待特定元素。2. 优化指令,使用更唯一、精确的描述(如“点击蓝色的、带有‘提交’文字的按钮”)。 3. 在失败时手动保存截图 ( await page.screenshot({ path: ‘debug.png’ })),检查AI看到的画面是否和你预期一致。 |
| API 调用超时或报错 | 1. 网络问题。 2. OpenAI API 额度用尽或限速。 3. 请求的 tokens 超长(截图太大)。 | 1. 检查网络连通性,考虑增加actionTimeout。2. 检查 OpenAI 账户仪表盘。 3. 优化截图范围或分辨率,或在发送前压缩图片。Midscene.js 可能内置处理,也可查阅其高级配置。 |
| 执行速度非常慢 | 1. 多模态大模型推理本身慢。 2. 网络延迟高。 3. 未使用无头模式。 | 1. 这是固有瓶颈。对于复杂流程,考虑混合模式:关键、易变的操作用AI,稳定、固定的流程用传统脚本。 2. 选择地理上更近的 API 端点(如果服务商提供)。 3. 确保生产环境使用 headless: true。 |
| 成本增长过快 | 1. 测试用例多,频繁调用昂贵模型(如 GPT-4V)。 2. 每次 action都发送全屏大图。 | 1.分层测试:核心冒烟测试用AI,大量回归测试用传统自动化。 2.模型降级:对简单、重复的识别任务,尝试使用更便宜的小模型(如 GPT-4o-mini 或专精OCR的本地模型)。 3.缓存与复用:对于不变页面的相同操作,可以考虑缓存AI返回的操作计划(但需谨慎,页面变化会导致缓存失效)。 |
| 无法处理非可视元素 | AI 依赖视觉,无法操作隐藏元素、文件上传(<input type=“file”>)等。 | 对于文件上传等非标准交互,回退到 Playwright 原生方法。这是混合自动化的典型场景。 |
| 脚本在不同环境不一致 | 浏览器视口大小、屏幕分辨率、字体渲染差异导致AI“看”到的画面不同。 | 在 CI 和本地都固定浏览器视口大小(如setViewportSize({ width: 1280, height: 720 }))。使用 Docker 容器化测试环境以保证一致性。 |
5.2 性能优化与成本控制实战技巧
指令工程优化:这是提升成功率、降低 token 消耗最有效的方法。
- 具体化:用“点击‘保存草稿’按钮”代替“点击保存按钮”。
- 结构化:对于列表操作,可以告诉AI规则,如“点击第一个商品下方的‘加入购物车’按钮”。
- 分步引导:如前所述,将复杂任务拆解。
截图优化:
// 在创建Midscene客户端时,可以探索是否支持传递截图选项 // 例如,只截取页面主体部分,排除固定导航栏/页脚,减少无关信息干扰AI // 这需要查阅Midscene.js的高级API或修改其源码。思路是:在调用AI前,先使用Playwright对特定区域截图。 const contentArea = await page.locator(‘#main-content’); const screenshotBuffer = await contentArea.screenshot(); // 然后将 screenshotBuffer 和指令一起发送给AI(需自定义调用逻辑)实现智能重试与降级机制:
async function robustAIAction(client, instruction, fallbackSelector = null, maxRetries = 2) { for (let i = 0; i < maxRetries; i++) { try { await client.action(instruction); return; // 成功则退出 } catch (error) { console.warn(`AI 指令执行失败 (第 ${i + 1} 次): ${error.message}`); if (i === maxRetries - 1) { // 最后一次重试也失败,尝试降级方案 if (fallbackSelector) { console.log(`🔄 降级:使用传统选择器 ${fallbackSelector}`); await page.click(fallbackSelector); } else { throw error; // 没有降级方案,抛出错误 } } await page.waitForTimeout(1000); // 重试前等待 } } } // 使用示例 await robustAIAction( client, ‘点击登录按钮’, ‘button:has-text(“登录”)’, // 降级用的Playwright选择器 2 );批量操作与并发控制:对于需要操作大量相似元素的场景(如勾选列表所有复选框),不要让AI一个个识别。可以先让AI识别一个样本,然后用 Playwright 的
locatorAPI 批量处理。// 1. 让AI先找到一个样本元素(例如第一个复选框) // 假设我们通过某种方式获得了该样本的定位信息(这需要Midscene.js提供更详细的返回) // 2. 使用Playwright定位所有同类元素 const allCheckboxes = await page.locator(‘.list-item input[type=“checkbox”]’); const count = await allCheckboxes.count(); for (let i = 0; i < count; i++) { await allCheckboxes.nth(i).check(); }
经过这些优化,你的 Midscene.js 测试环境将从一个“有趣的实验”转变为能在实际项目中稳定贡献价值的自动化工具。它可能不会完全取代传统自动化,但作为处理“变化”和“不确定性”的利器,无疑为测试左移和提升研发效能打开了新的大门。