Testland

Setting up Playwright with TypeScript from scratch

TestlandFebruary 13, 2026

Set up Playwright with TypeScript step by step. Covers project creation, configuration, writing your first test, and adding GitHub Actions CI integration.

Setup-to-first-pass timeline in seconds. The npm init wizard runs 0 to 30s. Installing all three browsers takes 30 to 120s. The example spec executes in about 3s, and Playwright's default auto-wait fires at 5s.

Playwright ships with TypeScript support out of the box. No Babel config, no separate compiler setup, no fiddling with tsconfig.json presets. Run one command, and you've got a working project with typed tests, auto-completion, and browser automation ready to go.

This tutorial walks through creating a Playwright project with TypeScript, writing a first test, running it, and setting up CI with GitHub Actions. By the end, you'll have a test suite that runs on every pull request.

Prerequisites: Node.js 20 or later and a terminal.

Key facts

  • Playwright 1.58 is the latest stable release (as of February 2026)
  • TypeScript is the default language when creating a new Playwright project
  • Playwright supports Chromium, Firefox, and WebKit from a single API
  • Auto-waiting and web-first assertions eliminate most timing-related flaky tests
  • The npm init playwright@latest command scaffolds a ready-to-run project in under a minute

Create the project

Run the Playwright initializer:

npm init playwright@latest

The setup wizard asks a few questions:

  • Language: TypeScript is the default. Hit Enter.
  • Tests folder: tests works fine for most projects.
  • GitHub Actions workflow: Select yes. It adds a CI config file.
  • Install browsers: Yes. Downloads Chromium, Firefox, and WebKit.

After it finishes, you'll have this structure:

my-project/
├── tests/
│   └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .github/
    └── workflows/
        └── playwright.yml

The whole process takes about 30 seconds, plus browser download time.

Configure playwright.config.ts

Open playwright.config.ts. Here's what the important settings do:

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

A few things worth noting:

  • fullyParallel: true runs all tests concurrently. Fast, but tests can't share state.
  • retries is set to 2 in CI and 0 locally. Retries catch intermittent failures without hiding bugs during development.
  • trace: 'on-first-retry' records a trace when a test fails and gets retried. Traces capture every network request, DOM snapshot, and action. They're far more useful than screenshots when you're debugging a CI failure at 5pm on a Friday.
  • forbidOnly: !!process.env.CI prevents accidentally leaving .only on a test, which would silently skip everything else in CI.

For a real project, add a webServer block to start your app automatically before tests:

webServer: {
  command: 'npm run dev',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
},

Write your first test

Replace the contents of tests/example.spec.ts:

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

test('homepage has Playwright in title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link navigates to installation page', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await page.getByRole('link', { name: 'Get started' }).click();
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

Two things stand out if you're coming from Selenium or Cypress:

No explicit waits. page.getByRole() and expect().toBeVisible() both auto-wait. Playwright retries assertions for up to 5 seconds by default. No Thread.sleep(), no cy.wait(), no WebDriverWait.

Role-based locators. getByRole('link', { name: 'Get started' }) finds elements the way a screen reader does. Tests stay resilient when CSS classes change, and they're accessible by design. Playwright's locators documentation covers the full API.

Run tests and view reports

Run the tests:

npx playwright test

Output looks like this:

Running 2 tests using 2 workers
  2 passed (3.2s)

Open the HTML report for detailed results:

npx playwright show-report

Other commands worth knowing:

# See the browser while tests run
npx playwright test --headed

# Interactive UI mode - step through tests visually
npx playwright test --ui

# Run a specific file
npx playwright test tests/example.spec.ts

# Generate test code by recording browser actions
npx playwright codegen https://playwright.dev

Try --ui early. It shows a live browser, step-by-step execution, and DOM snapshots in one window. It's the fastest way to understand what a test actually does.

Add CI with GitHub Actions

If you selected GitHub Actions during setup, you already have .github/workflows/playwright.yml:

name: Playwright Tests
on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - uses: actions/setup-node@v5
      with:
        node-version: lts/*
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright browsers
      run: npx playwright install --with-deps
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

The --with-deps flag installs system-level libraries that Ubuntu images don't include. The upload-artifact step saves the HTML report so you can download it after the run.

One tip: if you only need Chromium, install just that browser to speed things up:

npx playwright install chromium --with-deps

This cuts the browser install step from around 90 seconds to about 30.

Common issues

"browserType.launch: Executable doesn't exist"

Run npx playwright install to download browsers. This happens when you clone a repo and run npm install but skip the browser install step.

TypeScript type errors don't fail tests

Playwright doesn't type-check during execution. Add a separate step in CI:

npx tsc --noEmit

This catches type errors before they become runtime surprises.

Tests pass locally but fail in CI

Check for hardcoded URLs or ports. Use baseURL in the config and webServer to start your app automatically. Also confirm that retries is set for CI, since slower CI machines can cause timing-sensitive tests to fail on the first attempt.

Missing await causes confusing failures

Every Playwright action is async. A missing await leads to race conditions that are painful to track down. Enable the @typescript-eslint/no-floating-promises ESLint rule to catch these at lint time.

Next steps

The project is set up and running in CI. From here, write tests against your own app. Start with the most critical user flow: login, checkout, or signup. Whatever breaks and costs the most time.

For more on locators and assertions, see Playwright's writing tests guide. The trace viewer documentation is also worth reading before you need it for the first time in a real CI failure.