Quality Engineering: Selector Stability
Priority: P0 (CRITICAL)
Web Ladder
getByRole / getByLabel > getByTestId (data-testid) > attribute CSS. Never XPath, nth-child, or generated class names.
Mobile Ladder
Accessibility id > resource-id / testTag > predicate/uiautomator > XPath. Never XPath.
Framework Map
- React/Next.js:
data-testid. React Native:testID+accessibilityLabel. - Flutter:
Semantics(identifier:)for black-box E2E;WidgetKeysstay for widget tests. - SwiftUI/UIKit:
.accessibilityIdentifier. Compose:Modifier.testTag+testTagsAsResourceId = true.
Naming
<screen>-<element>-<role>, kebab-case (e.g. checkout-submit-button).
Insertion Policy
Add ids to leaf interactive/assertable elements only, never layout wrappers. Never rename an existing id — ids are a public contract other tests depend on.
Drift Classes
rename (id changed), restructure (DOM/tree moved), i18n (visible text changed, id untouched).
Anti-Patterns
- Text selectors on translated strings, index-based locators (
nth), generated/hashed class names. - Using
accessibilityLabelas a test id (it is user-facing a11y text, not a stable identifier). - "xpath just for now" — there is no temporary XPath; use the ladder from the first commit.
References
- Selector Ladder Details
- Test-ID Naming
- Insertion Policy
- Drift Classification