Files
nanocode/new-media-platform/website-new/docs/BROWSER_AUTOMATION.md
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

20 KiB
Raw Blame History

浏览器自动化技术架构与实现

概述

浏览器自动化(Browser Automation)是指通过程序代码控制真实浏览器执行用户操作的技术栈。它在 Web 测试、数据抓取、UI 验证、表单填写、截图对比等场景中被广泛应用。


核心技术架构

1. 控制协议层

浏览器自动化的本质是通过特定协议与浏览器进程通信,向浏览器发送指令并接收执行结果。目前业界主流的通信协议有三种:

1.1 Chrome DevTools Protocol (CDP)

CDP 是 Chromium/Chrome 浏览器内置的远程调试协议,基于 WebSocket 通信。

  • 原理:Chrome 启动时开放一个调试端口(如 --remote-debugging-port=9222),外部程序通过 WebSocket 连接到该端口,发送 JSON 格式的命令来控制浏览器
  • 能力:几乎可以控制浏览器的所有行为,包括 DOM 操作、网络拦截、性能监控、截图、执行 JavaScript、模拟鼠标键盘事件等
  • 优势:原生支持、速度快、功能全、无额外驱动依赖
  • 代表工具:Puppeteer、Playwright(Chromium 模式)、Chrome Remote Interface
┌─────────────┐      WebSocket      ┌──────────────────┐
│  控制程序    │  ◄──────────────►  │  Chrome 浏览器    │
│  (Node.js)  │    JSON-RPC 协议   │  (调试端口 9222)  │
└─────────────┘                     └──────────────────┘

1.2 WebDriver Protocol (W3C / JsonWire)

WebDriver 是 W3C 标准化的浏览器控制协议,基于 HTTP RESTful API。

  • 原理:浏览器厂商或第三方提供 WebDriver 驱动程序(如 ChromeDriver、GeckoDriver),控制程序通过 HTTP 请求向驱动发送指令,驱动再转译为浏览器内部操作
  • 能力:标准化、跨浏览器支持(Chrome、Firefox、Safari、Edge)
  • 劣势:相比 CDP 速度较慢,部分高级功能(如网络拦截)支持有限
  • 代表工具:Selenium、WebdriverIO
┌─────────────┐      HTTP       ┌─────────────┐      IPC      ┌──────────────────┐
│  控制程序    │  ◄──────────►  │ ChromeDriver │  ◄────────►  │  Chrome 浏览器    │
└─────────────┘   REST API     └─────────────┘               └──────────────────┘

1.3 BiDi Protocol (WebDriver-BiDi)

BiDi(Bidirectional)是 W3C 正在推进的新一代双向通信协议,目标是结合 WebDriver 的标准化和 CDP 的高性能。

  • 原理:基于 WebSocket 的双向通信,既保留了 WebDriver 的标准化语义,又支持事件订阅和实时推送
  • 状态:Firefox 已原生支持,Chrome 正在逐步推进
  • 代表工具:Playwright(部分实现)、Selenium 4+

2. 典型工具链实现

2.1 Playwright 架构

Playwright 是微软开源的浏览器自动化框架,支持 Chromium、Firefox、WebKit。

┌─────────────────────────────────────────────┐
│           Playwright API (Python/JS/...)      │
│  - page.click()                               │
│  - page.fill()                                │
│  - page.screenshot()                          │
│  - page.evaluate()                            │
└──────────────────┬────────────────────────────┘
                   │
┌──────────────────▼────────────────────────────┐
│         Playwright Driver (语言绑定层)          │
│  - 协议序列化/反序列化                           │
│  - 浏览器进程管理                                │
└──────────────────┬────────────────────────────┘
                   │ WebSocket (CDP) / Pipe
┌──────────────────▼────────────────────────────┐
│         Browser 进程                            │
│  - Chromium: CDP 协议                           │
│  - Firefox: Juggler / WebDriver BiDi           │
│  - WebKit: WebKit Inspector Protocol           │
└───────────────────────────────────────────────┘

核心特点:

  • 自动等待:操作前自动等待元素可见/可交互,避免 flaky 测试
  • 浏览器上下文隔离:每个测试用例运行在独立的浏览器上下文中(类似隐身窗口),彼此隔离 Cookie/Storage
  • 网络拦截:可拦截、修改、模拟 HTTP 请求响应
  • 多浏览器支持:一套 API 控制三种内核

