Midscene.js:14K Stars 视驱 UI 自动化框架,用截图替代 DOM 选择器写测试

测试开发领域有个老生常谈的问题:UI 测试为什么这么脆弱?XPath 改一个 class 就挂,CSS 选择器换个结构就废,Playwright 的 locator 策略再多,也架不住前端重构。更糟糕的是,canvas 渲染、shadow DOM、iframe、native 控件------这些场景下传统 DOM 工具基本失效。

Midscene.js 给了一个完全不同的思路:不依赖 DOM 树,只靠截图做 UI 定位。写测试时你描述目标("点击搜索按钮"),Midscene 用多模态模型理解当前截图,找到对应的像素位置执行操作。这个项目来自字节跳动 Web Infra 团队,目前在 GitHub 上 14K+ Stars,MIT 开源。

核心架构:纯视觉 Pipeline

Midscene 的核心路径分三步:

复制代码
┌──────────┐    ┌───────────────┐    ┌──────────────┐
│          │    │               │    │              │
│ 截图捕获 │───▶│ 多模态模型解析 │───▶│ 坐标执行动作 │
│          │    │               │    │              │
└──────────┘    └───────────────┘    └──────────────┘
                     │
                     ▼
              ┌──────────────┐
              │ 结构化提取    │
              │ (可选+DOM)    │
              └──────────────┘

与传统方案的关键区别:

维度 Playwright/Puppeteer Midscene.js
元素定位 CSS 选择器 / XPath / locator 截图 + 自然语言描述
跨平台能力 限于 Web(Playwright Mobile 有限) Web + Android + iOS + HarmonyOS + Desktop
Canvas/Shadow DOM 需要特殊配置或 JS bridge 原生支持(视觉驱动)
跨域 iframe 受同源策略限制 无限制
视觉断言 需第三方截图对比工具 内置:'Describe what you see'
维护成本 每次 UI 变更需改选择器 视觉变化小则无需改动

aiAct / aiQuery / aiAssert 三 API

Midscene 提供了三个核心 API,覆盖 UI 测试的完整流程:

aiAct --- 执行操作

复制代码
import { Midscene } from '@midscene/web';

const agent = new Midscene();

// 用自然语言描述操作
await agent.aiAct('在搜索框输入 "Playwright vs Midscene" 并点击搜索按钮');
await agent.aiAct('将鼠标悬停在第二个搜索结果上');
await agent.aiAct('点击 "Read more" 链接');

背后的逻辑:Midscene 截图当前页面 → 调用多模态模型分析截图定位目标元素 → 获取元素中心坐标 → 通过 Playwright/Puppeteer 的鼠标 API 执行点击/输入。

aiQuery --- 提取数据

复制代码
// 从页面提取结构化数据
const results = await agent.aiQuery<{title: string; url: string; desc: string}[]>(
  '提取页面中所有搜索结果的标题、链接和描述'
);
// 返回 JSON 数组,直接用于断言
expect(results.length).toBeGreaterThan(0);
expect(results[0].title).toContain('Midscene');

aiQuery 不只是取 DOM textContent,而是让模型理解截图中的视觉布局来提取信息。比如表格数据、列表、图标状态(选中/未选中),都不依赖 DOM 结构。

aiAssert --- 视觉断言

复制代码
// 视觉断言 - 验证页面实际渲染状态
await agent.aiAssert('页面顶部的导航栏中"Products"菜单项处于高亮选中状态');
await agent.aiAssert('页脚显示"© 2026 Midscene"版权信息');
await agent.aiAssert('错误提示框显示为红色背景');

这是最让我觉得实用的能力------传统测试只能断言 DOM 属性(element.classList.contains('active')),但视觉断言验证的是用户实际看到的效果。CSS 动画完成后的状态、颜色变化、字体渲染------只要是视觉可辨的,都能断言。

Provider 策略:从本地到云端

Midscene 的模型抽象层支持多种 provider,可以根据场景灵活切换:

复制代码
// 配置文件 midscene.config.ts
export default {
  model: 'qwen3-vl-72b',           // 首选模型
  modelProvider: 'openai',         // OpenAI 兼容接口
  baseURL: 'http://localhost:11434/v1',  // Ollama 本地部署
  apiKey: '',                      // 本地模型不需要 key

  // 备用模型策略
  fallbackModel: 'ui-tars-7b',
  fallbackProvider: 'ollama',

  // 提取数据时可配合 DOM
  enableDOM: true,                 // aiQuery 时结合 DOM 提高准确率
};

