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

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

## 前提

- 被测项目的 e2e 容器按 `../e2e/references/runner.md` 搭，还没搭就先搭。`<exec>` 是 `e2e/profile.md` 里的执行前缀，所有 Playwright 命令都经 `<exec>` 在容器里跑。
- 没有 `e2e/profile.md` 时，按 `../e2e/references/profile-template.md` 建，只填执行、入口、账号三节，其余写「未知」并列进「待确认」。
- 定位失败的顺序见 `../e2e/references/diagnose.md`，时序手段见 `../e2e/references/timing.md`。

## 次数规则

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

| 阶段 | 跑几次 |
|---|---|
| 确认复现 | 先跑 1 次。失败就再跑 2 次：3 次都失败按「必现」处理（p = 1）；有通过的，p = 失败次数 ÷ 3。第 1 次通过就逐次接着跑，出现第一次失败时停，p = 1 ÷ 已跑次数；跑满 30 次仍无失败，按步骤 2 的「复现不出」处理 |
| 调试中 | 必现跑 1 次；偶发跑 ⌈3/p⌉ 次 |
| 确认修好 | 连续 max(3, ⌈3/p⌉) 次全部通过 |

⌈3/p⌉ 超过 30 时，先用 `timing.md` 的手段把时序固定住、提高复现率，再按上表跑。多次运行用 `--repeat-each=<N>`。

## 步骤

1. **写复现**：先 grep `e2e/tests/` 找同一流程的 spec，有就照搬它的前置条件和步骤。复现写在 `e2e/tests/repro/<kebab-case 名>.spec.ts`，文件头 `// pin: <现象> <日期>`。断言写正确的行为。会建数据的，第一次运行前就写好 `afterEach` 删除。
2. **确认复现**：按次数规则跑。失败点必须就是这个现象，按 `diagnose.md` 确认；选择器写错、账号登不上这类失败，改测试直到失败原因对上。**复现出红之后，再读应用代码。** 跑满 30 次都没失败时，先用 `timing.md` 的手段固定时序，再按次数规则重跑；仍复现不出就停下，列出试过的写法，向我要能复现的环境、录屏或 HAR。
3. **缩到最少**：先把当前 spec 另存为同目录的 `<名>.original.spec.ts`。然后一次删一个步骤或一项前置数据，按次数规则「调试中」一行重跑：仍失败，这一项就删掉；不再失败，说明它是触发条件，放回去。每一项都试过一遍，这步完成。
4. **列假设**：读应用代码前，先在回复里写出 3–5 个按可能性排序的原因假设，接着按顺序验证，不用等我回复；我插话就按我的调整。原因看起来再明显也要列，收尾时逐条写明证实还是排除。
5. **定位**：按 `diagnose.md` 查证假设，一次只改一个变量。往应用代码或 spec 里加的调试日志，一律带同一个前缀 `[DEBUG-<4 位随机字符>]`。
6. **修应用代码**：修真正的原因。复现 spec 的断言和剩下的步骤是验收标准，要改断言、删步骤、加 `skip` / `fixme` 都先问我。role 和文本定位不了时，可以给元素加 `data-testid`，收尾时列出来。连续 3 个修法都没让第 7 步第一条通过：停下，把 3 次的假设、改动、结果列给我，一起重新看设计。
7. **确认修好**：
   - 复现 spec 按「确认修好」一行的次数跑，全部通过。
   - `<名>.original.spec.ts` 按同样的次数跑，全部通过后删掉，只留缩减版。
   - 相关回归：按 `../e2e/references/select.md` 挑出 spec（含全部 `@critical`），各跑 1 次。
8. **清理**：grep 前缀 `[DEBUG-`，把调试日志全部删掉，重跑一次复现 spec 确认仍通过。spec 留在 `e2e/tests/repro/`，`/flow:e2e run all` 会带上它。收尾时给出一行建议补进对照表的「改动路径 → 这个 spec」，并说明插在哪一行之前，我确认后再写进画像。

## 改用什么

- 按改动挑测试：用 `select.md` 的 `--grep`。`--only-changed` 在这里一个测试都选不中也不报错：`e2e/` 被 gitignore，应用代码也不在 spec 的 import 里。
- 单跑一条：用 `--grep`。`test.only` 容易漏在 spec 里。
- 等待：用 `timing.md` 里的手段。`page.waitForTimeout()`、`networkidle`、`sleep` 会让测试时好时坏。

## 护栏

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

## 收尾

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