Best Practices for Writing E2E Tests with Playwright
Esta página aún no está disponible en tu idioma.
End-to-end (E2E) testing ensures your entire application behaves as expected from the user’s perspective. Playwright is a powerful E2E framework that allows you to simulate real-world scenarios across multiple browsers. In this post, we’ll cover best practices to make your Playwright tests reliable, readable, and maintainable.
Focus on User-Centric Behavior
Section titled “Focus on User-Centric Behavior”• E2E tests should validate real user journeys, not internal implementations.
• Best Practice: Interact with your application as a user would—by clicking buttons, filling forms, and checking visible output.
// Bad: Directly triggering internal APIsawait page.evaluate(() => someInternalFunction());
// Good: Simulate user actionsawait page.getByRole('button', { name: 'Submit' }).click();Use Locators with Accessibility in Mind
Section titled “Use Locators with Accessibility in Mind”• Accessibility-first selectors are more stable and reflect user behavior.
• Best Practice: Prefer getByRole, getByLabel, or text-based locators over CSS classes or IDs.
await page.getByRole('textbox', { name: /username/i }).fill('JohnDoe');Keep Tests Isolated and Independent
Section titled “Keep Tests Isolated and Independent”• Tests that depend on each other are fragile and harder to debug.
• Best Practice: Ensure each test runs with a clean state and doesn’t rely on the outcome of previous tests.
test.beforeEach(async ({ page }) => { await page.goto('https://your-app.com/login');});Use Fixtures for Reusable Setups
Section titled “Use Fixtures for Reusable Setups”• DRY (Don’t Repeat Yourself) makes test code easier to maintain.
• Best Practice: Abstract common logic into fixtures (e.g., login, API mocks).
test('shows dashboard after login', async ({ page }) => { await login(page); // Custom fixture await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible();});Test Realistic Scenarios
Section titled “Test Realistic Scenarios”• The purpose of E2E testing is to validate end-to-end flows as users experience them.
• Best Practice: Cover full flows like signup, login, purchasing, or form submissions.
test('user can sign up successfully', async ({ page }) => { await page.goto('/signup'); await page.fill('#email', 'user@example.com'); await page.fill('#password', 'securePass123'); await page.click('button[type=submit]'); await expect(page.locator('text=Welcome')).toBeVisible();});Handle Asynchronous Elements Gracefully
Section titled “Handle Asynchronous Elements Gracefully”• Network latency and animations can delay UI updates.
• Best Practice: Use Playwright’s auto-waiting APIs or explicit assertions like toBeVisible and waitForURL.
await page.getByRole('button', { name: 'Submit' }).click();await expect(page.getByText(/success/i)).toBeVisible();Use Visual and Snapshot Testing Sparingly
Section titled “Use Visual and Snapshot Testing Sparingly”• Snapshots and visual diffs can be brittle if overused.
• Best Practice: Focus on meaningful visual differences, like layout regressions, not minor styling.
// Only when visual stability is essentialexpect(await page.screenshot()).toMatchSnapshot('dashboard.png');Mock External Services for Stability
Section titled “Mock External Services for Stability”• External APIs may be slow or unreliable.
• Best Practice: Use Playwright’s request interception to mock APIs.
await page.route('**/api/data', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: 'mocked' }), }));Use Meaningful Test Names
Section titled “Use Meaningful Test Names”• Clear test names help identify failures quickly.
• Best Practice: Write test names as user stories or behaviors.
test('displays error message when login fails', async ({ page }) => { // Test code});Run Tests in Parallel, But Watch for State Leaks
Section titled “Run Tests in Parallel, But Watch for State Leaks”• Parallelism speeds up test suites, but shared state can cause flakiness.
• Best Practice: Isolate sessions and environments per test.
test.use({ storageState: 'state/logged-in.json' });Clean Up and Reset Between Tests
Section titled “Clean Up and Reset Between Tests”• Residual state can lead to flaky tests.
• Best Practice: Use test.afterEach to clean cookies, localStorage, or reset mocks.
test.afterEach(async ({ page }) => { await page.context().clearCookies();});Monitor Performance Without Slowing Down Tests
Section titled “Monitor Performance Without Slowing Down Tests”• E2E tests shouldn’t become performance bottlenecks.
• Best Practice: Profile performance separately; keep E2E tests fast and lean.