Skip to main content
Juliano Alves
Back to blog

Playwright E2E practices that survive real teams

3 min read
By Juliano Alves

End-to-end tests are expensive: slow, brittle if written carelessly, and yet irreplaceable for critical paths (checkout, signup, permissions). Playwright’s API and tooling make disciplined E2E realistic—if you adopt a few conventions.

Prefer role and text selectors#

await page.getByRole('button', { name: /submit payment/i }).click();
await expect(page.getByRole('heading', { name: 'Receipt' })).toBeVisible();

Avoid CSS chains tied to styling (div.flex > span:nth(3)). They break on harmless refactors.

Fixtures for auth and seed data#

Centralize login in a fixture so specs start authenticated without copy-pasting page.goto('/login') flows.

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

export const test = base.extend<{ authedPage: Page }>({
  authedPage: async ({ page }, use) => {
    await page.goto('/login');
    // ...token injection or form login
    await use(page);
  },
});

Use API seeding when possible instead of driving UI for every prerequisite—it cuts runtime and flakiness.

No arbitrary timeouts#

Replace waitForTimeout(3000) with assertions that retry: expect(locator).toBeVisible() or page.waitForResponse keyed to the network call you care about.

Parallelism and sharding#

CI should run npx playwright test --shard=1/4 across workers. Combine with fullyParallel: true in config when tests do not share mutable server state.

Traces and reports#

Enable trace: 'retain-on-failure' and upload playwright-report as a CI artifact. First step when debugging a red build on CI should be opening the trace zip—not re-running locally blindly.

Summary#

Reliable Playwright suites lean on accessible selectors, fixtures, deterministic waits, and artifacts. Treat E2E as a small, high-value layer—not a replacement for unit and integration tests.

© 2026 Juliano Alves. All rights reserved.