AI 写代码越来越快,Web 测试为什么更需要一只“猴子”?
那些用户总能踩中的玄学 Bug,本质上不是玄学
很多 Web 项目现在都有一个明显变化:
页面上线越来越快,组件越来越复杂,AI 生成代码越来越多。
以前一个后台页面,可能就是表单、列表、详情、提交按钮。
现在不一样了。
低代码画布、拖拽排序、复杂筛选、动态表单、权限联动、文件上传、AI 对话框、流式输出、富文本编辑器、移动端适配,全都挤在一个 Web 应用里。
功能看起来都能跑。
但一上线,问题总会从奇怪的地方冒出来。
用户粘了一串 emoji,表单崩了。 浏览器窗口一缩放,布局乱了。 拖拽排序拖到一半,页面卡死了。 接口还没返回,用户连续点了几次提交,数据重复了。 测试环境怎么都复现不了,线上用户偏偏能踩中。
这类问题最烦的地方,不是 bug 难修。
而是你根本不知道它是怎么触发的。
没有稳定步骤。 没有明确输入。 没有固定路径。 只有一张截图、一段日志、一句“我就随便点了几下”。
所谓玄学 Bug,大多数不是玄学。
它只是没有出现在你的测试路径里。
目录
线上玄学 Bug,往往不是用户奇葩,是测试路径太乖
Web 风险正在从功能正确,转向交互稳定
Monkey 测试的价值,不是乱点,而是扩大事件空间
gremlins.js 的核心,不是工具名,而是测试结构
真正落地时,Monkey 测试不能停在“跑一下”
AI 时代的测试,会越来越像行为空间探索
一、线上玄学 Bug,往往不是用户奇葩,是测试路径太乖
很多测试用例,其实默认了一个前提:
用户会按照产品设计的路径操作。
登录之后点菜单。 进入页面后填表单。 必填项按顺序输入。 上传文件符合格式。 弹窗出现后认真点确认或取消。 接口请求没回来之前,不会重复点提交。 拖拽操作开始后,会完整拖到目标位置。
这套思路适合验证主流程。
但真实用户不是这样用系统的。
真实用户会连点。 会误触。 会复制一大段奇怪字符。 会在请求没返回时重复提交。 会在弹窗出现时按浏览器返回。 会在上传一半时刷新页面。 会把浏览器缩放到 80%。 会在手机横竖屏之间来回切。 会用低端设备、弱网环境、浏览器插件,把你的页面带进一些没人设计过的状态。
很多所谓“线上偶现”,其实都来自这些状态。
它不是完全随机。
它只是没有被测试覆盖。
传统测试更擅长回答一个问题:
按预期流程走,功能是不是对的?
但线上真实世界还会追问另一个问题:
用户不按预期流程走,系统会不会崩?
这就是 Web Monkey 测试存在的意义。
它不是替代功能测试,而是补上另一块能力:让系统提前面对那些“不讲武德”的用户行为。
二、Web 风险正在从功能正确,转向交互稳定
以前测 Web 页面,很多团队重点看功能是否正确。
按钮能不能点。 表单能不能提交。 列表能不能查询。 权限能不能控制。 接口返回是否符合预期。
这些当然要测。
但现在很多 Web 系统的问题,已经不只是“某个功能不对”,而是“多个交互叠在一起之后,系统状态乱了”。
尤其是下面几类系统,最容易出现这种问题。
系统类型 | 高风险交互 | 常见问题 |
|---|---|---|
中后台系统 | 表单、弹窗、权限、分页、筛选 | 校验失效、状态错乱、白屏 |
低代码平台 | 拖拽、画布、组件配置 | 拖拽卡死、组件丢失、配置污染 |
SaaS 系统 | 多角色、多租户、多页面联动 | 权限串页、缓存异常、数据错位 |
数据看板 | 图表、筛选、时间区间 | 渲染卡顿、筛选条件错乱 |
AI 应用前端 | 流式输出、多轮对话、文件上传 | 状态中断、重复请求、展示错乱 |
这些问题表面上是前端 bug,本质上往往是事件序列问题。
不是某一个按钮错了。
而是用户在某个中间状态下,连续触发了一组你没有预料到的事件。
比如:
单独看每一步,都不一定有问题。
连起来,就可能出事。
这也是为什么很多 bug 本地复现不了。
开发和测试复现时,往往会按“理性路径”操作。
但线上用户的动作是交错的、急躁的、不完整的。
这类问题靠人工探索,很容易漏。
因为人会下意识走合理路径。
机器反而不会。
三、Monkey 测试的价值,不是乱点,而是扩大事件空间
很多人一听 Monkey 测试,第一反应是:
不就是随机乱点吗?
如果只是乱点,价值确实有限。
真正有工程价值的 Monkey 测试,核心不在“乱”,而在三件事:
让随机行为覆盖更多交互类型。 让异常现场可以记录和复现。 让问题可以进入持续集成流程。
gremlins.js 就是 Web 端比较典型的 Monkey 测试工具。它是一个 JavaScript Monkey Testing Library,可以在浏览器或 Node.js 场景下使用,通过模拟随机用户动作来检查 Web 应用的健壮性。官方说明里提到,它可以随机点击页面、填写表单、滚动窗口、触发键盘输入等,用来暴露 JavaScript 错误或应用失败。([GitHub][1])
但要注意一点:
Monkey 测试和 Fuzz 测试思路接近,但不能简单画等号。
Monkey 测试更偏 UI 行为随机化。 Fuzz 测试更偏输入数据变异和边界探索。 放到 Web 测试里,两者可以结合,但关注点不完全一样。
比如:
对 Web 应用来说,最有价值的不是单纯随机点击,而是把随机行为和边界输入结合起来。
这时它能发现的问题就不只是“点崩了”,还包括:
表单校验是否健壮。 组件状态是否一致。 前端异常是否被捕获。 页面是否存在白屏风险。 接口是否会被重复提交。 低性能设备下是否会卡死。 特殊字符是否会击穿渲染逻辑。 异步请求中断后是否能恢复。
一句话:
自动化测试解决的是已知路径反复验证,Monkey 测试解决的是未知路径提前暴露。
这两类测试不是替代关系。
而是互补关系。
四、gremlins.js 的核心,不是工具名,而是测试结构
gremlins.js 的设计思路比较清晰。
它不是简单写一个脚本去乱点,而是把随机测试拆成了三类角色:
Species:负责制造随机行为。 Mogwai:负责观察错误和性能。 Strategy:负责控制执行节奏。
这比“随便写个脚本乱点页面”更接近工程化。
Species:负责制造随机行为
官方版 gremlins.js 提供的基础 species 主要包括:
Species | 作用 |
|---|---|
clicker | 在页面可视区域随机点击 |
toucher | 在页面可视区域模拟触摸 |
formFiller | 随机填写表单、勾选复选框、选择下拉项 |
scroller | 随机滚动页面 |
typer | 随机触发键盘输入 |
这些能力看起来不复杂,但组合起来就会产生大量边界状态。
比如:
用户滚动到页面底部后点击。 用户在输入框里随机输入后马上切走。 用户在弹窗出现时继续敲键盘。 用户在组件还没渲染完成时触发点击。 用户在列表分页切换时继续滚动页面。
这类问题靠人工设计用例,很难穷举。
但 Monkey 测试可以用较低成本扩大探索范围。
Mogwai:负责盯现场
光有随机行为还不够。
测试系统必须知道什么时候算出问题。
gremlins.js 里的 Mogwai 就是观察者。
官方内置的 Mogwai 包括:
Mogwai | 作用 |
|---|---|
alert | 防止 alert 阻塞测试 |
fps | 监控浏览器 FPS |
gizmo | 错误过多时停止测试 |
这很关键。
Monkey 测试不是为了把系统搞坏。
它的目标是提前发现:
有没有 JS Error。 有没有页面卡死。 有没有性能突然下降。 有没有弹窗阻塞。 有没有错误持续放大。 有没有异常状态无法恢复。
Strategy:负责控制节奏
随机也要有策略。
gremlins.js 支持不同的执行策略,例如默认分布执行、一起执行、按 species 执行等,也可以设置执行次数和延迟。([GitHub][1])
这意味着你可以根据系统特点调整测试方式。
表单系统,提高 formFiller 权重。 长页面系统,增加 scroller。 移动端 H5,引入 toucher。 复杂后台系统,控制点击范围和执行次数。 低代码平台,扩展拖拽、焦点、画布类操作。
随机不是目的。
可控随机,才有测试价值。
五、真正落地时,Monkey 测试不能停在“跑一下”
很多团队试工具最大的问题是:
跑了一次,看着页面乱点,觉得挺有意思,然后就没有然后了。
这不是工程化。
Monkey 测试要真正落地,至少要补齐 6 个环节。
1. 不要直接跑生产环境
Monkey 测试会触发大量随机行为。
它可能提交表单。 可能修改配置。 可能触发删除动作。 可能上传文件。 可能重复请求接口。 可能污染业务数据。
所以落地时要放在隔离环境里。
更适合的环境包括:
测试环境 预发环境 Mock 数据环境 沙箱租户 只读账号 低权限账号 隔离数据库
尤其是中后台系统,必须控制账号权限和数据影响范围。
否则不是测试系统。
是在制造事故。
2. 不要把官方能力和自定义扩展混在一起写
gremlins.js 官方基础能力已经能覆盖点击、滚动、表单、键盘、触摸。
但现代 Web 应用里,很多高风险交互并不是官方默认开箱即用的。
比如:
拖拽排序。 文件上传。 窗口缩放。 焦点切换。 复制粘贴。 日期选择。 富文本输入。 画布操作。 弹窗中断。
这些更适合写成自定义 species。
比如日期时间输入,就是一个很典型的扩展点。
function dateTimePickerGremlin({ logger, window }) { returnfunction attack() { constdocument = window.document; const inputs = Array.from( document.querySelectorAll( 'input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]' ) ); if (inputs.length === 0) return; const element = inputs[Math.floor(Math.random() * inputs.length)]; const year = 2020 + Math.floor(Math.random() * 11); const month = String(1 + Math.floor(Math.random() * 12)).padStart(2, '0'); const day = String(1 + Math.floor(Math.random() * 28)).padStart(2, '0'); const hour = String(Math.floor(Math.random() * 24)).padStart(2, '0'); const minute = String(Math.floor(Math.random() * 60)).padStart(2, '0'); let value = ''; if (element.type === 'date') { value = `${year}-${month}-${day}`; } if (element.type === 'time') { value = `${hour}:${minute}`; } if (element.type === 'datetime-local') { value = `${year}-${month}-${day}T${hour}:${minute}`; } if (element.type === 'month') { value = `${year}-${month}`; } if (element.type === 'week') { const week = String(1 + Math.floor(Math.random() * 52)).padStart(2, '0'); value = `${year}-W${week}`; } element.value = value; element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); logger.log(`dateTimePickerGremlin input ${value}`); }; }这个 gremlin 看起来很小,但它能覆盖很多真实风险:
日期范围校验是否严谨。 开始时间和结束时间联动是否正确。 组件依赖 input 事件还是 change 事件。 前端状态和 DOM 值是否一致。 后端接口能否处理边界日期。 周维度、月维度、日期维度是否兼容。
很多测试写用例时不会覆盖这些细节。
但用户会。
尤其是后台系统里,日期几乎贯穿所有业务:订单、账单、报表、审批、排班、投放、统计。
日期一乱,后面的业务链路都会跟着乱。
3. 要和 Playwright 结合,而不是孤立运行
gremlins.js 可以单独跑。
但如果只是在浏览器里跑一下,很难沉淀成团队能力。
更好的方式是和 Playwright、Cypress 这类自动化框架结合。
官方文档里也给了 Playwright 集成方式:可以通过page.addInitScript加载 gremlins 脚本,然后在页面里执行gremlins.createHorde().unleash()。([GitHub][1])
示例:
const { test, expect } = require('@playwright/test'); test('run web monkey test', async ({ page }) => { const errors = []; const failedRequests = []; page.on('pageerror', error => { errors.push(error.message); }); page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); page.on('requestfailed', request => { failedRequests.push(request.url()); }); page.on('response', response => { if (response.status() >= 500) { failedRequests.push(`${response.status()} ${response.url()}`); } }); await page.addInitScript({ path: './node_modules/gremlins.js/dist/gremlins.min.js', }); await page.goto('https://your-test-env.example.com'); await page.evaluate(() => { return gremlins.createHorde({ strategies: [ gremlins.strategies.allTogether({ nb: 1000 }), ], }).unleash(); }); expect(errors).toEqual([]); expect(failedRequests).toEqual([]); });这段代码的重点不是“能跑”。
重点是把随机测试纳入自动化框架。
这样你能拿到:
Console 错误 JS 异常 接口失败 5xx 响应 截图 视频 Trace 执行日志 失败上下文
这些东西才是真正能帮助定位问题的材料。
没有这些材料,Monkey 测试只会变成一句很尴尬的话:
“刚才好像崩了一下。”
研发听完也不知道怎么修。
4. 随机测试必须尽量可复现
不可复现的 bug,对研发来说价值会打折。
所以 Monkey 测试一定要记录现场。
至少要记录:
测试 URL 账号角色 浏览器类型 视口大小 随机种子 执行策略 事件数量 Console 日志 接口错误 截图视频 最后一次操作序列
gremlins.js 支持给随机数生成器设置 seed,用来让攻击过程可重复。([GitHub][1])
示例:
await page.evaluate(() => { const horde = gremlins.createHorde({ randomizer: new gremlins.Chance(1234), strategies: [ gremlins.strategies.allTogether({ nb: 1000 }), ], }); return horde.unleash(); });这样做的价值很直接:
这次崩了,下次还能用相同 seed 尝试复现。 研发修完后,也能用同一组随机序列做回归。 CI 失败时,不是只有一张截图,而是有完整上下文。
随机测试不是不能复现。
前提是你把随机过程记录下来。
5. 不要一上来就全页面乱打
Monkey 测试不是越猛越好。
更好的做法是分层推进。
可以先从低风险页面开始:
首页 列表页 查询页 只读详情页 Mock 表单页
稳定后再进入高风险页面:
配置页 审批页 拖拽页 上传页 低代码编辑器 AI 对话页 复杂运营后台
不要一开始就对复杂业务页面释放全部 Gremlin。
测试也要灰度。
6. 失败标准要提前定义
Monkey 测试不能只看“有没有崩”。
还要定义更具体的失败标准。
比如:
出现未捕获 JS 异常,失败。 出现 5xx 接口错误,失败。 页面白屏,失败。 关键 DOM 消失,失败。 URL 意外跳转,失败。 登录态丢失,失败。 FPS 长时间低于阈值,失败。 核心请求重复提交,失败。 控制台出现指定错误关键字,失败。
这才是工程化测试。
不是放一只猴子进去乱跑,而是给它装上监控、边界和裁判。
没有失败标准的 Monkey 测试,最后只会变成热闹。
有失败标准的 Monkey 测试,才会变成质量资产。
六、AI 时代的测试,会越来越像行为空间探索
AI 生成代码之后,很多团队的研发速度确实变快了。
但速度变快,并不等于质量风险变小。
以前前端代码大多是人一行行写出来的,开发至少知道每个交互大概怎么来的。
现在很多代码来自 AI 补全、组件生成、低代码配置、模板拼装。
短期看,功能出来更快。
长期看,系统里会出现更多“看似能跑,但边界没想清楚”的代码。
尤其是 Web 前端。
AI 很擅长生成一个看起来完整的页面。
但它不一定知道:
用户会不会连点。 接口失败后页面怎么办。 弹窗关闭时状态是否清理。 表单输入特殊字符是否安全。 流式输出中断后能否恢复。 重复提交会不会造成脏数据。 移动端横竖屏切换是否会破坏布局。 组件卸载后异步回调是否还在更新状态。
这些问题,不是靠“页面能显示”就能判断的。
它需要测试从功能验证,升级到行为探索。
AI 写代码越快,测试越不能只盯功能正确,还要验证系统在乱序、异常、极端输入下能不能稳住。
这也是未来测试工程师能力变化的方向。
只会点页面,不够。 只会写固定用例,也不够。 只会跑自动化脚本,还是不够。
更重要的是能设计测试策略:
哪些路径用自动化回归。 哪些输入用数据驱动。 哪些边界用 Fuzz 思路。 哪些交互用 Monkey 测试。 哪些异常用混沌测试。 哪些问题进入线上监控和反馈闭环。
对不同阶段的人来说,这件事的价值不一样。
在校生要先理解:
测试不是低门槛点点点,而是软件工程里的质量保障能力。
初级工程师要会落地:
能把 Playwright、接口日志、Console 监控、截图视频串起来。
中级工程师要能设计体系:
知道什么时候用用例回归,什么时候用随机探索,什么时候接入 CI,什么时候做稳定性巡检。
Web Monkey 测试不是银弹。
它不能替代功能测试。 不能替代接口测试。 不能替代代码 Review。 不能替代业务规则验证。
但它能补上很关键的一块:
那些你没想到、但用户一定会做的事。
如果你的系统里,有一类 bug 经常只能靠用户反馈才能发现,那它很可能不是“玄学”。
它只是还没有被你的测试体系提前踩出来。
