真实浏览器填表 + 系统性异常发现
核心原则
1. 提交成功 ≠ 测试通过。
点完提交只说明没抛异常。真正的测试是:提交后重新拉数据,比对实际存了什么。
2. 异常要能复现。 每条异常必须记录:操作步骤、实际值、期望值、复现次数。
「页面有点问题」不是结论,「第二次提交时 POST /api/save 返回 200 但字段没落库」才是。
3. 一次跑完采集,别边跑边查。 控制台报错、网络失败、UI 提示三类信息
要在同一个 session 里连续收集,中途查代码会丢失上下文。
4. 接口说成功但数据没进去,是最高频也最容易被漏的异常。
必须回查校验,不能只看 HTTP 状态码。
与 smart-form-filler(Web 逆向版)的区别
两者都会「填表单」,路径完全不同:
| 本技能 | smart-form-filler |
|
|---|---|---|
| 驱动方式 | 真实浏览器(Playwright) | 直接调接口 |
| 需要什么 | 只需网址(登录态可用 Cookie) | 需要逆向前端构建产物 |
| 强项 | 发现异常(前端/网络/UI 全链路) | 批量填数据(快、可脚本化) |
| 典型场景 | 回归测试、找 bug、提交报错排查 | 灌数据、迁移、造测试数据 |
| 产出 | 异常清单 + 复现步骤 | 落库数据 |
判断依据:要「测」→ 本技能;要「灌」→ 那个。
两者可串联:先用本技能测出异常,确认没有再用那个批量灌。
五步流程
Step 1 — 环境自检
cd <skill_dir>/web-form-e2e-tester && npm i
只依赖 playwright-core(只提供 API,不带浏览器),浏览器需自行探测:
--chrome 参数 > PLAYWRIGHT_CHROMIUM_EXECUTABLE > BROWSER_EXE >
chromium.executablePath() > 扫描 ms-playwright 目录 > 系统 Chrome/Edge。
全部失败会打印已尝试过的候选列表和四条解决办法。
Step 2 — 侦察:先看清表单长什么样
node scripts/recon.js --url <页面URL> [--cookie-file cookies.json] [--out recon.json]
它输出:
- 所有可见表单控件(input/select/textarea/日期/单选/下拉)
- 每个控件的 name/id/placeholder/label/当前值
- required 标记
- 页面的 console 报错与网络失败(进入表单前就先记下基线)
这一步的输出就是填数据的依据。 不要跳过直接猜字段。
Step 3 — 填表
node scripts/fill-and-submit.js --url <URL> --data data.json \
[--submit-button "保存"] [--cookie-file cookies.json]
data.json 格式:
{
"login": { "username": "u", "password": "p" },
"fields": { "姓名": "张三", "性别": "男" },
"submit": true
}
填写策略:
- el-select(Element UI)要先点开再点选项,不能直接 fill
- 日期控件优先用 input 直接填 + blur,比走日历点击稳
- 单选/复选要点,不是填
- 下拉是 <select> 的话用 selectOption
Step 4 — 异常采集(核心)
提交后等待并采集四类信号:
| 信号 | 来源 | 为什么重要 |
|---|---|---|
| console error / warning | page.on('console') |
JS 报错、Vue 异常 |
| page error | page.on('pageerror') |
未捕获异常,直接导致功能失效 |
| 请求失败 | page.on('requestfailed') |
网络层断、超时、跨域被拦 |
| 响应异常 | page.on('response') |
4xx/5xx、业务 code≠成功 |
| UI 报错提示 | .el-message--error 等 |
前端校验失败、业务拒绝 |
采集脚本把这些全部落到 findings.json,按发生顺序,不做过滤 ——
噪声也要留着,因为「同一步里既有警告又有报错」往往指向同一个根因。
Step 5 — 回查校验(不能省)
提交后重新请求该条数据的查询接口,逐字段比对:
提交前:{姓名: '', 性别: null}
提交后:{姓名: '', 性别: null} ← 接口 200 了,但没存进去
这是最容易被放过的一类 bug。判定:
- HTTP 200 + 业务 code=成功 + 字段确实变了 → 真成功
- HTTP 200 + 业务 code=成功 + 字段没变 → 假成功(严重)
- HTTP 200 + 业务 code=失败 → 业务拒绝,看 msg
- HTTP 4xx/5xx → 网络/权限问题
异常清单模板(按五个维度)
每条必须可复现、可定位:
### [A-前端校验] 必填项未拦住
- 步骤:清空「姓名」→ 直接点保存
- 实际:请求已发出,POST /api/save 200
- 期望:前端拦截并提示「请输入姓名」
- 复现:3/3 次
- 定位:xxx.vue 的 rules 里 patientName 的 required 未生效
五个维度依次查:
A. 前端校验
- 必填项是否真的拦得住(空值提交看是否发请求)
- 格式校验是否生效(身份证、手机号、金额)
- 长度/范围边界
- 校验提示文案是否与实际规则一致
B. 接口返回
- HTTP 状态码异常(4xx/5xx)
- 业务 code 非成功码
- 200 但 msg 里有错误文案
- 超时
C. 后端约束
- 前端能过、后端拒(两套规则不一致)
- 唯一性约束冲突
- 关联数据缺失导致的外键错误
- 并发/重复提交
D. 数据一致性
- 假成功:200 但字段没落库
- 部分落库:一批字段成功、一批失败
- 被意外清空:提交后原本有值的字段变空
- 编码不一致:同一字段两套取值规则
- 类型截断:小数精度损失、超长截断
E. 性能与体验
- 请求耗时超阈值
- 重复提交(连点按钮产生多次请求)
- 大表单卡顿
- 提交后无反馈(用户不知道成没成)
关键技巧
用假数据测,别用真数据
破坏性测试(提交、删除、清空)只在测试环境做。
本地造数据:主键类字段过校验位算法、时间链单调递增、枚举取字典内值。
同一操作跑多次
单次成功不代表稳定。关键路径至少跑 3 次,
间歇性 bug(竞态、缓存、时序)只在重复中暴露。
录 trace 便于复现
await context.tracing.start({ screenshots: true, snapshots: true });
// ... 操作 ...
await context.tracing.stop({ path: 'trace.zip' });
npx playwright show-trace trace.zip 可以逐帧回看,
比截图有用得多 —— 能看到提交瞬间的 JS 状态和网络时序。
控制台报错先分类
不是所有 console error 都是 bug。常见三类:
| 报错 | 含义 |
|---|---|
WebSocket connection failed |
独立服务未启动,不影响主流程 |
Failed to load resource: 502/504 |
后端服务异常,要追 |
Uncaught TypeError 等 |
真 bug,必须修 |
别把无关服务的报错当成表单 bug,先确认它是否影响当前操作。
报告产出
# 测试报告
## 环境
URL / 浏览器版本 / 测试时间 / 数据环境(测试 or 生产)
## 用例
| # | 场景 | 步骤 | 结果 |
## 异常清单
(按 A-E 五维分组,每条含复现步骤与定位建议)
## 未覆盖
(明确说明哪些没测到,不要让人误以为测全了)
「未覆盖」这一节不能省 —— 让人知道边界在哪,
比给一份看起来很全实际只测了两条的报告更有价值。
脚本
| 文件 | 用途 |
|---|---|
scripts/recon.js |
侦察:列控件、采基线异常 |
scripts/fill-and-submit.js |
填写 + 提交 + 采集异常 |
scripts/verify.js |
提交后回查比对 |
references/checklist.md |
异常排查清单与判定表 |
脚本不含任何目标站点信息,改 --url 与 data.json 即可复用。