Skip to content

Best Selectors for Cypress Tests

Choosing consistently good selectors across an entire test suite matters more than any individual technique. This lesson lays out a priority order for selector choices and a naming convention worth adopting team-wide.

The Selector Priority Order

Cypress's own "Best Practices" guide recommends a specific order of preference when choosing a selector: dedicated data attributes first, then semantic HTML/ARIA attributes, then stable content, and CSS classes or tag names only as a last resort.

Following a consistent order across a team means anyone reading a test can predict what kind of selector to expect, and reviewers can flag selectors that fall too far down the priority list during code review.

// Best: dedicated test attribute
cy.get('[data-cy="add-to-cart"]');

// Good fallback: semantic ARIA role
cy.get('[role="button"][aria-label="Add to cart"]');

// Avoid: implementation-coupled class
cy.get('.css-1a2b3c.btn-primary');

Each of these might target the same button, but they differ enormously in how likely they are to survive an unrelated refactor.

Recommended Priority Order

1. data-cy / data-testid attributes
2. Semantic HTML and ARIA roles/labels
3. Stable, meaningful text content (cy.contains)
4. Non-generated, semantic CSS classes
5. Tag names and structural selectors (last resort)
  • Dedicated test attributes should be your default choice whenever available.
  • ARIA-based selectors double as a lightweight accessibility check.
  • Avoid selectors coupled to visual styling classes, especially auto-generated ones.
  • Never rely on deeply nested structural selectors like div > div > span:nth-child(2).

Selector Naming Convention Cheat Sheet

A consistent data-cy naming pattern used across many real-world Cypress suites.

Pattern Example Use For
feature-element data-cy="login-submit" A single, unique element
feature-element-{id} data-cy="cart-item-42" A repeated item in a list
feature-state data-cy="cart-empty" A conditional/empty state
feature-error data-cy="login-error" Error/validation messaging

Building a Team-Wide Naming Convention

Individually sensible selectors can still create confusion if every developer invents their own naming style. Documenting a short, consistent convention, feature name first, then element purpose, keeps a growing suite predictable, and makes it easy to guess an existing selector's name before searching for it.

  • Agree on a single attribute name across the whole codebase (data-cy or data-testid, not both).
  • Use lowercase, hyphenated names matching your feature/component naming elsewhere.
  • Document the convention alongside your testing guidelines, not just in individual PR comments.

Auditing an Existing Suite's Selectors

When inheriting an existing Cypress suite with inconsistent or brittle selectors, it is rarely worth rewriting everything at once. Instead, replace selectors incrementally as you touch each spec file, prioritizing the flakiest or most frequently modified tests first.

// Before: coupled to styling
cy.get('.btn.btn-lg.btn-outline-primary');

// After: stable and intention-revealing
cy.get('[data-cy="continue-shopping"]');

Common Mistakes

  • Mixing data-cy and data-testid inconsistently across the same codebase.
  • Choosing selectors based on what happens to work today rather than what will remain stable after a refactor.
  • Attempting a full-suite selector rewrite in one large pass instead of improving selectors incrementally.
  • Never documenting the team's selector convention, leaving every new contributor to guess or invent their own.

Key Takeaways

  • Follow a clear selector priority order: dedicated attributes, then semantic HTML/ARIA, then stable text, then CSS classes as a last resort.
  • A consistent, documented naming convention keeps a large test suite predictable across contributors.
  • Improve existing brittle selectors incrementally rather than in one disruptive rewrite.
  • Good selector choices are a team-wide discipline, not just an individual test-writing skill.

Pro Tip

Add a lightweight lint rule or code review checklist item flagging new Cypress selectors that don't use data-cy/data-testid or a semantic ARIA attribute, catching brittle selectors in review is far cheaper than fixing flaky tests months later.