Runner, fixtures and CI

Projects and workers, typed fixtures for shared setup, and a CI plan that keeps traces when a test fails.

Configuring the runner

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: process.env.CI ? [['html'], ['github']] : 'list',
  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: 'mobile', use: { ...devices['Pixel 7'] } }
  ],
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI
  }
});
  • projects is how you cover browsers and devices: one suite, several targets.
  • webServer starts the application and waits for the URL, so CI needs no extra step.
  • Retries hide real races. Investigate a flaky test rather than leaning on them.
  • Use a single worker only when tests genuinely share state.

Fixtures

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

type Fixtures = { todoPage: TodoPage };

export const test = base.extend<Fixtures>({
  todoPage: async ({ page }, use) => {
    const todo = new TodoPage(page);
    await todo.goto();
    await use(todo);        // the test body runs here
    // teardown after use() returns
  }
});

test('adds an item', async ({ todoPage }) => {
  await todoPage.add('write the report');
  await expect(todoPage.items).toHaveCount(1);
});
💡
Fixtures are the idiomatic replacement for beforeEach blocks: they are typed, lazy (built only when a test asks for them) and each test receives an isolated instance, which is what parallel runs need.

Running in CI

# the ordered CI steps
npm ci
npx playwright install --with-deps chromium
npx playwright test --shard=1/3
npx playwright merge-reports --reporter html ./blob-report
  • Install only the browsers the job runs; --with-deps also pulls the Linux system libraries.
  • Shard the suite across machines and merge the reports into a single HTML artifact.
  • Upload test-results/ when the job fails so the trace survives the run.
  • Set CI=true so retries, reporters and reuseExistingServer switch automatically.

FAQ

How do I keep the suite fast?
Run browsers in parallel, shard across CI machines, and replace end-to-end coverage of pure logic with unit tests. Only critical user journeys need a real browser.
Should the tests run against a deployed environment?
Prefer the application started by webServer in the same job: it is faster and the version under test matches the commit. Keep a small smoke set for a deployed environment.

Locators and assertions A real test example

Last refreshed 2026-09-18.