ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AutoDog填充狗:表单自动填充与动态表单处理实战指南

AutoDog填充狗:表单自动填充与动态表单处理实战指南

简介:填充狗(AutoDog)是一款面向谷歌Chrome浏览器的表单自动填充插件,当前版本为3.0.0,主要针对注册、登录、购物结算等需要反复输入用户名、密码、地址等个人信息的网络场景,旨在减少重复劳动,并降低频繁复制粘贴带来的出错概率。压缩包大小为204KB,共包含66个文件,主要类型包括JavaScript功能脚本、HTML弹窗页面、manifest插件配置、图标资源、许可证与说明文档,同时附带Git仓库对象数据,便于查看版本演进与二次开发;其中内容脚本、弹出页面与后台脚本共同构成插件的运行核心。目前已有2759人学习下载,属于轻量实用的浏览器效率工具。通过这份完整源码,读者能深入理解表单字段识别、自动填充流程、加密存储以及多账号切换的具体实现方式;也可直接修改或扩展成满足自身需求的插件,适合前端开发者与Chrome插件入门者参考使用。

1. 表单自动填充为什么值得自己把规则握在手里

表单自动填充这个需求,听起来就一句话“省时间”,但真要在几十个字段的注册页、发布后台和测试环境里反复敲键盘,你会明白手填是最大的浪费。AutoDog填充狗作为一款表单自动填充Chrome插件,做的事情很直接:在页面上把字段规则配好,下次打开相同结构的页面,值自动写入、按钮自动点击,3.0.0版本把规则引擎和动态表单处理又向前推了一截,适合开发、测试和运营同学在本地重复性录入场景里使用。它不依赖云端账号同步,规则文件由本地管理,对隐私敏感的环境更友好。下面这套配置和使用思路,是我在多个实际页面上打磨出来的,从原理到踩坑都覆盖到了。

2. AutoDog填充狗的协作架构:background、popup 和 content script 各管哪一段

2.1 插件三角色:没有黑匣子,只剩三条消息通路

很多第一次打开扩展源码的人会被目录结构劝退,实际上它没有神秘的黑匣子,就是三个角色通过消息机制互相配合。AutoDog填充狗在 Chrome 扩展的标准模型下运作:background 负责全局状态和菜单事件;popup 提供配置界面,你在这里新建、编辑和导入规则;content script 被注入到匹配的页面里,负责找字段、填值和触发事件。三个角色之间靠 chrome.runtime.sendMessage 和 chrome.tabs.sendMessage 走消息,没有这一层通信,新规则就传不到页面上。

我一般会在配置完成后检查 background 的 console,很多“点了没反应”的情况,其实都是消息传到了 background 但回调里报错。注意 manifest 版本如果是 3,background 从常驻页面变成了 service worker,不能依赖全局变量保存跨标签页状态,要用 chrome.storage 或 storage.session。AutoDog 的 3.0.0 版本如果支持了 storage.session,那么从 popup 下发临时状态给当前标签页就是瞬时动作,不再受事件循环休眠影响。

{ "manifest_version": 3, "permissions": ["storage", "tabs"], "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html" }, "content_scripts": [ { "matches": ["<all_urls>"], "js": ["content.js"], "run_at": "document_idle" } ] }

这是插件目录里 manifest 文件的最小骨架。matches 决定了 content script 注入哪些页面,all_urls 适合本地测试环境,正式用建议收敛到公司内部域名。run_at 设成 document_idle,等页面大部分脚本跑完再注入,能减少和前端框架抢 DOM 的时间。如果你配置完打开页面,发现字段没被填上,先看 match 的 URL 白名单有没有把当前页面排除掉。

2.2 字段识别不靠猜:从优先级、标签文本到正则匹配的三级策略

AutoDog 的字段识别逻辑分三层,第一层是结构选择器,直接认 name、id、class,这种方式快而且稳定;第二层是文本匹配,读取 label 文本和 placeholder 提示文字;第三层才是 XPath 定位。我用的选择优先级是这样的:如果页面里的 input 既有 id 又有 name,以 id 为准;如果 id 是动态生成的随机串,就降到 label 文本匹配;如果页面是多层表格嵌套的老系统,XPath 反而最可靠。

