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 + NextAuth →Playwright + Better Auth →Playwright + Clerk →Full article on dev.to →