| |

Playwright Projects TypeScript Guide

Playwright projects TypeScript featured image showing browser matrix, setup dependencies, and CI gates

Day 29 of the Playwright + TypeScript tutorial series. Playwright projects TypeScript is the feature I use when one suite has to answer many questions: does it work in Chromium, WebKit, Firefox, mobile Safari-like viewports, logged-in flows, smoke mode, and setup-driven environments? A clean projects setup keeps that matrix explicit instead of hiding it inside npm scripts and random environment variables.

If your Playwright suite is still one big default project, this tutorial gives you a practical structure you can copy. I will show browser projects, setup dependencies, mobile projects, API setup, grep filters, CI commands, screenshots you should expect, and the mistakes that make projects painfully slow.

Table of Contents

Contents

What Are Playwright Projects?

Playwright calls a project a logical group of tests that run with the same configuration. The official Playwright projects documentation explains the common use case clearly: run the same tests across different browsers and devices by configuring projects in playwright.config.ts.

That sounds small, but it changes how you design your suite. Instead of writing separate commands for Chrome, Firefox, mobile, staging, production smoke, and logged-in tests, you define named projects once and run them by name when needed.

Why projects matter after the first 50 tests

A beginner suite can survive with one config. A team suite cannot. Once you have login state, seeded data, feature flags, API checks, and release gates, one default project becomes a dumping ground.

I see three problems in messy suites:

  • Developers do not know which command is safe before a pull request.
  • QA engineers rerun the full suite when a focused project would be enough.
  • CI time grows because every browser runs every test, even tests that only need one browser.

The adoption context

This is not a niche feature anymore. At the time of research for this tutorial, the Microsoft Playwright GitHub repository showed about 93,126 stars through the GitHub repository API, and the npm download API reported 181,316,212 last-month downloads for @playwright/test. The latest npm registry response showed version 1.61.1 for @playwright/test.

I cite those numbers for one reason: teams are building serious automation on Playwright now. Serious automation needs serious configuration hygiene.

A Practical Project Layout

The first decision is naming. Names should tell a human exactly what a project does. I avoid names like project1, full, or test. They look harmless on day one and become tribal knowledge by month three.

Folder structure I recommend

Here is a simple structure for a Playwright projects TypeScript setup:

tests/
  e2e/
    checkout.spec.ts
    profile.spec.ts
  api/
    health.spec.ts
    orders-api.spec.ts
  setup/
    auth.setup.ts
    data.setup.ts
  smoke/
    login-smoke.spec.ts
playwright.config.ts
.env.example

The important part is not the exact folder name. The important part is that setup tests, smoke tests, API tests, and E2E tests are easy to target from the config.

Start with a typed config

Use TypeScript types directly in the config. It gives you autocomplete and catches mistakes in properties like testMatch, dependencies, and use.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  expect: { timeout: 5_000 },
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: process.env.CI
    ? [['html'], ['github']]
    : [['list'], ['html', { open: 'never' }]],
  use: {
    baseURL: process.env.BASE_URL ?? 'https://example.com',
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
      dependencies: ['setup'],
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
      dependencies: ['setup'],
    },
  ],
});

Screenshot description: after running npx playwright test --list, you should see the same E2E test expanded under chromium, firefox, and webkit, plus the setup files under the setup project. This is the first sanity check.

Build a Browser Matrix Without Noise

The browser matrix is the most common reason teams use projects. But the trap is obvious: three browsers can turn a 10-minute suite into a 30-minute suite if you run everything everywhere.

Run critical flows across browsers

I usually split tests into two groups. Critical user journeys run across browsers. Deep business-rule tests run on Chromium unless there is a browser-specific risk.

projects: [
  { name: 'setup', testMatch: /.*\.setup\.ts/ },
  {
    name: 'chromium-critical',
    testMatch: /.*\.critical\.spec\.ts/,
    use: { ...devices['Desktop Chrome'] },
    dependencies: ['setup'],
  },
  {
    name: 'firefox-critical',
    testMatch: /.*\.critical\.spec\.ts/,
    use: { ...devices['Desktop Firefox'] },
    dependencies: ['setup'],
  },
  {
    name: 'webkit-critical',
    testMatch: /.*\.critical\.spec\.ts/,
    use: { ...devices['Desktop Safari'] },
    dependencies: ['setup'],
  },
  {
    name: 'chromium-regression',
    testIgnore: /.*\.critical\.spec\.ts/,
    use: { ...devices['Desktop Chrome'] },
    dependencies: ['setup'],
  },
]

This gives you cross-browser confidence without multiplying every single assertion. It also makes your CI report easier to read because the project name already explains intent.

Use command line targeting

Playwright lets you run a project by name:

npx playwright test --project=chromium-critical
npx playwright test --project=webkit-critical
npx playwright test tests/e2e/checkout.critical.spec.ts --project=chromium-critical

Teach the team these commands. A good project design is wasted if everyone still runs npx playwright test blindly for every change.

