Learn XPath in Selenium with real examples: absolute vs relative XPath, dynamic XPath, axes, contains(), starts-with(), and Chrome tips.
Vipul Gupta
December 25, 2025
On This Page
The reliability of your Selenium test scripts depends on how consistently you can find and interact with the right web elements, even as the application changes. XPath in Selenium is the most flexible locator strategy available -- it can navigate both up and down the DOM, match elements by text, attributes, or position, and handle dynamic elements that CSS selectors and standard locators cannot reach.
This guide covers everything: what XPath is, the difference between absolute and relative XPath, how to find XPath in Chrome DevTools, how to write and validate XPath expressions, every major XPath function and axis, dynamic XPath patterns, and the most common reason XPath works in the browser but fails in Selenium code -- with the exact fix.
Overview
What Is XPath in Selenium WebDriver?
XPath, short for XML Path Language, is a query syntax used to traverse and identify nodes within XML and HTML documents. In Selenium, it pinpoints DOM elements through their attributes, hierarchical relationships, or visible text, offering bidirectional navigation that other locator strategies cannot provide.
How Do You Write XPath Expressions in Selenium?
Writing XPath begins with selecting a dependable attribute and constructing an expression precise enough to match a single element. The format //tagname[@attribute='value'] forms the foundation, and combining multiple attributes with operators like and, or, or contains() produces maintainable locators that withstand minor DOM shifts.
Which XPath Functions Are Most Useful in Selenium?
contains(): Matches elements when an attribute value partially aligns with a given string, making it useful for React, Angular, or Vue apps that generate dynamic class names with stable prefixes or suffixes.starts-with(): Targets elements whose attribute begins with a predictable prefix, ideal for auto-generated IDs that follow patterns like user_101 or user_202.text(): Locates elements based on their visible text content, well-suited for static labels, buttons, and menu items where the displayed text remains consistent.Why Teams Use TestMu AI for Scalable Selenium Automation?
XPath (XML Path Language) is a query language used to locate elements in HTML or XML documents based on their attributes, relationships, or visible text.
In Selenium automation, XPath helps you locate elements inside the Document Object Model (DOM) based on their attributes, relationships, or text content.
When an element does not have a unique ID or predictable structure, XPath gives you the flexibility to locate it using combinations of conditions. You can target an element based on partial attribute matches, relative positioning, or even its displayed text. This flexibility makes XPath one of the most commonly used and reliable Selenium locators in real-world test automation -- and the only locator that can traverse the DOM in both directions, upward to parent elements and downward to children.
Syntax:
//tagname[@attribute='value']Here is an example of using XPath to locate a button element on a webpage. The highlighted SIGN UP button has a class attribute that includes justify-center.

