TypeScript August 12, 2026 16 min read

Playwright TypeScript Tutorial 2026: Complete Guide from Setup to CI/CD

TypeScript has become the default language for Playwright test automation in 2026. This tutorial walks you through every stage — from initial project setup and your first typed test to advanced patterns like Page Object Model, custom fixtures, API testing, and CI/CD pipelines with GitHub Actions. Real code examples throughout.

TS

TypeScript is the best language for Playwright in 2026

Type safety, IDE autocomplete, safer refactoring, and first-class support from the Playwright team. If you're starting a new Playwright project, TypeScript is the clear choice.

Playwright supports JavaScript, TypeScript, Python, Java, and .NET. But in 2026, TypeScript is the default. The Playwright team writes Playwright itself in TypeScript, the official docs show TypeScript examples first, and the Claude AI MCP Server integration requires TypeScript. If you want the best developer experience, the strongest tooling, and the most maintainable test suites, this is the language to use.

This Playwright TypeScript tutorial covers everything from zero to production-ready CI/CD. Whether you are learning Playwright for the first time or migrating existing JavaScript tests to TypeScript, this guide gives you the patterns and code you need.


Why TypeScript for Playwright?

Before writing a single test, it is worth understanding why TypeScript matters for test automation specifically — not just for application development.

Type Safety Catches Bugs Before They Run

In JavaScript, you can pass a number where a string is expected, misspell a method name, or forget to await an async call — and the error only appears at runtime, often as a confusing failure in CI. TypeScript catches all of these at compile time, in your editor, before you run the test.

JavaScript — silent runtime error
// No error in editor — fails at runtime
await page.getByRole('buton', { name: 'Submit' }); // typo: 'buton'
await page.goto(123); // wrong type: number instead of string
TypeScript — caught instantly in IDE
// TypeScript error: Argument of type '"buton"' is not assignable
// to parameter of type AriaRole
await page.getByRole('buton', { name: 'Submit' }); // red squiggly line

// TypeScript error: Argument of type 'number' is not assignable
// to parameter of type 'string'
await page.goto(123); // caught before you save

Autocomplete for Every Playwright API

TypeScript enables your IDE (VS Code, WebStorm, Cursor) to show every available method, parameter, and option as you type. When you type page., you see the full list of 80+ methods with descriptions. When you type getByRole(', you get a dropdown of valid ARIA roles. This eliminates documentation lookups and reduces typos to near zero.

Safe Refactoring

When you rename a Page Object method or change a test data interface, TypeScript updates every reference across your entire test suite — or shows you exactly where the breaking changes are. In a JavaScript codebase with 500 tests, a renamed method might silently break 30 tests that only fail when run. In TypeScript, you see all 30 errors before you commit.

Industry Standard in 2026

TypeScript adoption in the testing community has grown from roughly 40% in 2023 to over 75% in 2026. Job postings for QA Automation Engineers now overwhelmingly list TypeScript as a required skill. Learning Playwright with TypeScript positions you for the market as it exists today.


Project Setup

Setting up a Playwright TypeScript project takes one command. Playwright's initializer generates everything: config file, example tests, TypeScript configuration, and GitHub Actions workflow.

Step 1: Initialize the Project

Terminal
# Create a new directory and initialize Playwright
mkdir my-playwright-project
cd my-playwright-project
npm init playwright@latest

The initializer asks you a few questions. Choose these options for a TypeScript setup:

  • TypeScript or JavaScript? — Select TypeScript
  • Where to put end-to-end tests?tests (default)
  • Add a GitHub Actions workflow? — Yes
  • Install Playwright browsers? — Yes

Step 2: Generated Folder Structure

After initialization, your project looks like this:

Project Structure
my-playwright-project/
  playwright.config.ts      # Playwright configuration (TypeScript)
  tsconfig.json             # TypeScript compiler options
  package.json              # Dependencies
  tests/
    example.spec.ts         # Example test file
  tests-examples/
    demo-todo-app.spec.ts   # Full example: TodoMVC tests
  .github/
    workflows/
      playwright.yml        # GitHub Actions CI workflow

Step 3: tsconfig.json Configuration

The generated tsconfig.json works out of the box. Here is the recommended configuration with explanations:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",           // top-level await, modern syntax
    "module": "NodeNext",         // ESM-compatible module resolution
    "moduleResolution": "NodeNext",
    "strict": true,                // enable all strict type checks
    "esModuleInterop": true,       // allow default imports from CJS modules
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,           // faster compilation, skip .d.ts checks
    "baseUrl": ".",                // enables clean imports: 'pages/login'
    "paths": {
      "@pages/*": ["pages/*"],     // path alias for Page Objects
      "@fixtures/*": ["fixtures/*"] // path alias for custom fixtures
    }
  },
  "include": ["tests/**/*.ts", "pages/**/*.ts", "fixtures/**/*.ts", "playwright.config.ts"]
}