Connect projects with annotations

Projects become even stronger when paired with tags and annotations. I covered skip, fixme, fail, slow, and grep habits in the Playwright annotations TypeScript guide. The short rule is simple: use project selection for environment and browser differences, and use annotations for test intent.

Use Setup Dependencies for Login and Data

The cleanest project setup I use has a setup project that signs in once, creates storage state, and prepares predictable data. Playwright documents authenticated state in the authentication guide, including the pattern of saving state to a file and loading it in tests.

Create authentication state once

Here is a setup test that logs in and writes storage state:

// tests/setup/auth.setup.ts
import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate test user', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!);
  await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible();
  await page.context().storageState({ path: authFile });
});

Then attach that state to dependent projects:

const authFile = 'playwright/.auth/user.json';

export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium-authenticated',
      use: {
        ...devices['Desktop Chrome'],
        storageState: authFile,
      },
      dependencies: ['setup'],
      testMatch: /.*\.auth\.spec\.ts/,
    },
  ],
});

Do not put secrets in the repo

Never commit the generated auth JSON file. It can contain cookies and tokens. Add this to .gitignore:

playwright/.auth/
.env

If your team is still debating how to load environment variables safely, read the ScrollTest guide on secrets management in Playwright with dotenv. Projects and secrets are connected because project configs often decide which account, URL, and storage state to use.

Use API setup for test data

Setup projects are not only for login. You can seed data through APIs before UI tests run.

// tests/setup/data.setup.ts
import { test as setup, request, expect } from '@playwright/test';

setup('seed checkout product', async () => {
  const api = await request.newContext({
    baseURL: process.env.API_URL,
    extraHTTPHeaders: {
      Authorization: `Bearer ${process.env.TEST_ADMIN_TOKEN}`,
    },
  });

  const response = await api.post('/test-data/products', {
    data: { sku: 'PW-TS-29', price: 4999, stock: 10 },
  });

  expect(response.ok()).toBeTruthy();
  await api.dispose();
});

Screenshot description: in the HTML report, setup tests appear as their own project. If setup fails, dependent projects are skipped or blocked. That is good. It tells you the suite did not fail because checkout is broken. It failed because the environment was not ready.

Mobile, API, and Smoke Projects

Playwright projects TypeScript configuration is not only a browser matrix. You can model different test purposes. This is where projects become a release strategy, not just a config trick.

Add mobile emulation without copying tests

Use Playwright devices for mobile-like coverage:

{
  name: 'mobile-chrome-critical',
  testMatch: /.*\.critical\.spec\.ts/,
  use: { ...devices['Pixel 7'] },
  dependencies: ['setup'],
},
{
  name: 'mobile-safari-critical',
  testMatch: /.*\.critical\.spec\.ts/,
  use: { ...devices['iPhone 15'] },
  dependencies: ['setup'],
}

This does not replace real-device testing for every risk. It does catch layout, viewport, and responsive flow issues early. For most product teams in India, this is a practical middle ground before investing in a device lab.

Create a fast API project

Keep API tests in a separate project when they do not need a browser. They run faster and give earlier feedback.

{
  name: 'api-contracts',
  testDir: './tests/api',
  use: {
    baseURL: process.env.API_URL ?? 'https://api.example.com',
  },
}

If the API project fails, I want that failure before spending 15 minutes on UI flows. This is especially useful in CI where one broken backend dependency can waste the whole test budget.

Create a smoke project for pull requests

A smoke project should answer one question: is this build safe enough for a deeper run?

{
  name: 'pr-smoke',
  testDir: './tests/smoke',
  timeout: 20_000,
  retries: process.env.CI ? 1 : 0,
  use: { ...devices['Desktop Chrome'] },
  dependencies: ['setup'],
}

Keep it small. Five to fifteen tests are often enough for a pull request gate. Put login, home page, one business-critical path, and one API health check there. Do not let the smoke project become a hidden regression suite.

Playwright Projects TypeScript in CI

Playwright has dedicated CI documentation, and projects make that CI setup more intentional. The mistake is running all projects on every event. That burns minutes and trains the team to ignore failures.

Use different commands for different triggers

I like this split:

  1. Pull request: run api-contracts and pr-smoke.
  2. Main branch merge: run Chromium regression and critical browser projects.
  3. Nightly: run every browser, mobile critical flows, and visual checks.
  4. Release candidate: run the full suite with retries and trace retention.
name: playwright
on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps
      - name: PR smoke
        if: github.event_name == 'pull_request'
        run: npx playwright test --project=api-contracts --project=pr-smoke
      - name: Main regression
        if: github.event_name == 'push'
        run: npx playwright test --project=chromium-regression --project=chromium-critical --project=firefox-critical
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Sharding and projects

If your suite is large, combine projects with sharding. I covered CI sharding earlier in the series, and the same rule applies here: shard expensive projects, not setup projects. The setup project should run once per worker context unless your pipeline is designed to isolate each shard completely.

