Skip to content
Learni
View all tutorials
Tests Automatisés

How to Master Playwright for Advanced E2E Tests in 2026

Lire en français

Introduction

Playwright is the go-to tool for end-to-end (E2E) tests in 2026, outshining Selenium with its reliable multi-browser support (Chromium, Firefox, WebKit) and native async API. Unlike legacy frameworks, Playwright handles flakiness natively through auto-waiting and resilient locators, perfect for complex React, Vue, or Svelte apps.

This advanced tutorial is for seniors: we go beyond basics to implement the Page Object Model (POM), custom fixtures, visual tracing, hybrid API tests, and CI/CD integration. Why it matters? 70% of production bugs are UI-related; scalable E2E tests cut regressions by 40%. With 100% working TypeScript examples, copy-paste and adapt to your Next.js or Nuxt stack. Ready to bookmark? Let's dive in step by step, from foundations to production deploys.

Prerequisites

  • Node.js 20+ installed
  • Advanced TypeScript and async/await knowledge
  • An existing project (e.g., web app in React/Vue)
  • Git and a CI runner (GitHub Actions recommended)
  • VS Code with Playwright extension

Installation and Initial Setup

terminal
mkdir playwright-advanced && cd playwright-advanced
npm init -y
npm install -D @playwright/test
npx playwright install --with-deps
npx playwright test --project=chromium

These commands initialize a Playwright project, install dependencies, and browser binaries. The --with-deps option includes system libs (e.g., libnss3 on Linux) to prevent crashes. Test immediately with --project=chromium to validate the install without config.

Advanced Playwright Configuration

The config centralizes multi-browser projects, timeouts, and reporters. We enable tracing for debugging, retries for flaky CI runs, and sharding for parallelization.

Main Configuration File

playwright.config.ts
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'], ['json', { outputFile: 'test-results.json' }]],
  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    actionTimeout: 30000,
    navigationTimeout: 30000,
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

This config enables full parallelism, CI retries, and tracing/screenshots only on failure to save disk space. The three cross-browser projects ensure complete coverage. actionTimeout prevents hangs on slow apps.

First E2E Test with Advanced Locators

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

test('ajout et validation tâches', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc');
  await page.getByRole('textbox').fill('acheter du lait');
  await page.getByRole('button', { name: 'Add #1' }).click();
  await expect(page.getByText('acheter du lait')).toBeVisible();
  await page.getByLabel('acheter du lait').check();
  await expect(page.locator('.todo-list li.completed')).toHaveCount(1);
});

This test uses role-based locators (getByRole) for resilience to DOM changes, unlike fragile CSS. Auto-waiting eliminates manual waitFor. Run with npx playwright test.

Implementing the Page Object Model (POM)

To scale, separate UI logic into POM classes. This keeps tests DRY, improves readability, and simplifies mocks.

Page Object for TodoMVC

tests/pages/TodoPage.ts
import { Page, Locator } from '@playwright/test';

export class TodoPage {
  readonly page: Page;
  readonly input: Locator;
  readonly addButton: Locator;
  readonly todos: Locator;

  constructor(page: Page) {
    this.page = page;
    this.input = page.getByRole('textbox');
    this.addButton = page.getByRole('button', { name: /Add #/ });
    this.todos = page.locator('.todo-list li');
  }

  async addTodo(text: string): Promise<void> {
    await this.input.fill(text);
    await this.addButton.click();
  }

  async toggleTodo(index: number): Promise<void> {
    await this.todos.nth(index).getByLabel(text).check();
  }

  countCompleted(): Promise<number> {
    return this.todos.filter('.completed').count();
  }
}

The POM encapsulates locators and business actions. nth(index) targets dynamically, filter() scopes selectors. Reuse across tests to avoid duplication.

Test Using the POM

tests/pom-todo.spec.ts
import { test, expect } from '@playwright/test';
import { TodoPage } from './pages/TodoPage';

test('gestion multiple tâches avec POM', async ({ page }) => {
  const todoPage = new TodoPage(page);
  await page.goto('https://demo.playwright.dev/todomvc');

  await todoPage.addTodo('lait');
  await todoPage.addTodo('pain');
  await todoPage.toggleTodo(0);

  await expect(todoPage.todos).toHaveCount(2);
  expect(await todoPage.countCompleted()).toBe(1);
});

Integrate POM for readable, maintainable tests. Async methods propagate errors cleanly. Run with npx playwright test pom-todo.spec.ts.

Custom Fixtures for Mocks and Setup

Fixtures extend worker/page fixtures for reusable setups, like API mocks or user login.

Custom Fixtures with API Mocking

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

export const test = base.extend({
  mockedUser: async ({ page }, use) => {
    await page.route('**/api/user', route => route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ id: 1, name: 'John Doe', admin: true }),
    }));
    await use({ id: 1, name: 'John Doe' });
  },
});

export { expect };

This fixture mocks an entire API via page.route(), isolating tests from the backend. use() scopes the mock to the test. Import into your specs for reuse.

Test with Fixture and Tracing

tests/api-mock.spec.ts
import { test, expect } from './fixtures';

test('accès admin avec mock', async ({ page, mockedUser }) => {
  await page.goto('https://zero.webapp.io/login');
  await page.getByLabel('Username').fill('user');
  await page.getByLabel('Password').fill('pass');
  await page.getByRole('button').click();

  const userName = page.locator('[data-testid="user-name"]');
  await expect(userName).toHaveText(mockedUser.name);
});

Combines fixture mock, login flow, and data-testid assertion. Auto-generated traces (via config) aid visual debugging with npx playwright show-trace.

CI/CD Integration with GitHub Actions

For production, shard tests and upload artifacts.

GitHub Actions Workflow

.github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        project: [chromium, firefox, webkit]
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
    - run: npm ci
    - uses: microsoft/playwright-github-action@v1
      with:
        playwright-version: '1.47.0'
        project: ${{ matrix.project }}
        shard: ${{ matrix.shard }}
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

This workflow parallelizes by browser/shard, uses the official action for deps. Uploads HTML reports for post-merge review. Adjust playwright-version as needed.

Best Practices

  • Always use role/text locators: Resilient to UI refactors.
  • Fixtures over global hooks: Better isolation and performance.
  • Tracing on-retry only: Saves 90% storage in dev.
  • Sharding in CI: Cuts run time by 70% for >100 tests.
  • Hybrid API testing: Mock endpoints for offline tests.

Common Errors to Avoid

  • Default timeouts too low: Bump to 30s for slow SPAs; use expect.poll() for async.
  • Absolute CSS locators: Prefer getByRole/text for anti-flake.
  • No CI retries: Enable 2-3 for unstable networks.
  • Skipping WebKit/Firefox: 20% Safari-specific bugs; always test cross-browser.

Next Steps

Master visual comparisons with @playwright/test pixelmatch, or integrate Cypress migration via plugins. Explore Component Testing for React/Vue.

Check out our advanced testing courses and Playwright docs.