VS Code Extensions

Install these two extensions for the best Playwright TypeScript developer experience:

  • Playwright Test for VS Code (ms-playwright.playwright) — run, debug, and record tests directly from the editor. Includes the Test Explorer sidebar and pick-locator tool.
  • TypeScript Nightly (ms-vscode.vscode-typescript-next) — latest TypeScript language features and faster IntelliSense.

Tip: Use the VS Code command palette (Cmd+Shift+P) and run "Playwright: Install Browsers" to install browser binaries directly from the editor.


Writing Your First TypeScript Test

Let us write a complete Playwright TypeScript example test. This test navigates to a page, fills a form, submits it, and verifies the result.

tests/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Login Page', () => {

  test('successful login redirects to dashboard', async ({ page }) => {
    // Navigate to the login page
    await page.goto('https://app.example.com/login');

    // Fill in credentials using semantic locators
    await page.getByLabel('Email address').fill('user@example.com');
    await page.getByLabel('Password').fill('SecurePass123!');

    // Click the submit button
    await page.getByRole('button', { name: 'Sign In' }).click();

    // Assert: URL changed to dashboard
    await expect(page).toHaveURL(/dashboard/);

    // Assert: welcome heading is visible
    await expect(
      page.getByRole('heading', { name: 'Welcome back' })
    ).toBeVisible();
  });

  test('invalid credentials show error message', async ({ page }) => {
    await page.goto('https://app.example.com/login');

    await page.getByLabel('Email address').fill('wrong@example.com');
    await page.getByLabel('Password').fill('WrongPassword');
    await page.getByRole('button', { name: 'Sign In' }).click();

    // Assert: error message appears
    await expect(
      page.getByText('Invalid email or password')
    ).toBeVisible();

    // Assert: still on login page
    await expect(page).toHaveURL(/login/);
  });

});

Run this test with:

Terminal
npx playwright test tests/login.spec.ts

Notice that you did not need to compile TypeScript first. Playwright's test runner handles TypeScript transpilation internally using SWC — it is fast and requires zero configuration.

TypeScript-Specific Syntax Explained

Several elements in the test above are TypeScript-specific:

  • async ({ page }) — Destructured parameter with type inference. TypeScript knows page is of type Page because it comes from the test fixture.
  • await — TypeScript enforces that you await Playwright's promises. If you forget, TypeScript shows a warning (with strict: true).
  • getByRole('button', ...) — The first argument is typed as AriaRole, so TypeScript only accepts valid ARIA role strings.

TypeScript-Specific Features for Playwright

TypeScript brings several powerful features that make Playwright tests more robust and maintainable.

Interfaces for Test Data

Define interfaces for your test data to ensure consistency across tests:

types/test-data.ts
export interface UserCredentials {
  email: string;
  password: string;
}

export interface Product {
  id: number;
  name: string;
  price: number;
  category: ProductCategory;
}

export enum ProductCategory {
  Electronics = 'electronics',
  Clothing = 'clothing',
  Books = 'books',
  Home = 'home',
}

