自动化的隐形陷阱:你以为在操作 A 页面,其实在 B 页面
一、症状:所有选择器都返回空
做浏览器自动化时遇到一个很典型的坑:脚本按流程点按钮、填表单,前面几步都正常,到某个弹窗就卡住——查 `document.querySelectorAll('textarea')` 返回的元素尺寸是 `0x0`,`execCommand('insertText')` 返回 false,`focus()` 之后 `activeElement` 还是 `body`。
第一反应通常是"这个输入框是假元素"或者"组件用了特殊实现"。于是我按顺序排查了三条路:
- 是不是不可见? 打印 `offsetWidth x offsetHeight`,确实是 0x0。但页面上明明能看到输入框。
- 是不是在 iframe 里? 遍历所有 `iframe`,数量为 0。
- 是不是在 Shadow DOM 里? 递归遍历所有元素的 `shadowRoot`,一个都没有。
三条路都堵死。弹窗就在屏幕上,文字清晰,但 DOM 里找不到。
二、真正的原因:上下文跑偏了
最后我打印了一次 `location.href`,发现执行上下文根本不在我以为的页面上——它停在另一个标签页。
也就是说:前面所有"元素不存在"的结论都是假的。选择器没错、元素没变、弹窗也没坏,只是我在另一个页面上找它。
这种事情在单页应用里特别容易发生:自动化脚本点了某个链接,浏览器打开新标签页,但执行上下文还留在旧标签上。脚本继续在旧页面找新页面的元素,当然是空的。
更麻烦的是它的表现很"合理":`querySelectorAll` 不报错,只是返回空数组;`focus()` 不报错,只是没效果;`execCommand` 不报错,只是返回 false。没有异常,只有静默失败,所以很容易往"元素实现特殊"的方向排查。
三、更隐蔽的一层:同名元素
值得注意的是,旧页面里确实有一个 `textarea`——它是另一个隐藏的表单控件,尺寸 0x0。这就制造了更深的误导:在错误的页面上,恰好有一个同类型的元素存在,于是"元素存在但不可见"看起来像是一个合理的技术难题,而真正的答案——"这不是同一个页面"——被完全掩盖了。
这类"假阳性"比彻底找不到更难查。彻底找不到会立刻让人怀疑上下文;找到一个但行为异常,则会把人引向组件实现的深坑。
四、以后怎么避免
1. 每个关键操作前打印一次当前地址
成本几乎为零,但能直接排除上下文问题:
```js console.log(location.href) ```
我这次绕了二十多分钟,而这行代码本该是第一步。
2. 改变排查顺序:先验证上下文,再怀疑元素
原来的顺序是"元素找不到 → 查不可见 → 查 iframe → 查 shadow DOM",全是往 DOM 结构的深处挖。更有效的顺序应该是:
``` 元素找不到? ├─ 当前页面是对的吗?(location.href) ├─ 有没有新开标签页?(window count / tab 列表) ├─ 元素真的可见吗?(offsetWidth) ├─ 在 iframe 里吗? └─ 在 shadow DOM 里吗? ```
把上下文检查提到最前面,能省掉后面所有的无效排查。
3. 多标签场景要显式指定目标
自动化框架一般都有"当前标签"的概念。多标签流程里,每次跨标签操作后都要重新确认绑定,不能假设上下文自动跟随。稳妥的做法是:每次操作前设置一次目标,操作后再验证结果,而不是依赖默认值。
4. 给"静默失败"加断言
这次最大的教训是:`querySelectorAll` 返回空数组不会抛异常。所以每个关键步骤后都应该有一个明确的断言——比如"填充后立刻回读长度,为 0 就报错"。如果一开始就有这个断言,问题会在第一时间暴露成"输入失败",而不是拖到"分析组件实现"。
五、小结
自动化脚本的失败通常分两类:
- 真失败:元素改版了、接口变了、权限没了;
- 假失败:上下文漂了、标签页错位、iframe 未切换、等待时机不对。
真失败需要改代码,假失败只需要一行诊断。而假失败的代价往往更高——因为它会伪装成技术难题,让人在正确的方向上做无用功。
判断依据很简单:当"元素存在但行为异常"时,先问一句"我看的是同一个页面吗"。
- 点赞
- 收藏
- 关注作者
评论(0)