Playwright Configuration

By Syncfusion®, Inc. · Last updated

playwright.config.ts and defineConfig, core options (testDir, testMatch, outputDir, forbidOnly, shared use options, test.use), projects for browsers, branded channels, dependencies and setup projects, the webServer option, every kind of timeout, workers and parallel mode, retries and flaky tests, environment variables with dotenv, command-line overrides, and global setup.

Playwright Configuration

  • playwright.config.ts decides how your tests run: where they are, which browsers, how long to wait, how many at once.
  • You change behaviour for every test in one place, without editing the tests.
  • In this lesson, every lab has an editable config. Open playwright.config.ts in the explorer, change it, and run again.

Simple lesson: the config file controls how all your tests run.

A Real-World Example

  • Priya's tests repeat https://practice.devtutorials.ai in every goto(). They run in one browser only.
  • She sets baseURL once, and adds a Firefox project. Now every test is shorter and runs twice.
  • Try it: add a webkit project with devices['Desktop Safari'], then run again.

What You Will Learn

  1. The basics: defineConfig() and where the file lives.
  2. Core options, and use options shared by all tests.
  3. Projects: browsers, devices, dependencies and setup projects.
  4. Starting your app with webServer.
  5. Timeouts, parallel runs and retries.
  6. Environment variables and command-line overrides.
  7. Global setup (advanced).

Configuration Basics

playwright.config.ts and defineConfig()

  • The config is a TypeScript (or JavaScript) file that exports settings.
  • defineConfig({ ... }) wraps them, so your editor knows every option and its type.

Config File Locations

  • Playwright looks for playwright.config.ts (or .js, .mjs) in the folder where you run it.
  • Use another file with npx playwright test --config=other.config.ts.

Simple lesson: one defineConfig({ ... }) file, next to package.json, sets up every run.

Core Options

testDir, testMatch and testIgnore

  • testDir: './tests' is the folder to search.
  • testMatch says which files are tests. The default matches .spec and .test files.
  • testIgnore skips files, for example '**/drafts/**'.

outputDir and forbidOnly

  • outputDir is where traces, screenshots and videos go. Default: test-results.
  • forbidOnly: !!process.env.CI fails the run if test.only() is left in the code. Organizing Tests explains why.

Shared use Options

  • Everything inside use applies to every test. Common options:
OptionExampleEffect
baseURL'http://localhost:3000'goto('/login') becomes goto('http://localhost:3000/login')
headlessfalseShows the browser window
viewport{ width: 1280, height: 720 }Sets the page size
ignoreHTTPSErrorstrueAccepts test servers with self-signed certificates
proxy{ server: 'http://proxy:8080' }Sends traffic through a proxy
launchOptions{ slowMo: 500 }Passed to the browser launch, for example to slow every step down
testIdAttribute'data-qa'The attribute getByTestId() reads

Overriding Options with test.use()

  • test.use({ ... }) changes use options for one file or one describe group.

Simple lesson: use sets options for every test. test.use() changes them for a file or group.

Projects

What Projects Are

  • A project is a named set of options. Playwright runs your tests once per project.
  • Most configs have one project per browser. Others add phones, or a different baseURL for each environment.

Multi-Browser Projects and Branded Browsers

  • devices['Desktop Chrome'], devices['Desktop Firefox'] and devices['Desktop Safari'] give each project the right browser and settings.
  • channel: 'chrome' or channel: 'msedge' uses the real branded Chrome or Edge installed on the machine.
projects: [
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  { name: 'Google Chrome', use: { ...devices['Desktop Chrome'], channel: 'chrome' } },
  { name: 'Mobile Safari', use: { ...devices['iPhone 13'] } },
],

Per-Project Options

  • Each project has its own use, and can set testMatch, retries, timeout or grep.
  • default: false keeps a project in the config without running it every time. npx playwright test skips it, --project=firefox runs it, and --project="*" runs every project.

