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 的核心流程可以概括为“观察-思考-执行”的循环,这非常像一个人工测试员在操作电脑。

  1. 观察(Screenshot & Context) :当你下达一个指令(如 click “提交” button )后,Midscene.js 首先会命令 Puppeteer 或 Playwright 对当前浏览器页面进行截图。但这还不够,它还会收集当前页面的 文本上下文 。这个上下文通常是通过提取页面的可访问性树(Accessibility Tree)或者主要文本节点来获得的,包含了按钮文字、链接文本、标签等所有可见文本信息。 截图提供了视觉布局,文本上下文提供了语义信息,两者结合,构成了 AI 理解页面的“眼睛”

  2. 思考(LLM Reasoning) :截图和文本上下文会被一起打包,发送给你配置的多模态大模型(例如 OpenAI 的 GPT-4 with vision)。你的自然语言指令也会一并发送。模型的任务是进行“视觉问答”(VQA):基于看到的画面和文字,理解指令的意图,并精准定位到需要操作的元素。它的输出不是一个简单的坐标,而是一个结构化的操作计划,通常包括:

    • 操作类型 click , type , hover , select 等。
    • 元素定位描述 :一个基于画面和文本的自然语言描述,例如“那个位于‘用户名’输入框下方、带有‘登录’文字的蓝色矩形按钮”。
    • 操作参数 :对于 type 操作,就是需要输入的文本。

    注意 :这里的一个关键点是,模型并不直接输出浏览器可执行的 DOM 选择器。它输出的是人类可读的描述。这意味着 Midscene.js 本身不依赖于任何具体的页面实现技术(React, Vue 等),只要模型能“看”懂,它就能操作。

  3. 执行(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 chromium

3.2 大模型 API 配置(关键步骤)

这是 Midscene.js 的“大脑”配置,直接决定其智能程度。我们以 OpenAI GPT-4 Turbo with vision 为例,因为它目前在多模态理解和指令跟随上表现最均衡。

  1. 获取 API Key :前往 OpenAI 平台创建 API Key。妥善保管,不要提交到代码仓库。

  2. 创建环境变量文件 :在项目根目录创建 .env 文件。

    OPENAI_API_KEY=sk-your-actual-api-key-here
    # 可选:如果你需要通过代理访问(请注意,此处仅指企业内网或合规的网络代理服务,用于访问国际互联网服务,绝对不涉及任何违规翻墙行为)
    # HTTPS_PROXY=http://your-corporate-proxy:port
    
  3. 配置 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 测试用例能被项目管理、生成报告,需要集成进测试框架。

  1. 安装 Jest

    npm install --save-dev jest jest-playwright-preset
    
  2. 配置 Jest :创建 jest.config.js

    module.exports = {
      preset: 'jest-playwright-preset',
      testEnvironment: './config/custom-test-environment.js', // 自定义环境,用于初始化Midscene
      setupFilesAfterEnv: ['./config/jest-setup.js'],
      testMatch: ['**/__tests__/**/*.test.js'],
    };
    
  3. 创建自定义测试环境 config/custom-test-environment.js

    const 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;
    
  4. 编写 Jest 测试用例 __tests__/ai-search.test.js

    describe('百度搜索 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操作可能较慢
    });
    
  5. 运行测试

    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 性能优化与成本控制实战技巧

  1. 指令工程优化 :这是提升成功率、降低 token 消耗最有效的方法。

    • 具体化 :用“点击‘保存草稿’按钮”代替“点击保存按钮”。
    • 结构化 :对于列表操作,可以告诉AI规则,如“点击 第一个 商品下方的‘加入购物车’按钮”。
    • 分步引导 :如前所述,将复杂任务拆解。
  2. 截图优化

    // 在创建Midscene客户端时,可以探索是否支持传递截图选项
    // 例如,只截取页面主体部分,排除固定导航栏/页脚,减少无关信息干扰AI
    // 这需要查阅Midscene.js的高级API或修改其源码。思路是:在调用AI前,先使用Playwright对特定区域截图。
    const contentArea = await page.locator(‘#main-content’);
    const screenshotBuffer = await contentArea.screenshot();
    // 然后将 screenshotBuffer 和指令一起发送给AI(需自定义调用逻辑)
    
  3. 实现智能重试与降级机制

    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
    );
    
  4. 批量操作与并发控制 :对于需要操作大量相似元素的场景(如勾选列表所有复选框),不要让AI一个个识别。可以先让AI识别一个样本,然后用 Playwright 的 locator API 批量处理。

    // 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 测试环境将从一个“有趣的实验”转变为能在实际项目中稳定贡献价值的自动化工具。它可能不会完全取代传统自动化,但作为处理“变化”和“不确定性”的利器,无疑为测试左移和提升研发效能打开了新的大门。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