JavaScript 7 min read

Playwright: Modern End-to-End Testing Guide

Master Playwright for browser automation and testing. Learn selectors, assertions, API testing, visual testing, and build reliable E2E test suites.

MR

Moshiour Rahman

· Updated May 9, 2026

What is Playwright?

Playwright is a modern end-to-end testing framework by Microsoft. It supports all major browsers, provides powerful automation APIs, and enables reliable, fast testing.

Playwright Features

FeatureDescription
Cross-browserChrome, Firefox, Safari
Auto-waitReliable element detection
NetworkIntercept and mock
MobileDevice emulation
VisualScreenshot comparison

Getting Started

Installation

# Install Playwright
npm init playwright@latest

# Or add to existing project
npm install -D @playwright/test
npx playwright install

Configuration

// 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',

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  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'] },
    },
  ],

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

Writing Tests

Basic Test Structure

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

test.describe('Homepage', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('should display welcome message', async ({ page }) => {
    await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
  });

  test('should navigate to about page', async ({ page }) => {
    await page.click('text=About');
    await expect(page).toHaveURL('/about');
  });
});

Selectors

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

test('selector examples', async ({ page }) => {
  await page.goto('/');

  // Role-based selectors (recommended)
  await page.getByRole('button', { name: 'Submit' }).click();
  await page.getByRole('link', { name: 'Home' }).click();
  await page.getByRole('textbox', { name: 'Email' }).fill('test@example.com');
  await page.getByRole('checkbox', { name: 'Accept terms' }).check();

  // Text selectors
  await page.getByText('Welcome').click();
  await page.getByText(/hello/i).click();  // Regex

  // Label and placeholder
  await page.getByLabel('Username').fill('john');
  await page.getByPlaceholder('Enter email').fill('john@example.com');

  // Test ID
  await page.getByTestId('submit-button').click();

  // CSS selectors
  await page.locator('.my-class').click();
  await page.locator('#my-id').click();
  await page.locator('[data-test="value"]').click();

  // XPath
  await page.locator('xpath=//button').click();

  // Chaining
  await page.locator('.card').filter({ hasText: 'Product' }).click();
  await page.locator('.list').locator('.item').first().click();
});

Assertions

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

test('assertion examples', async ({ page }) => {
  await page.goto('/');

  // Visibility
  await expect(page.getByText('Hello')).toBeVisible();
  await expect(page.getByText('Hidden')).toBeHidden();

  // Text content
  await expect(page.locator('h1')).toHaveText('Welcome');
  await expect(page.locator('h1')).toContainText('Welc');

  // Attributes
  await expect(page.locator('input')).toHaveAttribute('type', 'email');
  await expect(page.locator('input')).toHaveValue('test@example.com');

  // Count
  await expect(page.locator('.item')).toHaveCount(5);

  // URL and title
  await expect(page).toHaveURL('/dashboard');
  await expect(page).toHaveTitle('Dashboard');

  // CSS
  await expect(page.locator('button')).toHaveCSS('color', 'rgb(255, 0, 0)');

  // State
  await expect(page.getByRole('button')).toBeEnabled();
  await expect(page.getByRole('button')).toBeDisabled();
  await expect(page.getByRole('checkbox')).toBeChecked();

  // Negation
  await expect(page.getByText('Error')).not.toBeVisible();
});

Form Interactions

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

test('form interactions', async ({ page }) => {
  await page.goto('/form');

  // Text input
  await page.getByLabel('Name').fill('John Doe');
  await page.getByLabel('Name').clear();
  await page.getByLabel('Name').type('Jane Doe', { delay: 100 });

  // Select dropdown
  await page.getByLabel('Country').selectOption('US');
  await page.getByLabel('Country').selectOption({ label: 'United States' });

  // Multi-select
  await page.getByLabel('Skills').selectOption(['javascript', 'python']);

  // Checkbox and radio
  await page.getByRole('checkbox', { name: 'Newsletter' }).check();
  await page.getByRole('checkbox', { name: 'Newsletter' }).uncheck();
  await page.getByRole('radio', { name: 'Male' }).check();

  // File upload
  await page.getByLabel('Upload').setInputFiles('path/to/file.pdf');
  await page.getByLabel('Upload').setInputFiles(['file1.pdf', 'file2.pdf']);

  // Submit
  await page.getByRole('button', { name: 'Submit' }).click();

  // Verify success
  await expect(page.getByText('Form submitted')).toBeVisible();
});
import { test, expect } from '@playwright/test';