参数里还有一个容易被忽略的值,叫 matchType,可设置成 exact、contains、regex 三种。很多新手在填自定义规则时全部选了 exact,但页面上 label 文本里带了星号或空格,匹配就失败。regex 更适合日期和银行卡一类有规律的值,但要在测试环境先验证,否则一个写错的正则可能把整张表单带偏。下面是一个简化后的规则文件结构,实际使用中我会按模块拆成多个规则块,而不是所有字段塞进一个数组。

[ { "name": "开户登记页", "urlPattern": "https://internal.example/accounts/create", "matchType": "contains", "fields": [ { "selector": "#companyName", "selectorType": "css", "value": "测试科技有限公司", "fillEvent": "input" }, { "selector": "label:text('联系电话')", "selectorType": "label", "value": "13800138000", "fillEvent": "input" }, { "selector": "XPath://input[contains(@class,'bank-card')]", "selectorType": "xpath", "value": "6222020200112233", "fillEvent": "change" } ] } ]

fillEvent 这个参数决定写入值后派发什么事件,是区分“填进去了”和“前端框架感知到值变了”的关键。React 和 Vue 这类框架如果只监听 input 或 change 事件,你用赋值形式把 value 直接写进去,框架是不知道的。AutoDog 在 3.0.0 里把 fillEvent 独立成一个字段,就是让使用者在不同前端框架间做显式选择。我个人的经验是:Vue 页面填 input 事件,React 页面大多也是 input,老式 jQuery 插件表单用 change 更稳。

你还需要留意 rules 文件里的预执行脚本,它会在填充前跑一小段 JavaScript,用来解锁禁用的字段、展开折叠面板或等待异步数据加载。这段脚本不是万能的,只做 DOM 状态修改,不要在里边发起异步请求等待回包,处理不好会把填充流程卡死。通常我只会写一两行移除 disabled 属性或点击“展开”按钮的代码,复杂逻辑放进后续版本单独设计的 pipeline 里。

3. 从零初始化第一版规则:安装、导入到实际填充的最小路径

3.1 本地加装插件与规则文件导入的完整步骤

第一次使用 AutoDog,不要直接上手配规则,先确定你是从本地目录加载还是通过打包后的 crx 格式安装。常见做法是:开发者自己用 Chrome 的“加载已解压的扩展程序”选择源码目录,日常使用的人则拿到分发的 crx 文件拖入扩展管理页。本地加载模式下修改代码后要在扩展管理页点刷新,否则改动不生效,这是最容易忽略的一步。

接着处理规则文件。AutoDog 支持 JSON 格式导入导出,我的习惯是先在 popup 界面把规则建好,再导出成文件,用 git 管理版本,这样改坏了可以回退。导入时有个细节:合并规则和覆盖规则是两回事。合并会把新文件里的规则追加到现有列表,覆盖则直接清空旧规则。如果你在别人的规则文件基础上改,导入时选错模式,可能把原有规则全部清掉。

# 在扩展目录下直接校验 JSON 格式,避免导入时报错 python3 -m json.tool ./rules/my-rules.json > /dev/null && echo "JSON 合法"

这行命令只做语法校验。导入失败的原因里,有个特别隐蔽的是 JSON 里带了 BOM 头,Windows 下另存的文本文件常带这个隐藏字符。我遇到过规则文件导入后列表里空白一片,用上面的命令检查语法也没问题,后来去掉 BOM 才正常。如果插件没有提供“去 BOM”选项,就自己用支持 UTF-8 无 BOM 的编辑器重新存一遍。

3.2 第一组可用配置:五个必调参数与对应场景

规则建好以后,重点看五个字段的取值。第一个是 selectorType,选 css 还是 xpath;第二个是 fillEvent,选 input 还是 change;第三个是 waitTimeout,页面出现异步加载时等待几秒;第四个是 retryTimes,字段第一次找不到时重试几次;第五个是 skipOnEmpty,当目标字段已经有值的时候要不要跳过填充。

这五个参数里,waitTimeout 和 retryTimes 是配合使用的。页面里的下拉选项是异步加载,比如填省份城市,input 先出现在 DOM 里,但选项数据要等接口返回。如果一上来就触发填充且触发了 change,选项还没渲染完,填进去的值会被框架重置。我一般把 waitTimeout 设为 3000ms,retryTimes 设为 3 次,覆盖大部分慢接口场景。

{ "name": "默认参数参考", "urlPattern": "https://internal.example/*", "waitTimeout": 3000, "retryTimes": 3, "fields": [ { "selector": "select[name='city']", "selectorType": "css", "value": "上海市", "fillEvent": "change", "skipOnEmpty": true } ] }

skipOnEmpty 这个参数看起来简单,实际能救你一次。在编辑页里,用户可能已经手动填了一个字段,AutoDog 运行时如果不判断空值,会把已有内容覆盖掉。我配置保存个人信息类页面时,几乎都给 skypeOnEmpty 或 skipOnEmpty 标为 true,只自动填空白字段。注意它判断“空”的方式是读 value 长度,而不是判断字段是否可见,所以一个被隐藏但仍保留值的字段不会被重复填。

3.3 规则运行时的执行顺序与观察手段

执行顺序不是按字段在页面里的物理位置来的,而是按 rules 数组里的声明顺序。这意味着你在前端页面看到“先填公司名再填税号”,规则里不一定也是这个顺序。如果字段之间有联动逻辑,比如填完税号才能加载出地区选项,声明顺序必须手动调整成“先税号后地区”。

观察填充时我会同时开两个面板:扩展 popup 里的执行日志和 Chrome DevTools 的 Elements。前者能告诉你规则有没有匹配、匹配到了几个元素、字段值是否写入成功;后者能确认前端框架有没有把值真正放到表单模型里。很多时候日志显示“写入成功”但页面提交仍然报错,问题就出在事件没有触发或者框架模型没同步。这时需要回看你选择的 fillEvent 是否匹配当前页面框架的监听事件。

// 在 DevTools Console 里验证事件绑定情况 // 如果返回结果是 input 或 change 的事件监听器列表 const el = document.querySelector('#companyName'); getEventListeners(el);

getEventListeners 是 DevTools 提供的内置方法,普通页面脚本里不可用。如果这个字段的事件监听列表里只有 focus 或 keyup,没有 input 和 change,说明 AutoDog 填充后框架并没有更新状态,需要检查当前页面是否用了特殊的事件委托方式。

4. 动态表单与复杂页面结构:iframe、Shadow DOM 和异步组件的调参点

4.1 iframe 内表单:跨上下文注入的边界条件

不少旧后台系统还在用 iframe 嵌套表单,AutoDog 要作用到内层页面,关键在注入配置。content script 的 matches 和 allFrames 这两项,必须允许在 iframe 子上下文里执行。如果插件只注入了顶层框架,你配的 selector 永远找不到内层字段。市面上很多“填充无效”的反馈,最后排查发现都是 host 页面嵌了三层 iframe。

{ "content_scripts": [ { "matches": ["https://internal.example/*"], "all_frames": true, "js": ["content.js"] } ] }

all_frames 设为 true 后,每个子框架都会运行一份 content script 副本。这里有个性能提醒:如果页面上挂着十几个 iframe,每次页面加载都会跑十几份脚本,各自监听消息、轮询 DOM。对低配办公电脑,开页面会明显变卡。我的建议是只在有表单的 iframe 上开 all_frames,同时对 content script 加一层判断,检测当前上下文是否为顶层窗口,不是的话就尽早返回。

同源 iframe 和跨源 iframe 的差异也要留意。同源场景下,父页面脚本可以直接拿到子页面的 DOM,AutoDog 也能直接操作。跨源 iframe 受同源策略限制,content script 可以注入进去,但如果插件里的设置项是通过 chrome.tabs.executeScript 往顶层上下文注入脚本再试图碰到子 iframe 的字段,那会直接被浏览器拦截。处理这类场景,我会把整个操作逻辑放进 content script 内部,在 iframe 的上下文里自行查找字段,而不是从顶层递归下去。

4.2 Shadow DOM 下的选择器穿透方案

现代前端组件库大量使用 Shadow DOM 封装内部结构,外层只暴露一个 host 标签。常规的 CSS selector 无法穿透 open 模式以外的 shadow boundary,你在规则里写 .el-input__inner 会匹配不到组件内部的输入框。AutoDog 的字段选择器里如果提供了 shadowDom 这个开关,开启后它会在匹配过程中自动递归进入 shadow root 查找字段。

{ "selector": "app-search .inner-input", "selectorType": "css", "shadowDom": true }

shadowDom 开关开启后的实际效果,是把 selector 解析成两个阶段:先在外层 DOM 里定位 host 元素,再在 shadow root 内继续用剩余选择器去匹配。我这里提一个容易踩的位置:如果你用的是 XPath,穿透 shadow root 需要额外的上下文切换能力,很多实现并不支持。所以在 Shadow DOM 场景下,我推荐优先用 CSS 选择器加 shadowDom 开关,而不是 XPath。

如果组件库使用的是 closed 模式,shadow root 对外不可访问,那就基本断绝了用选择器直接操作内部的路径。AutoDog 3.0.0 版本有没有额外的后门方法取决于实现,我不建议依赖任何 hack 方式去碰 closed shadow root。可行的替代动作,是用键盘事件模拟 Tab 键在表单元素间移动焦点。焦点在输入框上时,直接派发 input 事件也能让组件内部状态更新,但这种做法对每个组件的实现细节依赖很高,属于“能用但不优雅”的方案。

4.3 异步字段与防抖机制:从固定延时到轮询判断

动态页面的另一个身份是搜索数据驱动下拉。搜索框需要你先输入关键字、等待接口回包、再点下拉里某一项。如果 AutoDog 只是把值写进 input 然后触发 change,下拉列表根本没机会出现。这部分需要配合点击坐标或元素定位来选中下拉项。

我的做法是分两段:先填关键字,然后调用规则里的 waitForSelector 等待下拉项出现,再单独点击目标项。waitForSelector 和前面的 waitTimeout 不同,它等的是一个元素出现,而不只是固定 3000ms。假如下拉数据 1 秒就回来了,waitForSelector 能提前继续执行;假如接口挂了,等到 retryTimes 耗尽才会报告失败。固定延时方式只适合接口响应时间较稳定的内部系统,公网服务延迟波动大,硬等几秒容易浪费时间。

// 伪代码示例:轮询等待目标元素出现 // 实际由插件配置参数驱动,这里展示判断逻辑 function waitFor(selector, timeout) { const startedAt = Date.now(); return new Promise((resolve, reject) => { const timer = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(timer); resolve(el); } else if (Date.now() - startedAt > timeout) { clearInterval(timer); reject(new Error(`等待超时: ${selector}`)); } }, 200); }); }

轮询时间 200ms 是经验值。设置太快会让低端机器持续高频查询,占用主线程造成页面卡顿;设置太慢则会让整个填充流程变得拖沓。如果你的页面里同时存在多个异步组件,建议为每个组件单独设置 waitTimeout,不要都用默认值,避免一个慢接口拖垮全流程。

5. AutoDog填充狗避坑指南:规则失效、误填充和页面卡顿的典型现场

5.1 规则匹配到多个元素,填充结果时好时坏

现象:同一个规则文件在测试环境一切正常,到了生产页面部分字段填不进去,日志里显示 matched 2 elements。

原因:页面上存在隐藏的模板字段或列表行内组件,class 选择器粒度不够,导致一个选择器命中了多个元素,插件只能按第一个匹配项处理。

解决:把 selector 从单 class 改成层级限制,比如 .personal-info #phone 或者 .row-item input[name='phone']。如果用 XPath,就加上位置限定,path 的末尾 [1] 往往能帮你锁定目标。始终记住规则不要写得太宽松,宁可多花两步把定位写具体,也不要图省事。

5.2 写进了 value,但前端校验提示“字段为空”

现象:页面上的输入框里能看到值,点击提交时校验还是报错说该项为空。

原因:前端框架没有监听到对应事件,AutoDog 虽然把 value 写进了 DOM,但框架的模型数据还是旧的。fillEvent 参数没设对是直接原因。

解决:优先检查 fillEvent,React 和 Vue 页面用 input,老式 jQuery 页面用 change。如果改了事件类型还是不行,打开 DevTools 的 Elements 面板找字段的属性和事件响应,或者直接看框架的调试工具里有没有数据流。对付这种情况,我会在规则里把预执行脚本加一行,手动触发框架的校验方法。

5.3 插件在单页应用里只生效一次,路由切换后失联

现象:首次进入路由页面填充正常,点击菜单跳转到另一个表单页后,AutoDog 不执行了,刷新页面又恢复。

