lecture 8 of 170 completed

Projects as an architectural tool

Projects are not just a browser list. Dependencies, teardown and option fixtures make them the main structural feature of the config.

Most teams use projects for one thing: a list of browsers. That is the smallest use of the most structurally powerful feature in Playwright's config, and knowing the rest is a clear senior marker.

A project is a named configuration that runs a set of tests. What varies between projects is up to you: browser, device, environment, role, test directory, or your own option fixtures.

Use one: dependencies and ordering.

projects: [
  { name: 'setup', testMatch: /global\.setup\.ts/ },
  { name: 'e2e', dependencies: ['setup'], teardown: 'cleanup' },
  { name: 'cleanup', testMatch: /global\.teardown\.ts/ },
],

setup runs first. e2e runs after it succeeds. cleanup runs after e2e finishes, pass or fail.

This is strictly better than the older globalSetup option, and the reason is worth knowing: setup projects are real tests. They appear in the report, produce traces, retry according to your config, and fail with a normal error. A globalSetup function that throws gives you a stack trace and nothing else. When your seeding breaks at 3am, that difference decides how fast you diagnose it.

Use two: roles, via option fixtures.

projects: [
  { name: 'admin',    use: { userRole: 'admin' },    dependencies: ['setup'] },
  { name: 'readonly', use: { userRole: 'readonly' }, dependencies: ['setup'] },
],

With the option fixture from the fixtures lecture, the same tests run as different roles with no test changes. Powerful, and it doubles runtime, so reserve it for suites where full coverage per role is really wanted.

Use three: environments.

projects: [
  { name: 'local',   use: { baseURL: 'http://localhost:3000' } },
  { name: 'staging', use: { baseURL: 'https://staging.example.com' } },
],

Run one with --project=staging. The tests never learn which environment they are in, which is the rule from the intermediate track, now enforced structurally rather than by discipline.

Use four: test tiers.

projects: [
  { name: 'smoke', testMatch: /.*@smoke.*/ },
  { name: 'full',  testDir: './tests' },
],

Tiering by project rather than by tag has the advantage that the tier is visible in the report as a separate line.

The trap: the matrix multiplies.

Three browsers × two roles × two environments is twelve full runs of your suite. Teams build this because each dimension seems reasonable on its own, then discover the pipeline takes four hours.

Decide per dimension what actually needs full coverage. In practice: one browser on every commit, all browsers nightly. One role for most tests with a dedicated permission suite covering the rest. One environment per pipeline stage rather than all of them every time.

State the cost out loud when adding a dimension. "This doubles the run" is the sentence that makes the decision explicit rather than accidental.

Reporting across projects. With sharding, each machine writes a blob report and they merge afterwards:

npx playwright test --shard=1/4 --reporter=blob
npx playwright merge-reports --reporter=html ./blob-report

Without merging you get four partial reports and no overall view, which is a common and frustrating misconfiguration.

Remember this: projects are structure, not a browser list. Dependencies for ordering, options for roles, and say the multiplication cost out loud before adding a dimension.

Reference: Test projects.