Testing Better Auth email flows with Playwright and ZeroDrop — no Docker, no regex, no shared inboxes.
Covers all four email flows Better Auth supports — end to end, in CI, with real emails.
git clone https://github.com/zerodrop-dev/better-auth-playwright-zerodrop
cd better-auth-playwright-zerodrop
pnpm install
cp .env.example .env # add BETTER_AUTH_SECRET + RESEND_API_KEY
pnpm prisma migrate dev
pnpm test| Test file | Better Auth feature | ZeroDrop field |
|---|---|---|
email-verification.spec.ts |
emailVerification |
email.magicLink |
magic-link.spec.ts |
magicLink plugin |
email.magicLink |
email-otp.spec.ts |
emailOTP plugin |
email.otp |
password-reset.spec.ts |
sendResetPassword |
email.magicLink |
Better Auth sends real emails via Resend. ZeroDrop catches them at Cloudflare's edge and auto-extracts OTPs and magic links before your test reads them.
// Generate a unique inbox per test — no network request
const inbox = mail.generateInbox();
// Trigger the email flow in your app...
await page.fill('[name="email"]', inbox);
await page.click('[type="submit"]');
// ZeroDrop catches the email in <1s
const email = await mail.waitForLatest(inbox, { timeout: 15000 });
email.otp // "847291" — auto-extracted, no regex
email.magicLink // "https://..." — auto-extracted, no HTML parsingNo regex. No HTML parsing. No shared inboxes. No Docker.
better-auth-playwright-zerodrop/
├── app/
│ ├── api/auth/[...all]/
│ │ └── route.ts # Better Auth API handler
│ ├── lib/
│ │ ├── auth.ts # Better Auth server config
│ │ └── auth-client.ts # Better Auth client config
│ ├── signup/page.tsx # Sign up page
│ ├── login/page.tsx # Login (password, magic link, OTP)
│ ├── dashboard/page.tsx # Protected dashboard
│ ├── forgot-password/ # Password reset request
│ └── reset-password/ # Password reset form
├── tests/
│ ├── email-verification.spec.ts
│ ├── magic-link.spec.ts
│ ├── email-otp.spec.ts
│ └── password-reset.spec.ts
├── prisma/
│ └── schema.prisma # User, Session, Account, Verification
├── playwright.config.ts
├── package.json
└── .env.example
| Tool | Purpose |
|---|---|
| Better Auth | Authentication framework |
| Resend | Email sending |
| ZeroDrop | Email catching + OTP/magic link extraction |
| Playwright | E2E testing |
| Next.js 15 | App framework |
| Prisma + SQLite | Database (swap for Postgres in production) |
1. Clone and install
git clone https://github.com/zerodrop-dev/better-auth-playwright-zerodrop
cd better-auth-playwright-zerodrop
pnpm install2. Configure environment
cp .env.example .env| Variable | Where to get it |
|---|---|
BETTER_AUTH_SECRET |
openssl rand -base64 32 |
RESEND_API_KEY |
resend.com → API Keys |
DATABASE_URL |
file:./dev.db (SQLite, already set) |
3. Set up the database
pnpm prisma migrate dev4. Run the app
pnpm dev5. Run tests
pnpm testNo Docker. No SMTP service. ZeroDrop works out of the box.
- uses: actions/checkout@v4
- run: pnpm install
- run: pnpm prisma migrate deploy
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm test
env:
BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
DATABASE_URL: file:./test.dbEvery Better Auth email flow sends a real email. Without a way to catch and read that email, you can't test the full flow.
The common workarounds:
| Approach | Problem |
|---|---|
| Mock the email | Tests pass while broken emails ship to production |
| Shared Gmail inbox | Race conditions in parallel test runs |
| MailHog | Requires Docker, doesn't test your real email provider |
ZeroDrop gives each test a real isolated inbox. OTPs and magic links are extracted at Cloudflare's edge — your test just reads email.otp or email.magicLink.
Free, no signup required → zerodrop.dev