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
| Axis | Selects |
|---|---|
self | The current node |
child | Direct children |
parent | Direct parent |
descendant | All descendants (children, grandchildren, etc.) |
ancestor | All ancestors (parent, grandparent, etc.) |
following-sibling | Siblings after current node |
preceding-sibling | Siblings before current node |
following | Everything after in document order |
preceding | Everything before in document order |
attribute | Attributes of current node |
namespace | Namespace nodes |
descendant-or-self | Current node and all descendants |
ancestor-or-self | Current 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 Syntax | Shorthand |
|---|---|
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']
Related Form Elements
// All inputs in the same form-group as an error
//span[@class='error']/ancestor::div[@class='form-group']//input
Performance Considerations
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
parentoverancestorwhen you know the structure
Try It Yourself
Practice axes navigation on our nested structures sample:
Open in Playground →
Summary
| Need | Axis | Example |
|---|---|---|
| Go up one level | parent:: or .. | //input/.. |
| Go up multiple levels | ancestor:: | //input/ancestor::form |
| Next sibling | following-sibling:: | //label/following-sibling::input |
| Previous sibling | preceding-sibling:: | //input/preceding-sibling::label |
| All descendants | descendant:: 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.