export interface CheckoutData {
  user: UserCredentials;
  products: Product[];
  shippingAddress: {
    street: string;
    city: string;
    zipCode: string;
    country: string;
  };
}

Now your tests get autocomplete for every field, and TypeScript prevents you from passing invalid data:

TypeScript
import { UserCredentials } from '../types/test-data';

const validUser: UserCredentials = {
  email: 'test@example.com',
  password: 'SecurePass123!',
};

const invalidUser: UserCredentials = {
  email: 'test@example.com',
  pasword: 'typo', // TypeScript error: 'pasword' does not exist
};

Enums for Test States

Use enums to represent states and avoid magic strings scattered across tests:

TypeScript
export enum OrderStatus {
  Pending = 'pending',
  Processing = 'processing',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled',
}

// In your test — type-safe, no typos possible
await expect(page.getByTestId('order-status'))
  .toHaveText(OrderStatus.Shipped);

Type Inference with Locators

Playwright's locator methods return typed Locator objects. TypeScript infers the type automatically, giving you autocomplete for every subsequent method call:

TypeScript
// TypeScript infers: submitButton is of type Locator
const submitButton = page.getByRole('button', { name: 'Submit' });

// Full autocomplete: .click(), .fill(), .isVisible(), etc.
await submitButton.click();

// TypeScript error: Property 'clikc' does not exist on type 'Locator'
await submitButton.clikc(); // caught immediately


Page Object Model in TypeScript

The Page Object Model (POM) is the most important design pattern for maintainable Playwright test suites. TypeScript makes POM significantly better than JavaScript by enforcing typed method signatures, constructor parameters, and return types.

Defining a Page Object Class

pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';

export class LoginPage {
  // Typed locator properties
  private readonly emailInput: Locator;
  private readonly passwordInput: Locator;
  private readonly submitButton: Locator;
  private readonly errorMessage: Locator;

  // Constructor injection — receives typed Page object
  constructor(private readonly page: Page) {
    this.emailInput = page.getByLabel('Email address');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign In' });
    this.errorMessage = page.getByTestId('login-error');
  }

  // Type-safe navigation method
  async goto(): Promise<void> {
    await this.page.goto('/login');
  }

  // Accepts typed credentials
  async login(email: string, password: string): Promise<void> {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  // Returns typed string
  async getErrorMessage(): Promise<string> {
    return await this.errorMessage.textContent() ?? '';
  }
}

Using the Page Object in Tests

tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';

test('successful login', async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.goto();
  await loginPage.login('user@example.com', 'SecurePass123!');

  await expect(page).toHaveURL(/dashboard/);
});

test('shows error for invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.goto();
  await loginPage.login('wrong@example.com', 'WrongPassword');

  const error = await loginPage.getErrorMessage();
  expect(error).toContain('Invalid email or password');
});

Advanced POM: Composing Page Objects

For larger applications, page objects can reference each other. TypeScript ensures the composition is type-safe:

pages/dashboard.page.ts
import { type Page, type Locator } from '@playwright/test';

export interface DashboardStats {
  totalOrders: number;
  revenue: number;
  activeUsers: number;
}

export class DashboardPage {
  private readonly heading: Locator;

  constructor(private readonly page: Page) {
    this.heading = page.getByRole('heading', { name: 'Dashboard' });
  }

  // Returns a typed object — tests get autocomplete for stats.totalOrders
  async getStats(): Promise<DashboardStats> {
    return {
      totalOrders: Number(await this.page.getByTestId('total-orders').textContent()),
      revenue: Number(await this.page.getByTestId('revenue').textContent()),
      activeUsers: Number(await this.page.getByTestId('active-users').textContent()),
    };
  }

  async isLoaded(): Promise<boolean> {
    return await this.heading.isVisible();
  }
}

Fixtures with TypeScript

Playwright fixtures are the mechanism for injecting dependencies into your tests — authenticated pages, API clients, test data, or custom utilities. TypeScript makes fixtures type-safe through generics.

