OTPBox / Anleitungen / Playwright
PlaywrightEin Registrierungsablauf, der mit "sieh in deinem Postfach nach dem Code" endet, ist end-to-end schwer zu testen, weil der Test ein Postfach braucht, das wirklich existiert und wirklich Post empfängt. OTPBox gibt Playwright eine echte Wegwerf-Adresse und eine Möglichkeit, auf den empfangenen Code zu warten — ohne den E-Mail-Anbieter zu mocken, ohne ein gemeinsam genutztes Test-Konto, um das sich andere Testläufe streiten.
Jeder Ansatz unten folgt denselben vier Schritten: ein echtes Postfach erstellen, dessen Adresse im Registrierungsformular verwenden, blockieren, bis der OTP oder der Verifizierungslink eintrifft, dann den Ablauf abschließen und aufräumen. Der Unterschied liegt nur darin, wie viel Boilerplate-Code du selbst schreibst.
fetchKeine Abhängigkeit außer Playwright selbst. Erzeuge einmalig einen kostenlosen Schlüssel (200 Anfragen/Monat, oder nutze einen Organisations-Schlüssel für CI — siehe Einen Schlüssel erhalten), und rufe dann die API direkt im Test auf:
import { test, expect } from '@playwright/test';
test('sign up with a real OTP', async ({ page }) => {
const key = process.env.OTPBOX_KEY!;
const inboxRes = await fetch('https://otpbox.io/api/v1/inboxes', {
method: 'POST',
headers: { authorization: `Bearer ${key}`, 'content-type': 'application/json' },
body: '{}',
});
const inbox = await inboxRes.json(); // { id, address, domain, expiresAt, token }
await page.goto('https://your-app.example.com/signup');
await page.fill('[name="email"]', inbox.address);
await page.click('button[type="submit"]');
// Poll for the code (a real production test should back off / cap retries)
let code: string | null = null;
for (let i = 0; i < 15 && !code; i++) {
await new Promise((r) => setTimeout(r, 1500));
const msgs = await fetch(`https://otpbox.io/api/v1/inboxes/${inbox.id}/messages`, {
headers: { authorization: `Bearer ${key}` },
}).then((r) => r.json());
code = msgs.messages[0]?.code ?? null;
}
await page.fill('[name="otp"]', code!);
await page.click('button[type="submit"]');
await expect(page.locator('text=Welcome')).toBeVisible();
await fetch(`https://otpbox.io/api/v1/inboxes/${inbox.id}`, {
method: 'DELETE',
headers: { authorization: `Bearer ${key}` },
});
});
otpbox-sdk (empfohlen)otpbox-sdk ist der offizielle TypeScript-Client, auf npm veröffentlicht, und erspart dir die manuelle Polling-Schleife dank waitForOtp():
npm install otpbox-sdk
import { test, expect } from '@playwright/test';
import { OTPBox } from 'otpbox-sdk';
test('sign up with a real OTP', async ({ page }) => {
const client = new OTPBox({ apiKey: process.env.OTPBOX_KEY! });
const inbox = await client.createInbox();
try {
await page.goto('https://your-app.example.com/signup');
await page.fill('[name="email"]', inbox.address);
await page.click('button[type="submit"]');
const code = await client.waitForOtp(inbox.id, { timeoutMs: 20_000 });
await page.fill('[name="otp"]', code!);
await page.click('button[type="submit"]');
await expect(page.locator('text=Welcome')).toBeVisible();
} finally {
await client.deleteInbox(inbox.id);
}
});
waitForOtp() fragt getMessages() ab, bis eine nach Aufrufbeginn empfangene Nachricht einen extrahierten Code hat, oder bis das Zeitlimit abläuft (liefert bei Timeout null — code! oben ist in Ordnung, sobald du das in deinem eigenen Test geprüft hast, aber prüfe in echtem Code auf null). Es gibt außerdem waitForEmail(), falls du die ganze Nachricht willst, und getUsage(), falls eine Testsuite prüfen möchte, dass sie ihr Kontingent noch nicht aufgebraucht hat.
otpbox-playwright-Fixture (in Arbeit)Ein eigenes Playwright-Paket, otpbox-playwright, verpackt otpbox-sdk in eine eigene test-Fixture, sodass eine Spec nicht einmal selbst createInbox()/deleteInbox() aufrufen muss:
import { test, expect } from 'otpbox-playwright';
test('sign up with a real OTP', async ({ page, otpboxInbox }) => {
await page.goto('https://your-app.example.com/signup');
await page.fill('[name="email"]', otpboxInbox.address);
await page.click('button[type="submit"]');
const code = await otpboxInbox.waitForOtp();
await page.fill('[name="otp"]', code);
await page.click('button[type="submit"]');
await expect(page.locator('text=Welcome')).toBeVisible();
// no inbox.delete() to write - the fixture tears it down for you
});
otpbox-playwright ist noch nicht auf npm veröffentlicht. Es ist echter, funktionierender Code im selben Monorepo wie otpbox-sdk, aber bis es als Paket erscheint, würdest du es per npm link gegen einen Klon des Repos installieren statt per npm install otpbox-playwright. Bis dahin ist Option 2 (otpbox-sdk direkt) der unterstützte Weg und liefert dasselbe waitForOtp()/waitForVerificationLink()-Verhalten mit ein paar zusätzlichen Zeilen Setup/Teardown.Manche Registrierungsabläufe verschicken einen Bestätigungslink statt eines sechsstelligen Codes. Dasselbe Postfach funktioniert dafür — nutze waitForEmail() und lies message.link (oder hole dir mit otpbox-sdk die vollständige Nachricht via getMessage(messageId) für link.url):
const message = await client.waitForEmail(inbox.id, { timeoutMs: 20_000 });
const full = await client.getMessage(message!.id);
await page.goto(full.link!.url);
await expect(page.locator('text=Verified')).toBeVisible();
OTPBOX_KEY als Repo-Secret, niemals im Workflow-YAML — siehe den Abschnitt CI/CD der Dokumentation für Beispiele zu GitHub Actions, GitLab CI, Jenkins und CircleCI.POST /api/v1/inboxes einen Idempotency-Key-Header, falls deine CI instabile Netzwerkaufrufe wiederholt, damit ein erneuter Versuch kein zweites Postfach anlegt — siehe Idempotenzschlüssel.finally-Block, damit ein fehlgeschlagener Test es nicht verwaist zurücklässt — auch wenn der 15-Minuten-Ablauf-Cronjob es ohnehin aufräumt.Bereit, es an deiner eigenen App auszuprobieren? Kostenloses Konto erstellen
← Zurück zu OTPBox