Projects
A project is a named configuration that tests run with. I use projects for three things: running the same tests across browsers (chromium/firefox/webkit), running the same tests against staging vs production, and setting up a 'setup' project that logs in once before all other tests run. The setup project with dependencies is the pattern I use most.
Cross-browser testing
The most common use of projects: run everything in three browsers. devices['Desktop Chrome'] spreads in all the Chrome-specific settings (viewport, user agent, etc.). I pick the browsers based on the product's target audience — for a B2B SaaS I usually test Chromium and Firefox, skip WebKit unless the product has Mac users doing critical flows.
Testing against multiple environments
I use this pattern when I want to run the same tests against staging (with retries, more lenient) and production (no retries, must pass clean). Each project gets its own baseURL and retry count. The global timeout is shared.
Setup project — login once, share auth state
This is the pattern I use most. A setup project runs auth.setup.ts which logs into the CRM, saves the auth state to a file. Then the browser projects list setup as a dependency — they wait for setup to finish, then start in parallel using the saved auth state. No test has to log in again.
The storageState path in each browser project points to the file that the setup project created. Playwright automatically passes this state to every browser context in that project.
Splitting tests by type — smoke vs full suite
I use testMatch and testIgnore to create a smoke project that runs only the most critical tests without retries, and a full project that runs everything with retries. On PRs I run only smoke; on merge to main I run the full suite.
Teardown — cleanup after all tests finish
If the setup project creates data (test users, seeded orders), I add a teardown project to clean up after all tests finish. The teardown property on the setup project points to the project that runs the cleanup. Teardown runs after all dependent projects complete — even if some tests failed.