OAuth guides
Playwright OAuth testing: end-to-end login without flaking
Automate OAuth sign-in with Playwright using a mock IdP. Your app stays on localhost; consent runs on the issuer with stable selectors.
Playwright proves what users experience: click "Sign in," complete consent, land on a dashboard. Playwright OAuth testing stays maintainable when the authorization server is a predictable mock (HTTPS issuer, dummy users you control) and you separate one-time login setup from specs that only need an authenticated session.
What to cover in E2E tests
- Starting login from your app triggers a redirect to the expected authorize URL (issuer host, client_id, scope).
- After consent on the mock provider, the browser returns to your localhost callback and shows authenticated UI.
- Protected routes fail or redirect when the session is cleared.
- Optional: claim-based UI when your app reads
emailor custom claims from the ID token.
You do not need Playwright to hit Google in CI. Use a mock OAuth provider with a stable consent screen and dummy users.
Common Playwright patterns
storageState (fastest for most suites)
Run a dedicated auth.setup.ts project that performs login once and saves storageState to disk. Other projects load that file in use.storageState.
// auth.setup.ts — run before dependent projects
import { test as setup } from '@playwright/test'
setup('authenticate', async ({ page }) => {
await page.goto('http://127.0.0.1:3000/login')
await page.getByRole('button', { name: 'Sign in' }).click()
await page.getByRole('button', { name: 'Approve' }).click()
await page.waitForURL('**/api/auth/callback**')
await page.waitForURL('**/dashboard')
await page.context().storageState({ path: '.auth/user.json' })
})Full redirect flow every time
Slower but catches regressions in callback routes and cookie flags. Use for a small smoke project, not hundreds of specs.
Authorize and consent in the browser
Your app runs locally; the mock IdP serves authorize and consent over HTTPS.
- Click login in your app (localhost).
- Assert the URL host matches your configured issuer (e.g.
dummyoauth.com) and path includes/oauth/authorize. - Select a dummy user if offered, then approve.
- Wait for your loopback callback URL and an element only visible when logged in.
Keep PKCE and state enabled. See PKCE, state, and redirect URIs.
Speed and parallelism
- One shared auth setup per worker shard, not per test file.
- Run OAuth smoke tests in one project; use storageState elsewhere.
- Reuse the same project and dummy users across the team for consistent selectors.
Reducing flakes
- Use
waitForURLon your callback path instead of fixedwaitForTimeout. - Pin
OAUTH_ISSUERand redirect URI in CI env; do not rely on stale tunnel URLs. - Ensure runners have HTTPS egress to the mock provider.
- Isolate cookies per test with Playwright contexts when testing logout.
Pipeline setup: OAuth testing in CI.
Related guides
OAuth testing in CI: reliable login without production IdPs
Run automated OAuth and OIDC login tests in CI with a mock IdP. Keep localhost callbacks on your runner while tokens come from a stable issuer URL.
Mock OAuth and OIDC for development and tests
Use a mock IdP for dev and CI: your app keeps localhost callbacks while discovery, consent, and tokens come from an issuer like dummyÔauth.
PKCE, state, and redirect URIs for safer OAuth apps
Core defenses against authorization code interception, CSRF on the callback, and redirect manipulation.