For visual projects, connect this with the Playwright visual regression testing tutorial. Visual baselines become noisy when every browser and viewport writes snapshots without naming discipline.

Debugging and Common Pitfalls

Most project bugs are not Playwright bugs. They are design bugs in the config.

Pitfall 1: Too many projects

Do not create a project for every tiny variation. A matrix like browser × role × locale × viewport × feature flag can explode quickly. Use projects for stable dimensions. Use fixtures and test data for scenario-level variation. If you need a refresher on fixture composition, read merging multiple fixtures in Playwright with mergeTests.

Pitfall 2: Setup project runs tests by accident

A setup project should match only setup files. Keep a strict testMatch. If your setup project runs normal specs, your reports become confusing and your state creation may run repeatedly.

{ name: 'setup', testMatch: /.*\.setup\.ts/ } // good
{ name: 'setup' } // risky because it can match everything

Pitfall 3: Browser-specific expectations everywhere

If a test has a different expectation in each browser, ask whether it belongs in the shared cross-browser project. Sometimes the right fix is one browser-specific spec. Sometimes the right fix is improving product consistency.

Pitfall 4: Auth state expires silently

If login state expires in the middle of a run, tests fail far away from the real cause. Add a small assertion at the start of authenticated tests or create a fixture that verifies the logged-in marker. Do not let every page object assume the session is valid.

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

export const test = base.extend({
  verifiedPage: async ({ page }, use) => {
    await page.goto('/dashboard');
    await expect(page.getByTestId('user-menu')).toBeVisible();
    await use(page);
  },
});

Pitfall 5: Project names are not visible in failure messages

Name projects like they will appear in Slack at 2 AM. webkit-critical is useful. safari-1 is vague. mobile-safari-critical tells the owner exactly where to start.

Review Checklist Before You Merge

Before I approve a Playwright projects TypeScript config, I run a small review checklist. This catches most suite design problems before they become CI debt.

Check the project list output

Run the list command and read it like a new team member would read it:

npx playwright test --list
npx playwright test --project=pr-smoke --list
npx playwright test --project=chromium-regression --list

If the output surprises you, the config is not ready. The setup project should only list setup files. Smoke should only list smoke tests. Browser critical projects should not accidentally include API-only specs. This boring check saves hours later.

Check runtime budgets

Every project should have a rough runtime budget. I like simple numbers: PR smoke under 5 minutes, API contracts under 3 minutes, main branch regression under 20 minutes, nightly full suite whatever the team can tolerate. The exact number depends on your product, but the budget must exist.

Without budgets, teams keep adding tests until the pipeline becomes unusable. Once that happens, people bypass tests, mark failures as flaky, or merge with red builds. The problem looks technical, but the root cause is usually no ownership of runtime.

Check ownership

Each project needs an owner. API contract failures usually belong near backend owners. Visual failures may need frontend review. Smoke failures should block the release owner immediately. Put this mapping in your repository README so the CI report leads to action, not confusion.

  • pr-smoke: pull request owner fixes or reverts.
  • api-contracts: backend or platform owner checks the contract.
  • webkit-critical: frontend owner validates browser behavior.
  • setup: QA platform owner checks accounts, secrets, and data.

Screenshot description: the ideal CI job page has project names that match this ownership model. A failed setup job should not look like a failed checkout test. A failed mobile-safari-critical job should not be buried inside a generic regression job.

Key Takeaways

Playwright projects TypeScript gives your automation suite a clear operating model. It helps you decide what runs on pull requests, what runs after merges, what runs nightly, and what needs setup state before it starts.

  • Use projects for browser, device, setup, API, and release-gate differences.
  • Keep setup projects strict with testMatch and dependencies.
  • Do not run every test in every browser by default.
  • Use smoke projects for pull requests and full projects for nightly or release runs.
  • Name projects like a human will debug them under pressure.

If I had to give one rule, it would be this: project design should reduce decisions for the team. A junior QA engineer should know which command to run without asking the senior SDET on Slack.

FAQ

Should every Playwright test run in every browser?

No. Run critical flows across browsers. Run deeper business-rule regression on one primary browser unless you have a real cross-browser risk. This keeps the suite useful and fast.

Should setup be a global setup file or a setup project?

For small suites, global setup can work. For team suites, I prefer setup projects because they appear in the report, support dependencies, and make failures easier to understand.

Can I use projects for staging and production?

Yes, but be careful. I usually prefer separate environment variables for URLs and accounts, then projects for browser and test purpose. If staging and production need different safety rules, separate projects can make sense.

How many projects are too many?

If engineers cannot explain when each project runs, you have too many. Start with setup, chromium regression, critical cross-browser projects, API contracts, and PR smoke. Add more only when a release risk demands it.

What should I teach beginners first?

Teach the idea of a project as a named test configuration. Then show --project, browser devices, and setup dependencies. Beginners understand projects faster when they see the CLI output and HTML report.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.