Q16 of 42 · Playwright

How do you parameterise Playwright tests with test.describe.parallel and projects?

PlaywrightMidplaywrightprojectsparallelconfigmid

Short answer

Short answer: **Projects** parameterise tests by config — different browsers, viewports, or env vars. Each project runs the matching tests independently. **`test.describe.parallel`** marks describe blocks for concurrent execution within the same file (default is serial within a file, parallel across files).

Detail

Playwright has two levels of parallelism, controlled separately.

Across files: parallel by default. Each worker takes one file at a time. Configure with workers: N.

Within a file: serial by default. test.describe.parallel(...) opts a describe block into intra-file parallelism, useful when tests within a file are genuinely independent. test.describe.serial(...) does the opposite — useful when one test's state depends on the previous (rare and discouraged).

Projects are the cross-cutting parameterisation:

export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 14 Pro'] } },
  ],
});

npx playwright test runs every test against every project. --project=chromium filters to one. Each project gets its own worker(s) and its own report subsection.

Project-scoped tests: filter which tests run for which project via testMatch / testIgnore in the project config:

projects: [
  {
    name: 'mobile',
    use: { ...devices['iPhone 14 Pro'] },
    testMatch: /.*\.mobile\.spec\.ts/,
  },
]

Project dependencies for setup-then-test patterns:

projects: [
  { name: 'setup', testMatch: /global\.setup\.ts/ },
  { name: 'chromium', dependencies: ['setup'], use: { storageState: 'auth.json' } },
]

setup runs first; chromium runs after, consuming auth.json.

The combined model gives you parametrised + parallel + setup-aware execution from config alone, no test-level boilerplate.

// EXAMPLE

test-with-parallelism.spec.ts

import { test, expect } from '@playwright/test';

// Default: serial within this file
test.describe('serial group', () => {
  test('A', async ({ page }) => { /* ... */ });
  test('B', async ({ page }) => { /* depends on A */ });
});

// Opt into parallel within the file
test.describe.parallel('independent group', () => {
  test('checks header', async ({ page }) => { /* ... */ });
  test('checks footer', async ({ page }) => { /* ... */ });
  test('checks sidebar', async ({ page }) => { /* ... */ });
});

// WHAT INTERVIEWERS LOOK FOR

Distinguishing intra-file vs inter-file parallelism, knowing projects parameterise by config (browser, viewport, env), and naming `testMatch` and `dependencies` as the project-level filters.

// COMMON PITFALL

Treating projects as 'just for cross-browser' — they also handle env-specific config (staging vs prod) and per-project test selection.