ZERODROP
← INTEGRATIONSDOCS
PlaywrightSupabaseGitHub Actions

Testing Supabase Auth Email Flows with Playwright

Test Supabase email verification, OTP codes and magic links in real CI pipelines — without Docker, InBucket, or a local Supabase stack. Tests run against your real Supabase project.

InBucket vs ZeroDrop

InBucket is great for local development — it ships with supabase start and works well there. But in CI, it requires Docker, a full local Supabase stack, and doesn't test against your real Supabase project.

ZeroDrop works against your real Supabase project in real CI — no Docker service block, no local stack.

Install

npm install zerodrop-client @supabase/supabase-js

Email verification (OTP flow)

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

const mail = new ZeroDrop();

test("Supabase signup with OTP verification", async ({ page }) => {
  const inbox = mail.generateInbox();

  await page.goto("/signup");
  await page.fill('[name="email"]', inbox);
  await page.fill('[name="password"]', "TestPassword123!");
  await page.click('[type="submit"]');

  // Supabase sends the verification email
  // ZeroDrop catches it in <1s
  const email = await mail.waitForLatest(inbox, { timeout: 15000 });

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

  await page.fill('[name="otp"]', email.otp!);
  await page.click('[type="submit"]');

  await expect(page).toHaveURL("/dashboard");
});

Magic link login

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

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

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

  // magicLink auto-extracted
  expect(email.magicLink).toBeTruthy();
  await page.goto(email.magicLink!);

  await expect(page).toHaveURL("/dashboard");
});

Password reset

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

  await page.goto("/forgot-password");
  await page.fill('[name="email"]', inbox);
  await page.click('[type="submit"]');

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

  await page.goto(email.magicLink!);
  await page.fill('[name="password"]', "NewPassword123!");
  await page.click('[type="submit"]');

  await expect(page).toHaveURL("/login");
});

GitHub Actions CI — no Docker

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 chromium
      - run: npx playwright test
    env:
      NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
      NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}

No services: block. No supabase start. Tests run against your real Supabase project.

What you're actually testing

Supabase Auth email delivery is working correctly
OTP codes are valid and accepted
Magic links contain valid Supabase callback URLs
Password reset flow works end to end
Your real Supabase project — not a local mock

Ready to test your Supabase auth flows?

Free tier. No signup. No Docker. Works in CI in 5 minutes.

OPEN INBOX →

RELATED GUIDES

Playwright + NextAuthPlaywright + Better AuthPlaywright + ClerkFull article on dev.to