元素找到了,点击却没生效:一次浏览器自动化排障实录
最近在维护一个浏览器自动化流程时,卡在一个看起来很简单的地方:登录时需要点「获取验证码」,脚本明明点了,短信却迟迟不来。抓包一看更怪——点击之后,没有任何发码请求离开浏览器,按钮文案也没进入倒计时。折腾了好几轮才定位到原因,写出来给同样在做自动化的人提个醒。
一开始的怀疑:被风控拦了
点击没效果、请求没发出去,第一反应很容易往「环境被识别、组件把请求吞了」上想。为此我还补测了很多变量:无头换有头、换登录名、换点击方式,结论始终一样——请求就是不发。在没有证据之前,这类归因其实站不住:我观测到的只有「点了,但请求没出去」,中间到底是哪一环断的,并没有看到。
真正的原因:点中的是外壳,不是控件
转机来自把点击实际命中的元素打出来看。页面上文字等于「获取验证码」的元素不止一个:除了真正的链接本体,还有包在它外面的几层容器,容器的文字内容恰好也包含这几个字。我最初的定位策略是「文字包含目标词、离页面中心最近」,选中的是一个面积两万多像素的大容器——点它的中心,坐标落在链接旁边的空白处。页面当然会收到这次点击,但收到点击的不是控件,自然什么都不会发生。
真正的发码控件本体,其实只是一个 38×15 像素的小文本链接。
解法:同名元素里选面积最小的那个
改定位策略:候选元素要求文字与目标完全相等,再按包围盒面积从小到大排序,取第一个:
function findControl(page, text) {
return page.evaluate((t) => {
const els = [...document.querySelectorAll('div,button,a,span,li')]
.filter(e => (e.innerText || '').trim() === t && e.offsetParent !== null);
if (!els.length) return null;
els.sort((a, b) =>
a.getBoundingClientRect().width * a.getBoundingClientRect().height -
b.getBoundingClientRect().width * b.getBoundingClientRect().height);
const r = els[0].getBoundingClientRect();
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
}, text);
}
换上这个定位后第一次点击,发码请求立刻发出,按钮进入 57 秒倒计时,短信秒到。后面又重复了几轮,稳定复现。
三条经验
第一,用副作用验证点击,别只看"点过了"。 点击动作执行成功不等于业务生效。每次关键点击后,检查至少一个可观测的副作用:网络请求有没有发出、按钮文案/状态有没有变、页面有没有跳转。没有副作用,就当没点中。
第二,定位优先选控件本体,警惕大容器。 现代前端页面里,文字相同的元素往往有好几层:真正的按钮、它的外层容器、甚至整行布局。用「包含文本」匹配很容易选到外壳;「完全相等+面积最小」是个简单有效的启发式,尤其适合文字链接型的小控件。
第三,归因和观测分开记录。 这次我先入为主地把「请求没出去」归因于平台拦截,白白多测了好几轮无关变量。先把观测事实(点了什么、命中了什么、网络里有什么)钉死,再谈推测,排障会快得多。
结语
有意思的是,修好定位之后回头看,整条链路其实一直是通的:账密能提交、验证码能发能验。问题不在链路最难的那一环,而在最不起眼的第一下点击。自动化里这类「元素在、点击空」的小坑非常常见,希望这篇实录能帮你少绕一圈。
- 点赞
- 收藏
- 关注作者
评论(0)