Project Dependencies and Setup Projects

  • dependencies: ['setup'] makes a project wait for the setup project to pass first.
  • A setup project runs only setup files, for example a login that saves the session to a file.
  • A teardown project (teardown: 'cleanup') runs after everything that depends on the setup.
  • If setup fails, the dependent projects do not run.
  • Authentication and Storage State explains storageState in depth.

Simple lesson: projects run the same tests with different settings. Dependencies run setup first.

Web Server

webServer Option

  • Most apps must be running before tests can visit them. webServer starts your app for you.
webServer: {
  command: 'npm run start',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
  timeout: 120_000,
  env: { NODE_ENV: 'test' },
},

Command, URL and Port

  • command starts the app. url (or port) is where Playwright waits until the app answers.

reuseExistingServer

  • true reuses an app you already started yourself. On CI it should be false, so each run starts fresh.

Server Timeouts and Environment

  • timeout is how long to wait for the app to start (default 60 seconds). env passes environment variables to it.
  • In the lab, the practice sites are always running. A webServer block just prints a note.

Simple lesson: webServer starts your app before the tests and stops it after.

Timeouts

TimeoutDefaultSet it with
Test timeout: the whole test, hooks and fixtures30 secondstimeout: 60_000, or test.setTimeout()
Expect timeout: each web-first assertion5 secondsexpect: { timeout: 10_000 }
Action timeout: each click, fill…none (limited by the test)use: { actionTimeout: 10_000 }
Navigation timeout: each goto()none (limited by the test)use: { navigationTimeout: 30_000 }
Global timeout: the whole runnoneglobalTimeout: 60 * 60 * 1000
  • test.setTimeout(120_000) inside a test, or test.slow(), gives one test more time.
  • The report page below takes 7 seconds to load. Run it, then raise expect.timeout in the config until it passes.

Raise one timeout for one slow check (toHaveText(..., { timeout: 10_000 })) rather than raising it for every test. A bigger global timeout makes every real failure slower to report.

Simple lesson: test 30 s, expect 5 s, actions and navigation share the test's time unless you set them.

Parallelism

Workers

  • A worker is a separate process that runs tests. Several workers run tests at the same time.
  • workers: 4 uses four. The default is half the CPU cores. On CI, many teams use 1 or a fixed number.

fullyParallel and How Tests Are Distributed

  • By default, files run in parallel, and tests inside one file run one after another.
  • fullyParallel: true lets tests inside a file run in parallel too.

Animation: six tests from two files are shared among three workers. Each worker picks the next test when it finishes one

Worker 1 Worker 2 Worker 3

cart.spec › t1 cart.spec › t3 cart.spec › t2 login.spec › t5 login.spec › t4 login.spec › t6

With fullyParallel: true, any free worker takes the next test from any file.

Three workers share six tests. The run takes as long as the busiest worker.

Parallel and Serial describe Blocks

  • test.describe.configure({ mode: 'parallel' }) runs a file's tests in parallel.
  • test.describe.configure({ mode: 'serial' }) runs them in order. If one fails, the rest are skipped. Use it rarely: it makes tests depend on each other.

Named Test Locks

  • Some tests must never run at the same time, for example two tests that change the same shared account.
  • Since Playwright 1.63, a test can take a lock name in its details: test('update settings', { lock: 'shared-account' }, async ({ page }) => { ... }). Tests that hold the same lock never run at the same time, even in different workers. test.describe.configure({ lock: 'shared-account' }) puts a whole file or group under one lock.

Shuffling and Repeating

  • --shuffle runs test files in a random order. It prints a seed, so you can repeat that order. Hidden order dependencies show up fast.
  • --repeat-each=10 runs every test 10 times. Great for catching flaky tests before they reach CI.

The lab runs tests one at a time in your browser. Its first line prints the workers a real run would use, so you can see how your settings change it.

Simple lesson: workers run tests in parallel. fullyParallel splits files too. Keep tests independent so any order works.

Retries

Configuring retries and Flaky Tests

  • retries: 2 gives each failed test up to two more tries.
  • A test that fails and then passes is reported as flaky. It still counts as a problem to fix.
  • On CI, retries: process.env.CI ? 2 : 0 is common. On your laptop, see failures at once.

