Core Tools intermediate axes navigation

XPath Axes Navigation

Master the 13 XPath axes to navigate relationships between elements in any direction.

XPath Axes Navigation

XPath axes define relationships between nodes, allowing you to navigate the DOM in any direction. Understanding axes unlocks powerful element selection strategies.

What Are Axes?

An axis specifies the direction of navigation from the current (context) node:

axis::node-test[predicate]

Think of axes as “directions” you can travel from any element.

The 13 XPath Axes

AxisSelects
selfThe current node
childDirect children
parentDirect parent
descendantAll descendants (children, grandchildren, etc.)
ancestorAll ancestors (parent, grandparent, etc.)
following-siblingSiblings after current node
preceding-siblingSiblings before current node
followingEverything after in document order
precedingEverything before in document order
attributeAttributes of current node
namespaceNamespace nodes
descendant-or-selfCurrent node and all descendants
ancestor-or-selfCurrent node and all ancestors

Most Useful Axes for Testing

1. following-sibling

Select siblings that come after the current element:

// Label → Input relationship
//label[text()='Email']/following-sibling::input

// First sibling of specific type
//h2[text()='Products']/following-sibling::ul[1]

// Any following sibling
//tr[@class='header']/following-sibling::tr

2. preceding-sibling

Select siblings that come before:

// Get label before an input
//input[@id='email']/preceding-sibling::label

// Count preceding items
//li[text()='Current']/preceding-sibling::li

3. parent

Navigate up one level:

// Get parent container
//input[@name='email']/parent::div

// Shorthand syntax
//input[@name='email']/..

// Parent with specific attribute
//button[@type='submit']/parent::form

4. ancestor

Navigate up any number of levels:

// Find containing form
//input[@name='email']/ancestor::form

// Find specific ancestor
//td[text()='Total']/ancestor::table

// Nearest ancestor with class
//span[@class='error']/ancestor::div[@class='form-group'][1]

5. descendant

All nested elements (shorthand is //):

// All inputs in form
//form[@id='login']/descendant::input
// Same as:
//form[@id='login']//input

// Deep nested element
//div[@id='app']/descendant::button[@type='submit']

Practical Examples

Form Label-Input Relationships

<div class="field">
  <label>Username</label>
  <input type="text" name="username" />
</div>
// From label to input
//label[text()='Username']/following-sibling::input

// From input to label
//input[@name='username']/preceding-sibling::label

// Get parent field container
//label[text()='Username']/parent::div

Table Navigation

<table>
  <tr><th>Name</th><th>Price</th></tr>
  <tr><td>Product A</td><td>$10</td></tr>
  <tr><td>Product B</td><td>$20</td></tr>
</table>
// Get price for specific product
//td[text()='Product A']/following-sibling::td

// Get all data rows (skip header)
//tr[th]/following-sibling::tr

// Find row containing specific cell
//td[text()='$20']/parent::tr

Finding Context Elements

<section class="products">
  <h2>Featured</h2>
  <div class="product">...</div>
  <div class="product">...</div>
</section>
// Products under "Featured" heading
//h2[text()='Featured']/following-sibling::div[@class='product']

// Section containing specific product
//div[@class='product'][1]/ancestor::section

Error Message to Field

<div class="form-group">
  <input name="email" />
  <span class="error">Invalid email format</span>
</div>
// Find input related to error
//span[contains(text(), 'Invalid email')]/preceding-sibling::input

// Or via parent
//span[contains(text(), 'Invalid email')]/parent::div//input

Axis Shortcuts

Some axes have abbreviated syntax:

Full SyntaxShorthand
child::(default, no prefix)
attribute::@
self::node().
parent::node()..
descendant-or-self::node()///
// These are equivalent:
//div/child::span
//div/span

//input/attribute::type
//input/@type

//input/parent::form
//input/../form

Combining Axes

Chain multiple axes for complex navigation:

// From button, up to form, down to error message
//button[@type='submit']/ancestor::form//span[@class='error']

// Find next section's first paragraph
//section[@id='intro']/following-sibling::section[1]//p[1]

// Get the table containing a specific cell
//td[text()='Total']/ancestor::table/preceding-sibling::h2

Common Patterns

Dynamic Table Cell Selection

// Find cell in "Price" column for row containing "iPhone"
//tr[td[1][text()='iPhone']]/td[
  count(//th[text()='Price']/preceding-sibling::th) + 1
]

Accordion/Expandable Content

// Content panel for specific accordion header
//button[text()='FAQ Item 1']/following-sibling::div[1]

// Or if nested differently
//div[contains(@class, 'accordion-item')][.//button[text()='FAQ Item 1']]//div[@class='content']
// All inputs in the same form-group as an error
//span[@class='error']/ancestor::div[@class='form-group']//input

Performance Considerations

Note on Performance:

Axes like ancestor, preceding, and following can be slow on large documents as they may traverse many nodes. For better performance:

  • Limit scope with predicates
  • Use [1] to stop at first match when possible
  • Prefer parent over ancestor when you know the structure

Try It Yourself

Practice axes navigation on our nested structures sample:

Open in Playground →

Summary

NeedAxisExample
Go up one levelparent:: or ..//input/..
Go up multiple levelsancestor:://input/ancestor::form
Next siblingfollowing-sibling:://label/following-sibling::input
Previous siblingpreceding-sibling:://input/preceding-sibling::label
All descendantsdescendant:: or ////form//input

Key insight: Axes are bidirectional—you can go up (parent, ancestor), down (child, descendant), or sideways (sibling) from any element. This makes XPath more flexible than CSS selectors.