模拟真人走查 · 上线前把产品真用一遍
> 前置:产品已能跑通、有可登录的测试账号、有可用的真实浏览器自动化(如 agent-browser,或任何能驱动真实浏览器的等价工具)。
> 本技能回答一个问题:这东西真让人用起来,会不会出问题?
>
> 本技能与「静态代码审查」的分工(别搞混,两者互补,上线前都该跑):
>
> | | 静态代码审查 | 本技能 |
> |---|---|---|
> | 方式 | 静态审查:读代码、读配置、按风险类别查清单 | 动态走查:以真人身份、真实数据、真实操作跑一遍 |
> | 回答 | "代码里有没有埋雷" | "真用起来会不会出问题" |
> | 强项 | 并发、越权、金额精度、密钥泄露 —— 这些"读得出来" | 交互断裂、状态反馈缺失、空态短路、跨页衔接 —— 这些"读不出来" |
> | 典型漏网 | "校验写了但用户看不到反馈" | "扣库存非原子" |
>
> 一句话:静态审查能发现"没做校验",动态走查能发现"校验做了但用户看不见"。 后者只有真点一遍才知道。
什么时候用我 —— 判据与自然语言示例
一句话判据:你要验证的是"人用起来会不会出问题",而不是"代码写得对不对"。
该用的说法(这些原话直接触发本技能)
| 你会这么说 | 为什么该用 |
|---|---|
| "上线前帮我把这个网站真点一遍" | 走查本义 |
| "这版功能做完了,能不能上线?" | 要放行判据,就得有证据 |
| "以真实用户身份走一遍注册 / 下单 / 填表流程" | 身份 + 全流程 |
| "帮我模拟一个用户,把我后台每个页面都走一遍" | 覆盖 + 真操作 |
| "填表这块我不放心,你去试试会不会出问题" | 表单走查 |
| "新版本准备发了,先验收一下" | 上线前验收 |
| "帮我做一次探索性测试 / 上线前体验测试" | 同义说法 |
| "点提交之后有反馈吗?会不会点了没反应?" | 状态反馈 |
| "断网、超时的时候会怎么样?" | 边角态(G6) |
| "注册流程我自己没完整走过,你替我走一遍" | 主链路验收 |
| 英文:run a full QA pass before release / act as a real user and walk through the whole flow / smoke test the product as a user | 同义 |
不该用的说法(用错了我会给你错的答案)
| 你会这么说 | 该用什么 |
|---|---|
| "审查一下这段代码有没有并发 / 越权问题" | 静态代码审查(见开头分工表) |
| "帮我写单元测试 / 补齐测试用例" | 测试代码编写 |
| "帮我压测一下并发性能" | 性能压测工具 |
| "帮我修这个 bug" | 缺陷修复流程 |
| "帮我检查错别字 / 校对文案" | 内容校对 |
| "帮我评一下这个配色好不好看" | 设计 / 视觉评审 |
为什么要把这条写清:本技能查的是"读不出来、只能真点一遍才知道"的坑。让它去查并发、金额精度这类"读得出来"的问题,它给不出可靠结论——那是静态清单的活;反过来,静态审查也查不出"校验写了但用户看不见",那是本技能的活。
边界情况(先问一句再动手)
| 你会这么说 | 怎么处理 |
|---|---|
| "帮我测一下登录功能" | 触发,但只覆盖一条链路。要问清:只验这一条,还是作为整站放行依据?后者不能只测一处。 |
| "看看这个页面行不行" | 范围不明。先确认:只看渲染,还是要填表走全流程? |
| "随便点点看有没有问题" | 触发,但要提醒:抽查不等于覆盖,结论只能标"部分覆盖"。 |
我覆盖哪些产品形态
| 形态 | 支持度 | 说明 |
|---|---|---|
| Web 网站(含 SPA) | 完整 | 主场景 |
| 管理后台 | 完整 | 主场景 |
| 微信小程序 | 需自备驱动 | 本技能只提供规程;驱动用微信开发者工具自动化 / 真机调试 / Appium 一类。七道门禁完全通用 |
| 移动 App(原生或混合) | 需自备驱动 | 用 Appium 类工具 + 模拟器或真机 |
| 桌面客户端 | 需自备驱动 | 能驱动其 WebView 或原生控件即可 |
| 纯 API / CLI(无界面) | 不适用 | 没有"人操作界面"这一层,请用接口测试工具 |
| 纯静态展示页(无表单、无流程) | 不适用 | 没有"要填、要走"的东西,走查无事可做 |
一句话:只要有"表单要人填、流程要人走",规程就适用;区别只在用什么驱动。
0. 这个技能在解决什么
传统上线模式:做完 → 上线 → 等用户碰到问题 → 修。这个模式有两个前提——真人测试贵、真机验证慢。到了能自动驱动真实的年代,这两个前提都不成立了:AI 可以像真人一样把每个格子填一遍、把每条边界撞一遍,把问题在上线前挖干净。
但"能让 AI 去点"和"能放心"是两件事。 本技能要解决的就是中间那段——怎么保证 AI 这次走查的结论是可信的。
1. 核心命题:能跑通 ≠ 能放心
走查最大的风险不是"没发现问题",而是"报了一堆假绿"。假绿比没查更危险:它给你一个"已验证"的错觉,让本该修的问题带着通行证上线。
假绿的三种典型形态(都真实发生过):
- 身份错了——你没在目标身份上跑,验证的是另一个程序分支。
- 内容空了——空表单点按钮,验证的是空态短路,不是真实使用路径。
- 探针坏了——选择器失配 / 状态没回读 / 上下文失效,探针安静地返回"一切正常"。
本技能的七道门禁,就是分别堵这三种洞。
2. 七道门禁(主心骨)
任何一条走查结论,必须先过前四道(可信性),再过后三道(充分性)。任一条不通过,对应的结论作废,不得计入覆盖度。
| 组 | 门禁 | 一句话 | 不通过的后果 |
|---|---|---|---|
| 可信性 | G0 身份 | 我现在是谁? | 整轮结论作废 |
| 可信性 | G1 环境 | 我在哪台机器、哪个站点、哪个版本? | 结论不可复现、可能已失效 |
| 可信性 | G2 证据 | 凭什么说它通过了? | 假通过 |
| 可信性 | G3 反证 | 我的探针本身是好的吗? | 探针坏了却全绿 |
| 充分性 | G4 内容 | 我填的是什么? | 空表单上的按钮行为没有意义 |
| 充分性 | G5 覆盖 | 我走了多少格? | 抽查冒充全量 |
| 充分性 | G6 边角 | 非正常路径呢? | 只在顺境里验证过 |
G0 · 身份门禁 —— 我现在是谁?
为什么真实存在(本条是用一整轮作废换来的):
2026-09-14,某中学生目标管理 SaaS 的全流程走查,12 个页面的结论全部作废。链路是这样的:
- 自动化工具的
fill命令对该站登录页静默失效(命令作用在错误标签页上),登录从未成功; - 该产品游客可以试填,所以"填充 35/35 成功"这类数字看起来完全正常——没有任何异常信号;
- 直到游客点 AI 按钮触发 401 → 产品按设计跳登录引导注册 → 被误判成"AI 权限缺陷";
- 真相是:走查员自己没登录。
最要命的一点:游客态与登录态的行为差异,足以把产品缺陷和设计行为完全颠倒过来。那轮里两条"缺陷"(AI 返回 401、某页零输入框)最后都被证明是设计行为。
判据(缺一不可):
- 登录用真实击键(
keyboard type),不要依赖fill——受控组件可能收不到事件,且失败时未必报错; - 登录后回读断言身份,不能只看"跳转成功了":token 存在且长度合理 / 页面出现登录后专属元素(如"退出登录"而非"登录");
- 优先信"渲染结果",且 token 与渲染要一起看。2026-09-14 实测出现过
hasToken:false而页面仍停在学生工作台路由的情况——因为该路由游客也可试填,中间件不拦。只看 token 会误判"登录丢了",只看路由会误判"登录着"。判据 = token 有值 且 登录后专属元素在(本项目可用的专属信号:「试填中」横幅消失、已填计数由 0 变非 0)。 - 注入凭据优先走页面内:CLI 的
storage local set token实测会静默失败(返回Storage error: Uncaught,而页面照常加载、按钮照常能点,完全看不出问题)。可靠做法是在页面上下文里直接执行localStorage.setItem('token', ...),然后重新加载页面,再用上面的渲染结果验收; - 每个批次都要断言,不能只在第一批断言一次(会话通常不跨批次自动保持,需显式注入登录态);
- 断言结果写进产物(页面上插状态栏、或报告里逐批记录),不能只在执行者脑子里。
红线:token 不为目标身份的结果,一律作废,不得计入覆盖度。
G1 · 环境门禁 —— 我在哪跑?
为什么真实存在:本机、预发、生产三处的行为可能完全不同(缓存、数据、开关、构建版本)。报告不写清环境,结论既无法复现,也无法判断是否已被后续改动失效。
判据:
- 记录 站点 URL + 页面标题 + 构建版本(如有)+ 接口基址;
- 记录工具与浏览器版本(自动化行为本身会随版本变);
- 确认走查的是目标环境——别在预发上跑,报告却写成"生产没问题";
- 若跑生产:确认数据会被污染到什么程度、用的是什么账号、能否清理(见 G0 的测试号纪律)。
自动化通道的三个静默陷阱(2026-09-14 实测,agent-browser 系)——它们都不报错,只是让读数变得没有意义:
- 凭据不跨批次持久。同一批命令内
localStorage保持,跨批次调用会静默退回未登录态——页面照样渲染、按钮照样能点,你根本看不出身份已经变了。所以「注入凭据 → 断言身份」必须放在同一批命令内;且断言要看只在目标身份出现的元素(当年本项目可用:登录后"试填中"横幅消失、已填计数由 0 变非 0)。只断言"有 token"不够——要断言渲染结果。 - 单次长等待会断线。
wait超过约 50 秒会因宿主读超时失败(os error 10060)。致命之处是:它发生在"等完之后",于是紧接着读到的是"还没恢复"的旧状态,极易误判为缺陷仍然存在。→ 长观察窗一律改两段式:第一批"触发",命令外sleep,第二批"读取"。 - 读数可能来自别的标签页。活动标签会漂到
about:blank(届时所有读数都是空值/0)。每次eval的返回里都带origin字段,必须断言它等于目标页 URL,否则该条读数作废——把它当硬断言,不要当可选项。
G2 · 证据门禁 —— 凭什么说它通过了?
为什么真实存在:走查最容易犯的错是"看起来成功了"。三个真实陷阱:
- 截图会骗人:两张截图字节数完全相同 = 页面根本没变,但肉眼看不出差异,极易被当成"点击生效了";
- 状态码只有接口知道:页面渲染正常 ≠ 接口返回 200,可能走了降级文案分支;
- 日志会被编码搞乱:UTF-16 与 UTF-8 混杂的日志里,中文全变 Mojibake,真实内容被掩盖。
判据:
- 每步留档 截图 + DOM 断言 + 网络状态码(三者互证);
- 判据写成机器可判定的断言(
AI_STATUS === 200、filled === total),不是"看起来正常"; - 截图字节数相同 = 强信号:页面没变,必须用 DOM / 文本快照交叉验证;
- 日志一律做编码还原后再判读,不要对着乱码下结论;
- 关键判据要能从至少两个独立来源确认。
G3 · 反证门禁 —— 我的探针本身是好的吗?
为什么真实存在:坏掉的探针会安静地返回"一切正常"。这是假绿里最隐蔽的一种,因为它同时污染了 G0–G2 的全部产出。
真实案例(同一次走查):find text 卡住了 返回 Element not found,而截图前后字节数相同——当时差点被解读成"页面正常、无变化"。实际是选择器失配,命令一次都没生效。
判据:
- 走查开始前先跑探针自检:确认探针能抓到他应该抓到的(能查到已知存在的元素、能记录到已知会发生的请求);
- 负向对照(negative control):故意断言一个必须失败的条件(如"页面上不存在某个必然不存在的元素")。如果它也"通过"了,说明探针坏了,整轮作废。
- 关键判据多次运行结果要一致;偶发不一致本身就是发现。
探针自检必问三句:它能抓到已经存在的吗?它能抓到我预期会发生的吗?它对着必然失败的条件会报错吗?
第四问(2026-09-14 血证补)——替身探针必须复刻真实语义。
用 mock / 劫持做异常注入时(如劫持 fetch 模拟断网、挂起、限流),替身的行为契约必须与真身一致,否则探针失去分辨力:它既会把"已修好的"测成坏的,也会把"没修的"测得一样好。
具体案例:为验证"AI 请求有了 45s 客户端超时后会解锁按钮",把 fetch 换成返回永不 settle 的裸 Promise。问题在于——真 fetch 会在 AbortSignal abort 时以 TimeoutError reject,而这个替身根本不监听 signal。于是:
| 被测实现 | 真实 fetch 下 | 裸 Promise 替身下 |
|---|---|---|
| 无超时(旧代码) | 永久锁死 | 永久锁死 |
| 有超时(已修复) | 45s 后解锁 | 永久锁死 |
两种情况读数完全相同 ⇒ 探针没有分辨力,测出来的"仍锁死"是假红,不是缺陷。
判据(写探针前先做):
- 列出真身的行为契约(超时?中止?错误类型?重定向?),逐条在替身里实现;
- 区分度自检:拿"已知有缺陷"和"已知已修复"两个实现各跑一遍,两者读数必须不同。读数一样 ⇒ 替身是坏的,整轮作废;
- 让替身自报证据(是否收到 signal、信号何时触发、实际中止发生在第几毫秒),别只信被测方的表述。
G4 · 内容门禁 —— 真实内容原则
为什么真实存在:空表单上点按钮,和填满真实内容后点按钮,是两条不同的代码路径。空表单往往命中"空态短路"直接返回——你验证的是空态分支,不是真实使用路径。在空表格上点来点去,查不出真正的问题。
判据:
- 每个字段都要填,且填该字段语义对应的真实内容(填"数学 118 分"而不是"测试测试 123");
- 内容覆盖五类代表性输入:
- 正常值:符合典型用户的合理输入
- 边界值:刚好在下限/上限(0、1、最大值、刚达阈值)
- 极端值:超长文本、超大数字、填满所有格
- 空值:什么都不填(验证必填校验与空态)
- 非法值:格式错误、特殊字符、emoji、负数、超范围
- 填充率必须量化:
已填 26/26;未填的逐条列出并说明原因(条件渲染?懒加载?还是真漏了?); - 填充后回读验证:
input/textarea的值不在innerText里,必须查元素的.value,否则会误判"没填进去"。
红线:填充率 < 100% 时,结论必须显式标注"未覆盖字段",不得默认为已覆盖。
G5 · 覆盖门禁 —— 全覆盖而非抽查
为什么真实存在:抽查是抽样,抽样通过不等于全量通过。一个 60 格的表单,你想不到会坏的是第 47 格——而用户偏偏会用到第 47 格。抽查的价值是"快速摸一遍",覆盖面不足时不能拿它当放行依据。
判据:
- 走查前先建元素清单,双来源交叉:代码侧(grep 组件/字段定义)+ 运行时(DOM 查询实际渲染的元素)。两边数量不一致必须解释(是条件渲染?还是漏渲染?这本身就是发现);
- 逐个遍历,每个元素一条记录,不是"整页通过";
- 覆盖度量化写进报告:
已覆盖 47/60 格(78%),未覆盖的逐条列出原因; - 禁止用"抽查通过"冒充"全量通过"。
G6 · 边角态门禁 —— 非正常路径
为什么真实存在:真实用户不按剧本操作。空态、加载态、错误态、断网、连点、超长输入——顺境里的验证不能说任何问题。这类问题在静态审查里读不出来,只有真撞一次才知道。
判据:按 references/edge-states.md 逐类过,至少覆盖——
- 三态:加载态、空态、错误态(是否都有、是否说人话)
- 异常网络:断网、超时、慢响应(AI 类接口常 20s+,用户会不会以为卡死?)
- 重复与并发:连点两次提交、双击保存、多标签页同时操作
- 输入边界:超长文本、特殊字符、emoji、全空格
- 时间边界:跨天、跨周、跨月、跨年(按周/按月聚合的功能尤其高危)
- 持久化:填完刷新页面、退出重进、换设备登录,内容还在不在
- 破坏性操作:删除、清空、覆盖,有没有二次确认
- 状态反馈:点了有没有反应;降级时的提示是否说人话("AI 暂时不可用"这类)
3. 七步作业流程
| 步 | 动作 | 对应门禁 | 产出 |
|---|---|---|---|
| 1 | 定身份与环境:拿测试账号、确认目标站点与版本、确认数据可污染 | G0 G1 | 身份与环境记录 |
| 2 | 建元素清单:页面 → 可交互元素 → 状态维度(双来源) | G5 | 覆盖清单 |
| 3 | 探针自检:插入探针,验证它抓得到该抓的(含负向对照) | G3 | 自检结果 |
| 4 | 逐页走查:填真实内容 → 触发功能 → 回读断言 → 留档 | G4 G5 | 每页记录 |
| 5 | 边角态走查:按清单撞边界 | G6 | 边角记录 |
| 6 | 汇总复核:算覆盖度、逐条核门禁、作废不可信结果 | G2 G3 | 覆盖矩阵 |
| 7 | 出报告:缺陷分级 + 覆盖度 + 未覆盖清单 + 被推翻的结论 | G2 | 走查报告 |
顺序不能颠倒的两个理由:
- 第 3 步必须在第 4 步之前——探针没自检就开跑,后面所有数据都不可信(G3);
- 第 6 步的"作废"是必做动作——发现门禁不通过时,要主动划掉已经产出的结论,而不是"下次注意"。
4. 覆盖度:三维矩阵与量化
走查不是"跑一遍",而是填满一张矩阵。三个维度:
| 维度 | 取值 | 说明 |
|---|---|---|
| 页面 | 全部路由 | 从路由表/导航枚举,不靠记忆 |
| 格子 | 每个可交互元素 | 每个输入框、每个按钮、每个下拉 |
| 状态 | 正常 / 空 / 边界 / 极端 / 非法 + 异常网络 + 重复操作 | 见 references/edge-states.md |
量化口径(写进报告,不含糊):
格子覆盖率 = 实际访问过的格子 / 清单中的格子总数
状态覆盖率 = 实际验证过的 (格子 × 状态) 组合 / 应有组合总数
接口覆盖率 = 实际调用并断言的接口 / 页面涉及的接口总数
注意:三个覆盖率都要写。只写格子覆盖率会掩盖"每格只测了正常值"这件事。
5. 缺陷分级与放行
沿用业界通行的缺陷级差,便于与静态审查报告互相引用:
| 级别 | 定义 | 处理 |
|---|---|---|
| P0 阻断 | 主链路走不通、数据会丢、钱或账号出错 | 修完才能上线 |
| P1 严重 | 影响真实使用体验或稳定性 | 本次发布前修 |
| P2 优化 | 可改进但不紧急 | 记录,后续处理 |
| 非缺陷(设计行为) | 看起来像 bug,实际是设计如此 | 必须显式标注并说明判据 |
"非缺陷"这一行是本技能特有的。 走查会发现大量"看起来不对其实是设计"的现象(空态需要先点按钮、游客点受限功能跳登录引导)。把它们和真缺陷混在一起会导致两件事同时发生:报告不可信、真缺陷被稀释。判据是:找到代码位置的依据,或找到明确的设计意图说明。
放行规则:P0 清零;P1 尽量清零,延期须写明"已知风险 + 缓解措施 + 计划修复时间"。
6. 报告
模板见 references/report-template.md。报告必须包含一节「被推翻的结论」——记录走查过程中发现自己前一版结论不成立的情况。原因:走查结论会互相引用,如果只改结论不留痕,后续读报告的人会继续引用已废的数据。诚实记录推翻过程,是这个技能可信度的一部分。
7. 与其它技能衔接
| 触发 | 去哪 |
|---|---|
| 走查前先做静态风险审查 | 用你惯用的静态审查清单(读代码查并发、越权、金额精度、密钥泄露等"读得出来"的高频坑) |
| 命中的缺陷要修 | 走"先定性缺陷类型 → 再分流处理"的流程,不要上来就改代码 |
| 走查发现"整层缺失"(如某功能压根没做) | 回到设计阶段补方案,不要在代码里打补丁 |
需要在 Windows 上跑 agent-browser |
参见该工具的平台适配说明;探针层面的坑见 references/probe-cookbook.md |
| 探针脚本遇到工具链怪癖 | references/probe-cookbook.md(本技能内,硬约束合集) |
遇到具体卡点(身份怎么断言、探针读数全 0、脚本报错码 E0xx/E10x) |
references/faq.md(按"你卡在哪"分类) |
8. 给小白说人话
为什么要在上线前走查?
你把产品做出来,在你自己电脑上、自己一个人点,怎么点都没事。但真实用户会:往输入框里粘贴一大段东西、点到一半断网了、连点两下提交、填完刷新了一下页面。这些你没想到的操作,才是出问题的地方。
传统做法是"先上线,等用户碰到再说"——但那时候用户已经生气了,而且你可能根本不知道他碰到了什么。
让 AI 像真人一样把每个格子填一遍、每条岔路走一遍,就能在上线前把这些坑挖出来。
但有个前提:AI 走查的结论,可能是假的。比如它说"12 个页面全部通过",实际上它根本没登录成功,测的是另一个身份下的行为。所以本技能的重点不是"怎么点",而是"怎么保证这次的结论是真的"——那七道门禁就是干这个的。
你只需要在最后问三句话:
- 这次是以谁的身份测的?验证过吗?
- 每个格子都填过了吗?没填的有哪些?
- 有没有哪条结论是后来发现不成立的?
答不上来,这份报告就先别信。
9. 常见误区
- "能点开就是没问题"——能点开只说明没崩,不说明做对了。要看反馈(点了有没有反应、结果对不对)。
- "跑通一遍就行了"——抽查不是覆盖。清单里没走到的地方,就是没验证过的地方。
- "空表单也能测按钮"——空表单走的是空态分支,不是真实路径。先填内容,再测功能。
- "截图看着正常"——截图会骗人。字节数相同就是没变;状态码只有接口知道。
- "上次测过了"——环境、版本、身份、数据任一变了,旧结论就可能失效。走查结论要带环境和时间。
- "发现的问题都记下来了"——只记不带分级、不带复现路径的问题,等于没记。
- "看起来不对就是 bug"——很可能只是设计行为。先找设计依据,再定性。
- "报告要写得漂亮"——报告的价值在于准确,不在于好看。被推翻的结论、没覆盖到的部分、不确定的判断,都要写进去。
卡住了先查 FAQ
references/faq.md 按"你卡在哪"分类,覆盖:什么时候该用 / 不该用、身份怎么断言、探针读数全 0 怎么办、格子数两边对不上怎么解释、长等待为什么读到旧状态、报告里"非缺陷"怎么写、脚本报 E0xx / E10x 怎么修。
如果 FAQ 里没有你的问题,那多半是一个值得补进去的新失败模式——按"现象 → 真实原因 → 如何识破"记下来。