//button[contains(@class, 'justify-center')]This XPath selects the button element whose class attribute includes justify-center. Because it uses contains() rather than an exact match, it remains valid even if other classes are added to the element later.
XPath is important in Selenium because it handles scenarios that every other locator strategy fails on, including unstable IDs, dynamic elements, and deeply nested DOM structures.
Standard locators like ID, Name, and Class require stable, unique attributes -- which many modern web applications do not provide, especially when elements are dynamically generated or deeply nested.
text() function -- something CSS selectors cannot do at all.XPath expressions in Selenium are of two types: Absolute XPath, which traces the full path from the root, and Relative XPath, which can start from any node.
Both serve different purposes, and choosing the right one determines how resilient your locators are when the application changes.
Absolute XPath provides the complete path from the root element to the target element, following every node in the hierarchy. It is straightforward to understand but fragile in practice -- any structural change in the DOM, even adding a single new wrapper div, can break it entirely.
Syntax:
/html/body/div[1]/form/input[2]Relative XPath starts from any node within the DOM rather than the root. It uses double slashes (//) to search anywhere in the document, making it far more flexible and maintainable across real-world test suites.
Syntax:
//input[@type='email']Note: Run automation tests across 3000+ real browsers and platforms. Try TestMu AI Today!
To find XPath in Chrome, right-click the element, choose Inspect, then in DevTools right-click the highlighted node and select Copy > Copy XPath.
Chrome DevTools gives you the DOM structure of any page and lets you copy, write, and validate XPath expressions before putting them into your test code. There are three methods -- DevTools copy, console validation, and an extension.
This is the fastest way to get an XPath for any element on a page. Follow these steps on any website -- the example below uses the TestMu AI eCommerce Playground:
Note: The XPath Chrome copies automatically is always absolute -- it starts from /html/body and breaks easily. Treat it as a starting point, then convert it to a relative XPath using the element's stable attributes like ID, name, or data-testid.
Before putting any XPath into your Selenium script, always validate it first. Chrome's Console tab lets you test XPath expressions live against the current page using the $x() function. This saves significant debugging time.
How to validate:
$x('') and press Enter.// Test a relative XPath in Chrome Console
$x("//input[@name='search']")
// Returns: [input#search] if found, [] if not found
// Test with contains() for partial match
$x("//button[contains(@class, 'search-btn')]")
// Test with text() for visible text
$x("//a[text()='Login']")If the Console returns more than one element, your XPath is too broad and will cause a Selenium test to fail or interact with the wrong element. Refine the expression by adding more attributes or conditions until only one element is returned. You can also use the free TestMu AI XPath tester tool to validate expressions outside the browser.
SelectorsHub is the recommended Chrome extension for finding XPath in Selenium. It generates optimized relative XPath and CSS selectors directly in DevTools, shows how many elements match the expression on the current page, and handles Shadow DOM elements that standard DevTools cannot reach. ChroPath was the popular alternative but has been deprecated and removed from the Chrome Web Store -- SelectorsHub is the current standard replacement.
For a full list of Chrome extensions that help find XPath, see our dedicated guide on Chrome extensions to find XPath in Selenium.
To write XPath in Selenium, use the format //tagname[@attribute='value'], starting with a stable attribute and refining the expression until it matches exactly one element.
A good XPath is resilient to minor DOM changes and readable enough to maintain.
The basic structure of any XPath expression is:
XPath = //tagname[@attribute='value']Consider a login page on the TestMu AI eCommerce Playground. Inspecting the email input field in Chrome DevTools reveals:
<input type="email" name="email" placeholder="E-Mail Address" id="input-email" class="form-control" />From this, you can write several valid XPath expressions targeting this element:
// Using the ID attribute -- most stable
//input[@id='input-email']
// Using the name attribute
//input[@name='email']
// Using the type and placeholder together
//input[@type='email' and @placeholder='E-Mail Address']
// Using contains() for partial placeholder match
//input[contains(@placeholder, 'E-Mail')]Use this expression in your Selenium script to fill the email field:
WebElement emailField = driver.findElement(By.xpath("//input[@id='input-email']"));
emailField.sendKeys("[email protected]");Always validate the XPath in Chrome Console using $x("//input[@id='input-email']") before adding it to your script. If it returns exactly one element, the locator is ready to use.
Most XPath expressions in real Selenium test suites fall into five patterns. Learning these patterns covers the majority of locator scenarios you will encounter in practice.
//input[@id='input-password']//input[@type='text' and @name='username']//button[text()='Login']contains() function to match elements when attributes are only partially known or change slightly between sessions.//input[contains(@name, 'user')]//div[@class='form-container']//input[@type='email']XPath functions in Selenium handle dynamic, partial, or text-based element matching. The three most used are contains(), starts-with(), and text().
These functions let you write expressions that handle dynamic, partial, or text-based element identification across modern web applications.
contains(): Matches elements when the attribute value partially matches the given string. Ideal for React, Angular, or Vue applications where CSS class names are generated dynamically and include a consistent prefix or suffix.//input[contains(@id,'email')]starts-with(): Finds elements whose attribute value begins with a predictable prefix. Best for auto-generated IDs that follow a naming pattern like user_101, user_202, where the number at the end changes but the prefix is stable.//input[starts-with(@name,'user')]text(): Locates elements using their visible text content. Works well for static UI labels, buttons, and menu items. Be cautious with dynamic or localized text that changes between environments or locales.//a[text()='Forgot Password?']Writing stable XPath locators is the first step. The second is running those locators against real browsers at scale. TestMu AI provides a cloud-based online Selenium Grid that executes Selenium automation tests across thousands of real browser and operating system combinations on demand -- without maintaining any local infrastructure.
To get started, see the guide on Selenium testing with TestMu AI.
Selenium XPath supports three logical operators and, or, and not(), that let you combine or exclude conditions to build precise, adaptable locators.
and: Matches elements only when all conditions are true. Use when you need to combine two attributes to uniquely identify an element that would match too many elements on its own.//input[@type='email' and @name='email']or: Matches elements that satisfy either condition. Useful when the same element uses different attributes across environments or when you want to create a fallback locator.//input[@type='submit' or @name='login']not(): Excludes elements that match the given condition. Useful for filtering out hidden fields, disabled inputs, or elements you want to skip while interacting with the visible and active ones.//input[not(@type='hidden')]XPath axes in Selenium let you navigate the DOM relative to a known element using directions like parent, child, ancestor, descendant, following, and preceding.
They are essential for dynamic UIs where element identifiers change frequently and the only stable reference is a nearby element like a label, heading, or container. The correct syntax for axes uses a single slash before the axis name -- not double slashes.
//label[text()='Password']/following::input[1]//div[@class='field-label']/following-sibling::div//button[@id='submit']/preceding::input[@type='password']//div[@class='error-message']/preceding-sibling::label//ul[@class='menu']/child::li//input[@id='email']/parent::div//div[@class='user-section']/descendant::input[@type='text']//input[@id='username']/ancestor::formChained XPath in Selenium links multiple XPath queries through parent-child paths to narrow the search to a specific section of the DOM and avoid ambiguous matches.
Instead of writing a single expression that must uniquely identify the element from anywhere in the document, you first locate a stable parent container, then find the target element within it.
This is especially useful when multiple elements share similar attributes across different sections of the page. By anchoring the search to a specific container, you avoid ambiguity without needing overly complex conditions.
Syntax:
//parentTag[@attribute='value']//childTag[@attribute='value']Example using the eCommerce Playground login form:
<div class="login-form-container">
<form>
<input id="input-email" type="email" name="email" />
<input id="input-password" type="password" name="password" />
</form>
</div>To locate the email field specifically inside the login form container:
//div[@class='login-form-container']//input[@id='input-email']This remains stable even if another email input exists elsewhere on the page, because the search is scoped to the login container.
Create dynamic XPath in Selenium using functions like contains() and starts-with(), logical operators, axes, careful indexing, and wildcards to handle changing attributes.
Modern JavaScript frameworks like React, Angular, and Vue often generate IDs and class names programmatically, making static locators brittle. These five techniques keep your XPath valid regardless of how the DOM changes.
Functions like contains(), starts-with(), and text() match the stable part of an attribute rather than the full value. This keeps locators valid when only part of the attribute changes.
//button[contains(@class, 'submit')]Merging conditions with and or or makes locators resilient when one attribute changes or when the same element uses different attributes in different environments.
//input[@type='email' or @name='userEmail']When the element itself has no stable attributes, navigate from a stable neighbor using axes. The locator stays valid even when surrounding elements are added or reordered dynamically.
//label[text()='Email Address']/following-sibling::inputIndexing selects a specific element among multiple matches. Use sparingly -- if the DOM order changes (items added, removed, or reordered), the index picks the wrong element silently.
//div[@class='row']//input[2]When tag names vary or are generated by a framework, use the wildcard * to match any tag and focus on the reliable attribute instead.
//*[@data-testid='login-btn']Pro-Tip: Keep a reference to the XPath locator cheat sheet alongside this guide. It covers every XPath expression pattern with syntax and examples in one place.
Both XPath and CSS selectors are valid locator strategies in Selenium, and each has scenarios where it is the better choice. The key difference is that XPath can navigate both up and down the DOM tree, while CSS selectors can only move downward to child elements.
| Criteria | XPath | CSS Selectors |
|---|---|---|
| Best for | Complex or irregular HTML structures | Simple, consistent HTML layouts |
| DOM Navigation | Both up and down (parent, sibling, child, ancestor) | Downward only (child elements) |
| Text Matching | Yes -- using text() and contains(text()) | No -- cannot match text content |
| Performance | Slightly slower in most browsers | Generally faster, browser-optimized |
| Readability | More verbose | Cleaner and shorter |
| XML Support | Fully compatible with XML and namespaces | HTML/CSS environments only |
| Dynamic Elements | Handles well with contains(), starts-with(), axes | Limited -- no partial text matching |
| Common Use Cases | Web scraping, complex DOM traversal, legacy apps | UI testing, front-end automation, modern apps |
For an in-depth comparison with worked examples, see the guide on XPath vs CSS Selectors.
Most XPath failures in Selenium fall into predictable categories. Understanding the root cause of each failure type lets you fix them quickly rather than cycling through trial-and-error locator changes.
Fix: Use Chrome Console with $x('your_xpath') or SelectorsHub to validate and correct the expression. The error message usually indicates which part of the syntax is wrong.
Fix: Check for iframes using DevTools. Use $x() in the Console to confirm the element is present after the page fully loads. Add explicit waits using WebDriverWait instead of fixed sleeps.
Fix: Add more conditions to narrow the match. For example, change //div[@class='item'] to //div[@class='item' and @data-id='123']. Always verify in Console that $x() returns exactly one element.
Fix: Switch from exact attribute matching to contains() or starts-with() targeting the stable part of the value. For example: //div[contains(@class, 'btn')].
findElement(By.xpath()) returns NoSuchElementException.Fix: Use JavaScript to access the Shadow DOM: element.shadowRoot.querySelector(). Alternatively, consider Playwright which handles Shadow DOM natively without JavaScript workarounds.
This is one of the most common questions in Selenium automation, and it has a specific answer. When you inspect an element in Chrome DevTools, you are looking at the DOM after the page has fully rendered, including all JavaScript, dynamic content, and lazy-loaded elements. When Selenium executes the same XPath, it may do so before the element appears in the DOM.
There are three common causes and three corresponding fixes:
Thread.sleep() , it is better to use WebDriverWait in Seleniumfor reliable synchronization. WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(
By.xpath("//input[@id='input-email']")
)
);If you're unsure about the difference between implicit, explicit, and fluent waits, this guide on Selenium waits explains when to use each approach. Fix: Replace Thread.sleep() with an explicit wait using WebDriverWait and ExpectedConditions.visibilityOfElementLocated().
driver.switchTo().frame(driver.findElement(By.id("iframe-id")));
WebElement element = driver.findElement(
By.xpath("//input[@name='email']")
);
driver.switchTo().defaultContent(); // switch back afterWriting stable XPath is a key part of effective test automation. Following proven Selenium best practices helps create locators that remain reliable across UI updates, deployments, and changing DOM structures.
contains() for Partial Matching: When dealing with dynamic or generated attribute values, match only the stable portion with contains().text() for Visible Labels: When an element's visible text is consistent and unique, text() provides a readable, stable locator.starts-with() for Prefixed Dynamic Attributes: For auto-generated IDs that share a consistent prefix, starts-with() handles the variation without breaking.$x('your_xpath') or in the TestMu AI XPath tester before adding it to your script.XPath in Selenium is the most flexible locator strategy available -- covering everything from simple attribute-based targeting to complex structural navigation with axes and functions. The key to writing XPath that stays reliable is preferring relative paths, using functions like contains() and starts-with() for dynamic attributes, validating every expression in Chrome Console before adding it to your script, and reaching for axes when the element itself has no stable attributes.
To scale your Selenium automation beyond local browsers, TestMu AI online Selenium Grid lets you run tests in parallel across 3000+ real browser and OS combinations, debug with full logs and video recordings, and integrate directly into your CI/CD pipeline without managing any infrastructure.
CEO, Vercel
Discovered @TestMu AI yesterday. Best browser testing tool I've found for my use case. Great pricing model for the limited testing I do 👏
Deliver immersive digital experiences with Next-Generation Mobile Apps and Cross Browser Testing Cloud
Did you find this page helpful?
More Related Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance