ZERODROP
← INTEGRATIONSDOCS
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.

OPEN INBOX →

RELATED GUIDES

Playwright + ClerkPlaywright + Auth0Full Playwright guideGitHub Actions guide