test('navigation and waits', async ({ page }) => {
  // Navigate
  await page.goto('https://example.com');
  await page.goto('/dashboard', { waitUntil: 'networkidle' });

  // Click with navigation
  await Promise.all([
    page.waitForNavigation(),
    page.click('text=Next Page')
  ]);

  // Wait for element
  await page.waitForSelector('.loading', { state: 'hidden' });
  await page.waitForSelector('.content', { state: 'visible' });

  // Wait for response
  const response = await page.waitForResponse('**/api/users');
  const data = await response.json();

  // Wait for request
  const request = await page.waitForRequest('**/api/submit');

  // Wait for function
  await page.waitForFunction(() => {
    return document.querySelector('.count')?.textContent === '10';
  });

  // Wait for timeout (avoid if possible)
  await page.waitForTimeout(1000);

  // Wait for load state
  await page.waitForLoadState('domcontentloaded');
  await page.waitForLoadState('networkidle');
});

API Testing

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

test.describe('API Tests', () => {
  test('GET request', async ({ request }) => {
    const response = await request.get('/api/users');

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

    const users = await response.json();
    expect(users).toHaveLength(10);
  });

  test('POST request', async ({ request }) => {
    const response = await request.post('/api/users', {
      data: {
        name: 'John Doe',
        email: 'john@example.com'
      }
    });

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

    const user = await response.json();
    expect(user.name).toBe('John Doe');
  });

  test('with headers', async ({ request }) => {
    const response = await request.get('/api/protected', {
      headers: {
        Authorization: 'Bearer token123'
      }
    });

    expect(response.ok()).toBeTruthy();
  });
});

Mocking Network

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

test('mock API response', async ({ page }) => {
  // Mock response
  await page.route('**/api/users', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Mock User' }
      ])
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Mock User')).toBeVisible();
});

test('modify response', async ({ page }) => {
  await page.route('**/api/users', async route => {
    const response = await route.fetch();
    const json = await response.json();

    // Modify response
    json.push({ id: 999, name: 'Added User' });

    await route.fulfill({
      response,
      body: JSON.stringify(json)
    });
  });

  await page.goto('/users');
});

test('block requests', async ({ page }) => {
  // Block images
  await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

  // Block analytics
  await page.route('**/analytics/**', route => route.abort());

  await page.goto('/');
});

Visual Testing

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

test('visual comparison', async ({ page }) => {
  await page.goto('/');

  // Full page screenshot
  await expect(page).toHaveScreenshot('homepage.png');

  // Element screenshot
  await expect(page.locator('.hero')).toHaveScreenshot('hero.png');

  // With options
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixels: 100,
    threshold: 0.2,
    animations: 'disabled'
  });
});

test('take screenshot', async ({ page }) => {
  await page.goto('/');

  // Save screenshot
  await page.screenshot({ path: 'screenshot.png' });

  // Full page
  await page.screenshot({ path: 'full.png', fullPage: true });

  // Specific element
  await page.locator('.card').screenshot({ path: 'card.png' });
});

Authentication

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

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

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page.getByText('Dashboard')).toBeVisible();

  // Save auth state
  await page.context().storageState({ path: authFile });
});
// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      dependencies: ['setup'],
      use: {
        storageState: 'playwright/.auth/user.json',
      },
    },
  ],
});

Page Object Model

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

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign in' });
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

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

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

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

Running Tests

# Run all tests
npx playwright test

# Run specific file
npx playwright test login.spec.ts

# Run specific test
npx playwright test -g "should login"

# Run in headed mode
npx playwright test --headed

# Run specific browser
npx playwright test --project=chromium

# Debug mode
npx playwright test --debug

# Show report
npx playwright show-report

# Update snapshots
npx playwright test --update-snapshots

Summary

FeatureUsage
SelectorsgetByRole(), getByText()
Assertionsexpect() with matchers
Navigationgoto(), waitFor*()
API Testingrequest.get(), request.post()
Mockingpage.route()
VisualtoHaveScreenshot()
AuthstorageState

Playwright provides reliable, cross-browser end-to-end testing for modern web applications.

Is your AI API down right now? Check live status for OpenAI, Claude, Gemini, Groq, and more.
Check status →
MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.