Getting Started with Playwright: A Practical Guide to End-to-End Testing
On this page
Getting Started with Playwright: A Practical Guide to End-to-End Testing
Playwright is an open-source end-to-end testing framework developed by Microsoft. It supports Chromium, Firefox, and WebKit with a single API, runs tests in parallel by default, and has built-in auto-waiting that eliminates most flakiness. This guide walks you through everything you need to go from zero to a working test suite.
Installation and Setup
Start by installing Playwright in your project:
npm init playwright@latest
This command scaffolds the project for you. It creates a playwright.config.ts file, an example.spec.ts test file, and a tests directory. It also installs the browsers Playwright needs. If you prefer to add Playwright to an existing project manually:
npm install -D @playwright/test
npx playwright install
The playwright install command downloads the browser binaries. You only need to run it once, or again after updating Playwright.
Configuring Playwright
The playwright.config.ts file controls how your tests run. Here is a practical starting configuration:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
},
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,
},
});
Key options to note: fullyParallel runs all tests concurrently, trace: 'on-first-retry' captures a trace when a test fails and is retried (invaluable for debugging CI failures), and webServer automatically starts your dev server before tests run.
Writing Your First Test
Create a file at tests/home.spec.ts:
import { test, expect } from '@playwright/test';
test('homepage has the correct title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
test('navigation links work', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL(/\/about/);
await expect(page.getByRole('heading', { name: 'About Us' })).toBeVisible();
});
Playwright's expect assertions auto-wait for conditions to be met before passing or failing. You do not need manual waits or sleeps. The getByRole locator finds elements the way a screen reader would, which makes your tests resilient to markup changes and validates accessibility at the same time.
Running Tests
Run your full suite with:
npx playwright test
Useful flags for day-to-day development:
# Run a specific test file
npx playwright test tests/home.spec.ts
# Run in headed mode (see the browser)
npx playwright test --headed
# Run in UI mode (interactive test explorer)
npx playwright test --ui
# Debug a specific test
npx playwright test tests/home.spec.ts --debug
The UI mode is especially useful when writing new tests. It shows a live browser, a timeline of actions, and DOM snapshots at each step.
Integrating with CI
Playwright works well in any CI environment. Here is an example GitHub Actions workflow:
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
The --with-deps flag installs system-level dependencies the browsers need on Linux. The report upload step preserves the HTML report as a build artifact so you can inspect failures after the fact.
Best Practices
Keep tests focused on critical user flows. End-to-end tests are the most expensive tests to run and maintain. Cover the paths that matter most—signup, checkout, core features—and leave edge cases to unit and integration tests.
Use test.describe to group related tests and share setup logic. Use meaningful test names that describe the expected behavior.
FAQ
How do I test an application that requires authentication?
Use Playwright's storageState feature. Write a setup step that logs in and saves the browser state to a file, then load that state in tests that need an authenticated user. The Playwright docs call this "global setup" and it avoids repeating login steps across your entire suite.
Can I use Playwright with frameworks like Next.js, Vite, or Remix?
Yes. The webServer config option works with any dev server command. Set command to whatever starts your app (npm run dev, npx vite, etc.) and set url to the address it serves on. Playwright waits for the server to be ready before running tests.
How do I handle flaky tests?
First, identify the root cause. Common sources of flakiness include: hardcoded timeouts (waitForTimeout), race conditions with animations, and tests that depend on external services. Replace timeouts with Playwright's built-in auto-waiting and expect assertions. Mock external APIs with page.route(). If a test is genuinely non-deterministic, use test.retry(2) on that specific test while you investigate.
Should I use test IDs or role-based locators?
Prefer role-based locators (getByRole, getByLabel, getByText) as your first choice. They make your tests more readable and also validate that your app is accessible. Fall back to getByTestId for elements that don't have a meaningful accessible role, like a specific container or a canvas.
How do I run tests against a staging or production environment?
Override the baseURL via the command line:
BASE_URL=https://staging.example.com npx playwright test
Then reference process.env.BASE_URL in your config, or use the --config flag to point to a separate config file for different environments.
How fast are Playwright tests compared to Cypress or Selenium?
Playwright tests are typically faster due to native browser control (no WebDriver protocol overhead), parallel execution by default, and efficient browser context isolation. A suite of 50 tests that takes minutes in other tools often runs in under a minute with Playwright's parallelism.
Wrapping Up
Playwright gives you a modern, reliable foundation for end-to-end testing. The setup takes minutes, the developer tools are excellent, and the auto-waiting behavior eliminates the most common source of test flakiness. Start with a few tests covering your most critical user flows, run them in CI on every push, and expand coverage as your confidence grows.