Playwright E2E Setup
Install and configure Playwright for end-to-end testing of your Next.js application.
Search across all documentation pages
Install and configure Playwright for end-to-end testing of your Next.js application.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card -- copy-paste ready.
# Install Playwright
npm init playwright@latest
# Or install manually
npm install -D @playwright/test
npx playwright install// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? "github" : "html",
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});# Run all tests
npx playwright test
# Run in headed mode (see the browser)
npx playwright test --headed
# Run with Playwright UI
npx playwright test --ui
# Run a specific file
npx playwright test e2e/home.spec.ts
# Debug mode (step through)
npx playwright test --debug
# Show HTML report
npx playwright show-reportWhen to reach for this: When you need to test full user flows in a real browser -- navigation, form submissions, authentication, and cross-page interactions.
// e2e/home.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Home Page", () => {
test("has correct title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/My App/);
});
test("navigates to about page", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: /about/i }).click();
await expect(page).toHaveURL("/about");
await expect(page.getByRole("heading", { level: 1 })).toHaveText("About Us");
});
test("search works", async ({ page }) => {
await page.goto("/");
await page.getByRole("searchbox").fill("react testing");
await page.getByRole("button", { name: /search/i }).click();
await expect(page).toHaveURL(/q=react\+testing/);
await expect(page.getByRole("heading")).toContainText("Search Results");
});
test("responsive navigation", async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto("/");
// Mobile menu should be hidden initially
await expect(page.getByRole("navigation")).not.toBeVisible();
// Open mobile menu
await page.getByRole("button", { name: /menu/i }).click();
await expect(page.getByRole("navigation")).toBeVisible();
});
});What this demonstrates:
webServer config starts your Next.js dev server before tests run and waits for it to be readyBrowserContext (isolated cookies, storage) -- no state leaks between testsfullyParallel: true runs tests in different files concurrently for speedUsing a production build for testing:
// playwright.config.ts
webServer: {
command: "npm run build && npm run start",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},GitHub Actions CI configuration:
# .github/workflows/e2e.yml
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test --project=chromium
env:
CI: true
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 7Running only chromium in CI for speed:
// playwright.config.ts
projects: process.env.CI
? [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }]
: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
],// Playwright tests use TypeScript natively -- no extra config needed
// The @playwright/test package includes all types
import { test, expect, Page } from "@playwright/test";
// Typed page fixture
test("example", async ({ page }: { page: Page }) => {
// page is fully typed with all Playwright methods
});Port conflicts -- If port 3000 is already in use, Playwright's webServer fails to start. Fix: Use reuseExistingServer: true locally, or configure a different port.
Slow CI startup -- Building Next.js before each E2E run is slow. Fix: Cache the .next build directory in CI, or run E2E tests only on merge to main.
Browser installation missing -- npx playwright install downloads browsers. In CI, use --with-deps to install system dependencies too. Fix: Add npx playwright install --with-deps to your CI script.
Flaky tests from timing -- E2E tests that rely on exact timing break. Fix: Use Playwright's built-in auto-waiting and expect assertions which retry automatically.
Test data pollution -- Tests that create real data in a shared database can conflict. Fix: Use a test database, seed data before tests, and clean up after.
| Alternative | Use When | Don't Use When |
|---|---|---|
| Cypress | You prefer Cypress's API or need its component testing | You want multi-browser support or faster execution |
| Testing Library (unit) | You want fast component tests without a browser | You need to test real browser behavior (navigation, cookies) |
| Selenium | You have existing Selenium infrastructure | You are starting fresh (Playwright is more modern) |
It automatically starts your Next.js dev (or production) server before tests run and waits for it to be ready. It also shuts it down when tests complete.
--headed shows the browser window while tests run.--ui opens Playwright's interactive UI for selecting, running, and debugging individual tests with a timeline view.npx playwright test --project=chromiumOr configure projects in playwright.config.ts to include only chromium in CI.
Playwright's webServer fails to start. Use reuseExistingServer: true locally to reuse a running dev server, or configure a different port.
npx playwright install --with-deps chromiumThe --with-deps flag installs system dependencies (fonts, libraries) needed for browser rendering.
It runs tests in different files concurrently. Each test gets a fresh browser context, so there is no state leakage between parallel tests.
npx playwright test --debugThis opens the browser with a step-through debugger. You can also use --trace on to capture traces for post-mortem debugging.
Common causes: timing issues, missing environment variables, or slow CI machines. Use Playwright's built-in auto-waiting and expect assertions which retry automatically. Avoid page.waitForTimeout().
await page.setViewportSize({ width: 375, height: 667 });
await page.goto("/");
await expect(page.getByRole("button", { name: /menu/i })).toBeVisible();No extra configuration needed. The @playwright/test package includes all types:
import { test, expect, Page } from "@playwright/test";Change the webServer command:
webServer: {
command: "npm run build && npm run start",
url: "http://localhost:3000",
},- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 7Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