Skip to main content

Playwright React E2E Testing Guide: Setup & First Test

Playwright is a Node.js library that controls a headless or headed Chromium, Firefox, or WebKit browser, letting you automate clicks, form fills, assertions, and full user journeys—all from JavaScript. For React developers, Playwright offers the critical advantage of testing your actual app in a real browser, catching rendering bugs, timing issues, and state synchronization failures that unit tests never see. This guide walks you through installation, your first passing test, and the core concepts you'll build on throughout this series.

What Is Playwright and Why Use It for React E2E Testing?

Playwright is a browser automation framework that lets you programmatically control a browser instance to test your React application as a user would. Unlike unit testing libraries like Vitest or Jest (which use jsdom—a fake DOM), Playwright launches real Chromium, Firefox, and WebKit processes, rendering your React components with a genuine browser engine, network stack, and JavaScript runtime. This means you catch visual regressions, timing bugs, and integration failures before users do. Playwright is maintained by Microsoft and used by teams at Google, Meta, and Shopify for testing complex, high-traffic applications.

Installing Playwright and Initializing a Project

Begin by adding Playwright to your React project. If you don't have a React app yet, use Create React App or Vite (both work equally well with Playwright):

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install -D @playwright/test
npx playwright install

The first command installs Playwright's test harness. The second downloads browser binaries for Chromium, Firefox, and WebKit (about 600 MB total). After installation, Playwright scaffolds a playwright.config.ts file and an example tests/ directory:

npx playwright codegen http://localhost:5173

This opens a browser where you can click and fill, and Playwright auto-writes test code. For learning, this is invaluable—but hand-written tests are often cleaner and more maintainable.

Writing Your First Playwright Test

Create a file tests/example.spec.ts:

import { test, expect } from '@playwright/test';

test('user can see the homepage heading', async ({ page }) => {
// Navigate to your React app
await page.goto('http://localhost:5173');

// Verify the heading exists and has the right text
const heading = page.locator('h1');
await expect(heading).toContainText('Vite + React');
});

Run the test:

npm run test:e2e

(You may need to add a script to your package.json: "test:e2e": "playwright test")

If your React app is running (on port 5173 for Vite), you should see:

PASS tests/example.spec.ts
1 passed (800ms)

Congratulations—you've written your first E2E test.

Understanding the Core Playwright Workflow

Every Playwright test follows a standard pattern:

  1. Fixture injection (the { page } object): Playwright provides a page fixture—a single browser tab context where your test runs.
  2. Navigation (page.goto(url)): Load your application.
  3. Interaction (page.click(), page.fill(), etc.): Simulate user actions.
  4. Assertion (expect(locator).toContainText(...)): Verify the expected outcome.

Here's a slightly more complex test that fills a form:

import { test, expect } from '@playwright/test';

test('user can submit a signup form', async ({ page }) => {
// 1. Navigate
await page.goto('http://localhost:5173/signup');

// 2. Interact: fill form fields
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'SecurePass123');

// 3. Click submit
await page.click('button:has-text("Sign Up")');

// 4. Assert: wait for success message
const successMsg = page.locator('text=Account created!');
await expect(successMsg).toBeVisible();
});

This test demonstrates the four pillars: navigate, interact, click, assert.

Key Configuration Settings for React Apps

Open playwright.config.ts. The defaults are sensible, but consider these tweaks for React development:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: process.env.CI ? true : false, // fail if you forget .only
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined, // single worker in CI for stability
reporter: 'html', // generates an interactive HTML report
use: {
baseURL: 'http://localhost:5173', // avoid repeating localhost in every test
trace: 'on-first-retry', // save traces when tests fail (for debugging)
},

webServer: {
command: 'npm run dev',
port: 5173,
reuseExistingServer: !process.env.CI, // skip startup if already running
},

projects: [
{ name: 'chromium', use: { ...devices.chromium } },
{ name: 'firefox', use: { ...devices.firefox } },
{ name: 'webkit', use: { ...devices.webkit } },
],
});

The webServer config tells Playwright to start your dev server before tests run. The baseURL sets a default host for all page.goto() calls, so you can write await page.goto('/signup') instead of the full URL.

Running Tests in Headed and Headless Mode

By default, Playwright runs tests headless (no visible browser window)—great for CI. To watch tests run in a real browser during development:

npx playwright test --headed

Or run a single test file:

npx playwright test tests/example.spec.ts --headed

Append --debug to open Playwright Inspector, a tool that steps through each action and shows the DOM state:

npx playwright test tests/example.spec.ts --debug

Key Takeaways

  • Playwright automates a real browser, catching bugs that unit tests miss because it tests the actual DOM, network, and JavaScript runtime.
  • Install with npm install -D @playwright/test and npx playwright install to download browser binaries.
  • Every test follows: navigate → interact → assert; Playwright's page fixture handles all three.
  • Set baseURL and webServer in playwright.config.ts to streamline your tests and auto-start your dev server.
  • Use --headed and --debug flags during development to watch tests and troubleshoot visually.

Frequently Asked Questions

How is Playwright different from Cypress or Selenium?

Playwright supports multiple browser engines (Chromium, Firefox, WebKit) in one library, whereas Cypress is Chromium-only. Playwright is faster (no WebDriver overhead), uses modern async/await syntax, and has better stability for rapid assertions. Selenium is older and heavier but supports legacy IE. For React in 2026, Playwright is the standard choice.

Do I need to run my React dev server separately?

No—set webServer in your playwright.config.ts and Playwright will start it before tests run. If your server is already running, Playwright reuses it (unless you're in CI).

Can I run tests against a production URL?

Yes, set baseURL: 'https://prod.example.com' or pass the full URL to page.goto(). However, E2E tests are best run against a staging environment to avoid polluting real user data.

How long do E2E tests typically take?

A single test usually runs in 200–800 ms. Playwright runs tests in parallel by default (across multiple workers), so a suite of 50 tests might finish in 10–20 seconds total.

What's the difference between page.click() and page.locator().click()?

page.locator(selector) is the modern approach—it's more readable, supports waits, and auto-retries. page.click(selector) is the older, simpler API. Use locators for new tests.

Further Reading