Playwright E2E Testing Initialization
Sets up Playwright for end-to-end testing in Next.js and React applications.
When to Use
This skill should be used when:
Adding E2E tests to a Next.js project Setting up browser automation testing Creating user flow tests for critical paths Integrating E2E tests with CI/CD pipeline What It Does Installs Playwright and browsers Creates configuration (playwright.config.ts) Sets up test directory (e2e/) Creates example tests for common flows Adds npm scripts for running tests Updates CI/CD to run E2E tests Quick Start
Ask Claude to:
Add Playwright E2E tests to this project
Or be specific:
Set up E2E tests for the authentication flow
Installation bun add -D @playwright/test bunx playwright install chromium
Configuration 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: [["html", { open: "never" }], ["list"]], use: { baseURL: "http://localhost:3000", trace: "on-first-retry", screenshot: "only-on-failure", }, projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] } }, ], webServer: { command: "bun run dev", url: "http://localhost:3000", reuseExistingServer: !process.env.CI, timeout: 120 * 1000, }, });
Test Structure e2e/ ├── home.spec.ts # Homepage tests ├── auth.spec.ts # Authentication flow ├── navigation.spec.ts # Navigation tests └── fixtures/ └── test-data.ts # Shared test data
Example Tests Basic Navigation Test import { test, expect } from "@playwright/test";
test.describe("Homepage", () => { test("should load successfully", async ({ page }) => { await page.goto("/"); await expect(page).toHaveTitle(/My App/); });
test("should navigate to about page", async ({ page }) => { await page.goto("/"); await page.click('a[href="/about"]'); await expect(page).toHaveURL("/about"); }); });
Authentication Flow Test import { test, expect } from "@playwright/test";
test.describe("Authentication", () => { test("should login successfully", async ({ page }) => { await page.goto("/login");
await page.fill('input[name="email"]', "test@example.com");
await page.fill('input[name="password"]', "password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/dashboard");
await expect(page.locator("text=Welcome")).toBeVisible();
});
test("should show error for invalid credentials", async ({ page }) => { await page.goto("/login");
await page.fill('input[name="email"]', "wrong@example.com");
await page.fill('input[name="password"]', "wrongpassword");
await page.click('button[type="submit"]');
await expect(page.locator("text=Invalid credentials")).toBeVisible();
}); });
Form Submission Test import { test, expect } from "@playwright/test";
test.describe("Contact Form", () => { test("should submit form successfully", async ({ page }) => { await page.goto("/contact");
await page.fill('input[name="name"]', "John Doe");
await page.fill('input[name="email"]', "john@example.com");
await page.fill('textarea[name="message"]', "Hello, this is a test message");
await page.click('button[type="submit"]');
await expect(page.locator("text=Thank you")).toBeVisible();
}); });
NPM Scripts
Add to package.json:
{ "scripts": { "e2e": "playwright test", "e2e:ui": "playwright test --ui", "e2e:headed": "playwright test --headed", "e2e:debug": "playwright test --debug", "e2e:report": "playwright show-report" } }
CI/CD Integration GitHub Actions
Add to your CI workflow:
-
name: Install Playwright Browsers run: bunx playwright install --with-deps chromium
-
name: Run E2E tests run: bun run e2e env: CI: true
-
name: Upload Playwright Report uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 7
Best Practices 1. Test Critical User Flows
Focus on:
Authentication (login, logout, signup) Core features (main value proposition) Payment/checkout flows Error handling 2. Use Page Object Model // e2e/pages/login.page.ts import { Page } from "@playwright/test";
export class LoginPage { constructor(private page: Page) {}
async goto() { await this.page.goto("/login"); }
async login(email: string, password: string) { await this.page.fill('input[name="email"]', email); await this.page.fill('input[name="password"]', password); await this.page.click('button[type="submit"]'); } }
- Use Data Attributes for Selectors
await page.click('[data-testid="submit-button"]');
- Keep Tests Independent
Each test should:
Set up its own state Not depend on other tests Clean up after itself 5. Use Fixtures for Common Setup import { test as base } from "@playwright/test";
const test = base.extend({ authenticatedPage: async ({ page }, use) => { await page.goto("/login"); await page.fill('input[name="email"]', "test@example.com"); await page.fill('input[name="password"]', "password"); await page.click('button[type="submit"]'); await use(page); }, });
Troubleshooting Tests timing out
Increase timeout in config:
timeout: 60000, // 60 seconds
Elements not found
Use waitFor:
await page.waitForSelector('[data-testid="element"]');
Flaky tests
Add retries and use toPass:
await expect(async () => { await expect(page.locator("text=Success")).toBeVisible(); }).toPass({ timeout: 10000 });
Integration with Other Skills Skill Integration testing-cicd-init Sets up unit tests first testing-expert Provides testing patterns webapp-testing Alternative automation skill
When this skill is active, Claude will:
Install Playwright and browsers Create configuration file Set up e2e/ directory Create example tests for existing pages Add npm scripts Update CI/CD workflow