Selector Best Practices
This guide provides tips and best practices for creating and maintaining reliable selectors in TrueAssert tests.
Overview
Good selectors are the foundation of reliable automated tests. This guide covers:
What makes a selector good or bad
How to improve selector quality
Best practices for different scenarios
Common pitfalls to avoid
The Golden Rule
Stability Over Simplicity: A slightly longer but stable selector is better than a short but brittle one.
Selector Quality Hierarchy
Tier 1: Excellent (Use These)
ID Attributes:
//button[@id='submit']✅ Unique and stable
✅ Rarely changes
✅ Fast to locate
✅ Best possible selector
Data-TestID Attributes:
//div[@data-testid='user-menu']✅ Explicit test contract
✅ Designed for testing
✅ Stable across UI changes
✅ Second best option
Tier 2: Very Good (Good Choice)
ARIA Attributes:
//button[@role='button' and @aria-label='Submit']✅ Semantic and accessible
✅ User-facing meaning
✅ Relatively stable
⚠️ May change with accessibility improvements
Form Attributes:
//input[@name='email']
//input[@type='submit']✅ Stable for form elements
✅ Semantic meaning
✅ Good for forms
⚠️ Only works for form elements
Tier 3: Acceptable (Use When Needed)
Stable Content Attributes:
//a[@href='/login']
//img[@alt='Logo']✅ Stable when attribute value is meaningful
⚠️ Can change if content changes
⚠️ Context-dependent
Text Content:
//button[normalize-space()='Login']✅ Works when text is unique and stable
⚠️ Breaks with i18n (translations)
⚠️ Breaks if text changes
⚠️ Avoid for very long text
Tier 4: Avoid When Possible
CSS Classes:
//div[contains(@class, 'container')]⚠️ Styling-coupled (breaks with design changes)
⚠️ Dynamic classes filtered out (e.g.,
css-abc123)⚠️ Can be unstable
✅ OK for single-word, semantic classes
Tier 5: Last Resort
Structural Positioning:
//div[1]/div[2]/button[1]❌ Very brittle
❌ Breaks with any DOM changes
❌ Only use when no other option
✅ Algorithm uses this only as fallback
Best Practices
For Developers (Improving Your App)
Add IDs to Important Elements:
<button id="submit-button">Submit</button>Makes selectors excellent
Zero maintenance for tests
Use data-testid for Test Elements:
<div data-testid="user-menu">...</div>Explicit test contract
Won't break with styling changes
Add ARIA Labels:
<button role="button" aria-label="Submit form">Submit</button>Good for accessibility
Bonus: Better selectors
Avoid Dynamic Classes:
<!-- Bad --> <div class="css-abc123 ember-456">...</div> <!-- Good --> <div class="container primary">...</div>
For Test Creators
Review Generated Selectors:
Check test detail page
Verify selectors use good attributes
Update if needed
Prefer Recording:
Recording generates better selectors
Algorithm optimizes automatically
Less manual work
Use Alternative Selectors:
If primary selector breaks
Try alternative selectors
Save alternatives to repository
Update When Needed:
Fix selectors if page changes
Don't wait for failures
Proactive maintenance
Common Scenarios
Scenario 1: Element Has ID
Best Practice: Use the ID directly
//button[@id='submit']Why: Fastest, most stable, best quality
Scenario 2: Element Has data-testid
Best Practice: Use data-testid
//div[@data-testid='user-menu']Why: Explicit test contract, very stable
Scenario 3: Element Has ARIA
Best Practice: Use ARIA attributes
//button[@role='button' and @aria-label='Submit']Why: Semantic, user-facing, relatively stable
Scenario 4: Form Element
Best Practice: Use form attributes
//input[@name='email']
//input[@type='submit']Why: Stable for forms, semantic meaning
Scenario 5: No Good Attributes
Best Practice: Use parent anchor
//div[@id='form-container']//button[@type='submit']Why: Parent provides stability, child provides specificity
Scenario 6: Multiple Similar Elements
Best Practice: Add positional index
(//button[@aria-label='Submit'])[2]Why: Makes selector unique when multiple match
Avoiding Common Pitfalls
Pitfall 1: Using CSS Classes
Problem: Classes change with styling updates
Bad:
//button[contains(@class, 'btn-primary')]Better:
//button[@id='submit']
//button[@data-testid='submit-button']Pitfall 2: Relying on Text Content
Problem: Text changes with translations or content updates
Bad:
//button[normalize-space()='Submit Form']Better:
//button[@id='submit']
//button[@aria-label='Submit form']Pitfall 3: Structural Only
Problem: Position changes break selector
Bad:
//div[1]/div[2]/button[1]Better:
//div[@id='container']//button[@id='submit']Pitfall 4: Dynamic IDs
Problem: IDs that change (e.g., id-12345)
Bad:
//div[@id='item-12345']Better:
//div[@data-testid='item']
//div[contains(@class, 'item') and @data-index='0']Improving Selector Quality
When Recording
Click Elements with IDs: Prefer clicking elements with IDs
Use Semantic Elements: Prefer buttons over divs
Avoid Dynamic Content: Wait for content to load before interacting
Complete Actions: Finish actions completely (don't rush)
When Editing
Check Selector Quality: Review selector penalty/quality
Update to Better Attributes: Change to ID or data-testid if available
Add Parent Anchor: Use stable parent if element lacks attributes
Test Selector: Verify selector works before saving
When Debugging
Identify Issue: Understand why selector fails
Find Better Attribute: Look for ID, data-testid, ARIA
Update Selector: Change to use better attribute
Verify Fix: Test selector works
Selector Maintenance
Regular Review
Check Test Results: Review failed tests regularly
Identify Patterns: Look for common selector issues
Update Selectors: Fix selectors proactively
Document Changes: Note what changed and why
When Page Changes
Identify Affected Tests: Find tests using changed elements
Update Selectors: Fix selectors to match new structure
Test Immediately: Run tests to verify fixes
Update Documentation: Note changes if needed
Advanced Tips
Using Parent Anchors
When element lacks good attributes, use stable parent:
//div[@id='sidebar']//a[@href='/settings']Parent provides stability, child provides specificity.
Combining Attributes
Combine multiple attributes for uniqueness:
//button[@type='submit' and @aria-label='Submit' and contains(@class, 'primary')]More specific = more stable.
Positional Indexing
When multiple elements match, add index:
(//div[@class='item'])[3]Makes selector unique.
Related Topics
Understanding XPath Selectors - How selectors work
Troubleshooting Selectors - Fix selector issues
Browser Plugin Recording - Generate good selectors
Manual Test Creation - Edit selectors
Last updated