| name | e2e-testing |
| description | Playwright E2E testing best practices for this project. Use when writing, modifying, or reviewing E2E tests. |
E2E Testing Best Practices (Playwright)
Setup
- Use Playwright helpers from
tests/e2e/helpers.ts
- Use
data-testid attributes for stable selectors
- Write descriptive test names that explain user behavior
- Ensure tests are isolated and can run independently
Never Use Hard-Coded Timeouts
await element.click();
await page.waitForTimeout(200);
await element.click();
await expect(otherElement).toBeVisible();
Use Playwright's Auto-Waiting
if (await element.isVisible()) {
await element.click();
}
await expect(element).toBeVisible();
await element.click();
Never Use Non-Null Assertions
const box = await element.boundingBox();
const x = box!.x;
const box = await element.boundingBox();
if (!box) {
throw new Error("Unable to locate element bounding box");
}
const x = box.x;
Don't Await Locators (They're Lazy)
const button = await page.getByTestId("submit");
const button = page.getByTestId("submit");
await expect(button).toBeVisible();
Use Consistent Assertion Patterns
expect(await element).toHaveText("text");
expect(await element.isVisible()).toBe(true);
await expect(element).toHaveText("text");
await expect(element).toBeVisible();
Prefer Semantic Selectors
Priority order:
getByRole() - Best for accessibility
getByTestId() - Best for test stability (preferred for this app)
getByText() - Good for static content
locator() with data attributes - When above don't work
- CSS selectors - Last resort
Test Isolation
- Each test should set up its own state
- Don't depend on test execution order (unless using serial mode intentionally)
- Clean up after tests in
afterEach or afterAll
Helper Functions
- Leverage existing helpers from
tests/e2e/helpers.ts
- Create new helpers for repeated workflows
- Keep helpers focused and reusable
Add Meaningful Error Context
await expect(element).toBeVisible();
await expect(element, "Component should appear after loading").toBeVisible();
Test User Behavior, Not Implementation
await expect(button).toHaveClass("bg-blue-500");
await expect(button).toBeVisible();
await expect(button).toBeEnabled();
await expect(button).toHaveText("Submit");