DEV Community

nareshipme
nareshipme

Posted on

How to Add Playwright E2E Auth Fixtures for Clerk in Next.js

Testing a Next.js app that uses Clerk for authentication? Getting mysterious 302 redirects instead of your expected API responses? Here's a clean, minimal pattern that works.


The Problem

Clerk intercepts unauthenticated requests and redirects them to /sign-in. This is great for production, but it means Playwright tests that don't set up auth properly will silently "pass" by landing on a 200 sign-in page instead of actually testing your API.

There are two things you need to solve:

  1. Global Clerk setup — initialise Clerk's testing infrastructure once before all tests
  2. Per-test token injection — attach a Clerk testing token to each page so middleware recognises the session

Step 1: Global Setup with clerkSetup

Create e2e/global-setup.ts:

import { clerkSetup } from "@clerk/testing/playwright";

async function globalSetup() {
  await clerkSetup();
}

export default globalSetup;
Enter fullscreen mode Exit fullscreen mode

Wire it up in playwright.config.ts:

export default defineConfig({
  globalSetup: require.resolve("./e2e/global-setup"),
  // ... rest of your config
});
Enter fullscreen mode Exit fullscreen mode

clerkSetup() initialises the Clerk test environment and sets the CLERK_SECRET_KEY env var for token generation. Without this, setupClerkTestingToken will throw.


Step 2: Custom Auth Fixture

Instead of calling setupClerkTestingToken in every test, wrap it in a custom fixture:

// e2e/fixtures/auth.ts
import { test as base } from "@playwright/test";
import { setupClerkTestingToken } from "@clerk/testing/playwright";

export const test = base.extend({
  page: async ({ page }, use) => {
    await setupClerkTestingToken({ page });
    await use(page);
  },
});

export { expect } from "@playwright/test";
Enter fullscreen mode Exit fullscreen mode

Now import test from this fixture in your spec files instead of from @playwright/test:

// e2e/dashboard.spec.ts
import { test, expect } from "./fixtures/auth";

test("dashboard loads for authenticated user", async ({ page }) => {
  await page.goto("/dashboard");
  // Clerk middleware will recognise the token — no redirect
  await expect(page.getByText("Your projects")).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

Every test using this test automatically gets a Clerk testing token attached to its page. Clean.


Step 3: Testing API Auth (Don't Trust 200s)

Here's a subtle gotcha: if you're testing that unauthenticated API requests are properly rejected, Playwright's default behaviour follows redirects. So a 302 → /sign-in → 200 HTML page will look like a 200 success.

Fix it by disabling redirect following:

const NO_REDIRECT = { maxRedirects: 0, failOnStatusCode: false };

test("unauthenticated GET /api/projects is rejected", async ({ request }) => {
  const res = await request.get("/api/projects", NO_REDIRECT);
  // Clerk returns 302 to /sign-in — never a 200 API response
  expect(res.status()).not.toBe(200);
});
Enter fullscreen mode Exit fullscreen mode

maxRedirects: 0 stops Playwright from following the redirect. failOnStatusCode: false prevents the test from throwing on non-2xx before your assertion runs. Now you're testing actual auth behaviour, not the sign-in page HTML.


When Tests Should Skip vs Fail

If you run E2E tests in CI without a real test user (or without CLERK_SECRET_KEY), tests that require an authenticated session will fail. One pragmatic approach: check for the env var and skip gracefully:

test.beforeEach(async () => {
  if (!process.env.CLERK_SECRET_KEY) {
    test.skip(true, "No CLERK_SECRET_KEY — skipping auth-dependent tests");
  }
});
Enter fullscreen mode Exit fullscreen mode

This way your CI pipeline doesn't break on auth tests that simply can't run without credentials.


Summary

Concern Solution
Clerk initialisation clerkSetup() in globalSetup
Per-test token injection setupClerkTestingToken in a custom fixture
Avoiding redirect false-positives maxRedirects: 0, failOnStatusCode: false
Skipping without credentials Check CLERK_SECRET_KEY in beforeEach

The fixture pattern is the key insight here — it keeps your tests clean and ensures you never accidentally run an "authenticated" test without the token. One import swap and you're done.


If you found this useful, check out the Clerk Playwright testing docs for deeper configuration options like signIn and signUp helpers.

Top comments (0)