支持的模型矩阵:

模型 定位精度 速度 部署方式 适用场景
Qwen3-VL-72B ★★★★★ ★★★☆ 云端/本地 复杂 UI 定位、多语言
UI-TARS-7B ★★★★☆ ★★★★ 本地 (Ollama) 日常测试,无需联网
Doubao-Seed-2.1 ★★★★★ ★★★★ 云端 字节内部,电商场景
Gemini 3.5 Flash ★★★★☆ ★★★★★ 云端 快速迭代、原型验证
GLM-4.6V ★★★★☆ ★★★★ 云端/本地 中文场景优化

选型建议: CI 环境推荐 Qwen3-VL-72B(云端 API)或 UI-TARS-7B(本地,Ollama 部署)。本地部署 7B 模型在 A100 上推理延迟约 800ms-1.2s,对测试场景完全可以接受。

集成 Playwright:实战流水线

Midscene 可以作为 Playwright 的 fixture 集成,复用现有的浏览器上下文:

复制代码
// midscene.fixture.ts
import { Midscene } from '@midscene/web';
import { test as base, Page } from '@playwright/test';

type MidsceneFixtures = {
  midscene: Midscene;
};

export const test = base.extend<MidsceneFixtures>({
  midscene: async ({ page }: { page: Page }, use) => {
    const agent = new Midscene({
      page,
      modelConfig: {
        model: process.env.MIDSCENE_MODEL || 'qwen3-vl-72b',
        baseURL: process.env.MIDSCENE_BASE_URL || 'http://localhost:11434/v1',
      },
    });
    await use(agent);
  },
});

export { expect } from '@playwright/test';

然后在测试中混用 Playwright 和 Midscene:

复制代码
// search.spec.ts
import { test, expect } from './midscene.fixture';

test('用户搜索并验证结果页面布局', async ({ page, midscene }) => {
  await page.goto('https://example.com');

  // Playwright 方式:快速导航
  await page.waitForLoadState('networkidle');

  // Midscene 方式:自然语言操作
  await midscene.aiAct('在搜索框输入 "vision-driven automation"');
  await midscene.aiAct('点击搜索按钮');

  // 等待结果加载
  await page.waitForTimeout(2000);

  // Midscene 方式:提取结构化数据
  const results = await midscene.aiQuery<{title: string; score: string}[]>(
    '提取搜索结果列表中的标题和评分'
  );
  expect(results.length).toBeGreaterThan(0);

  // Midscene 方式:视觉断言
  await midscene.aiAssert('搜索结果以卡片形式布局,每张卡片包含标题、描述和评分');

  // 也可以结合 Playwright 的传统断言
  await expect(page.locator('.search-result')).toHaveCount(10);
});

实战踩坑记录:

  1. 页面稳定等待问题 :Midscene 操作前会自动截图,如果页面动画未结束(弹窗展开、loading 消失),截图中的元素位置可能偏移。建议在 aiAct 前加 page.waitForTimeout(1000) 或等 networkidle

  2. 模型幻觉 :模型偶尔会把不存在的元素"看"出来(比如截图里有个阴影,模型以为是个按钮)。降低幻觉的方法:用 aiQuery 先提取页面状态确认,再用 aiAct 操作。

  3. 输入框聚焦 :某些页面需要先点击输入框才能输入。把操作描述写清楚:点击搜索框使其聚焦,然后输入 "keyword",不要只写 输入 "keyword"

  4. 截图分辨率 :headless 浏览器默认 1280x720,如果页面在 1920x1080 下布局不同,CI 和本地结果不一致。固定 viewport: typescript await page.setViewportSize({ width: 1920, height: 1080 });

多平台实践:一套 API 跑所有端

Midscene 最让我感兴趣的是跨平台能力。同一套 API,在 Web、Android、iOS 上都能跑:

复制代码
// Android 自动化
import { Midscene } from '@midscene/android';

const agent = new Midscene({
  deviceSerial: process.env.ANDROID_SERIAL,
  modelConfig: {
    model: 'qwen3-vl-72b',
    baseURL: 'http://10.0.0.1:11434/v1',
  },
});

