---
name: pin
description: 用 Playwright 脚本把肉眼难确认的界面 bug（时序、偶发、多步交互）钉住：先复现、按复现率反复验证，再修应用代码，spec 留作回归
disable-model-invocation: true
argument-hint: "<现象：哪个页面、做了什么、期望什么、实际怎样>"
---

把一个界面 bug 钉住再修。一眼能看出对错的问题（文案、样式）不用这个，直接改。

## 前提

- 被测项目的 e2e 容器已按 `../e2e/references/runner.md` 搭好，`<exec>` 是 `e2e/profile.md` 里的执行前缀。没有 profile.md 时只填本次要用的执行前缀、入口、账号三项，其余写「待确认」。
- 宿主机不装任何 Playwright 组件，命令一律走 `<exec>`。
- 定位失败的顺序见 `../e2e/references/diagnose.md`，时序手段见 `../e2e/references/timing.md`。

## 次数规则

复现率 p = 失败次数 ÷ 运行次数。依据：零失败 N 次只能说明失败率低于约 3/N（95% 置信，统计学三法则）。

| 阶段 | 跑几次 |
|---|---|
| 确认复现 | 先 1 次。失败就再跑 2 次，3 次都失败按「必现」处理（p = 1）。第 1 次通过就继续跑到出现失败，最多 30 次，据此估 p |
| 调试中 | 必现 1 次；偶发 ⌈3/p⌉ 次 |
| 确认修好 | 连续 max(3, ⌈3/p⌉) 次全部通过 |

⌈3/p⌉ 超过 30 时不硬跑，先用 `timing.md` 的手段把时序固定住、提高复现率。多次运行用 `--repeat-each=<N>`；含写操作时每次运行建的数据在 `afterEach` 里删。

## 步骤

1. **写复现**：`e2e/tests/repro/<kebab-case 名>.spec.ts`，文件头 `// pin: <现象> <日期>`。断言写正确行为，不写现状。会建数据的，第一次运行前就写好 `afterEach` 删除。先 grep `e2e/tests/` 有没有同流程的 spec，有就在上面加用例。
2. **确认复现**：按次数规则跑。失败点必须就是这个现象，按 `diagnose.md` 确认；选择器写错、账号登不上导致的失败不算，改测试直到失败原因对上。**复现出红之前不读应用代码猜原因。** 30 次都没失败、固定时序后仍复现不出：停下，列出试过的写法，向我要能复现的环境、录屏或 HAR。
3. **缩到最少**：先把当前 spec 另存为同目录 `<名>.original.spec.ts`。再从 spec 里一次删一个步骤或一项前置数据，按次数规则重跑，删掉后不再失败的留下，直到剩下的每一步都不可少。
4. **列假设**：读应用代码前，先在回复里写出 3–5 个按可能性排序的原因假设，不等回复，按顺序往下验证；我插话就按我的调整。原因看起来再明显也要列，收尾时逐条写明证实还是排除。
5. **定位**：按 `diagnose.md` 查证假设，一次只改一个变量。往应用代码或 spec 里加的调试日志一律带同一个前缀 `[DEBUG-<4 位随机字符>]`。
6. **修应用代码**：修真正的原因。复现 spec 的断言和剩下的步骤是验收标准，要改断言、删步骤、加 `skip` / `fixme` 都先问我。role / 文本定位不了时可以给元素加 `data-testid`，收尾时列出来。同一个 bug 连续 3 次修复都没通过：停下，不再试第 4 个修法，把 3 次的假设、改动、结果列给我，一起重新看设计。
7. **确认修好**：
   - 复现 spec 按次数规则全部通过。
   - `<名>.original.spec.ts` 跑 1 轮（次数同上）全部通过后删掉，只留缩减版。
   - 相关回归：看应用代码的 `git diff`，用改动涉及的路由、页面、组件名、选择器 grep `e2e/tests/`，只跑命中的 spec 文件各 1 次；有失败紧接着用 `--last-failed` 复跑（记录每次运行都会覆盖，中间不要插别的运行）。
   - 不跑全量。diff 改到公共组件、布局、路由、状态管理时，收尾提醒我考虑跑 `/flow:e2e run`。
8. **清理**：grep 前缀 `[DEBUG-` 把调试日志全部删掉，重跑一次复现 spec 确认仍通过。spec 留在 `e2e/tests/repro/`，以后 `/flow:e2e run` 会一起跑。

## 不用

- `--only-changed`：`e2e/` 被 gitignore、应用代码也不在 spec 的 import 里，一个测试都选不中且不报错。
- `test.only`：容易漏在 spec 里。
- `page.waitForTimeout()`、`networkidle`、`sleep`。

## 护栏

- 副作用分级同 `/flow:e2e`：1 只读 / 2 库内可逆 / 3 库内不可逆 / 4 出库外部。复现步骤含 2 级写操作先按画像备份，画像里没有备份命令就先问我；3 / 4 级先问我。
- 不 mock 外部网关。只用 Playwright 自带 chromium。
- 密码放 `e2e/.env`，spec 里用 `process.env.E2E_*`。
- 数据被弄乱需要还原时，先问我再跑恢复命令。

## 收尾

只说三件事：复现情况（复现率、缩减后的步骤、各假设证实或排除）、改了哪些应用代码（含新加的 `data-testid`）、验证结果（复现 spec 跑了几次、相关回归跑了哪些文件、结果）。需要我拍板的放最后。
