t requires a structured approach to configuration, test organization, and execution strategy. The framework supports TypeScript natively, enabling type-safe test development.
Implementation Steps
- Initialize the Project: Use the official scaffolding command to generate configuration files and install dependencies.
- Configure Environments: Define base URLs, browser projects, and retry policies in the configuration file.
- Structure Tests: Group related scenarios using
test.describe and leverage semantic locators for robust element selection.
- Execute and Validate: Run tests locally or in CI, utilizing the built-in reporter for results analysis.
Code Implementation
The following example demonstrates a resilient checkout flow. It prioritizes accessibility-based locators (getByRole) over CSS selectors, which are prone to breaking when UI classes change. The test validates navigation, form interaction, and state transitions without manual waits.
import { test, expect } from '@playwright/test';
test.describe('Secure checkout validation', () => {
test('processes order with valid payment details', async ({ page }) => {
// Navigate to the storefront
await page.goto('/shop');
// Add item to cart using semantic role
const addToCartButton = page.getByRole('button', { name: 'Add to Cart' });
await addToCartButton.click();
// Verify cart update indicator
const cartBadge = page.locator('.cart-count');
await expect(cartBadge).toHaveText('1');
// Proceed to checkout
await page.getByRole('link', { name: 'View Cart' }).click();
await page.getByRole('button', { name: 'Proceed to Checkout' }).click();
// Auto-wait ensures the checkout form is rendered before interaction
await expect(page).toHaveURL('/checkout');
// Fill billing information
await page.locator('#billing-email').fill('engineer@codcompass.dev');
await page.locator('#billing-name').fill('Technical Lead');
// Submit order
await page.getByRole('button', { name: 'Place Order' }).click();
// Validate success state
await expect(page.locator('.order-success')).toBeVisible();
await expect(page).toHaveURL(/.*\/order-confirmation/);
});
});
Architecture Decisions
- Semantic Locators: Using
getByRole aligns tests with accessibility standards and reduces brittleness. If a button's class changes but its role remains, the test continues to pass.
- Implicit Assertions: The
expect API integrates with auto-waiting. expect(locator).toBeVisible() will retry until the element appears or the timeout expires, eliminating the need for waitForSelector.
- Isolated Contexts: Each test runs in a fresh browser context, preventing state leakage between scenarios. This ensures tests are independent and repeatable.
Pitfall Guide
Production E2E suites often degrade due to common implementation errors. The following pitfalls and fixes are derived from real-world deployment experience.
-
Pitfall: Over-reliance on CSS Selectors
- Explanation: Selectors like
.btn-primary or #submit-form break when developers refactor styles or IDs.
- Fix: Prioritize
getByRole, getByText, or getByLabel. Use data-testid attributes only when semantic locators are insufficient.
-
Pitfall: Shared State Between Tests
- Explanation: Tests that depend on the outcome of previous tests create fragile chains. If one test fails, subsequent tests fail regardless of application health.
- Fix: Ensure every test sets up its own prerequisites. Use fixtures or API calls to seed data rather than relying on UI interactions from prior tests.
-
Pitfall: Hardcoded Credentials
- Explanation: Embedding usernames and passwords in test files exposes secrets and complicates environment management.
- Fix: Store sensitive data in environment variables or
.env files. Access them via process.env and configure Playwright to load them during initialization.
-
Pitfall: Using waitForTimeout
- Explanation: Fixed delays slow down test execution and do not guarantee stability. They are a band-aid for synchronization issues.
- Fix: Trust Playwright's auto-waiting. If an action fails, investigate the underlying condition (e.g., network request, animation) and use specific waits like
waitForResponse or waitForLoadState.
-
Pitfall: Ignoring Test Isolation in CI
- Explanation: Running tests sequentially in CI increases pipeline duration and masks concurrency bugs.
- Fix: Enable
fullyParallel: true in the configuration. Ensure tests do not share global state or modify shared resources without cleanup.
-
Pitfall: Neglecting Trace Collection
- Explanation: Failing tests in CI often provide insufficient context, requiring developers to reproduce issues locally.
- Fix: Configure
trace: 'on-first-retry' to capture video, DOM snapshots, and network logs automatically. Review traces using npx playwright show-report to diagnose failures efficiently.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Small Team / MVP | Single project config with Chromium only | Minimizes setup complexity and CI resource usage | Low |
| Multi-Environment | Projects array in config for staging/production | Isolates configurations and enables targeted execution | Medium |
| High Flakiness | Enable retries + Trace Viewer | Improves pipeline reliability and reduces debugging time | Low |
| Cross-Browser Requirement | Define projects for Chromium, Firefox, WebKit | Ensures compatibility across all supported engines | Medium |
| Auth-Heavy Workflows | storageState with API-based login | Reduces test execution time by skipping UI login | Low |
Configuration Template
Copy this template to playwright.config.ts to establish a production-ready baseline. It includes parallel execution, retry policies, and multi-browser support.
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', { open: 'never' }],
['list']
],
use: {
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || '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'] },
},
],
});
Quick Start Guide
- Install: Run
npm init playwright@latest in your project root. Accept defaults for TypeScript and browser installation.
- Run Tests: Execute
npx playwright test to run the full suite. Use npx playwright test --ui for interactive debugging and test development.
- View Report: After execution, open the HTML report with
npx playwright show-report to analyze results, traces, and screenshots.
- Debug Failures: If a test fails, inspect the trace file in the report to see step-by-step execution, network activity, and DOM changes.
- Integrate CI: Add
npx playwright install --with-deps to your pipeline before running tests to ensure browser binaries are available.