IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Intermediate·8 / 27

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.

ts
// playwright.config.ts
export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    // Мобільні пристрої
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
})
bash
# Запустити всі проєкти
npx playwright test

# Запустити тільки Firefox
npx playwright test --project=firefox

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.

ts
// playwright.config.ts
export default defineConfig({
  timeout: 60000,
  projects: [
    {
      name: 'staging',
      use: { baseURL: 'https://staging.mycrm.com' },
      retries: 2,
    },
    {
      name: 'production',
      use: { baseURL: 'https://mycrm.com' },
      retries: 0,
    },
  ],
})

Setup project — login once, share auth state

The setup project runs once; all browser projects depend on it and load the saved auth state — no test logs in again

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.

ts
// playwright.config.ts
export default defineConfig({
  projects: [
    {
      name: 'setup',
      testMatch: '**/*.setup.ts',
    },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: {
        ...devices['Desktop Firefox'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
})
ts
// auth.setup.ts
import { test as setup } from '@playwright/test'

setup('authenticate', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('Email').fill('test@mycrm.com')
  await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!)
  await page.getByRole('button', { name: 'Sign in' }).click()
  await page.waitForURL('/dashboard')

  // Зберегти стан авторизації для всіх тестів
  await page.context().storageState({ path: 'playwright/.auth/user.json' })
})

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.

ts
// playwright.config.ts
export default defineConfig({
  timeout: 60000,
  projects: [
    {
      name: 'Smoke',
      testMatch: /.*smoke.spec.ts/,
      retries: 0,
    },
    {
      name: 'Full',
      testIgnore: /.*smoke.spec.ts/,
      retries: 2,
    },
  ],
})
bash
# Запустити тільки smoke тести на PR
npx playwright test --project=Smoke

# Запустити всі тести на злиття
npx playwright test --project=Full

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.

ts
// playwright.config.ts
export default defineConfig({
  projects: [
    {
      name: 'setup',
      testMatch: '**/*.setup.ts',
      teardown: 'cleanup',
    },
    {
      name: 'cleanup',
      testMatch: '**/*.teardown.ts',
    },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      dependencies: ['setup'],
    },
  ],
})