Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.

E2E Testing Rules

Writing end-to-end tests that stay reliable and fast: covering critical user journeys not everything, resilient role/test-id selectors, waiting on conditions instead of sleeping, test data isolation, treating flakiness as a bug, and keeping the suite runnable.

Mby @markdownersPublished August 21, 2026 · ~4 min read

0 downloads · Used by 0 stacks

End-to-end tests exist to catch the failures unit tests structurally cannot see — real browser rendering, real navigation, real integration between frontend and backend — and that value only holds if the suite stays fast and reliable enough that people actually run it and trust its failures.

Cover critical journeys, not everything

  • Write e2e tests for the handful of journeys where a break would be a genuine incident — signup, login, checkout, the core create/publish flow — not for every UI state and permutation, which belongs in unit/component tests that run faster and fail with a clearer signal.
  • Treat e2e coverage as a pyramid's thin top layer: broad logic coverage lives in unit tests, component-level interaction in component tests, and e2e verifies that the real integrated system serves the critical paths correctly end to end.
  • Resist "let's e2e test this too" for anything a faster test type can already verify — every e2e test added is suite runtime and flakiness surface added permanently.

Resilient selectors

  • Select elements by role and accessible name (getByRole('button', { name: 'Submit' })) or by a dedicated data-testid attribute — never by CSS class, DOM structure, or text content that's likely to change with a redesign or a copy edit unrelated to the behavior being tested.
  • Treat brittle selectors (nth-child, deep CSS descendant chains, auto-generated class names from CSS-in-JS) as a test smell to fix immediately, not something to patch around — they turn every unrelated markup change into a wave of test failures.
  • Prefer role-based selectors over data-testid where practical — they also verify the page is accessible, catching a real regression (missing label, wrong role) as a side effect.

No arbitrary sleeps

  • Never use a fixed sleep/wait(2000) to "give the page time to load" — wait on the actual condition (element visible, network request settled, specific text present) using the framework's built-in auto-waiting or explicit wait-for assertions.
  • A fixed sleep is either too short (flaky under load) or too long (slows the whole suite for no benefit) — waiting on a condition is both faster on the common case and correct on the slow one.
  • When a wait condition is genuinely hard to express (an animation, a debounce), wait on the resulting state change (final position, element becoming stable/interactable) rather than an elapsed duration.

Test data isolation

  • Give each test (or each test file, depending on framework parallelism) its own data — a fresh seeded account, a fresh record — rather than sharing mutable fixtures across tests, which creates order dependence: test B only passes because test A ran first and left state behind.
  • Clean up data a test creates, or run against a database that's reset between suite runs — tests that accumulate state run-over-run eventually hit unrelated failures (duplicate-key errors, unexpectedly long lists) that have nothing to do with the behavior under test.
  • Avoid tests that depend on execution order to pass — a suite must produce the same results whether run in full, in parallel, or as a single isolated test, otherwise "run just this one test to debug it" stops being a reliable technique.

Flakiness is a bug

  • Treat any intermittently failing e2e test as a bug to root-cause and fix — never as something to retry until green, skip, or quarantine indefinitely; a suite with tolerated flakiness trains everyone to ignore red CI, which is worse than not having the test.
  • When a flaky test is found, reproduce it locally (often by removing an implicit wait or slowing the network) before attempting a fix — a fix applied without reproducing the failure is usually a sleep added in the wrong place, which hides the symptom instead of curing it.
  • If a genuine retry policy is used for infrastructure-level flakiness (network blips in CI), log and track retry counts — a test that needs frequent retries to pass is still signaling a real problem even if it eventually goes green.

Keeping the suite fast enough to run

  • Run e2e tests in parallel across independent workers/browsers by default, since serial e2e execution scales linearly with suite size and quickly becomes too slow to run on every change.
  • Keep the e2e suite separate from the fast unit/lint gate that runs on every save — run it on PRs/pre-merge or on a schedule, so its necessarily higher latency doesn't punish the fast local feedback loop.
  • Periodically prune e2e tests that no longer cover a critical journey (a removed feature, a consolidated flow) — an e2e suite that only grows becomes slow enough that people stop running it locally, which defeats its purpose.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/e2e-testing-rules)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion
E2E Testing Rules — AI agent instructions — Markdowners