Saltearse al contenido

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.

• 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 APIs
await page.evaluate(() => someInternalFunction());
// Good: Simulate user actions
await page.getByRole('button', { name: 'Submit' }).click();

• 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');

• 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');
});

• 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();
});

• 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();
});

• 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();

• 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 essential
expect(await page.screenshot()).toMatchSnapshot('dashboard.png');

• 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' }),
})
);

• 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' });

• 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.