2.2 Puppeteer 架构

Puppeteer 是 Google Chrome 团队维护的 Node.js 库,专注于 Chromium 自动化。

┌─────────────────────────────────────────────┐
│        Puppeteer API (Node.js)              │
│  - puppeteer.launch()                         │
│  - page.goto()                                │
│  - page.pdf()                                 │
└──────────────────┬────────────────────────────┘
                   │
┌──────────────────▼────────────────────────────┐
│      Chrome DevTools Protocol (CDP)           │
│  - Target domain (创建/管理页面)               │
│  - DOM domain (元素查询/操作)                  │
│  - Input domain (鼠标/键盘事件)                │
│  - Network domain (请求拦截)                   │
│  - Emulation domain (设备模拟)                 │
└──────────────────┬────────────────────────────┘
                   │ WebSocket
┌──────────────────▼────────────────────────────┐
│      Chrome/Chromium Browser                  │
└───────────────────────────────────────────────┘

2.3 Selenium 架构

Selenium 是最老牌的浏览器自动化框架,跨语言和跨浏览器支持最广。

┌─────────────────────────────────────────────┐
│     Selenium WebDriver Client (多语言)        │
│  - Java / Python / C# / JavaScript / Ruby     │
└──────────────────┬────────────────────────────┘
                   │ HTTP (W3C WebDriver Protocol)
┌──────────────────▼────────────────────────────┐
│     WebDriver Server (Browser-specific)       │
│  - ChromeDriver (Chrome)                      │
│  - GeckoDriver (Firefox)                      │
│  - SafariDriver (Safari)                      │
│  - EdgeDriver (Edge)                          │
└──────────────────┬────────────────────────────┘
                   │ Browser-specific protocol
┌──────────────────▼────────────────────────────┐
│           Target Browser                      │
└───────────────────────────────────────────────┘

3. 关键实现方法详解

3.1 页面导航与 DOM 操作

// 启动浏览器并打开页面
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');

// 等待元素出现并点击
await page.waitForSelector('button.submit');
await page.click('button.submit');

// 填写表单
await page.type('input[name="username"]', 'admin');
await page.type('input[name="password"]', 'secret');

// 执行自定义 JavaScript
const title = await page.evaluate(() => document.title);

底层实现:

  1. goto 调用 CDP 的 Page.navigate 命令
  2. waitForSelector 调用 CDP 的 DOM.querySelector,轮询直到元素存在
  3. click 先通过 CDP DOM.getBoxModel 获取元素坐标,再调用 Input.dispatchMouseEvent 模拟鼠标事件

3.2 截图实现

// 整页截图
await page.screenshot({ path: 'page.png', fullPage: true });

// 元素截图
const element = await page.$('.chart');
await element.screenshot({ path: 'chart.png' });

底层实现:

  1. CDP 的 Page.captureScreenshot 命令
  2. 浏览器渲染引擎将当前视口内容编码为 base64 PNG
  3. 如需整页截图,先通过 Page.getLayoutMetrics 获取页面总高度,调整视口后截取
  4. 元素截图先获取元素边界框,再裁剪整页截图

3.3 网络拦截

await page.setRequestInterception(true);
page.on('request', (req) => {
  if (req.url().endsWith('.png')) {
    req.abort();  // 拦截图片加载
  } else {
    req.continue();
  }
});

底层实现:

  1. CDP 的 Network.setRequestInterception 启用拦截
  2. 浏览器在发起请求前暂停,通过 CDP 事件通知控制程序
  3. 控制程序发送 Network.continueInterceptedRequest 或 Network.failRequest

4. 元素定位机制:截图 vs DOM 分析(重要)

4.1 核心原理:直接操作 DOM,而非分析图片像素

常见误解:很多人以为自动化工具是先截图,再用图像识别找到按钮的屏幕像素坐标,然后让鼠标移过去点击。

实际机制:完全不是这样。自动化工具直接通过 CDP/WebDriver 与浏览器内核通信,查询 DOM 树,获取元素的逻辑标识和渲染位置,再触发浏览器原生事件。这与图像识别自动化(如 PyAutoGUI)是两种截然不同的技术路线。

错误理解(图像识别路线):
  截图 → OCR/图像识别 → 找到像素坐标(320, 45) → 移动鼠标 → 点击

实际机制(DOM 操作路线):
  CDP 查询 DOM 树 → 找到元素节点 → 获取 Layout 坐标 → 触发浏览器输入事件

