自动化填表的两种失败:填了却没生效,和提交了却没反馈
一类问题,两个现场
用浏览器自动化去填登录框和发布表单,最近踩到两次「看起来失败其实成功」和「看起来成功其实没生效」。它们表面无关,根因是同一个:我们用脚本改了 DOM,但前端框架没有收到它期待的事件。人点一下按钮,浏览器会派发一整套 pointerdown、mousedown、focus、pointerup、mouseup、click;脚本改个 value,什么都不会派发。
把这两件事记下来,因为它们浪费的时间不在技术上,而在判断上。
现场一:六位验证码填进去了,点验证没反应
双因素登录页有六个 input[type=tel],每格一位。第一反应是用原生 setter 赋值,再派发 input 和 change:
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
inputs.forEach((inp, i) => {
setter.call(inp, code[i]);
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
});
检查 DOM,六格的值一字不差。点「验证」,按钮有响应,然后什么都没有:不跳转、不报错、验证码框还留在原地。
换真实键盘输入,问题立刻暴露:
for 第 i 格:
聚焦 input#m{i}
按下 code[i] 对应的数字键
结果第一格显示最后按下的那个数字,其余五格是空的。也就是说这六个格子的自动跳转是靠 keydown 事件驱动的,收到一次 keydown 才把焦点推到下一格;用 setter 一把填满,焦点永远停在第一格,框架认为你只填了一位。六个格子填一位,校验当然不通过。
改成逐格聚焦再按键:
for (let i = 0; i < 6; i++) {
聚焦第 i 格
按下第 i 位数字
}
回读六格拼起来正好等于验证码,点验证,跳转成功。
这里的两个判断错误
一是把「DOM 里有值」当成「表单里有效」。input.value 是 DOM 属性,框架的响应式状态是另一份数据,两者之间靠事件同步。事件派得不对,两边就分叉。
二是把「点击无反应」当成按钮坏了。按钮真的收到了点击,只是校验没通过。页面上没有错误提示,是因为很多 MFA 组件把错误提示做成了防枚举设计,故意不告诉你哪儿错了。
现场二:文章提交成功,页面一动不动
博客编辑器的发表流程改版了。改版前提交成功会跳到 /processcomplete 页面,看到那个地址就知道成了。改版后:
- URL 不变,还停在编辑页
- 发表按钮依然是灰色(带
-no类名) - 页面没有任何提示
按页面判断,这是彻底的失败。于是重试。重试三次,三次都"失败"。
直到去看网络请求:
POST .../v1/blog/blogging
→ {"code":"HD.92300000","msg":"success","blogId":"01cb40d2..."}
每一次都成功了,于是账号里出现四篇同标题的文章,得手工删掉三篇。
按钮为什么一直是灰的
读一遍源码就明白了。提交函数的第一行是:
function newBlogSubmit() {
submitFlag = 1;
$('.write-blog-publish-btn').removeAttr("onclick");
$('.write-blog-publish-btn').addClass('write-blog-publish-btn-no'); // ← 先置灰
...
}
它进函数就先把按钮置灰防止连点,然后才做各项校验。校验失败会 return,但没有任何代码把按钮恢复。所以提交成功之后按钮照样是灰的,这个类名根本不是成功与否的判断依据。
两条可复用的判断原则
第一,判断结果看数据流,不看界面。表单提交这类操作,唯一可信的信号是请求的响应体。把 XHR 包一层,记录每次请求的 URL 和返回,比盯着页面跳转可靠得多:
const orig = $.ajax;
$.ajax = function (opt) {
const os = opt.success;
opt.success = function (d) {
记录(opt.url, JSON.stringify(d).slice(0, 200));
return os && os.apply(this, arguments);
};
return orig.call($, opt);
};
这一步在动手前做,成本很低;出事后补做,就得先面对一堆可能已经造成的副作用。
第二,重复提交之前先查列表。凡是「提交建对象」的操作(发文章、提 Issue、下单、建工单),失败重试前先去列表页确认上一条是否已经创建。这次四篇重复文章,本质是把幂等判断的工作交给了用户界面,而界面恰恰是最不可信的。
小结
这两次都不是技术难题,setter 和 dispatchEvent 的用法文档里都写着。真正的成本在于:一次把「没生效」误判成「按钮有问题」,一次把「已成功」误判成「没提交」,后者还真的写坏了数据。
自动化做表单,值得默认假设两件事:框架靠事件工作,界面不告诉你真相。
- 点赞
- 收藏
- 关注作者
评论(0)