协作办公
端到端测试邮件依赖流程(注册确认/密码重置/magic-li…
当需要端到端验证依赖邮件的注册确认、密码重置、magic-link 登录、OTP/MFA 或通知邮件,并避免固定 sleep 和并行串信时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
端到端测试邮件依赖流程(注册确认/密码重特定
解决思路
- 先检查 `.agents/qa-project-context.md`;若存在则直接采用其中配置,跳过已回答项。
- 确定邮件需要在哪里接收:本地捕获用 Mailpit;需要真实可投递地址(staging、第三方 ESP、真实 DNS)用 Mailosaur/MailSlurp。
- 确定测试套件并行度;高并行下必须为每个测试使用唯一地址或独立收件箱,不能共享邮箱。
- 明确要覆盖的流程:注册确认、密码重置、magic-link 登录、OTP/MFA、通知邮件;它们共享 capture/extract/complete 形状,但提取物不同(链接 vs 6 位验证码)。
- 明确可投递性是否在范围内;SPF/DKIM/DMARC 是独立非阻塞套件,不属于功能性 OTP 测试。
- 区分本地模板预览与 CI 断言:开发时预览模板用 Ethereal;CI 中断言用 Mailpit/Mailosaur/MailSlurp。
- 捕获真实邮件,不要绕过邮件:在 UI 上提交,读取收件箱;不要直接调用重置端点或硬编码 token。
- 轮询收件箱,绝不 sleep:使用 `expect.poll` / `.toPass` 轮询 `/api/v1/messages`,或使用内置 waiter(`messages.get`、`waitForLatestEmail`)。
- 每个测试使用唯一收件人:用 plus-addressing(如 `user+${randomUUID()}@example.com`)或每测试独立收件箱;读取时按收件人过滤。测试间清空收件箱不足以解决并行冲突。
- 从邮件正文中用带锚点的正则提取 OTP/链接,并加 null guard:从 `message.Text` / `message.HTML` 提取,而不是从实时页面提取;使用 `\d{6}` / `.match(` 并断言匹配不为 null;缺失验证码要显式失败,不能变成 `undefined`。
- 断言邮件内容,而不只是断言存在:检查 `subject`、`from` 地址,以及链接指向正确域名。
- 将可投递性(SPF/DKIM/DMARC)作为独立非阻塞套件;它们来自真实接收服务器的认证,不是正文文本,不能阻塞功能流程测试。
- 默认选择 Mailpit 用于本地 + CI 功能测试:自托管、单二进制或 docker、免费开源,REST API 在 `:8025`(`/api/v1/messages`),可接收任意本地 SMTP 收件人。
- 需要真实可投递地址时选择 Mailosaur:托管(API key)、付费、真实 `*.mailosaur.net` 地址;使用自动等待的 `messages.get(serverId, { sentTo })`、结构化 links/codes、真实 SPF/DKIM/DMARC。
- 需要每测试一次性收件箱时选择 MailSlurp:托管(API key)、付费、真实 per-inbox;用 `createInbox()` + `waitForLatestEmail`,获得强并行隔离。
- 仅本地开发预览模板时选择 Ethereal:托管 throwaway、免费、捕获但不投递;用 `createTestAccount` + `getTestMessageUrl`;不要用于 CI 断言。
- 预算有限的注册流程(本地 + GitHub Actions,可自托管)使用 Mailpit;之后若需要真实可投递地址,再将套件升级到 Mailosaur 或 MailSlurp。
- 不要用“检查数据库”替代检查邮件:数据库行只证明写入发生,不证明邮件已发送、已正确寻址、链接可用。
- 避免 MailHog:它自 2020 年起归档/无人维护;Mailpit 是其 drop-in replacement(相同端口、兼容 API)。新套件也跳过 smtp4dev / Papercut,因为 Mailpit 的 API 和全文搜索更适合自动化断言。
- 轮询 Mailpit 时,用 Playwright `request` fixture 或普通 `fetch` 读取列表端点,找到发给本测试收件人的邮件,再按 ID 获取完整正文。
- 使用 `expect.poll` 并设置 `timeout` 和 `intervals`:例如轮询 `http://localhost:8025/api/v1/messages?query=to:${encodeURIComponent(to)}`,在 `messages` 中 `.find` 收件人匹配的 `ID`,`timeout: 30_000`,`intervals: [500, 1_000, 2_000]`,断言不为 null;随后 `request.get(http://localhost:8025/api/v1/message/${id})` 获取 `{ Text, HTML, Subject, From, To }`。
- 使用 `query=to:` 过滤加 `.find` 收件人匹配,使并行测试确定性;绝不无过滤地取 `messages[0]` / `messages.at(-1)`。
- 提取 OTP 示例:`const otp = body.match(/\b(\d{6})\b/)?.[1]; expect(otp, 'no OTP in email body').toBeTruthy();`;提取链接示例:`const link = body.match(/https?:\/\/\S*(?:verify|confirm|reset|token=)\S*/i)?.[0]; if (!link) throw new Error('no verification link in email body');`。
- 不要用索引切片提取:`body.split(' ')[3]`、`substring(0, 6)`、`indexOf('code')` 会在模板改词后失效;也不要在本意是邮件正文时读取实时页面的 `innerText`。
- 如果 6 位验证码可能与其他数字冲突,锚定标签:`body.match(/code[:\s]+(\d{6})/i)`;托管服务提供结构化 `message.html.links` / `message.html.codes` 时优先使用。
- 在确定性地址策略中,按优先级使用:每测试唯一地址(plus-addressing/sub-addressing,如 `user+${randomUUID()}@example.com` 或 `signup.${Date.now()}@...`);每次读取按收件人过滤(Mailosaur `sentTo`,Mailpit `query=to:` + `.find`);每测试专用收件箱(MailSlurp `createInbox()`,Mailosaur 在服务器域上给每个测试唯一地址)。
- 捕获后断言内容:`expect(message.subject).toBe('Welcome to Example')` 捕获错误模板;`expect(message.from?.[0].email).toBe('hello@example.com')` 捕获发件人/reply-to 配置错误;当产品设置时检查 headers(`List-Unsubscribe`、自定义 `X-` headers);检查链接域名:`expect(links.every((l) => new URL(l.href).hostname.endsWith('staging.example.com'))).toBe(true)`。
- 可投递性套件中,SPF/DKIM/DMARC 的 `pass`/`fail` + alignment 来自真实接收服务器对发送域的认证;绝不要 `body.includes('spf')` 或 `body.match(/dkim/)`。
- Mailpit 不验证 SPF/DKIM/DMARC alignment,只做基础 SpamAssassin 内容评分,因为没有经过真实 DNS;真实 alignment 需要托管发送并接收(Mailosaur deliverability report,或一次性 mail-tester.com)。
- 给可投递性套件打标签 `@deliverability`,在 CI 中作为非必需 job 运行(`continue-on-error`),绝不在 OTP/reset 流程测试中断言可投递性。
- CI 邮件 flaky 时诊断四个根因,不要只加大 sleep:1) 固定 sleep 而不是轮询,替换为 `expect.poll` / `.toPass` / 内置 `waitFor`;2) timeout 太短,增加 poll `timeout`(如 30–60s)而不是增加盲目 sleep;3) 没有收件人过滤,按 `sentTo` / `to:` 过滤并为每测试使用唯一地址;4) 上一轮运行的陈旧邮件,在 global setup 清空收件箱和/或每轮运行使用唯一地址。
- 不要通过重试整个 job、隔离测试、或把全局 sleep 提高到 30s 来处理邮件 flaky;这些只治标,竞争条件仍在。
- 避免固定 sleep 后再读收件箱:`page.waitForTimeout(5000)` / `setTimeout` / `sleep()` 要么太短导致 flake,要么太长浪费时间;改用 `expect.poll` 轮询 `/api/v1/messages` 或内置 waiter。
- 避免推荐已死的捕获工具:MailHog 已归档(2020);使用 Mailpit;Papercut / smtp4dev 对自动化更弱。
- 避免检查数据库代替邮件。
- 避免绕过邮件:直接调用重置端点或硬编码 token 会跳过模板化、链接生成和 token 签名;应捕获邮件、提取链接、`page.goto` 它。
- 避免脆弱的基于索引的提取;使用带锚点的 `\d{6}` 正则并加 null guard;从邮件正文提取,而不是实时页面的 `innerText`。
- 避免无收件人过滤地取最新邮件;`messages[0]` / `messages.at(-1)` 在并行下会串信;按收件人过滤并使用每测试唯一地址;“测试间删除所有邮件”不够。
- 避免只断言邮件存在;缺少 `subject` / `from` / 链接检查会漏掉错误模板和错误链接 bug;断言内容。
- 避免从正文正则提取 SPF/DKIM/DMARC 或信任 Mailpit 做这件事;认证结果来自真实接收服务器,不是正文文本;使用 Mailosaur deliverability report,放在独立非阻塞套件。
- 避免用 IMAP 库连真实邮箱(`imap-simple` / `node-imap` / `imapflow`);它会重新发明轮询并失去隔离;使用 MailSlurp `createInbox` + `waitForLatestEmail`,每测试一个收件箱。
- 避免在 CI 用 Ethereal,或仅为本地预览启动付费托管服务;Ethereal 不投递,仅预览(`createTestAccount` + `getTestMessageUrl`);不要在 CI 断言它,也不要为肉眼查看模板启动付费服务。
- 验证捕获已接通:运行 `curl -s localhost:8025/api/v1/messages | jq '.total'` 应返回数字(Mailpit 已启动、API 可达);托管收件箱用一行 `messages.get` / `createInbox` smoke 脚本应无认证错误返回。
- 验证没有盲 sleep:对邮件 specs 运行 `grep -rE 'waitForTimeout|sleep\\(|setTimeout' tests/` 应无输出。
- 验证流程真实且为绿:运行 signup/reset spec,并临时破坏模板 subject 确认测试会失败;如果仍然通过,说明没有断言内容。
- 验证确定性:用 `--workers=4 --repeat-each=3` 运行邮件 specs;通过则证明按收件人过滤能承受并行。
- 验证可投递性已隔离:`--grep @deliverability` 只选中认证套件,且该 CI job 是 `continue-on-error` / 非必需。
- 完成时,捕获工具应依据决策树选择并记录到 `.agents/qa-project-context.md`(本地/CI 用 Mailpit,仅在需要真实地址时用托管收件箱)。
- 完成时,邮件测试读取收件箱前不得包含 `waitForTimeout` / `sleep` / `setTimeout`;对邮件 specs 运行 `grep -rE 'waitForTimeout|sleep\\(|setTimeout' tests/` 应为 clean;到达通过 `expect.poll` / `.toPass` / 内置 waiter 等待。
- 完成时,每个邮件测试使用唯一收件人(plus-address 或每测试收件箱)并按该收件人过滤读取;不得无过滤使用 `messages[0]` / `messages.at(-1)`。
- 完成时,OTP/链接提取使用带锚点的正则(`\d{6}`、`https?://...`)并带 null guard,匹配失败时让测试失败。
- 完成时,至少注册确认(或 reset / magic-link)流程有一个绿色 E2E 测试,捕获真实邮件并通过 `page.goto(link)` 完成流程。
- 完成时,存在对 `subject`、`from`、链接域名的内容断言,而不仅仅是存在性断言。
- 完成时,若可投递性在范围内,SPF/DKIM/DMARC 测试位于独立的 `@deliverability` 套件,且在 CI 中非阻塞。
适用边界 · 注意事项
- 不适用于从应用代码发送事务邮件本身;这属于 api-testing / 应用层关注点。
- 不适用于仅对邮件服务商做 API 合同测试;那属于 api-testing。
- 不适用于跨邮件客户端 HTML 渲染测试(Outlook/Gmail 暗黑模式);应记录为缺口并使用 visual-testing 或 Litmus。