Back to Posthog

End-to-End Testing

playwright/README.md

1.43.12.3 KB
Original Source

End-to-End Testing

Running tests

Spin up a full local E2E environment (backend, frontend, docker services, Playwright UI):

bash
./bin/e2e-test-runner

This uses bin/mprocs-e2e.yaml under the hood. If you need to reset the E2E database, trigger the reset-db process in the mprocs UI.

To run tests against an already-running PostHog instance:

bash
LOGIN_USERNAME='[email protected]' LOGIN_PASSWORD="the-password" BASE_URL='http://localhost:8010' pnpm --filter=@posthog/playwright exec playwright test --ui

You might need to install Playwright first: pnpm --filter=@posthog/playwright exec playwright install

Writing tests with Claude Code

Use the /playwright-test skill to have Claude Code write and validate end-to-end tests for you. It will explore the UI with Playwright MCP tools, plan the tests, implement them, and run them in a loop until they pass reliably (including a flakiness check with --repeat-each 10).

Writing tests

Best practices

  • Don't use CSS selectors — prefer accessibility roles (getByRole) or getByTestId() which maps to data-attr in our config. Add data-attr to components if needed.
  • Write fewer, longer tests that do multiple things. Split logical steps with test.step().
  • Use page object models for common tasks and accessing common elements (see page-models/).
  • After UI interactions, assert on UI changes — don't assert on network requests resolving.
  • Never put conditional logic (if) in a test.

Gotchas

Flaky tests are almost always due to not waiting for the right thing. Consider adding a better selector, an intermediate step like waiting for URL or page title to change, or waiting for a critical network request to complete.

Loose selectors cause strict mode violations. If a selector matches multiple elements, Playwright will show all matches — use the output to narrow down:

text
Error: locator.click: Error: strict mode violation: locator('text=Set a billing limit') resolved to 2 elements:
1) <span class="LemonButton__content">Set a billing limit</span> aka getByTestId('billing-limit-input-wrapper-product_analytics').getByRole('button', { name: 'Set a billing limit' })
2) <span class="LemonButton__content">Set a billing limit</span> aka getByTestId('billing-limit-input-wrapper-session_replay').getByRole('button', { name: 'Set a billing limit' })