| name | e2e-playwright |
| description | End-to-end testing with Playwright in Next.js applications — user flow coverage, component testing, and CI integration. Use when writing E2E tests, debugging flaky tests, or setting up test infrastructure. |
E2E Testing with Playwright
End-to-end testing strategy for Next.js applications using Playwright.
Setup
npm init playwright@latest
Recommended config (playwright.config.ts):
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
})
What to Test
Critical User Journeys
Test the paths users actually take, not every component:
✓ User can sign up → verify email → login → see dashboard
✓ User can create/edit/delete content
✓ User can complete checkout flow
✓ Error states: 404, 500, network failure
✓ Responsive breakpoints (mobile, tablet, desktop)
What NOT to Test with E2E
- Unit-verifiable logic (test in Vitest/Jest)
- Individual component states (test in Storybook/Component tests)
- Visual snapshots of static content
Patterns
Page Object Pattern
export class LoginPage {
constructor(private page: Page) {}
async goto() { await this.page.goto('/login') }
async login(email: string, password: string) {
await this.page.fill('[name="email"]', email)
await this.page.fill('[name="password"]', password)
await this.page.click('button[type="submit"]')
}
async waitForDashboard() {
await expect(this.page).toHaveURL(/\/dashboard/)
}
}
Data Seeding
import { seedTestData } from './helpers/seed'
export default async () => {
await seedTestData({
user: { email: 'test@example.com' },
posts: 3,
})
}
Mocking API/Network
await page.route('**/api/analytics', route => route.abort())
await page.route('**/api/posts/**', async route => {
await route.fulfill({ json: { title: 'Mock post' } })
})
CI Integration
name: E2E
on: [deployment_status]
jobs:
test:
if: github.event_name == 'deployment_status' && github.event.state == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install
- run: npx playwright test
Debugging Flaky Tests
- Check trace (
trace: 'on-first-retry') — Playwright captures full DOM + network + console
- Add
await page.waitForLoadState('networkidle') after navigation
- Use
toHaveURL / toHaveText instead of arbitrary timeouts
- Isolate tests — each test should be self-contained, no shared state
Checklist