Defining Custom Fixtures

fixtures/my-fixtures.ts
import { test as base, type Page } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { DashboardPage } from '../pages/dashboard.page';

// Define the shape of your custom fixtures
interface MyFixtures {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
  authenticatedPage: Page;
}

// Extend the base test with typed fixtures
export const test = base.extend<MyFixtures>({

  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },

  dashboardPage: async ({ page }, use) => {
    const dashboardPage = new DashboardPage(page);
    await use(dashboardPage);
  },

  authenticatedPage: async ({ page }, use) => {
    // Login before the test
    await page.goto('/login');
    await page.getByLabel('Email address').fill('admin@example.com');
    await page.getByLabel('Password').fill('AdminPass123!');
    await page.getByRole('button', { name: 'Sign In' }).click();
    await page.waitForURL(/dashboard/);

    // Provide the authenticated page to the test
    await use(page);
  },

});

export { expect } from '@playwright/test';

Using Custom Fixtures in Tests

tests/dashboard.spec.ts
// Import from your fixtures file, not from @playwright/test
import { test, expect } from '../fixtures/my-fixtures';

test('dashboard shows correct stats', async ({ authenticatedPage, dashboardPage }) => {
  // authenticatedPage is already logged in
  // dashboardPage has full autocomplete for all methods

  const stats = await dashboardPage.getStats();

  // TypeScript knows stats has: totalOrders, revenue, activeUsers
  expect(stats.totalOrders).toBeGreaterThan(0);
  expect(stats.revenue).toBeGreaterThan(0);
});

Key insight: The test.extend<MyFixtures>() generic parameter is what gives TypeScript the type information. Without it, destructured fixtures like { loginPage } would be typed as any and you'd lose all autocomplete and type checking.


API Testing with TypeScript

Playwright includes a built-in API testing client. TypeScript makes API testing significantly more reliable by typing request bodies and response objects.

Defining Response Interfaces

types/api.ts
export interface ApiUser {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'viewer';
  createdAt: string;
}

export interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

export interface CreateUserPayload {
  name: string;
  email: string;
  role: 'admin' | 'user' | 'viewer';
}

Writing Typed API Tests

tests/api/users.spec.ts
import { test, expect } from '@playwright/test';
import type { ApiUser, ApiResponse, CreateUserPayload } from '../../types/api';

test.describe('Users API', () => {

  test('GET /api/users returns user list', async ({ request }) => {
    const response = await request.get('/api/users');

    expect(response.status()).toBe(200);

    // Type the JSON response
    const body = await response.json() as ApiResponse<ApiUser[]>;

    // TypeScript knows body.data is ApiUser[]
    expect(body.data.length).toBeGreaterThan(0);
    expect(body.data[0].email).toContain('@');
    expect(['admin', 'user', 'viewer']).toContain(body.data[0].role);
  });

  test('POST /api/users creates a new user', async ({ request }) => {
    // Typed payload — TypeScript enforces required fields
    const payload: CreateUserPayload = {
      name: 'Jane Doe',
      email: 'jane@example.com',
      role: 'user',
    };

    const response = await request.post('/api/users', {
      data: payload,
    });

    expect(response.status()).toBe(201);

    const body = await response.json() as ApiResponse<ApiUser>;
    expect(body.data.name).toBe('Jane Doe');
    expect(body.data.id).toBeDefined();
  });

});

Tip: Use as type assertions for API responses carefully. For production test suites, consider using a runtime validation library like Zod to validate response shapes at runtime and get TypeScript types from the same schema definition.


Configuration Deep Dive: playwright.config.ts

The Playwright TypeScript config file is where you define browsers, timeouts, base URLs, reporters, and project-level settings. TypeScript ensures every option is valid.

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