原因:单页应用切换路由时没有发生完整页面刷新,content script 只注入一次,URL 变化后实例可能被销毁,状态也丢失了。

解决:确认插件版本是否监听浏览器 history 变化事件。AutoDog 3.0.0 如果按常规方案实现,会通过监听 popstate 和 replaceState 来触发内容端重新扫描。但要注意的是,content script 如果已经因页面卸载被销毁,单靠监听不能恢复。此时要用 manifest 里的 matches 配合页面内的自定义事件,或者让 content script 定期检测 location 变化。我遇到这种情况,最常见的兜底方案是设置一个短延时后调用 window.location.reload,但这是下策,能不用就不用。

5.4 规则库无限膨胀,填充速度肉眼可见地变慢

现象:规则数量从几十条涨到上千条后,打开页面时填充动作明显迟滞,Chrome 标签页加载变慢。

原因:插件在 document_idle 阶段要遍历所有规则,对每条规则执行一次 DOMApi 查询和 URL 匹配,规则多了以后这部分操作线性增长。

解决:把规则按域名拆分到不同配置文件,每个配置只匹配一个站点的 URL。AutoDog 如果支持分组启停,就把不同环境的规则关掉,只保留当前要用的组。我现在使用的项目里,公司的汇总规则文件维持在 500 条以内,超出后我会拆分归档,长期不用的规则直接删掉。

5.5 修改规则文件后所有配置丢失

现象:改了规则文件重新加载扩展,popup 里的规则列表变成空,或者恢复到两天前状态。

原因:没有区分“规则的存储位置”。直接用 Chrome 扩展的 storage.local 保存时,重新加载扩展一般不会丢数据;但如果规则被保存在 background 的内存变量里,service worker 休眠后变量被回收,下次唤醒读取时只剩空数据。

解决:打开扩展管理页的“查看视图”里的 service worker,在 console 里执行检查。如果是 POST 项目,需要将 storage.local 里的内容备份成 JSON 后再修改文件。这里有件事要养成习惯,任何较大改动前先导出一次配置,存成带日期的文件,这是最有效的后悔药。

6. 把 AutoDog 填充狗接进日常开发流:批量校验、规则版本化和验证闭环

6.1 用测试数据表驱动多页面批量触发

实际工作中,我很少每次只填一个页面,而是把测试账号、测试手机号、假银行卡号等数据整理成一批 JSON 记录,按场景分批执行。AutoDog 的填充逻辑如果支持从外部变量注入,可以在规则的 value 里引用占位符,执行时再替换成不同数据。比如 value: "{bankcard}",配合预先设置好的数据字典。这样做的好处很明显:规则本身不变,不同场景只需要换数据。

填充完成后,要验证的不只是字段有没有值,而是整条表单链路是否成立。最简单的验证方法是打开一个预览空白页面,走一遍规则填充,再手动提交,确认后端收到的是预期数据。表单组件多的页面我会分三步验证:先看字段数量是否匹配,再看关键字段值是否正确,最后检查联动字段是否全部展开。这个流程多跑几遍,基本能把规则里的隐性 BUG 逼出来。

6.2 规则库合并冲突与回归自查的收尾习惯

当多个人维护同一个规则仓库时,合并冲突是家常便饭。我见过的规则冲突大多不是语法冲突,而是语义冲突:A 同学给某个按钮配置了 click,B 同学在同页面配置了不点击直接跳过。先把规则文件按页面模块拆开,能让你在 git 合并时更精确地看出差异。每次合入后一定要跑一遍回归清单,用最典型的三个页面确认填充没有退化。

版本管理上,我建议从规则文件归档的第一天就采用日期命名加说明注释的方式。比如 rules-2024-11-20.json,然后在文件内加一个注释字段说明本次改动目的。虽然 JSON 不支持注释,但可以在结构里保留一个 description 字段。这样做三个月后你会发现,你能准确说出每条规则的来历,而不需要打开 diff 一脸懵。

日常使用中最让我受益的是这个习惯:每次修改规则前,先导出一份当前可用配置作为基线,再动手。如果真的改坏了,直接导入基线就能恢复,对比两次文件差异也方便定位问题。做这个方向,值得投入的其实不是填数据本身,而是把规则变成可维护的资产。希望这些经验能帮你少走几个来回。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进