C.W.K.
Stream
Lesson 02 of 04 · published

playwright.config.ts — Projects, Retries, Workers

~16 min · playwright-setup, config, projects

Level 0Test Curious
0 XP0/32 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Config is where you decide what 'one test' means — one browser? Three? With auth? At mobile width?"

The Eight Fields That Earn Their Keep

playwright.config.ts can grow large, but a working config rests on a handful of fields:

  • testDir — where the tests live (default ./tests; init usually sets ./e2e).
  • use.baseURL — every page.goto('/path') resolves against this. Set it to http://localhost:3000 for dev; override in CI via env.
  • use.trace — when to capture traces. 'on-first-retry' is the sweet spot: no overhead on success, full forensics on failure.
  • retries — how many times to retry a failing test before marking it failed. 0 locally, 2 in CI (a single flake retry catches most network jitter).
  • workers — parallelism. '50%' of CPU cores locally is a good default; lower in CI containers to avoid OOM.
  • reporter — output format. 'html' for dev (opens a browser report); 'list' or 'github' for CI.
  • webServer — Playwright can start your dev server before tests and tear it down after. Critical for ensuring tests run against a fresh build.
  • projects — the multi-browser / multi-config dimension.

Projects Are the Killer Feature

A 'project' is a named configuration variant. { name: 'chromium', use: { ...devices['Desktop Chrome'] } } says "run my tests in desktop Chrome." Add three more entries and you've got chromium + firefox + webkit, all running in parallel, results reported per-browser.

Projects also handle viewport / mobile / authenticated states. A common setup:

  • setup project — runs a single login spec that saves auth state to a file.
  • chromium project — depends on setup, uses the saved state, runs the main suite.
  • mobile-chrome project — same suite, mobile viewport.
  • firefox, webkit — cross-browser pass.
Projects scale to your needs, not against them. Day one: one project, default Chromium, no auth. Add a project the day you need it — mobile testing, cross-browser sweep, authenticated flows. Don't pre-configure five projects 'just in case' — every project triples the CI time.

The webServer Field — Don't Forget

Tests need something to talk to. The webServer field tells Playwright how to start your dev server before tests and what URL to wait for. Without it, you'd have to remember to start the server manually before every test run — fine locally, fatal in CI.

Per-Test Overrides

The config sets defaults. Individual tests or test files can override most fields with test.use({...}). Useful for tests that need a different viewport, a different storage state, or a different user-agent. Don't reach for it on day one — most tests want the config defaults.

Code

Working config — eight fields, three browsers, optional mobile·typescript
// playwright.config.ts — a working starting point
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,    // fail CI if .only is committed
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : '50%',
  reporter: process.env.CI ? 'github' : 'html',

  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
    // Optional: mobile viewports
    { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});
Auth-aware project setup (full pattern in track 8)·typescript
// Auth project pattern — log in once, reuse the state in every test
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    // 1. Runs first, no dependency. Saves auth state to a file.
    {
      name: 'setup',
      testMatch: /global\.setup\.ts/,
    },
    // 2. Depends on setup. Loads the saved state automatically.
    {
      name: 'chromium',
      dependencies: ['setup'],
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
    },
    // 3. A second project that DOESN'T log in (e.g., public pages).
    {
      name: 'chromium-public',
      testMatch: /\.public\.spec\.ts/,
      use: { ...devices['Desktop Chrome'] },
      // No storageState — no auth state.
    },
  ],
});
Per-test / per-file override·typescript
// Per-test override — when one test needs different defaults
import { test, expect } from '@playwright/test';

// Override the viewport for this entire file
test.use({ viewport: { width: 320, height: 568 } });

test('mobile menu opens', async ({ page }) => {
  await page.goto('/');
  // Mobile-only assertions...
});

// Or override just one test inside a describe
test.describe('admin features', () => {
  test.use({ storageState: 'playwright/.auth/admin.json' });

  test('admin can delete posts', async ({ page }) => {
    // Logged in as admin via the override
  });
});
CLI — filter by project, file, or test name·bash
# Run all projects (default)
npx playwright test

# Run only one project
npx playwright test --project=chromium

# Run multiple
npx playwright test --project=chromium --project=webkit

# Run a single test file across all projects
npx playwright test e2e/login.spec.ts

# Combine — one file, one project
npx playwright test e2e/login.spec.ts --project=chromium

# Headed (see the browser) — great for local debugging
npx playwright test --headed --project=chromium

# Run only tests whose name matches a pattern
npx playwright test -g 'admin can delete'

External links

Exercise

Take the config from init and add a mobile-chrome project using devices['Pixel 5']. Run npx playwright test --project=mobile-chrome and watch the example test run at mobile width. Then add a webServer block for your actual dev server. Re-run without manually starting the server — Playwright should start it for you. If your test was hard-coded to https://playwright.dev/, change it to / and rely on baseURL.
Hint
If webServer fails with 'timed out waiting for URL,' your dev server probably needs longer than 60s to be ready, or the url you specified doesn't actually respond 200. Bump timeout: 180_000 and double-check the URL with curl.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.