export default defineConfig({
  // Test directory
  testDir: './tests',

  // Run tests in parallel
  fullyParallel: true,

  // Fail the build on CI if test.only is left in source
  forbidOnly: !!process.env.CI,

  // Retry failed tests in CI only
  retries: process.env.CI ? 2 : 0,

  // Limit parallel workers in CI
  workers: process.env.CI ? 1 : undefined,

  // Reporter configuration
  reporter: [
    ['list'],
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
  ],

  // Shared settings for all projects
  use: {
    // Base URL for page.goto('/login') → 'https://staging.example.com/login'
    baseURL: 'https://staging.example.com',

    // Capture trace on first retry of failed test
    trace: 'on-first-retry',

    // Screenshot on failure
    screenshot: 'only-on-failure',

    // Video recording
    video: 'retain-on-failure',

    // Global timeout per action (click, fill, etc.)
    actionTimeout: 10_000,

    // Navigation timeout
    navigationTimeout: 30_000,
  },

  // Browser projects
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    // Mobile viewports
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 15'] },
    },
  ],
});

The defineConfig() function is the key to TypeScript support in the config file. It provides full type checking and autocomplete for every option. If you mistype an option name or pass an invalid value, TypeScript catches it immediately.

Config Option Purpose Recommended Value
fullyParallel Run tests in parallel within each file true
retries Retry failed tests 2 in CI, 0 locally
trace Record step-by-step DOM snapshots on-first-retry
screenshot Capture page on failure only-on-failure
video Record test execution video retain-on-failure
forbidOnly Prevent .only from reaching CI !!process.env.CI

CI/CD with TypeScript

Running Playwright TypeScript tests in CI/CD is straightforward — see our dedicated Playwright GitHub Actions CI/CD guide for the full setup. Playwright's test runner handles TypeScript transpilation, so you do not need a separate build step. However, adding a type-checking step ensures you catch type errors before tests run.

GitHub Actions Workflow

.github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Type check (catch TS errors before running tests)
        run: npx tsc --noEmit

      - name: Run Playwright tests
        run: npx playwright test

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: '!cancelled()'
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

The tsc --noEmit Step

The tsc --noEmit step is critical. Playwright's test runner uses SWC to transpile TypeScript — it strips types but does not perform type checking. This means a test with a type error will still run (and probably fail with a confusing runtime error). Adding tsc --noEmit as a separate step catches type errors with clear messages before any tests execute.

Warning: Without the tsc --noEmit step, TypeScript type errors in your tests will not be caught in CI. Playwright will transpile and run the tests regardless — you'll get cryptic runtime errors instead of clean type errors.

Parallel Execution in CI

For faster CI runs, you can shard tests across multiple jobs:

GitHub Actions — sharding
strategy:
  matrix:
    shard: [1/4, 2/4, 3/4, 4/4]

steps:
  - name: Run Playwright tests (shard ${{ matrix.shard }})
    run: npx playwright test --shard=${{ matrix.shard }}

This splits your test suite across 4 parallel jobs, reducing total CI time by up to 75%.


Common TypeScript Mistakes in Playwright

Even experienced TypeScript developers make these mistakes when writing Playwright tests. Here are the most common ones and how to avoid them.

1. Forgetting to await

Every Playwright interaction is async. Forgetting await means the action fires but your test continues without waiting for it to complete.

TypeScript
// BAD: missing await — click fires but test doesn't wait for it
page.getByRole('button', { name: 'Submit' }).click(); // no await!
await expect(page).toHaveURL(/success/); // may fail — click hasn't completed

// GOOD: always await Playwright actions
await page.getByRole('button', { name: 'Submit' }).click(); // awaited
await expect(page).toHaveURL(/success/); // works reliably

Fix: Enable the @typescript-eslint/no-floating-promises ESLint rule. It flags any un-awaited promise as an error, catching this mistake in your editor.

2. Overusing Type Assertions (as)

Type assertions (as) tell TypeScript to trust you. Overusing them defeats the purpose of type safety.

TypeScript
// BAD: asserting a type without validation
const user = await response.json() as ApiUser; // what if the API returns something else?