4.2 元素定位的完整底层流程

以 page.click('button[type=submit]') 为例,CDP 层面实际发生了什么:

第 1 步:定位 DOM 节点

发送 CDP Runtime.evaluate 命令,在浏览器 JS 环境执行 document.querySelector,返回节点的 objectId 和 nodeId(例如 nodeId=42)。

第 2 步:获取渲染坐标(Box Model)

发送 CDP DOM.getBoxModel 命令,传入 nodeId=42,返回元素四个顶角的屏幕坐标:

content: [120, 40,  200, 40,
          200, 60,  120, 60]  ← 左上、右上、右下、左下
计算中心点: x = (120+200)/2 = 160,  y = (40+60)/2 = 50

第 3 步:触发鼠标事件

连续发送三次 CDP Input.dispatchMouseEvent 命令:

{ type: mouseMoved,    x: 160, y: 50 }           ← 鼠标移入
{ type: mousePressed,  x: 160, y: 50, button: left }
{ type: mouseReleased, x: 160, y: 50, button: left }

整个过程完全不依赖截图或图像识别,是纯 DOM 查询 + 浏览器事件驱动。


4.3 agent-browser 的定位方式:Accessibility Tree(无障碍树)

本项目使用的 agent-browser 工具在 DOM 查询基础上更进一步,默认使用 Accessibility Tree(无障碍语义树) 来定位元素,而非原始 CSS 选择器。

传统 CSS 选择器(脆弱,依赖 DOM 层级):
  div.nav-bar > ul > li:nth-child(3) > a.btn-primary

Accessibility Tree(语义稳定):
  role=button, name="立即咨询"
  role=textbox, label="用户名"

底层工作原理:

浏览器为每个 DOM 元素生成对应的 AX 节点,包含 role(语义角色)、name(可读名称)、state、boundingBox 等属性。CDP 提供 Accessibility.getFullAXTree 命令导出完整的 AX 树。

agent-browser snapshot -i 就是调用此命令,解析后为每个可交互节点生成 @e1、@e2 等 ref 引用:

$ agent-browser snapshot -i
# 输出(AX 树的可交互节点):
  textbox  "用户名"   [ref=e1]    <- role=textbox
  textbox  "密码"     [ref=e2]    <- role=textbox
  button   "登录"     [ref=e3]    <- role=button
  button   "立即咨询" [ref=e8]    <- role=button
  link     "首页"     [ref=e10]   <- role=link

# 直接用 ref 操作,无需写任何选择器:
$ agent-browser fill @e1 "admin"
$ agent-browser fill @e2 "abcd1234"
$ agent-browser click @e3

AX Tree 的优势:

  • React/Vue 组件重构、CSS 类名变化后依然稳定,因为语义角色不变
  • 对异步渲染的 SPA 应用更鲁棒(等待节点出现而非等待选择器)
  • 天然对齐无障碍标准(和屏幕阅读器使用同一接口)

4.4 截图的真正用途

截图不是用来定位元素的,而是用于视觉验证。

截图用途 具体场景
验证渲染结果 图片是否正常显示(无破图)、CSS 样式是否正确
验证交互状态 hover 后面板是否展开、点击后弹窗是否出现
调试记录 留存测试过程的视觉证据,便于人工排查问题
视觉回归测试 与基线截图做像素级对比,检测 UI 意外变化

操控阶段使用 DOM/AX 树,验证阶段才使用截图:

操控阶段(DOM/AX 操作,不需要截图)
  snapshot    -> 获取元素 ref 列表
  fill @e1    -> 直接写入 DOM value,无需截图
  click @e3   -> 触发浏览器输入事件,无需截图
  wait --url  -> 监听 Page.frameNavigated 事件

验证阶段(截图,确认视觉结果正确)
  screenshot  -> 保存截图,人工或程序检查渲染是否符合预期  ✓

4.5 与图像识别自动化的对比

维度 DOM/AX 方案(CDP / agent-browser) 图像识别方案(PyAutoGUI / SikuliX)
定位方式 查询 DOM/AX 语义树 截图 → 模板像素匹配
定位速度 毫秒级 秒级(截图+识别开销高)
稳定性 高(语义不变则稳定) 低(分辨率/主题/字体变化即失效)
无头模式 完全支持(无需屏幕) 需要真实屏幕渲染
适用范围 Web 应用(有 DOM 的页面) 桌面软件、游戏等无 API 的场景
可读性 高(click "登录" 语义清晰) 低(依赖截图文件匹配)

