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.tsdecides 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.tsin 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.aiin everygoto(). They run in one browser only. - She sets
baseURLonce, and adds a Firefox project. Now every test is shorter and runs twice.
- Try it: add a
webkitproject withdevices['Desktop Safari'], then run again.
What You Will Learn
- The basics:
defineConfig()and where the file lives. - Core options, and
useoptions shared by all tests. - Projects: browsers, devices, dependencies and setup projects.
- Starting your app with
webServer. - Timeouts, parallel runs and retries.
- Environment variables and command-line overrides.
- 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.testMatchsays which files are tests. The default matches.specand.testfiles.testIgnoreskips files, for example'**/drafts/**'.
outputDir and forbidOnly
outputDiris where traces, screenshots and videos go. Default:test-results.forbidOnly: !!process.env.CIfails the run iftest.only()is left in the code. Organizing Tests explains why.
Shared use Options
- Everything inside
useapplies to every test. Common options:
| Option | Example | Effect |
|---|---|---|
baseURL | 'http://localhost:3000' | goto('/login') becomes goto('http://localhost:3000/login') |
headless | false | Shows the browser window |
viewport | { width: 1280, height: 720 } | Sets the page size |
ignoreHTTPSErrors | true | Accepts 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({ ... })changesuseoptions for one file or onedescribegroup.
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
baseURLfor each environment.
Multi-Browser Projects and Branded Browsers
devices['Desktop Chrome'],devices['Desktop Firefox']anddevices['Desktop Safari']give each project the right browser and settings.channel: 'chrome'orchannel: '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 settestMatch,retries,timeoutorgrep. default: falsekeeps a project in the config without running it every time.npx playwright testskips it,--project=firefoxruns it, and--project="*"runs every project.
Project Dependencies and Setup Projects
dependencies: ['setup']makes a project wait for thesetupproject 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
storageStatein 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.
webServerstarts 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
commandstarts the app.url(orport) is where Playwright waits until the app answers.
reuseExistingServer
truereuses an app you already started yourself. On CI it should befalse, so each run starts fresh.
Server Timeouts and Environment
timeoutis how long to wait for the app to start (default 60 seconds).envpasses environment variables to it.- In the lab, the practice sites are always running. A
webServerblock just prints a note.
Simple lesson: webServer starts your app before the tests and stops it after.
Timeouts
| Timeout | Default | Set it with |
|---|---|---|
| Test timeout: the whole test, hooks and fixtures | 30 seconds | timeout: 60_000, or test.setTimeout() |
| Expect timeout: each web-first assertion | 5 seconds | expect: { 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 run | none | globalTimeout: 60 * 60 * 1000 |
test.setTimeout(120_000)inside a test, ortest.slow(), gives one test more time.- The report page below takes 7 seconds to load. Run it, then raise
expect.timeoutin 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: 4uses four. The default is half the CPU cores. On CI, many teams use1or 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: truelets 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
--shuffleruns test files in a random order. It prints a seed, so you can repeat that order. Hidden order dependencies show up fast.--repeat-each=10runs 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: 2gives 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 : 0is 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-testsmakes flaky tests fail the run. Use it when your suite is stable and you want to keep it that way.
- Set
retriesto0and run again: the test fails. Then run with--fail-on-flaky-testsandretries: 1.
Simple lesson: retries rerun failed tests. Passing on a retry means flaky, not fixed.
Environment Variables
Using process.env
process.env.NAMEreads 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
dotenvpackage loads a.envfile intoprocess.env. Install it withnpm install -D dotenv. - Add
.envto.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
| Flag | Overrides | Example |
|---|---|---|
--workers / -j | workers | --workers=1 |
--retries | retries | --retries=2 |
--timeout | timeout (test timeout) | --timeout=60000 |
--max-failures / -x | stop after N failures | -x stops at the first |
--list | lists tests without running them | --list --grep @smoke |
- Try
--list,--retries=1, and--timeout=2000too.
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.globalTeardownruns 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
globalSetupfor 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
.envfile that git ignores.
Simple lesson: use one idea from this lesson on your own app this week.
TL;DR
defineConfig()inplaywright.config.tscontrols every run.useoptions apply to all tests.test.use()overrides them locally.- Projects run the same tests in different browsers or setups.
dependenciesrun setup first. webServerstarts your app.- Timeouts: test 30 s, expect 5 s. Workers and
fullyParallelcontrol parallel runs. retriesreruns failures. Passing on a retry means flaky.- Read secrets from
process.env, loaded withdotenv. Flags override the config.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Full URLs in every goto() | Changing environments means editing every test | Set baseURL in use |
| Raising the global timeout to fix one slow test | Every failure takes longer to report | Use test.slow() or a per-assertion timeout |
retries: 3 everywhere and ignoring flaky tests | Real bugs hide behind retries | Retry on CI only, and fix flaky tests |
| Passwords written in the config | They leak into your repository | Use environment variables and .env |
mode: 'serial' to share state | One failure skips many tests | Make tests independent. Use fixtures for shared setup |
Check Yourself
Exercises
What to Remember
- The config sets defaults. Tests and flags override them.
baseURL, projects andwebServerare 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