实习作品集 · 2026
不靠点击、不靠截图对比,而是把"产品的隐式契约"显式写出来,让 agent 机械地、可重复地把它们跑一遍——并自动修复它能修的部分。
01问题
一个最简单的场景:登录后点 Logout。表面上一切正常,按钮有反应、没有报错、页面没崩溃——但用户其实并没有真正登出。
这种 bug 在生产环境随处可见:"工作了,但工作错了一半"。如果按钮完全不响应,反而容易发现;恰恰是因为它表面看起来在工作,所以 QA agent、单元测试、视觉 diff 都会放它过。
02结构性原因
这不是懒,也不是模型不够强。是结构性的——下面 6 点全部对一个 LLM-driven QA agent 同时成立。
QA agent 的判定信号:DOM 没异常、控制台没报错、网络没 500。但 bug 在 localStorage 和 React state 树里——它默认不去翻这两个地方。
人类做 QA 会想"登出后试访问 /agents 应该被踢回 /login"。Agent 照剧本走——剧本没说,它就停了。它不会自然地"链接动作的承诺"。
点 logout 前后页面几乎一模一样:桌子列表还在、顶栏还在。视觉 diff 容忍微小变化或者根本不报。"看起来都还在 = 没问题"。
这站点有 cookie session 和 Supabase JWT 两条认证链。这种信息只在源码注释里,Agent 光从外部点击是看不出来的。
互联网上 99% 的登出按钮都正常。Agent 倾向合理化"可能在异步处理"。确认偏差 + 沉默错误 = 报 PASS。
每个 action 都有 4–6 条"承诺"。要 agent 全部验证,得有一份完整的 product invariant spec——这种东西在仓库里默认不存在。
03我的产品
QA-Agent 的核心思路:与其训练 agent 拥有人类直觉,不如把人类的隐式契约显式落到磁盘上。 Agent 没有直觉,但 agent 读文件、跑断言、自动修源码都很擅长。
形如 api-tables-create-requires-csrf,对应一段独立可重放的脚本。结果只有 PASS / FAIL,不靠"看起来对"。
读源码、读路由、读组件——把藏在代码里的"承诺"写成 markdown 契约。一次写、永久跑。
一次 run 可以跑几十上百条契约,全部并发执行。失败的会直接给出最小复现路径。
失败不是终点。Agent 会读契约、读失败原因、读相关源码,然后生成补丁——人类只要 review diff。
04演示
下面是一段 autopilot run 的录屏——读契约 → 执行 → 失败 → 自动修复 → 重跑通过。
↑ 点击播放 · 建议开声音
05跑出来的结果
来自 poker/qa/contracts/ 最新一次 autopilot run(16m 11s,74 个契约里挑出 12 个)。每一行都对应一类肉眼点击、单元测试、通用 LLM review 都跑不出来的真实 bug。
| # | 区域 / 严重度 | 契约 ID | 普通人为何看不见 |
|---|---|---|---|
| 1 | auth/P0 | api-agents-get-ownership-isolation | IDOR:登录后用别人 ID 拿 agent,必须返回 404(不是 403,避免存在性泄漏)。普通 QA 只测自己的资源能不能拿到,从不构造别人 ID 去试。 |
| 2 | core/P0 | api-hand-summary-anonymization | 公开手牌摘要必须剥掉 players[*].holeCards、players[*].handEvaluation、seed。漏一个就是对手能透视底牌。看接口不会去想"哪些字段在公开通道里属于私有信息"。 |
| 3 | core/P1 | api-matches-list-excludes-seed | 比赛列表不能带 RNG seed。泄露 seed = 洗牌可预测 = 扑克作弊。这种在业务字段里偷偷藏随机种子的情况,只有懂业务的人才会盯。 |
| 4 | core/P1 | api-agents-list-excludes-secrets | /api/me/agents 必须排除 authHeaderValue,只能给布尔 hasAuthHeader。"懒序列化整行 DB 记录" 是 SaaS 最常见的密钥泄漏路径;QA 看到 200 就过。 |
| 5 | auth/P0 | api-tables-create-requires-csrf | POST /api/tables 不带 x-csrf-token 头必须 403。SPA + cookie 鉴权下最容易漏的一道防线 —— 前端 QA 因为浏览器自动带 token 永远跑不出。 |
| 6 | auth/P1 | api-auth-login-rate-limited | 登录超阈值必须返回 429 + Retry-After 头。没人会写"连续打 100 次登录"的脚本,这种通常是上线被撞库之后才补。 |
| 7 | auth/P0 | auth-context-refresh-clears-user-on-401 | /auth/me 返回 401 时,前端必须把 user 清空。经典"服务端注销了但 UI 还显示登录" —— 用户拿失效会话做敏感操作。只有模拟 token 提前过期才能复现。 |
| 8 | auth/P1 | logged-in-user-redirected-from-login | 已登录用户访问 /login 必须被踢回 /lobby。绝大多数 QA 只测登出态的登录流程,不会想到测"已登录状态点登录按钮"。 |
| 9 | auth/P1 | login-success-redirects-to-next-param | /login?next=/dashboard 登录成功必须跳 /dashboard,不是写死的 /lobby。深链接(被踢去登录后回原页面)经常被硬编码登录跳转覆盖。 |
| 10 | auth/P0 | ws-table-subscribe-requires-auth | 匿名 WS subscribe table:xxx 必须返回 UNAUTHENTICATED + close code 1008。WebSocket 经常绕过 HTTP 鉴权中间件,是登录围栏最常见的破口。 |
| 11 | core/P1 | ws-table-subscribe-restricts-topics | 已登录用户订阅 seat:other-user:table-id 必须被拒绝(订阅列表为空)。订阅式 IDOR —— 监听别人座位流 = 实时看牌。 |
| 12 | core/P1 | ws-resubscribe-after-reconnect | WS 断线重连后必须主动对每个原有 topic 重发 subscribe。静默 bug 之王:UI 显示"已连接"实则无推送,用户面对一张冻结的牌桌;不模拟断网跑不出。 |
06第二个项目
一个把产品经理工作流交给 Agent 的项目:从需求挖掘到任务推进,由 Agent 端到端跑通。
它有两种工作模式——Goal 模式聚焦一个具体目标,端到端推进到 deliverable;Daemon 模式常驻后台,沿着代码、日志、用户行为持续巡查产品的演进方向。
给一个明确目标("把登录流跑通"、"把 lobby 的体验打磨到 9 分"),Agent 会自动拆任务、找上下文、写 plan、推进到 deliverable。一次一个目标,结束即收尾。
没有具体目标,但有"嗅觉"。Daemon 模式持续读最新提交、跑契约、对比期望和现状,主动产出 backlog:哪里值得做、为什么值得做、做完会变好多少。长跑的 PM 副本。
07第三个项目
QA-Agent 和 PM-Agent 跑在后端;Duck 是把它们的"思考过程"对外展示的前端壳。 一个有人格、有节奏的小入口——让用户能直观感受到 agent 正在"想什么、做什么"。