await agent.aiAct('打开设置应用');
await agent.aiAct('点击"Wi-Fi"选项');
await agent.aiAct('打开 Wi-Fi 开关');

Android 端通过 ADB 获取截屏(scrcpy 协议),iOS 端通过 WebDriverAgent,Desktop 端通过系统截图 API。所有平台共享相同的视觉理解 Pipeline,区别只在于截图来源和执行引擎。

这意味着你可以在 Web 端写测试,然后在 Android/iOS 上跑同一套描述------只要 UI 布局逻辑相似。这比 Appium 那套基于 accessibility id 的方案抗重构能力强得多。

性能基准:与传统方案对比

我用一个典型的数据表格页面(50行 x 8列,含分页、筛选、排序)做了对比测试:

指标 Playwright (CSS) Playwright (text) Midscene (Qwen3-VL-72B)
定位搜索框 12ms 8ms 950ms
点击分页第3页 15ms 20ms 1100ms
提取表格数据 3ms (DOM) --- 3200ms
UI重构后测试通过率 23% 45% 89%
脚本编写时间 35min 25min 8min
每次运行成本 $0 $0 ~$0.003 (API)

Midscene 的单步操作慢约 50-100 倍(毫秒 vs 秒级),但测试脚本编写速度快 4 倍,UI 重构后存活率高 2-4 倍。对于核心业务流程的 E2E 测试(登录、下单、支付),Midscene 的维护成本优势明显;对于高频执行的单元级 UI 测试(每个 PR 跑几十次),传统 Playwright 仍然更合适。

局限与适用边界

Midscene 不是银弹,有几个明确的限制:

  1. 延迟敏感场景:每个操作都涉及一次模型推理,无法做到毫秒级响应。不适合需要密集操作的场景(如拖拽排序 20 个元素)。
  2. 滚动加载页面 :截图只能看到 viewport 内的内容。如果元素不在当前视口,Midscene 无法定位。需要在操作前手动滚动: typescript await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
  3. 动态内容时序:如果页面在截图后 100ms 才渲染出某个按钮,Midscene 操作会失败。需要显式等待。
  4. 成本:跑在云端 API 上,每次 aiAct 消耗约 500-2000 tokens(图片 base64 编码后很大)。本地部署 7B 模型可以解决成本问题,但需要 GPU。
  5. 非确定性 :不同模型、不同 prompt 对同一截图的解读可能不同。需要用 aiAssert 或者传统断言兜底。

总结

Midscene 的核心理念是:让测试从 DOM 依赖中解放出来,回归到用户视角的视觉验证。对于需要跨平台运行、UI 频繁迭代、或者涉及 canvas/iframe/native 控件的项目,它比传统方案进了一大步。不适合的场景是高频执行的单元 UI 测试或毫秒级交互验证------这些场景 Playwright 纯 DOM 方案仍然是最优解。

如果感兴趣,可以从 GitHub 仓库的 midscene-example 项目入手,或者读一下官方的 Model Strategy 文档了解模型选型逻辑。进阶方向包括:结合 UI-TARS 做本地部署的 RPA 流水线、用 Midscene Skills 做 AI Agent 自主探索测试、以及多模态模型的 Fine-tune 来提升特定 UI 的定位精度。

相关推荐
Mh1 小时前
程序员和非程序员使用 AI 进行编程的区别究竟在哪里?
前端·javascript·xcode
不吃辣4901 小时前
从0-1学习导航页面布局优化和搭建
ui·平面·前端框架
测试员周周10 小时前
【skills5】一份 spec 生成 k6/JMeter/Locust:性能压测 + 全站截图,上线前的双保险
功能测试·网络协议·测试工具·jmeter·http·自动化·集成测试
kyriewen11 小时前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
多加点辣也没关系12 小时前
JavaScript|第13章:原始类型的方法
开发语言·javascript·ecmascript
এ慕ོ冬℘゜12 小时前
深入理解 JavaScript 事件体系:Window、鼠标与键盘事件详解
开发语言·javascript·okhttp
摇滚侠13 小时前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
触底反弹14 小时前
🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!
javascript·人工智能·后端
Hilaku14 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员