import type { APIRequestContext } from '@playwright/test' const mailpitApiBase = process.env.MAILPIT_API_URL?.replace(/\/$/, '') || 'http://localhost:8025' interface MailpitAddress { Name: string Address: string } interface MailpitMessageSummary { ID: string To: MailpitAddress[] Subject: string } interface MailpitMessagesResponse { messages: MailpitMessageSummary[] } interface MailpitMessageDetail { Text?: string HTML?: string } export async function clearMailpit(request: APIRequestContext): Promise { await request.delete(`${mailpitApiBase}/api/v1/messages`) } export async function countMessagesTo( request: APIRequestContext, recipientEmail: string, ): Promise { const listResponse = await request.get(`${mailpitApiBase}/api/v1/messages`) if (!listResponse.ok()) return 0 const list = (await listResponse.json()) as MailpitMessagesResponse const normalized = recipientEmail.toLowerCase().trim() return (list.messages ?? []).filter((msg) => msg.To?.some((to) => to.Address.toLowerCase() === normalized), ).length } export async function waitForPasswordResetToken( request: APIRequestContext, recipientEmail: string, options: { timeoutMs?: number; publicBaseUrl?: string } = {}, ): Promise { const timeoutMs = options.timeoutMs ?? 20_000 const deadline = Date.now() + timeoutMs const normalizedRecipient = recipientEmail.toLowerCase().trim() while (Date.now() < deadline) { const listResponse = await request.get(`${mailpitApiBase}/api/v1/messages`) if (!listResponse.ok()) { await sleep(500) continue } const list = (await listResponse.json()) as MailpitMessagesResponse for (const summary of list.messages ?? []) { const matchesRecipient = summary.To?.some( (to) => to.Address.toLowerCase() === normalizedRecipient, ) if (!matchesRecipient) continue const detailResponse = await request.get( `${mailpitApiBase}/api/v1/message/${summary.ID}`, ) if (!detailResponse.ok()) continue const detail = (await detailResponse.json()) as MailpitMessageDetail const body = detail.Text ?? detail.HTML ?? '' const token = extractResetToken(body, options.publicBaseUrl) if (token) return token } await sleep(500) } throw new Error( `No password reset email for ${recipientEmail} in Mailpit within ${timeoutMs}ms`, ) } function extractResetToken(body: string, publicBaseUrl?: string): string | null { const pathPattern = /\/aterstall-losenord\?token=([A-Za-z0-9_-]+)/ const pathMatch = body.match(pathPattern) if (pathMatch) return pathMatch[1] if (publicBaseUrl) { const escaped = publicBaseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const fullPattern = new RegExp( `${escaped}/aterstall-losenord\\?token=([A-Za-z0-9_-]+)`, ) const fullMatch = body.match(fullPattern) if (fullMatch) return fullMatch[1] } return null } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) }