// BETTER: validate the shape at runtime
const body = await response.json();
expect(body).toHaveProperty('id');
expect(body).toHaveProperty('email');
const user = body as ApiUser; // now the assertion is backed by runtime checks

3. Using any to Silence Errors

When TypeScript shows an error, reaching for any is tempting — but it turns off type checking for that entire value chain.

TypeScript
// BAD: 'any' kills all type checking downstream
const data: any = await response.json();
data.naem; // no error — 'any' allows anything, typo goes undetected

// GOOD: use 'unknown' and narrow the type
const data: unknown = await response.json();
if (isApiUser(data)) {
  data.name; // TypeScript knows this is ApiUser now
}

4. Incorrect Generic Usage with Fixtures

TypeScript
// BAD: missing generic — fixtures are typed as 'any'
export const test = base.extend({
  myPage: async ({ page }, use) => { ... }, // myPage is 'any' in tests
});

// GOOD: generic parameter provides type information
export const test = base.extend<{ myPage: MyPage }>({
  myPage: async ({ page }, use) => { ... }, // myPage is MyPage in tests
});

5. Not Using strict Mode

Without "strict": true in your tsconfig.json, TypeScript is permissive — it allows implicit any, doesn't check for null, and misses many potential bugs. Always enable strict mode for Playwright projects.


Frequently Asked Questions

Do I need to know TypeScript before learning Playwright?

No. If you know JavaScript, you can start writing Playwright tests in TypeScript immediately. TypeScript adds type annotations on top of JavaScript, and Playwright's types are inferred automatically in most cases. You will learn TypeScript patterns naturally as you write tests. The Playwright + Claude AI course covers TypeScript fundamentals alongside Playwright so you can learn both together.

Is TypeScript better than JavaScript for Playwright tests?

Yes, for any project beyond a handful of tests. TypeScript catches type errors at compile time, provides autocomplete for every Playwright API, makes refactoring safe, and serves as living documentation for your test data structures. The Playwright team writes Playwright itself in TypeScript, and the official documentation defaults to TypeScript examples.

How do I configure tsconfig.json for Playwright?

Running npm init playwright@latest generates a tsconfig.json automatically. The key settings are: target ES2020 or later for top-level await support, module NodeNext for ESM compatibility, strict mode enabled for maximum type safety, and baseUrl set to your project root for clean imports.

Can I use Playwright TypeScript tests in CI/CD pipelines?

Absolutely. Playwright TypeScript tests run directly without a separate build step — the test runner handles TypeScript compilation internally. For CI/CD, add a tsc --noEmit step to catch type errors before running tests. GitHub Actions, GitLab CI, Jenkins, and Azure DevOps all support this workflow natively.

What is the Page Object Model in Playwright TypeScript?

The Page Object Model (POM) is a design pattern where each page or component gets its own TypeScript class. The class encapsulates locators and actions as typed methods. POM classes accept a Page object via constructor injection, expose async methods for user interactions, and return typed data. If a selector changes, you update one class instead of dozens of tests.

How do I create custom fixtures in Playwright with TypeScript?

Use test.extend<MyFixtures>() where MyFixtures is a TypeScript interface defining your fixture types. Each fixture is a function that receives existing fixtures and a use callback. TypeScript ensures every test that destructures your custom fixtures gets full type safety and autocomplete.

Does Playwright compile TypeScript before running tests?

Playwright uses a built-in TypeScript transformer (based on SWC) that transpiles TypeScript to JavaScript on the fly during test execution. This means you do not need a separate tsc build step to run tests. However, the transformer only strips types — it does not perform type checking. Run tsc --noEmit separately to catch type errors, ideally as a CI step before test execution.


Asim Noaman - Playwright and Claude AI course instructor

Asim Noaman

Senior QA Automation Engineer & AI Testing Specialist

With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.

Udemy Instructor Published course author
Playwright + AI Expert Specialized in AI-powered QA
Production Experience Enterprise-grade frameworks
Connect on LinkedIn