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

449 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 浏览器自动化技术架构与实现
## 概述
浏览器自动化(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 操作
```javascript
// 启动浏览器并打开页面
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 截图实现
```javascript
// 整页截图
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 网络拦截
```javascript
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 引用:
```bash
$ 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)
模拟真实用户从打开页面到完成业务流程的完整路径:
```javascript
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 意外变化:
```javascript
// 基线截图
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 采集页面加载指标:
```javascript
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 浏览器,执行操作并截图返回验证结果!