DEV Community

Cover image for How to E2E Test Supabase Auth Email Flows in Playwright (No Docker, No InBucket)
zerodrop
zerodrop

Posted on

How to E2E Test Supabase Auth Email Flows in Playwright (No Docker, No InBucket)

If you've tried to test Supabase authentication flows end-to-end in a CI pipeline, you've probably hit the same wall: the email.

Supabase's signup flow sends a verification email. Your Playwright test needs to click that verification link or enter that OTP. But the email lands in... nowhere your test can reach.

The standard advice is to use InBucket — Supabase's local email interceptor. But InBucket requires running Supabase locally via the CLI, which means Docker in CI. That's a Docker service block in your GitHub Actions YAML, slower builds, and a local Supabase instance that drifts from production.

There's a simpler way.

The Problem with Existing Approaches

InBucket / Local Supabase: InBucket is great for local development — it ships with supabase start and catches emails instantly. But it requires Docker, a full local Supabase stack, and doesn't test against your real Supabase project. In CI, that means a Docker service block, slower builds, and a local instance that can drift from production.

Mocking Supabase: You're not testing the real flow. Your tests pass while production auth could be broken.

Database triggers to set predictable OTPs: Clever, but adds complexity and requires direct database access in CI.

Shared Gmail inbox: Race conditions everywhere in parallel test runs.

The ZeroDrop Approach

ZeroDrop gives you real disposable email inboxes caught at Cloudflare's edge. No Docker. No local Supabase. No shared inbox collisions.

You send a real signup to your real Supabase project, using a ZeroDrop inbox as the email address. The verification email arrives in under a second. Your test reads email.otp or email.magicLink — auto-extracted, no regex needed.

Your app → Supabase → sends verification email → ZeroDrop catches it → your test reads email.otp
Enter fullscreen mode Exit fullscreen mode

Setup

Install the SDK:

npm install zerodrop-client
Enter fullscreen mode Exit fullscreen mode

No API key needed. No signup required.

Testing Supabase Email Verification (OTP Flow)

Supabase's default signup flow sends a 6-digit OTP for email verification. Here's how to test it end-to-end:

import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';

const mail = new ZeroDrop();

test('Supabase signup with email verification', async ({ page }) => {
  // Generate a unique inbox for this test run
  const inbox = mail.generateInbox();

  // Navigate to your signup page
  await page.goto('/signup');

  // Fill in the signup form with the ZeroDrop inbox
  await page.fill('[name="email"]', inbox);
  await page.fill('[name="password"]', 'TestPassword123!');
  await page.click('[type="submit"]');

  // Wait for Supabase to send the verification email
  // ZeroDrop catches it at the edge — arrives in under 1 second
  const email = await mail.waitForLatest(inbox, { timeout: 15000 });

  // OTP is auto-extracted — no regex needed
  expect(email.otp).toBeTruthy();

  // Enter the OTP on the verification screen
  await page.fill('[name="otp"]', email.otp!);
  await page.click('[type="submit"]');

  // Verify the user is now authenticated
  await expect(page).toHaveURL('/dashboard');
});
Enter fullscreen mode Exit fullscreen mode

Testing Supabase Magic Link Flow

If you're using Supabase's passwordless magic link auth:

import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';

const mail = new ZeroDrop();

test('Supabase magic link login', async ({ page }) => {
  const inbox = mail.generateInbox();

  // Request a magic link
  await page.goto('/login');
  await page.fill('[name="email"]', inbox);
  await page.click('button:has-text("Send magic link")');

  // Catch the magic link email
  const email = await mail.waitForLatest(inbox, { timeout: 15000 });

  // magicLink is auto-extracted from the email body
  expect(email.magicLink).toBeTruthy();

  // Navigate directly to the magic link
  await page.goto(email.magicLink!);

  // User should now be authenticated
  await expect(page).toHaveURL('/dashboard');
});
Enter fullscreen mode Exit fullscreen mode

Testing Password Reset

test('Supabase password reset flow', async ({ page }) => {
  const inbox = mail.generateInbox();

  // First, create a user (or use an existing test account)
  // Then trigger a password reset
  await page.goto('/forgot-password');
  await page.fill('[name="email"]', inbox);
  await page.click('button:has-text("Send reset email")');

  // Catch the reset email
  const email = await mail.waitForLatest(inbox, { timeout: 15000 });

  // Navigate to the reset link
  await page.goto(email.magicLink!);

  // Set a new password
  await page.fill('[name="password"]', 'NewPassword123!');
  await page.fill('[name="confirmPassword"]', 'NewPassword123!');
  await page.click('[type="submit"]');

  await expect(page).toHaveURL('/login');
});
Enter fullscreen mode Exit fullscreen mode

Running in GitHub Actions CI

No Docker required. Add ZeroDrop's GitHub Action to generate isolated inboxes per test run:

name: 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: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
    env:
      NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
      NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
Enter fullscreen mode Exit fullscreen mode

That's it. No supabase start. No Docker service block. ZeroDrop inboxes are generated client-side — no network request, no setup.

Parallel Test Runs — No Collisions

Because generateInbox() creates a unique inbox on every call with no network request, parallel CI runs are safe by default:

// Each worker gets its own inbox — no shared state, no collisions
test.describe.configure({ mode: 'parallel' });

test('user A signup', async ({ page }) => {
  const inbox = mail.generateInbox(); // unique to this test
  // ...
});

test('user B signup', async ({ page }) => {
  const inbox = mail.generateInbox(); // different inbox, zero collision risk
  // ...
});
Enter fullscreen mode Exit fullscreen mode

50 parallel workers. 50 isolated inboxes. Zero race conditions.

InBucket vs ZeroDrop — When to Use Which

InBucket is the right choice for local development — it's built into Supabase's local stack and works great there. ZeroDrop is the right choice for CI pipelines against real Supabase projects.

InBucket ZeroDrop
Best for Local development CI pipelines
Docker required
Tests real Supabase project
OTP auto-extraction
Magic link auto-extraction
Parallel-safe
CI setup complexity High None

Conclusion

Testing Supabase auth flows end-to-end doesn't have to mean running a full local stack. ZeroDrop gives you real email delivery verification against your real Supabase project, with no Docker, no InBucket, and no shared inbox collisions.

email.otp and email.magicLink are auto-extracted at Cloudflare's edge before they reach your test suite. No regex. No HTML parsing. No race conditions.

Free to use. No signup required. Works in CI out of the box.

zerodrop.dev · npm · GitHub Action

Top comments (0)