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)

  1. Add IDs to Important Elements:

    <button id="submit-button">Submit</button>
    • Makes selectors excellent

    • Zero maintenance for tests

  2. Use data-testid for Test Elements:

    <div data-testid="user-menu">...</div>
    • Explicit test contract

    • Won't break with styling changes

  3. Add ARIA Labels:

    <button role="button" aria-label="Submit form">Submit</button>
    • Good for accessibility

    • Bonus: Better selectors

  4. Avoid Dynamic Classes:

    <!-- Bad -->
    <div class="css-abc123 ember-456">...</div>
    
    <!-- Good -->
    <div class="container primary">...</div>

For Test Creators

  1. Review Generated Selectors:

    • Check test detail page

    • Verify selectors use good attributes

    • Update if needed

  2. Prefer Recording:

    • Recording generates better selectors

    • Algorithm optimizes automatically

    • Less manual work

  3. Use Alternative Selectors:

    • If primary selector breaks

    • Try alternative selectors

    • Save alternatives to repository

  4. 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

  1. Click Elements with IDs: Prefer clicking elements with IDs

  2. Use Semantic Elements: Prefer buttons over divs

  3. Avoid Dynamic Content: Wait for content to load before interacting

  4. Complete Actions: Finish actions completely (don't rush)

When Editing

  1. Check Selector Quality: Review selector penalty/quality

  2. Update to Better Attributes: Change to ID or data-testid if available

  3. Add Parent Anchor: Use stable parent if element lacks attributes

  4. Test Selector: Verify selector works before saving

When Debugging

  1. Identify Issue: Understand why selector fails

  2. Find Better Attribute: Look for ID, data-testid, ARIA

  3. Update Selector: Change to use better attribute

  4. Verify Fix: Test selector works

Selector Maintenance

Regular Review

  1. Check Test Results: Review failed tests regularly

  2. Identify Patterns: Look for common selector issues

  3. Update Selectors: Fix selectors proactively

  4. Document Changes: Note what changed and why

When Page Changes

  1. Identify Affected Tests: Find tests using changed elements

  2. Update Selectors: Fix selectors to match new structure

  3. Test Immediately: Run tests to verify fixes

  4. 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.


← Back to Documentation | Next: Troubleshooting Selectors →

Last updated