Retry Strategies and --fail-on-flaky-tests

  • Retry on CI to keep the pipeline moving, and record a trace on the first retry (trace: 'on-first-retry').
  • --fail-on-flaky-tests makes flaky tests fail the run. Use it when your suite is stable and you want to keep it that way.
  • Set retries to 0 and run again: the test fails. Then run with --fail-on-flaky-tests and retries: 1.

Simple lesson: retries rerun failed tests. Passing on a retry means flaky, not fixed.

Environment Variables

Using process.env

  • process.env.NAME reads an environment variable. Configs use it to change behaviour on CI: process.env.CI.
  • Never write passwords in test files. Read them from the environment.

Loading .env Files with dotenv

  • The dotenv package loads a .env file into process.env. Install it with npm install -D dotenv.
  • Add .env to .gitignore, so secrets never reach your repository.

A .env file with real passwords must never be committed. On CI, set the same variables as secrets instead. CI/CD and Scaling shows how.

Simple lesson: read settings and secrets from the environment. dotenv loads them from a .env file.

Command-Line Overrides

FlagOverridesExample
--workers / -jworkers--workers=1
--retriesretries--retries=2
--timeouttimeout (test timeout)--timeout=60000
--max-failures / -xstop after N failures-x stops at the first
--listlists tests without running them--list --grep @smoke
  • Try --list, --retries=1, and --timeout=2000 too.

Simple lesson: command-line flags override the config for one run.

Global Setup (Advanced)

globalSetup and globalTeardown

  • globalSetup: './global-setup' runs a file once before all tests. globalTeardown runs one after all tests.
  • Use them for work that must happen once per run, such as seeding a database.
// global-setup.ts
import type { FullConfig } from '@playwright/test';

export default async function globalSetup(config: FullConfig) {
  console.log('Seeding test data for', config.projects.length, 'projects');
}

Global Setup vs Setup Projects

  • Setup projects are the recommended way. They appear in the report, get traces and use fixtures such as page.
  • globalSetup is a plain function. It has no fixtures, no trace and no report entry.
  • Use a setup project for logging in. Keep globalSetup for simple one-off jobs.

Simple lesson: prefer setup projects. Use globalSetup only for simple jobs with no browser.

Try It in Your Project

  • These steps take the lesson from the lab to your own app.
  • Set baseURL, trace: 'on-first-retry' and CI-only retries in your config.
  • Add a project for each browser your users need, and a mobile project if you have mobile users.
  • Move secrets and URLs into environment variables and a .env file that git ignores.

Simple lesson: use one idea from this lesson on your own app this week.

TL;DR

  • defineConfig() in playwright.config.ts controls every run.
  • use options apply to all tests. test.use() overrides them locally.
  • Projects run the same tests in different browsers or setups. dependencies run setup first.
  • webServer starts your app.
  • Timeouts: test 30 s, expect 5 s. Workers and fullyParallel control parallel runs.
  • retries reruns failures. Passing on a retry means flaky.
  • Read secrets from process.env, loaded with dotenv. Flags override the config.

Common mistakes

MistakeWhat happensFix
Full URLs in every goto()Changing environments means editing every testSet baseURL in use
Raising the global timeout to fix one slow testEvery failure takes longer to reportUse test.slow() or a per-assertion timeout
retries: 3 everywhere and ignoring flaky testsReal bugs hide behind retriesRetry on CI only, and fix flaky tests
Passwords written in the configThey leak into your repositoryUse environment variables and .env
mode: 'serial' to share stateOne failure skips many testsMake tests independent. Use fixtures for shared setup

Check Yourself

Exercises

What to Remember

  • The config sets defaults. Tests and flags override them.
  • baseURL, projects and webServer are in almost every real config.
  • Know your four timeouts: test, expect, action and navigation.
  • Retries reveal flaky tests. Fix them.
  • Secrets come from the environment.

Next: Browser Contexts, Pages, and Emulation