XPath Selectors

This guide explains how TrueAssert's intelligent XPath selector generation works and why XPath selectors are more robust than CSS selectors.

Overview

TrueAssert automatically generates XPath selectors for all recorded and AI-generated tests. These selectors are designed to be:

  • Robust: Resist breaking when UI changes

  • Unique: Match exactly one element

  • Stable: Use semantic attributes over styling

  • Intelligent: Prioritize better selectors automatically

Why XPath Over CSS?

Advantages of XPath

  1. More Expressive: Can navigate DOM structure more flexibly

  2. Better for Complex Pages: Handles nested structures better

  3. Text Matching: Can match elements by text content

  4. Positional Queries: Can find elements by position

  5. Attribute Combinations: Can combine multiple attributes easily

When CSS is Used

CSS selectors are still supported but XPath is preferred because:

  • XPath is more robust for complex UIs

  • XPath handles dynamic content better

  • XPath provides more selection options

How XPath Generation Works

The Algorithm: "Optimus-SA" (Stable Anchor)

TrueAssert uses a penalty-based algorithm that tries multiple strategies in order of quality:

  1. ID-Based (Penalty: 0) - Best

  2. Data-TestID (Penalty: 1)

  3. ARIA Attributes (Penalty: 5)

  4. Form Attributes (Penalty: 10)

  5. Text Content (Penalty: 20)

  6. Stable Attributes (Penalty: 30)

  7. CSS Classes (Penalty: 50)

  8. Structural Positioning (Penalty: 1000+) - Last resort

Strategy Execution

The algorithm:

  1. Tries each strategy in order (lowest penalty first)

  2. Generates XPath selector for that strategy

  3. Verifies selector matches exactly one element

  4. Returns first successful selector

  5. Falls back to next strategy if current fails

Selector Priority System

Priority 0: ID Selectors

Format: //*[@id='element-id']

Example: //button[@id='submit']

When Used: Element has a unique ID attribute

Quality: Best possible selector

Penalty: 0 (lowest)

Priority 1: Data-TestID

Format: //*[@data-testid='test-id']

Example: //div[@data-testid='user-menu']

When Used: Element has data-testid attribute

Quality: Excellent (explicit test contract)

Penalty: 1

Priority 5: ARIA Attributes

Format: //*[@role='button' and @aria-label='Submit']

Example: //button[@role='button' and @aria-label='Login']

When Used: Element has ARIA role and/or label

Quality: Very good (semantic, user-facing)

Penalty: 5

Note: Can also use standalone @aria-label without role

Priority 10: Form Attributes

Format: //input[@name='email'] or //input[@type='submit']

Example: //input[@name='username']

When Used: Form elements with name, type, or placeholder

Quality: Good (stable for forms)

Penalty: 10

Priority 20: Text Content

Format: //button[normalize-space()='Click Me']

Example: //a[normalize-space()='Login']

When Used: Element has unique, stable text content

Quality: Medium (can break with i18n)

Penalty: 20

Note: Avoided for very long text content

Priority 30: Stable Attributes

Format: //a[@href='/login'] or //img[@alt='Logo']

Example: //a[@href='/dashboard']

When Used: Element has stable, meaningful attributes

Quality: Medium (context-dependent)

Penalty: 30

Priority 50: CSS Classes

Format: //div[contains(@class, 'container')]

Example: //button[contains(@class, 'primary')]

When Used: Element has stable CSS classes

Quality: Lower (styling-coupled)

Penalty: 50

Note: Dynamic classes (like css-abc123) are filtered out

Priority 1000+: Structural Positioning

Format: //div[1]/div[2]/button[1]

Example: //body/div[3]/div[2]/button[1]

When Used: No stable attributes available

Quality: Lowest (brittle, breaks easily)

Penalty: 1000+

Note: Only used as last resort

Advanced Strategies

Parent-Based Selection

When element itself doesn't have good attributes, algorithm looks for stable parent:

  1. Walk up DOM tree

  2. Find parent with good attributes (ID, data-testid, ARIA)

  3. Build path: //parent[@id='container']//button

Example: //div[@id='form-container']//button[@type='submit']

Positional Indexing

When multiple elements match, add positional index:

Format: (//button[@aria-label='Submit'])[2]

Example: (//div[@class='item'])[3]

When Used: Multiple elements with same attributes

Child-Based Selection

Use stable children to identify parent:

Format: //div[.//button[@id='submit']]

Example: //form[.//input[@name='email']]

When Used: Parent needs identification via children

XPath Syntax

Basic Syntax

Element Selection:

  • //button - All buttons

  • //button[@id='submit'] - Button with ID

  • //div[@class='container'] - Div with class

Attribute Matching:

  • [@id='value'] - Exact match

  • [contains(@class, 'value')] - Partial match

  • [@role='button' and @aria-label='Submit'] - Multiple conditions

Text Matching:

  • [normalize-space()='Text'] - Exact text (normalized)

  • [contains(text(), 'Text')] - Contains text

Positional:

  • [1] - First element

  • [last()] - Last element

  • [position() > 2] - Position greater than 2

Combining Conditions

AND:

//button[@role='button' and @aria-label='Submit']

OR:

//input[@type='text' or @type='email']

Complex:

//div[@id='container' and contains(@class, 'active')]//button[@type='submit']

Special Cases

SVG Elements

SVG elements use local-name():

  • //*[local-name()='path'] - SVG path element

  • //*[local-name()='svg']//*[local-name()='circle'] - Nested SVG

Shadow DOM

Shadow DOM elements are handled specially:

  • Algorithm detects shadow boundaries

  • Generates path through shadow hosts

  • Uses shadow host as anchor point

Iframes

Iframe content requires special handling:

  • Algorithm detects iframe context

  • Generates path including iframe

  • May require iframe navigation

Selector Quality

What Makes a Good Selector

  1. Stable Attributes: Uses IDs, data-testid, ARIA

  2. Unique: Matches exactly one element

  3. Semantic: Based on meaning, not styling

  4. Resistant to Change: Won't break with minor UI updates

What Makes a Bad Selector

  1. CSS Classes: Styling changes break selectors

  2. Structural Only: Position changes break selectors

  3. Dynamic IDs: IDs that change (e.g., id-12345)

  4. Long Text: Text content that changes

Best Practices

For Developers

  1. Add IDs: Give important elements stable IDs

  2. Use data-testid: Add data-testid for test elements

  3. ARIA Labels: Use ARIA attributes for accessibility (bonus: better selectors)

  4. Avoid Dynamic Classes: Don't use randomly generated classes

For Test Creators

  1. Review Selectors: Check generated selectors in test detail

  2. Prefer Recorded: Recording generates better selectors than manual

  3. Update When Needed: Fix selectors if page changes

  4. Use Alternatives: Try alternative selectors if available

Troubleshooting

Selector Not Unique

Problem: XPath matches multiple elements

Solution: Algorithm should add positional indexing automatically

  • If not, manually add [1] or [2] etc.

  • Or use more specific parent anchor

Selector Too Brittle

Problem: Selector breaks with minor UI changes

Solution:

  1. Check if element has better attributes (ID, data-testid)

  2. Update selector to use stable attributes

  3. Consider using alternative selector

Selector Not Found

Problem: Element doesn't exist on page

Solution:

  1. Verify element actually exists

  2. Check if element loads after page load (add WAIT)

  3. Verify selector syntax is correct


← Back to Documentation | Next: Selector Best Practices →

Last updated