import { request, type APIRequestContext } from '@playwright/test' /** * Helpers pour interagir avec Mailpit (SMTP catcher local). * * Mailpit tourne sur http://localhost:8025 via docker-compose dev * (cf. /docker-compose.dev.yml). L'API REST permet de : * - Lister les messages reçus * - Récupérer le contenu HTML/texte d'un message * - Vider la boîte (utile en beforeEach E2E) * * Doc API : https://mailpit.axllent.org/docs/api-v1/ */ const MAILPIT_URL = process.env.E2E_MAILPIT_URL ?? 'http://localhost:8025' let _ctx: APIRequestContext | null = null async function ctx(): Promise { if (_ctx) return _ctx _ctx = await request.newContext({ baseURL: MAILPIT_URL }) return _ctx } export type MailpitMessage = { ID: string MessageID: string Read: boolean From: { Name: string; Address: string } To: Array<{ Name: string; Address: string }> Subject: string Created: string Snippet: string } export type MailpitMessageDetail = MailpitMessage & { Text: string HTML: string ReplyTo: Array<{ Name: string; Address: string }> } /** * Vide TOUS les messages Mailpit. À appeler en `beforeEach` E2E pour * isoler les assertions email entre scénarios. */ export async function clearMailpit(): Promise { const c = await ctx() await c.delete('/api/v1/messages') } /** * Liste les messages reçus, plus récent en premier. */ export async function listMessages(): Promise { const c = await ctx() const r = await c.get('/api/v1/messages') if (!r.ok()) throw new Error(`Mailpit list failed: ${r.status()}`) const json = (await r.json()) as { messages: MailpitMessage[] } return json.messages ?? [] } /** * Attend qu'au moins un message arrive pour l'adresse `to`, avec polling * toutes les 200 ms (timeout 10 s par défaut). Renvoie le 1er match. * * Utile parce que les emails partent via BullMQ worker (async) — l'instant * où on clique sur "Relancer maintenant" et l'instant où Mailpit reçoit * peuvent être espacés de 1-2 s. */ export async function waitForMessageTo( to: string, opts: { timeoutMs?: number; subjectIncludes?: string } = {}, ): Promise { const timeoutMs = opts.timeoutMs ?? 10_000 const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { const messages = await listMessages() const match = messages.find( (m) => m.To.some((t) => t.Address.toLowerCase() === to.toLowerCase()) && (!opts.subjectIncludes || m.Subject.includes(opts.subjectIncludes)), ) if (match) return match await new Promise((r) => setTimeout(r, 200)) } throw new Error( `Mailpit : aucun message reçu pour ${to}${opts.subjectIncludes ? ` (subject contient "${opts.subjectIncludes}")` : ''} dans ${timeoutMs} ms`, ) } /** * Récupère le détail d'un message (HTML + texte). */ export async function getMessage(id: string): Promise { const c = await ctx() const r = await c.get(`/api/v1/message/${id}`) if (!r.ok()) throw new Error(`Mailpit get failed: ${r.status()}`) return (await r.json()) as MailpitMessageDetail }