March 1, 2026 9 min read

E2E Testing Automation with Playwright — A Practical Guide

After writing 20+ production E2E test suites across government systems, fintech platforms, and SaaS products, here's what actually works with Playwright — and what wastes your time.

Why Playwright Over Cypress/Selenium

Setup in 60 Seconds

npm init playwright@latest
# Creates: playwright.config.ts, tests/, .github/workflows/

# Run tests
npx playwright test

# Run with UI mode (best for debugging)
npx playwright test --ui

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

# Generate test from browser recording
npx playwright codegen https://your-app.com

The Page Object Pattern

The single most important pattern for maintainable tests. Without it, changing one selector breaks 50 tests. With it, you change one file.

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

export class LoginPage {
  constructor(private page: Page) {}

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

  async login(email: string, password: string) {
    await this.page.fill('[data-testid="email"]', email);
    await this.page.fill('[data-testid="password"]', password);
    await this.page.click('[data-testid="submit"]');
  }

  async expectError(message: string) {
    await expect(this.page.locator('.error')).toContainText(message);
  }
}

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

test('successful login', async ({ page }) => {
  const login = new LoginPage(page);
  await login.goto();
  await login.login('user@test.com', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

5 Patterns That Save Hours

1. Use data-testid, not CSS selectors

// Bad — breaks when design changes
await page.click('.btn-primary.mt-4');

// Good — stable regardless of styling
await page.click('[data-testid="submit-form"]');

2. API-first authentication

Don't log in through the UI for every test. Use API calls to get auth tokens, then inject them.

// fixtures/auth.ts
test.beforeEach(async ({ page, request }) => {
  const res = await request.post('/api/auth/login', {
    data: { email: 'test@test.com', password: 'pass' }
  });
  const { token } = await res.json();
  await page.addCookies([{
    name: 'auth_token', value: token,
    domain: 'localhost', path: '/'
  }]);
});

3. Network mocking for flaky APIs

await page.route('**/api/payments', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ success: true, id: 'mock_123' })
  });
});

4. Visual regression with screenshots

// First run creates baseline, subsequent runs compare
await expect(page).toHaveScreenshot('dashboard.png', {
  maxDiffPixels: 100
});

5. Retry flaky assertions, not flaky tests

// Bad — retries the entire test
test('...', { retries: 3 }, async () => { ... });

// Good — retries just the assertion with auto-wait
await expect(page.locator('.toast'))
  .toBeVisible({ timeout: 5000 });

CI/CD Integration (GitHub Actions)

name: E2E Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

Our Testing Checklist

  1. Smoke tests first — Can the user log in, see the dashboard, perform the core action?
  2. Happy paths second — Complete user journeys (sign up → use feature → check result)
  3. Error paths third — Invalid input, network errors, permission denied
  4. Edge cases last — Empty states, very long text, concurrent users, browser-specific bugs

This order matters. If smoke tests fail, nothing else matters. If happy paths work, you have a shippable product. Error handling and edge cases are polish.

We reduced our client's manual testing time by 70% with this approach. The test suite runs in 4 minutes on CI and catches 95% of regressions before human QA touches the build.

Need test automation for your project?

We build Playwright test suites that run in CI/CD and catch bugs before production.

Get a Free QA Assessment