# 让 agent 点进跨域 iframe：chrome-use 啃下这块硬骨头

给 AI agent 接一个浏览器，开局都很顺：打开页面、读内容、填个搜索框。真正卡死人的，是那种**藏在跨域 iframe 里的表单**——Google Payments 的收款资料、各种结账组件、KYC 控件。agent 能把里面的字读出来，能把值填进去，**就是点不动那颗「保存」按钮**。读得到、办不成。

![一个 agent 想伸手点进「窗口里的窗口」，结果点了个寂寞](https://img.leeguoo.com/media/398a3962-c4fd-4f9a-bf8d-ae19aa2dd257/hero.png)

这篇记一次把这道坎啃下来的过程。主角是 [chrome-use](https://github.com/leeguooooo/chrome-use)——一个用 Rust 写的、给 agent 用的浏览器自动化 CLI，直接驱动你**真正登录着**的那个 Chrome，不用 Playwright、不开 headless。

项目主页：https://chrome-use.leeguoo.com/

## 为什么跨域 iframe 这么难

普通页面好办：抓可访问性树、拿元素引用、点就完了。但跨域 iframe（比如 `adsense.google.com` 页面里嵌了一个 `payments.google.com` 的 iframe）会一次性踩中三个雷：

1. **选择器进不去**。同源策略下，你在外层文档跑的 CSS 选择器、`eval` 全都摸不到 iframe 内部的 DOM。`document.querySelector` 在这里是瞎的。
2. **滚动落空**。你以为在滚页面，其实能滚的是 iframe 内部那个滚动容器；wheel 事件发给了外层文档，里面纹丝不动——目标那一行永远在「屏幕外」，连看都看不到。
3. **只能盲点坐标**。前两条逼着你退回到「截图 + 猜像素坐标」，而那是最不精准、最容易点错相邻字段的方式。在一个改的是**全局支付资料**的表单上，点错一下的代价不小。

## chrome-use 的底子：给 agent 的是「引用」，不是 HTML

在讲怎么破之前，得先说它的基本盘——这也是它和「把 HTML 喂给模型」那一派的根本区别。

![把一坨吓人的 HTML 换成 @e1 @e2 @e3 这样的干净引用](https://img.leeguoo.com/media/201c5b38-f3ff-4aec-ab93-b26c5d77f494/refs.png)

chrome-use 不把页面源码丢给 agent，而是抓一棵**可访问性树快照**，每个可交互元素配一个紧凑引用：

```
- textbox "邮箱" [ref=e2]
- listbox "国家/地区" [ref=e60]
- button "保存" [ref=e41]
```

agent 直接对引用动手：`fill @e2 "..."`、`click @e41`。一页大概 200–400 token，而不是一整屏 DOM 噪声。这套引用机制，正是后面能穿透 iframe 的前提——只要快照能「看见」iframe 里的节点，引用就能拿到。

## 三道坎，一道一道过

**第一坎:让快照看见 iframe 里的东西。**
让可访问性树穿透跨域 iframe，把里面的节点也带上引用。修好之后，`snapshot` 直接列出来：

```
- textbox "电话号码 (可选)" [ref=e59]
- listbox "国家/地区代码：日本 (+81)" [ref=e60]
```

——选择器进不去的地方，引用进得去。

**第二坎:让滚动作用在 iframe 的滚动容器上。**
不再把 wheel 一股脑发给外层文档，而是滚真正该滚的那个容器。下面的表单行终于能滚进视野，引用也才取得到。

**第三坎(最硬):跨域 iframe 里那颗「已启用」的提交按钮，点了没反应。**
这一关最折磨人，因为它**看起来全对**：

- 用真实键击把号码填进去了，`get value` 一读，确实在；
- 「保存」按钮该亮的时候亮了（填入合法值前它是 disabled，填完才出现）；
- 然后 `click @e41`——表单纹丝不动。`find text "保存"`？跨域取不到。聚焦后敲 Enter / 空格？还是没反应。

对上了，又哪哪都不对。根因是：跨域 iframe 里那种 Material/框架按钮，对**合成点击**不买账；而且 `fill` 只改了输入框的值、没派发框架要的 `input`/`change` 事件，表单自己以为「没变化」，于是保存按钮要么禁用、要么点了等于没点。

破法分两半：填值改走**真实键击**（每个字符都触发真事件，框架才认）；点击则要对 iframe 内的内容节点派发一套**真实的鼠标/键盘激活**，而不是糊一个 `click()` 上去。

## 收尾:点进去,存下来

![戴着派对帽的 agent 把手伸进 iframe，成功按下 SAVE，绿色对勾 saved](https://img.leeguoo.com/media/0b45b5f8-4e78-439c-8017-08335e823848/fixed.png)

三道坎过完，整条链路就通了：**打开 → 滚到目标行 → 快照取引用 → 真实键击填值 → 按下保存**。那种「能读、办不成」的死局，到此为止。

## 给同样在做 agent 浏览器自动化的人，几条用血换的经验

- **优先用可访问性引用，别默认截图点坐标**。一旦快照能看见 iframe，引用永远比猜像素稳。截图留给 canvas/WebGL 那种真没结构的场景。
- **跨域 iframe 是道明确的边界**。选择器和 `eval` 到此为止，要么让工具把 a11y 树穿透进去，要么你就只剩盲点。
- **测「能不能提交」，而不只是「能不能填」**。值填进去 ≠ 框架收到了。`fill` 不派发事件这种坑，只有你真去点保存才会暴露。
- **能用真实登录的浏览器，就别用 headless**。登录态、cookie、扩展全都现成，还没有自动化指纹——这也是 chrome-use 走「驱动你自己的 Chrome」这条路的原因。

## 试试

```bash
curl -fsSL https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.sh | sh
```

仓库在 [github.com/leeguooooo/chrome-use](https://github.com/leeguooooo/chrome-use)。我一直在做这类「用你自己的订阅、把 agent 接到真实浏览器/设备上」的工具，进展都发在 [X @leeguooooo](https://x.com/leeguooooo)。

相关文档：[chrome-use · 填表与操作](https://chrome-use.leeguoo.com/interacting.html)、[安装](https://chrome-use.leeguoo.com/install.html)。
