PlaywrightNextAuth.jsGitHub Actions
How to End-to-End Test NextAuth.js Magic Links with Playwright
A copy-paste runbook for testing real NextAuth EmailProvider flows in Playwright CI. No MailHog. No mocking. No Docker.
The problem
Mocks are lying to you. Bypassing your NextAuth EmailProvider in Playwright tests might get you a green checkmark, but it doesn't verify if your SMTP credentials are valid, if your Edge runtime is properly signing the JWT, or if your actual email templates are rendering the magic link correctly.
You need to test the physical email delivery in your CI/CD pipeline without the overhead of Dockerizing MailHog or sharing a real inbox between parallel test runs.
Install
npm install zerodrop-client
GitHub Actions workflow
name: Playwright E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Generate test inbox
id: inbox
uses: zerodrop-dev/create-inbox@8706a59 # v1.0.0
- name: Run Playwright tests
run: npx playwright test
env:
TEST_INBOX: ${{ steps.inbox.outputs.inbox }}
NEXTAUTH_URL: ${{ secrets.STAGING_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
EMAIL_SERVER: ${{ secrets.EMAIL_SERVER }}
EMAIL_FROM: ${{ secrets.EMAIL_FROM }}The Playwright test
import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';
const mail = new ZeroDrop();
test('NextAuth magic link sign-in flow', async ({ page }) => {
// Use CI inbox or generate locally
const inbox = process.env.TEST_INBOX ?? mail.generateInbox();
// 1. Navigate to NextAuth default sign-in page
await page.goto('/api/auth/signin');
// 2. Fill in the disposable email address
await page.fill('input[name="email"]', inbox);
await page.click('button[type="submit"]');
// 3. NextAuth shows "Check your email" screen
await expect(page.locator('text=Check your email')).toBeVisible();
// 4. Catch the magic link email — auto-extracted, no regex
const email = await mail.waitForLatest(inbox, { timeout: 30000 });
// 5. Verify it's a NextAuth callback URL
expect(email.magicLink).not.toBeNull();
expect(email.magicLink).toContain('/api/auth/callback/email');
// 6. Complete the auth flow
await page.goto(email.magicLink!);
// 7. Verify successful session
await expect(page.locator('text=Sign out')).toBeVisible();
await expect(page).toHaveURL('/dashboard');
});NextAuth configuration
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import EmailProvider from 'next-auth/providers/email';
export const { handlers, auth } = NextAuth({
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
pages: {
signIn: '/auth/signin',
verifyRequest: '/auth/verify',
},
});What you're actually testing
✓Your SMTP credentials are valid and working
✓NextAuth's JWT signing is correct in your Edge runtime
✓Your email template renders the callback URL correctly
✓The callback URL authenticates and creates a session
✓The session redirects to the right page
Ready to test your NextAuth flows?
Free tier. No signup. No Docker. Works in CI in 5 minutes.