Skip to content

Commit 43e017c

Browse files
test(e2e): add mailpit helper for email-based test flows
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 88d1114 commit 43e017c

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

playwright/support/mailpit.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { MailpitClient } from 'mailpit-api'
7+
8+
export type { MailpitClient }
9+
10+
type Message = Awaited<ReturnType<MailpitClient['getMessageSummary']>>
11+
12+
/** Creates a MailpitClient using MAILPIT_URL (default: http://localhost:8025). */
13+
export function createMailpitClient(): MailpitClient {
14+
return new MailpitClient(process.env.MAILPIT_URL ?? 'http://localhost:8025')
15+
}
16+
17+
/** Fetches the latest email sent to `toAddress`, optionally filtered by `subject`. */
18+
export async function getLatestEmailTo(
19+
client: MailpitClient,
20+
toAddress: string,
21+
subject?: string,
22+
): Promise<Message> {
23+
const query = subject
24+
? `to:${toAddress} subject:"${subject}"`
25+
: `to:${toAddress}`
26+
const result = await client.searchMessages({ query })
27+
if (!result.messages || result.messages.length === 0) {
28+
throw new Error(`No email found for "${toAddress}"${subject ? ` with subject "${subject}"` : ''}`)
29+
}
30+
return await client.getMessageSummary(result.messages[0].ID)
31+
}
32+
33+
/**
34+
* Polls MailPit until an email matching `toAddress` (and optional `subject`) is found,
35+
* or until `timeout` ms elapse. Checks every `interval` ms (defaults: 30 s / 1 s).
36+
*/
37+
export async function waitForEmailTo(
38+
client: MailpitClient,
39+
toAddress: string,
40+
subject?: string,
41+
options?: { timeout?: number; interval?: number },
42+
): Promise<Message> {
43+
const timeout = options?.timeout ?? 30_000
44+
const interval = options?.interval ?? 1_000
45+
const deadline = Date.now() + timeout
46+
while (Date.now() < deadline) {
47+
try {
48+
return await getLatestEmailTo(client, toAddress, subject)
49+
} catch {
50+
// email not arrived yet
51+
}
52+
await new Promise(resolve => setTimeout(resolve, interval))
53+
}
54+
throw new Error(
55+
`Timeout (${timeout} ms) waiting for email to "${toAddress}"${
56+
subject ? ` with subject "${subject}"` : ''
57+
}`,
58+
)
59+
}
60+
61+
/** Extracts a LibreSign sign link from an email body matching /p/sign/{uuid}. */
62+
export function extractSignLink(body: string): string | null {
63+
const match = body.match(/\S+\/p\/sign\/[\w-]+/)
64+
return match ? match[0] : null
65+
}
66+
67+
/** Extracts a numeric token from an email body. Default pattern: 4-8 digit sequence. */
68+
export function extractTokenFromEmail(
69+
body: string,
70+
pattern: RegExp = /Use this code to sign the document:[\s\S]*?(\d{6})/,
71+
): string | null {
72+
const match = body.match(pattern)
73+
return match ? match[1] : null
74+
}
75+
76+
/** Extracts the first URL from an email body (email.Text). */
77+
export function extractLinkFromEmail(body: string): string | null {
78+
const match = body.match(/https?:\/\/\S+/)
79+
return match ? match[0] : null
80+
}

0 commit comments

Comments
 (0)