5. 在 Web 测试中的应用模式

5.1 端到端测试 (E2E Testing)

模拟真实用户从打开页面到完成业务流程的完整路径:

test('用户登录流程', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="username"]', 'user1');
  await page.fill('[data-testid="password"]', 'pass123');
  await page.click('[data-testid="login-btn"]');
  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('.welcome-msg')).toContainText('欢迎回来');
});

5.2 视觉回归测试 (Visual Regression)

通过截图对比检测 UI 意外变化:

// 基线截图
await page.screenshot({ path: 'baseline/homepage.png' });

// 后续对比
const diff = await compareImages('baseline/homepage.png', 'current/homepage.png');
expect(diff.pixelDifference).toBeLessThan(0.01);

5.3 性能监控

利用 CDP 的 Performance 和 Tracing domain 采集页面加载指标:

await page.tracing.start({ path: 'trace.json' });
await page.goto('https://example.com');
await page.tracing.stop();
// 分析 trace.json 中的 firstContentfulPaint, largestContentfulPaint 等指标

6. 无头模式 vs 有头模式

模式 说明 适用场景
Headless(无头) 浏览器在后台运行,不显示 GUI CI/CD 流水线、服务器端爬虫、大规模自动化
Headful(有头) 浏览器正常显示窗口 本地调试、需要人工观察的测试、反爬虫对抗

现代无头浏览器(Chromium Headless、Firefox Headless)已能完美渲染 CSS/Canvas/WebGL,与有头模式行为一致。


7. 反检测与指纹对抗

当自动化工具被用于数据抓取时,目标网站可能通过以下方式检测:

  • navigator.webdriver:Chrome 在自动化模式下会设置该属性为 true
  • 插件列表:真实浏览器有 PDF 插件、Chrome 插件等,无头环境通常缺失
  • WebGL/Canvas 指纹:无头环境的 GPU 指纹与真实设备不同
  • 鼠标移动轨迹:真实用户的鼠标移动有随机性和曲线,自动化工具通常是直线瞬移

对抗手段:

  • 使用 puppeteer-extra-plugin-stealth 等插件修改指纹
  • 模拟真实鼠标移动轨迹(贝塞尔曲线)
  • 使用真实用户代理和视口尺寸

8. 选型建议

需求 推荐工具
现代 Web 应用测试,追求稳定性 Playwright
纯 Chromium 生态,需要 PDF/打印 Puppeteer
跨浏览器兼容性测试 Selenium / Playwright
已有 Selenium 生态,渐进升级 Selenium 4 + BiDi
反爬虫数据抓取 Puppeteer + Stealth 插件

9. 总结

浏览器自动化的技术本质可以概括为:

通过标准化协议(CDP / WebDriver / BiDi)与浏览器进程通信,将高级 API(click、fill、screenshot)翻译为底层的浏览器内部指令,从而程序化为浏览器赋予"机器手"和"机器眼"。

无论使用哪种具体工具,其架构都遵循相似的分层设计:应用 API 层 → 协议适配层 → 浏览器内核层。理解这一通用架构,有助于在不同工具和场景之间灵活迁移和深度定制。

对话记录

你要把你的这个测试方法记录下来,记忆下来。后续我可以让你经常通过这个方法进行测试

已记录到工作区记忆中,关键词:测试、E2E、Playwright、浏览器自动化、agent-browser。后续你想做测试时,直接说类似这样的话即可: "帮我测试一下访客端首页的导航菜单下拉效果" "用浏览器测试后台登录和菜单管理功能" "截图看一下移动端菜单的显示效果"

刚才记录的是 workspace 级别(仅当前项目有效)。但 agent-browser 技能本身是全局通用的,任何有 Web 界面的项目都能测。

我把它升级为 global 记忆,这样你其他项目也能直接调用: 已升级为 global 记忆,现在跨项目通用。

agent-browser 不限技术栈,只要项目有 Web 界面(React/Vue/纯 HTML/后台管理系统都可以),我都能打开真实浏览器进行测试。后续任何项目你都可以直接说:

"测试一下这个页面" "截图看看效果" "验证一下功能是否正常"

我会自动调用浏览器自动化执行。

我会自动调用 agent-browser 打开真实 Chromium 浏览器,执行操作并截图返回验证结果!