QA How-To
How to Use Playwright projects config (2026)
Learn Playwright projects config for browsers, devices, environments, authentication, dependencies, and reliable CI execution with practical TypeScript.
20 min read | 3,037 words
TL;DR
Playwright projects are named test configurations inside the projects array of playwright.config.ts. Put shared settings at the top level, override browser, device, environment, retry, or test-selection settings per project, and run one profile with npx playwright test --project=<name>.
Key Takeaways
- Define each execution profile as a named object in the projects array.
- Keep shared defaults at the top level and place only real differences inside each project.
- Use project dependencies for observable authentication or data setup that must finish before browser tests.
- Select projects with the CLI instead of maintaining separate configuration files for routine runs.
- Control test scope with testMatch, testIgnore, and grep without duplicating test code.
- Give projects stable names because reports, snapshots, commands, and dependencies rely on them.
- Design the local matrix first, then reduce or split it intentionally in CI.
Playwright projects config lets one test suite run under several named execution profiles without copying test files. Put a projects array in playwright.config.ts, give every project a stable name, and specify only the settings that differ, such as browser, device, base URL, storage state, retries, or test filters.
That simple model scales from three browser checks on a laptop to authenticated, environment-aware CI pipelines. This guide explains the configuration hierarchy, provides runnable TypeScript, and shows how to avoid matrices that cost more than the confidence they provide.
TL;DR
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'https://playwright.dev',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
Run every project with npx playwright test. Run one with npx playwright test --project=firefox. Top-level values are shared defaults, while project values override the corresponding settings for that project.
| Need | Project setting | Typical value |
|---|---|---|
| Browser or device coverage | use |
devices['Desktop Firefox'] |
| Different deployment | use.baseURL |
Staging URL |
| Select test files | testMatch, testIgnore |
Glob or regular expression |
| Ordered setup | dependencies |
['setup'] |
| Project concurrency cap | workers |
1 or '50%' |
| Project-specific retry policy | retries |
0, 1, or 2 |
1. What Is Playwright Projects Config?
A Playwright project is a logical group of tests that share resolved configuration. The group is not a folder, browser instance, or CI job by itself. It is a named TestProject entry. When Playwright collects tests, it associates eligible tests with each project and executes them using that project's options. A single test can therefore appear three times in a report, once for Chromium, once for Firefox, and once for WebKit.
Projects solve more than cross-browser coverage. Teams use them for signed-in and signed-out states, desktop and mobile emulation, regional settings, smoke and regression scopes, production-safe checks, feature variants, API roles, and accessibility profiles. The common thread is that the test logic remains useful while its execution context changes.
The projects property belongs to the top-level config object returned by defineConfig. Each entry can use project-level forms of many familiar settings, including use, testDir, testMatch, testIgnore, grep, retries, timeout, workers, metadata, dependencies, and teardown. Project names appear in terminal output, HTML reports, traces, and the testInfo.project object. They also become command-line identifiers.
A healthy design treats projects as meaningful test dimensions, not as labels pasted onto arbitrary groups. If two entries have identical configuration and test selection, one is probably redundant. If one entry changes six unrelated dimensions at once, failures become hard to interpret. Prefer a name such as staging-chromium only when both dimensions genuinely belong in the same execution profile.
2. Install and Verify a Minimal Playwright Projects Config
Start from a normal Playwright Test installation. In an empty Node project, the following commands install the runner and browser binaries. If the repository already contains @playwright/test, keep its existing package-management policy and update deliberately rather than mixing lockfiles.
npm init playwright@latest
npx playwright install
Create playwright.config.ts with the three-browser example from the TL;DR, then add a small test. This test uses the configured baseURL, so the path passed to page.goto is resolved for every project.
// tests/docs.spec.ts
import { test, expect } from '@playwright/test';
test('documentation home exposes the main heading', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Playwright/);
await expect(page.getByRole('heading', { name: /Playwright/ }).first()).toBeVisible();
});
Run npx playwright test --list before the first full execution. Listing tests is a fast way to verify project names and test collection without opening browsers. You should see the same test associated with chromium, firefox, and webkit. Then run npx playwright test and open the report with npx playwright show-report if HTML reporting is enabled.
The devices registry supplies a consistent set of browser context options, such as browser name, viewport, user agent, device scale factor, touch capability, and mobile flags. Spread the descriptor when adding overrides: use: { ...devices['Desktop Chrome'], locale: 'en-US' }. This makes the intended merge visible and avoids losing fields. For a deeper treatment of device profiles, see Playwright device emulation.
3. Build a Browser and Device Matrix Without Waste
The obvious matrix includes all browser engines for every test. That is a defensible starting point for a small suite, but it can become expensive as coverage grows. Decide which risks each project addresses. Chromium often provides fast feedback for every pull request, while Firefox and WebKit may run for high-value journeys, nightly regression, or release candidates. Mobile emulation is useful for responsive behavior, but it is not a substitute for real-device testing of hardware and operating-system behavior.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'desktop-chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'desktop-firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'desktop-webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 12'] },
},
{
name: 'branded-chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
},
],
});
Bundled Chromium and branded Chrome are different targets. Use a channel only when testing the installed branded browser matters. CI then needs that channel installed and available. The same warning applies to Microsoft Edge with channel: 'msedge'.
Avoid the Cartesian-product trap. Three browsers multiplied by two environments, two roles, and two viewports produces 24 profiles before test tags enter the picture. Often the correct design is pairwise risk coverage: comprehensive Chromium on staging, critical WebKit and Firefox journeys on staging, and a tiny read-only Chromium smoke project against production. Document the reason for each entry so future maintainers can remove obsolete profiles rather than preserving them forever.
4. Understand Inheritance and Project Use Options
Playwright resolves configuration in layers. Top-level use supplies shared browser-context and test options. A project's use overrides the relevant properties. Test files can override options with test.use, and individual APIs can accept operation-specific options. Keep stable policy near the top and narrow exceptions close to their purpose.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
timeout: 30_000,
expect: { timeout: 5_000 },
use: {
baseURL: 'https://example.com',
actionTimeout: 10_000,
navigationTimeout: 20_000,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium-en',
use: { ...devices['Desktop Chrome'], locale: 'en-US' },
},
{
name: 'webkit-fr',
use: {
...devices['Desktop Safari'],
locale: 'fr-FR',
timezoneId: 'Europe/Paris',
},
},
],
});
Do not assume every nested object is deeply merged in the way a general configuration library might merge it. Make the resulting object explicit, especially when spreading a device descriptor and adding values. Placing ...devices[...] first lets later fields intentionally override descriptor values. Reversing that order can silently replace your custom viewport or locale-related option.
The use object controls the environment created for tests, not the test-runner policy itself. Browser name, base URL, storage state, permissions, geolocation, locale, timezone, proxy, tracing, and artifact capture belong there. Retries, test matching, dependencies, metadata, timeout, and workers belong on the project entry. This distinction is a common interview topic because it shows whether a candidate understands the runner rather than only page automation.
Inside a test, resolved information is available through testInfo.project. Prefer configuration-driven behavior over long if statements, but the name and metadata can help with diagnostics or the rare assertion that truly varies by target.
5. Run and Select Playwright Projects Config Profiles
Running without a selector executes all configured projects. The --project option limits the primary selection by project name. It can be supplied more than once, and the value can match a project name pattern. Quote values containing spaces so the shell passes them as one argument.
# All projects
npx playwright test
# One named project
npx playwright test --project=desktop-chromium
# Two selected projects
npx playwright test --project=desktop-chromium --project=desktop-firefox
# Inspect collection without running browsers
npx playwright test --list
# Select a project and a tagged test subset
npx playwright test --project=desktop-chromium --grep @smoke
Stable, lowercase names with simple separators make CI commands less error-prone. A display-oriented name such as Desktop Chrome (Staging) works, but it creates quoting and snapshot-path noise. Project names are also referenced by dependencies, so renaming one is a configuration migration rather than a cosmetic edit. Search the repository and pipeline definitions when names change.
Project selection and test selection are different axes. --project chooses execution profiles. A file path, --grep, or --grep-invert chooses tests. If selected projects depend on setup projects, Playwright also runs those dependencies unless --no-deps is passed. Use --no-deps as a deliberate diagnostic switch, not a routine speed hack, because dependent tests may then start without required state.
In CI, log the exact command and retain the HTML report or blob reports needed for merging. A report that shows project names clearly answers whether a failure is product-wide, engine-specific, environment-specific, or limited to a setup stage.
6. Configure Multiple Environments and Authentication Dependencies
Environment projects are useful when the same assertions are safe and meaningful across deployments. Read URLs and credentials from the environment, validate required values early, and never embed secrets in the config. Production projects should select read-only or reversible tests and normally disable retries that could repeat a mutating action.
import { defineConfig, devices } from '@playwright/test';
const stagingURL = process.env.STAGING_URL ?? 'https://staging.example.com';
const productionURL = process.env.PRODUCTION_URL ?? 'https://example.com';
export default defineConfig({
projects: [
{
name: 'auth-setup',
testMatch: /auth\.setup\.ts/,
teardown: 'auth-cleanup',
},
{
name: 'auth-cleanup',
testMatch: /auth\.teardown\.ts/,
},
{
name: 'staging-chromium',
use: {
...devices['Desktop Chrome'],
baseURL: stagingURL,
storageState: 'playwright/.auth/user.json',
},
dependencies: ['auth-setup'],
retries: 1,
},
{
name: 'production-smoke',
testMatch: /.*\.prod-smoke\.spec\.ts/,
use: { ...devices['Desktop Chrome'], baseURL: productionURL },
retries: 0,
},
],
});
A setup project is ordinary test code selected by testMatch. It can use fixtures, produce a trace, appear in reports, and save authenticated storage state. Dependent projects start only after all required dependency tests pass. A teardown named on the setup project runs after dependent projects finish. This visibility is a major advantage over hidden setup scripts.
Keep each worker's data requirements in mind. One shared account can collide when tests run in parallel even if login setup succeeded. Use isolated accounts, create data through APIs, or cap a project with workers: 1 when the resource is truly exclusive. If login itself is the behavior under test, do not bypass it with storage state. For broader authentication troubleshooting, fixing Playwright connection failures helps separate application availability from test-state problems.
7. Split Smoke, Regression, and Specialized Tests
Projects can control collection with testMatch, testIgnore, grep, and grepInvert. This is valuable when a profile requires a distinct scope, such as a production-safe smoke pack or Chromium-only visual checks. Keep selection rules mutually understandable. If the same test intentionally runs in multiple projects, that overlap should be visible in names and documentation.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'smoke-chromium',
grep: /@smoke/,
use: { ...devices['Desktop Chrome'] },
retries: 0,
},
{
name: 'regression-chromium',
grepInvert: /@production-only/,
use: { ...devices['Desktop Chrome'] },
retries: 1,
},
{
name: 'visual-chromium',
testMatch: '**/*.visual.spec.ts',
use: { ...devices['Desktop Chrome'] },
},
],
});
Tag tests at declaration time so intent travels with the test. Playwright's current test details syntax supports a tag property, for example test('checkout', { tag: '@smoke' }, async ({ page }) => { ... }). A title containing @smoke can also be matched, but structured tags are easier to inspect and standardize.
Do not create a smoke project and a regression project that both select every test by accident. Use npx playwright test --list --project=<name> to audit the resolved set. Also watch for setup files that match the default test pattern and run as regular tests. Give setup files a deliberate suffix and select them only in their setup project.
A specialized project may set ignoreSnapshots: true to skip snapshot expectations on targets where they add no value. Another can choose a snapshot path template containing {projectName} to prevent baseline collisions. Projects provide the isolation mechanism, but the team still owns a clear coverage model.
8. Manage Parallelism, Retries, and Artifacts Per Project
Global workers limit total worker processes. A project-level workers value caps concurrency for that project within the global ceiling. This is useful for a serial external system, rate-limited test tenant, or resource-heavy WebKit profile. It is not a repair for tests that share mutable data accidentally. Fix isolation first, then cap concurrency only where the system constraint is real.
Retries are likewise diagnostic policy, not a pass generator. A retry can classify a failure as flaky and collect a trace on first retry, but it should not hide a deterministic product defect. Use retries: process.env.CI ? 2 : 0 globally when appropriate, then override a sensitive production project to zero.
export default defineConfig({
workers: process.env.CI ? '50%' : undefined,
retries: process.env.CI ? 1 : 0,
use: { trace: 'on-first-retry' },
projects: [
{ name: 'fast-isolated' },
{
name: 'shared-test-tenant',
workers: 1,
retries: 0,
metadata: { owner: 'checkout-qa', risk: 'external-tenant' },
},
],
});
Use a unique outputDir only if a project needs a separate base location. Playwright already creates unique output directories for individual test runs, which protects parallel artifacts. Snapshot baselines need additional attention because their identity depends on snapshot path configuration. Include the sanitized project name when rendering differs by engine, device, platform, or locale.
Metadata is serialized into reports and is useful for ownership, environment, or risk classification. Keep it stable. Playwright may evaluate the configuration file multiple times, so generating random metadata or time-based project names creates inconsistent collection and reporting. Resolve external environment variables deterministically and fail with a useful message when required values are missing.
9. Design an Efficient CI Matrix
There are two main CI patterns. One job can run several projects through a single Playwright command, which is simple and lets the runner coordinate dependencies. Alternatively, CI can fan out jobs by project, which improves isolation and wall-clock time but requires each job to install browsers, manage setup, and publish mergeable results. Choose based on suite size and operational clarity.
| CI pattern | Advantages | Tradeoffs | Best fit |
|---|---|---|---|
| One job, all projects | Simple command, unified setup, one report | Longer critical path, broad reruns | Small and medium suites |
| One job per project | Clear ownership, parallel engines | Repeated setup, report merging | Large browser matrix |
| Shards within a project | Scales a large profile | Requires stable isolation and merged reports | Large regression project |
| Tiered pull request and nightly runs | Fast feedback plus broad coverage | Two schedules to maintain | Mature risk-based pipelines |
A practical pull request pipeline might run smoke-chromium on every change, then run Firefox, WebKit, and mobile profiles after merge or on a release gate. Critical user journeys can still run across all engines before merge. This is risk selection, not browser favoritism.
When a selected project has dependencies, those dependency projects run too. If CI creates a separate job for every browser project, authentication setup may be repeated in each isolated job. That can be correct, because files and browser state do not automatically cross job boundaries. If setup is costly, use a secure job artifact only after evaluating the sensitivity and expiration of storage state. Never publish authenticated state in a public artifact. Docker for Playwright covers consistent browser dependencies when runner images differ.
Use --shard to divide tests within the selected profiles, not to define the profiles themselves. Keep project names identical across shards so reports merge cleanly. Record the project, shard index, commit, and target environment in CI logs.
10. Troubleshoot Playwright Projects Config Systematically
When a project seems ignored, start with npx playwright test --list. Confirm that the config file was discovered, the project name is exact, and the expected test files appear beneath it. If using a nonstandard config path, pass --config. A glob is matched against absolute test paths, so a plausible-looking testMatch can still select nothing. Compare it with the documented patterns already working in the repository.
If every test runs twice, inspect overlapping projects rather than test duplication first. If setup runs as a normal browser test, tighten both the setup project's testMatch and the browser projects' testIgnore. If --project reports no matching project, quote names containing spaces or adopt shell-friendly names.
For configuration differences, print resolved data in a targeted diagnostic test:
import { test, expect } from '@playwright/test';
test('project diagnostic', async ({ browserName }, testInfo) => {
console.log({
project: testInfo.project.name,
browserName,
retries: testInfo.project.retries,
metadata: testInfo.project.metadata,
});
expect(testInfo.project.name).toBeTruthy();
});
A missing browser executable is not a project-selection defect. Install the required browser binaries for the installed Playwright version. A failing navigation across every project is more likely an unavailable base URL, proxy, certificate, or application issue. One-engine failures deserve engine-specific investigation. If failures are timeouts rather than configuration errors, use the Playwright 30000ms timeout guide before increasing limits globally.
Finally, remove stale entries. A smaller matrix with explicit risk coverage is more trustworthy than a large matrix whose red projects are routinely ignored.
Interview Questions and Answers
Q: What is a project in Playwright Test?
A project is a named group of tests executed with a shared resolved configuration. The same test may be collected into several projects, such as Chromium, Firefox, and WebKit. Project-level settings can change browser options, selection, retries, timeouts, workers, dependencies, and metadata.
Q: Where should shared and project-specific options live?
Put shared runner policy and use defaults at the top level. Put only the settings that differ on a project entry. This reduces drift and makes the purpose of each project readable.
Q: How do you run only one project?
Use npx playwright test --project=<project-name>. Project selection limits execution profiles, while file arguments and grep options limit tests. Dependencies still run unless --no-deps is used.
Q: What is the difference between global setup and project dependencies?
A dependency is a normal Playwright project whose tests run before dependent projects. It appears in reports, supports fixtures, and can produce traces. That visibility usually makes dependencies preferable for authentication and observable setup flows.
Q: Can each project use a different base URL?
Yes. Set use.baseURL inside each project and use relative navigation paths in tests. Keep environment secrets outside the config and make production test selection safe.
Q: How do retries and workers behave across projects?
Top-level values act as defaults. Projects can override retries and can cap their worker usage, while the global worker limit still caps total concurrency. Neither setting should compensate for shared-state defects.
Q: How would you prevent snapshot collisions?
Use a snapshot path template that includes {projectName} when rendering is expected to differ. Keep project names stable because they become part of baseline identity. Review baseline changes per target rather than accepting all updates blindly.
Q: How do you keep a project matrix efficient?
Map each project to a specific risk, then tier execution by feedback needs. Run fast, high-signal profiles on pull requests and broader engine or device coverage on release or scheduled pipelines. Audit overlaps with --list and delete profiles that no longer add confidence.
Common Mistakes
- Copying the whole top-level configuration into every project, which creates drift and hides the few meaningful differences.
- Naming projects inconsistently, then breaking CLI commands, snapshot paths, reports, or dependency references during casual renames.
- Multiplying browsers, roles, devices, and environments into a huge matrix without a risk-based reason.
- Using one storage-state file with mutable shared server data and assuming authentication isolation also isolates test data.
- Letting setup files match normal browser projects, so initialization runs more times than intended.
- Treating retries or
workers: 1as permanent fixes for flaky tests caused by shared state. - Spreading a device descriptor after custom fields, which can overwrite the custom values.
- Running destructive scenarios against production because the project changed only
baseURL, not test selection. - Passing
--no-depsroutinely and then debugging failures caused by missing setup. - Creating dynamic project names or metadata even though the config can be evaluated multiple times.
Conclusion
A maintainable Playwright projects config starts with stable named profiles, shared defaults, and narrow per-project overrides. Use projects to express real execution dimensions, dependencies to make setup visible, and test filters to keep specialized profiles safe.
Begin with a three-browser configuration, verify collection with --list, then add environments, authentication, mobile emulation, or CI fan-out only when each addition covers a defined risk. The result is a matrix engineers can explain, trust, and operate when a failure reaches the report.
Interview Questions and Answers
Explain Playwright projects config in practical terms.
It is the projects array in `playwright.config.ts`, where each named entry defines an execution profile. The same test suite can run with different browsers, devices, environments, states, or selection rules. I keep shared values at the top level and override only meaningful differences per project.
How does configuration inheritance work for projects?
Top-level settings provide shared defaults, and project-level settings override their project-specific equivalents. Test files can narrow some options further with `test.use`. I make device spreading and overrides explicit so object order does not silently replace intended values.
How would you run Chromium on every pull request and all engines nightly?
I would define stable Chromium, Firefox, and WebKit projects in one config. The pull request job selects Chromium with `--project`, while the scheduled job runs all projects or selects all three explicitly. This keeps coverage policy in CI without duplicating test code.
When would you use project dependencies?
I use them for required setup that should be observable as test execution, such as creating authenticated storage state. Dependent browser projects start only after setup passes, and the report can include setup traces. I still isolate accounts and server data because storage state alone does not prevent parallel collisions.
What is the difference between project selection and test filtering?
`--project` chooses an execution profile. File paths, `--grep`, `--grep-invert`, `testMatch`, and `testIgnore` choose test cases. Combining both lets a pipeline run a targeted scope under a targeted environment.
How would you diagnose tests unexpectedly running twice?
I would run `npx playwright test --list` and inspect which projects collect the test. Overlapping `testMatch` or grep rules often explain the duplicate executions. I would then make the overlap intentional and documented or tighten the project selection rules.
How do project workers relate to global workers?
The global worker value limits total runner concurrency. A project's workers value places an additional cap on that project's concurrent workers. I use a project cap only for a real external constraint, not to mask tests that fail because they share data.
What makes a good Playwright project name?
It is stable, unique, shell-friendly, and describes the risk dimension, such as `mobile-safari` or `production-smoke`. Names appear in commands, reports, dependencies, and often snapshot paths. Renaming therefore needs the same care as changing an interface.
Frequently Asked Questions
What is projects in Playwright config?
The projects property is an array of named Playwright Test configurations. Each project can run eligible tests with different browser, context, selection, retry, timeout, dependency, and worker settings.
How do I run a specific Playwright project?
Run `npx playwright test --project=project-name`. You can repeat the option to select more than one project, and dependent setup projects run unless you add `--no-deps`.
Can Playwright projects use different base URLs?
Yes. Set `use.baseURL` in each project, then navigate with relative paths in tests. Keep credentials and deployment secrets in the runtime environment rather than the configuration file.
Do Playwright projects run in parallel?
Independent projects can run concurrently subject to the global worker limit and each project's optional worker cap. A dependent project waits for its dependency projects to pass before it begins.
Can one test run in multiple Playwright projects?
Yes. If a test matches several projects, Playwright creates an execution for each matching project. The report identifies each result by project name.
Should I create separate Playwright config files for each browser?
Usually no. A single config with browser projects keeps shared policy together and supports CLI selection. Separate configs are justified only when workflows are genuinely independent and clearer that way.
What does a Playwright project dependency do?
It names one or more projects that must pass before the dependent project's tests run. Dependencies are useful for authentication, environment preparation, and other setup that benefits from normal test reporting and traces.