Playwright keeps nearly all its settings in one file, which makes playwright.config.ts the most important file in the repository after the tests themselves. Here is the version you will meet in real projects, explained line by line.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
],
});
fullyParallel runs tests inside a file at the same time, not just separate files. Fast, and it only works if your tests are independent.
retries re-runs a failed test. Note it is 2 on CI and 0 locally, which is the standard arrangement and worth understanding rather than copying.
⚠️ Retries hide flakiness. Use them with a rule attached.
A test that passes on the second attempt is reported as passed. That is useful for keeping a build usable, and it is also how a suite quietly rots: the flakiness is still there, and nobody sees it.
Playwright marks these as flaky in the report rather than silently passing them. Read that number every week. Retries are a shock absorber, not a fix.
workers is how many tests run at once. Fixed on CI so runs are predictable, automatic locally.
baseURL from an environment variable is how one suite targets many environments:
BASE_URL=https://staging.example.com npx playwright test
Same tests, different target, nothing edited. As with Cypress, the rule holds: tests must not know which environment they are in. No if (env === 'staging') inside a spec. Configuration decides where, tests decide what.
projects is Playwright's cross-browser mechanism, and its most-copied feature. Each project runs the whole suite with different settings. Browsers here, but also device emulation:
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
Run one project when you do not need all of them:
npx playwright test --project=chromium
Most teams run Chromium on every commit and the full set nightly, because three browsers is three times the runtime.
One more setting worth knowing early:
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
This starts your app before the tests and waits for it to answer. It removes the most common cause of a confusing local failure, which is running the suite against nothing.
Remember this: one config file, environment through variables, and read the flaky count instead of trusting retries.
Reference: Test configuration.