How to use XPath in Selenium in 2026?

Understand what XPath in Selenium is, how it woks, and the types of XPath.

Written by Siddhi Rao Siddhi Rao
Reviewed by Bhumika Babbar Bhumika Babbar
Last updated: 26 May 2026 24 min read

How to use XPath in Selenium in 2026?

Recent industry data shows the proportion of teams experiencing test flakiness grew from 10% in 2022 to 26% in 2025, alongside a 23% increase in pipeline complexity.

As modern frontend applications become more dynamic, even small UI or DOM changes can break unstable Selenium locators and create unreliable test behavior. XPath is often blamed for this instability, especially in applications built with React, Angular, and component-heavy frontend architectures.

In many cases, though, the issue is not XPath itself, but how the locator is written.

In this guide, you’ll learn how to write stable XPath locators in Selenium, handle dynamic elements, avoid brittle locator patterns, and use XPath effectively in modern web applications.

XPath Syntax: Attributes, Text, and Partial Matches in Selenium

XPath syntax defines how Selenium locates elements inside the DOM using tags, attributes, text values, and element relationships.

Most XPath locators in Selenium follow this structure:

//tagname[@attribute='value']

For example:

//input[@id='email']

This locator searches for an input element whose id attribute equals email.

XPath becomes useful when stable IDs or simple selectors are unavailable. Instead of depending only on one attribute, XPath can combine multiple conditions and relationships to identify elements more reliably.

Selecting Elements Using Attributes

Attributes are the most common way to write XPath locators in Selenium.

Example:

//button[@type='submit']

You can also combine multiple attributes:

//input[@type='text'][@name='username']

This reduces ambiguity when similar elements exist on the page.

However, modern frontend applications often generate dynamic attributes during runtime. In React or component-based applications, IDs and class names may change between builds or sessions.

In these cases, exact attribute matching becomes brittle.

For example:

//button[@class='btn-1a2b3c']

This locator may fail if the generated class changes after deployment.

A partial match is usually more stable.

Using Partial Matches with contains()

The contains() function helps locate elements when part of an attribute remains stable.

Example:

//button[contains(@class,'btn')]

This locator still works even if the full class value changes dynamically.

Using Partial Matches with contains

contains() is commonly used for:

  • dynamic class names
  • generated IDs
  • reusable UI components
  • partially changing attributes

But it should be used carefully.

Overly broad partial matches can unintentionally select multiple elements and create unstable tests.

For example:

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

may match several unrelated elements on large pages.

Experienced automation engineers usually anchor partial matches to more stable surrounding relationships or combine them with additional conditions.

Using starts-with() for Dynamic Attributes

Some frontend systems generate IDs with predictable prefixes.

Example:

<input id="user_48392" />

Instead of matching the entire value:

//input[@id='user_48392']

you can use:

//input[starts-with(@id,'user_')]

This makes the locator more resilient to changing numeric suffixes.

Using starts with for Dynamic Attributes

Selecting Elements Using Visible Text

XPath can also locate elements using visible text content.

Example:

//button[text()='Login']

This is useful for:

  • buttons
  • links
  • menu items
  • validation messages

However, exact text matching can become fragile in applications with:

  • localization
  • dynamic spacing
  • changing labels

For example, this may fail because of extra whitespace:

//button[text()='Sign In']

A more stable approach is:

//button[contains(text(),'Sign In')]

Or:

//button[normalize-space()='Sign In']

normalize-space() removes unnecessary whitespace and makes text matching more reliable in dynamic UIs.

Which XPath Strategy Should You Use?

Different UI structures require different XPath strategies. The goal is to choose the locator approach that remains stable as the frontend changes.

SituationRecommended XPath StrategyAvoid
Stable data-testid exists//button[@data-testid=’checkout’]Text-based XPath
Dynamic React or generated classescontains() or relationship-based XPathExact class match
Repeated cards or componentsScope XPath to parent containerGlobal XPath
Forms with labelsfollowing-sibling axisPositional indexes
Localized or changing UI textSemantic attributesExact text matching
Dynamic IDs with stable prefixesstarts-with()Full ID match
Dynamic tables and gridsancestor + descendant axesDeep absolute XPath

How to Write XPath Locators in Selenium

Writing XPath locators in Selenium is not about creating the longest or most specific path to an element. It is about choosing the most stable way to identify that element in the DOM.

A good XPath locator should answer two questions:

  1. What uniquely identifies this element?
  2. Will that identifier remain stable after small UI changes?

Follow this process.

Step 1: Inspect the Element and Its Surrounding DOM

Open the page in the browser, right-click the element, and inspect it.

Do not copy the full XPath from DevTools and use it directly in your test. Browser-generated XPath often depends on the exact DOM hierarchy, which makes it fragile.

Look for stable signals such as:

  • id
  • name
  • type
  • aria-label
  • data-testid
  • visible text
  • nearby labels
  • parent containers

The goal is to understand the element’s context before writing the locator.

Step 2: Choose the Most Stable Anchor

Next, decide what the XPath should depend on.

Use this order of preference:

  1. Stable test-specific attributes, such as data-testid
  2. Stable semantic attributes, such as name, type, or aria-label
  3. Visible text, when the label is unlikely to change
  4. Nearby labels or parent containers, when direct attributes are dynamic
  5. Index-based selection only as a last option

This step matters because most flaky XPath locators fail by depending on unstable implementation details, such as generated class names, layout wrappers, or element position.

Step 3: Write the Simplest XPath That Identifies the Element

Start with the simplest reliable XPath.

For example:

//input[@name='email']

or:

//button[@type='submit']

Simple locators are easier to read, debug, and maintain.

Do not make the XPath more complex unless the page requires it.

Step 4: Add Conditions Only When the Match Is Ambiguous

If the XPath matches more than one element, add another stable condition.

For example:

//input[@type='text'][@name='username']

This is better than using an index like:

(//input[@type='text'])[2]

The index-based locator depends on element order. If another text input is added before it, Selenium may interact with the wrong field.

Step 5: Use Text When the Visible Label Is Stable

For buttons, links, tabs, menu items, and alerts, visible text can be a strong locator anchor.

Example:

//button[normalize-space()='Login']

normalize-space() is usually safer than exact text() matching because it handles extra spaces and line breaks in the DOM.

Use text-based XPath carefully in applications with frequent copy changes or localization. If the UI text changes often, prefer stable attributes or relationships instead.

Step 6: Use DOM Relationships When Direct Attributes Are Weak

If the element does not have stable attributes, locate it through a nearby stable element.

For example, instead of:

/html/body/div[2]/form/div[3]/input

use:

//label[normalize-space()='Email']/following-sibling::input

This is more resilient because it depends on the relationship between the label and the input, not the exact page structure.

Relationship-based XPath is especially useful for forms, tables, reusable components, and enterprise dashboards where direct attributes are missing or dynamic.

Step 7: Scope the XPath to the Correct Section

If the same element appears multiple times, scope the XPath to a parent container.

For example:

//form[@id='loginForm']//input[@name='email']

or:

//div[@data-testid='user-card']//button[normalize-space()='Edit']

Scoping prevents Selenium from selecting the wrong matching element elsewhere on the page. This is important in component-based applications where the same button, input, or card pattern appears multiple times.

Step 8: Validate the XPath Before Using It in Selenium

Before adding an XPath to your Selenium test script, validate it in browser DevTools.

You can test XPath directly in Chrome DevTools by opening the Elements panel and pressing Ctrl + F or Cmd + F. Enter the XPath in the search box and check which element is highlighted.

A reliable XPath should pass these checks:

  • It matches the intended element
  • It does not match unrelated duplicate elements
  • It still works after a page refresh
  • It does not depend on generated classes or unstable indexes
  • Another tester can understand it later

For example, if the page has an email input field, this XPath is usually easier to validate and maintain:

//input[@name='email']

But this XPath is more fragile:

/html/body/div[2]/form/div[3]/input

The first XPath identifies the field by its purpose. The second XPath depends on the exact DOM position. If a developer adds a wrapper div, banner, or layout section, the second XPath may break even if the email field still exists.

A locator that works once is not enough. It should be stable, readable, and specific enough for long-term test maintenance.

To make the next Selenium example easier to understand, consider this simple login form:

<form id="loginForm">

 <label>Email</label>

 <input type="text" name="email" />



 <label>Password</label>

 <input type="password" name="password" />



 <button type="submit">Login</button>

</form>

In this form, the email field can be located using its name attribute:

//input[@name='email']

The login button can be located using its visible text:

//button[normalize-space()='Login']

Login form

Step 9: Use the XPath in Selenium Code

Once the XPath is validated in DevTools, use it with By.xpath() in Selenium.

The example below opens a sample login page, finds the email field with XPath, enters an email address, finds the login button, and clicks it.

Save the sample HTML from the previous step as login.html, then replace the file path in the code with the correct path on your system.

Java example:

import org.openqa.selenium.By;

import org.openqa.selenium.WebDriver;

import org.openqa.selenium.WebElement;

import org.openqa.selenium.chrome.ChromeDriver;



public class XPathLoginExample {

   public static void main(String[] args) {

       WebDriver driver = new ChromeDriver();



       try {

           driver.get("file:///path/to/login.html");



           WebElement emailInput = driver.findElement(By.xpath("//input[@name='email']"));

           emailInput.sendKeys("user@example.com");



           WebElement loginButton = driver.findElement(By.xpath("//button[normalize-space()='Login']"));

           loginButton.click();



       } finally {

           driver.quit();

       }

   }

}

Python example:

from selenium import webdriver

from selenium.webdriver.common.by import By



driver = webdriver.Chrome()



try:

   driver.get("file:///path/to/login.html")



   email_input = driver.find_element(By.XPATH, "//input[@name='email']")

   email_input.send_keys("user@example.com")



   login_button = driver.find_element(By.XPATH, "//button[normalize-space()='Login']")

   login_button.click()



finally:

   driver.quit()

In both examples, Selenium first loads the page in the browser. Then it searches the DOM using the XPath expression.

This line finds the email input:

//input[@name='email']

This line finds the login button:

//button[normalize-space()='Login']

The important point is that XPath needs a browser page and a DOM to work against. Without that context, an XPath expression is only a locator string. Selenium can execute it only after the browser loads a page that contains matching elements.

Relative vs Absolute XPath in Selenium

XPath locators in Selenium are generally written in two ways:

  • Absolute XPath
  • Relative XPath

The difference is not just syntax. It directly affects how stable and maintainable the locator will be as the application changes.

What Is Absolute XPath?

Absolute XPath starts from the root of the DOM and follows the complete hierarchy to the target element.

Example:

/html/body/div[2]/div[1]/form/input

This XPath tells Selenium to follow the exact DOM path from the top of the page to the input field.

Absolute XPath is usually easy to generate because browser DevTools can copy it automatically. However, this convenience creates one of the most common Selenium anti-patterns.

A small frontend change can break the locator immediately.

For example:

  • adding a wrapper <div>
  • moving a form section
  • inserting a banner
  • restructuring layout containers

can change the DOM hierarchy and invalidate the XPath, even though the actual element still exists and behaves correctly.

This problem becomes much more common in modern frontend applications built with:

  • React
  • Angular
  • Vue
  • component-based UI libraries

because these frameworks frequently update layout structures during UI refactors.

That is why deeply nested absolute XPath locators are considered brittle in large Selenium test suites.

What Is Relative XPath?

Relative XPath starts from anywhere in the DOM instead of beginning at the root.

Example:

//input[@name='email']

or:

//label[normalize-space()='Email']/following-sibling::input

Relative XPath focuses on stable identifying information instead of the exact page hierarchy.

This makes the locator more resilient to frontend changes because Selenium does not depend on every intermediate container remaining unchanged.

For example, if additional layout wrappers are introduced, the following XPath usually continues working:

//button[normalize-space()='Checkout']

because it targets the button itself rather than its position inside the DOM tree.

Why Relative XPath Is Preferred in Selenium

Most Selenium frameworks prefer relative XPath because modern frontend applications change frequently.

Small UI updates, reusable components, or additional wrapper elements can easily break absolute XPath locators that depend on the exact DOM hierarchy.

Relative XPath is generally more stable because it relies on identifiable attributes, visible text, or element relationships instead of exact node position.

Absolute XPathRelative XPath
Depends on exact DOM hierarchyDepends on stable identifying information
Breaks easily after layout changesMore resilient to UI updates
Harder to read and maintainEasier to debug and maintain
Usually generated automatically by DevToolsUsually written manually for stability

Example:

Absolute XPath:

/html/body/div[3]/div[2]/form/div/input

Relative XPath:

//form[@id='loginForm']//input[@name='email']

The second locator is easier to understand and less likely to fail after frontend changes.

Absolute XPath can still help during debugging or DOM inspection, but relative XPath is generally preferred for scalable Selenium automation frameworks.

Absolute VS Relative XPath

Locating Elements by DOM Relationships Using XPath Axes in Selenium

XPath axes help Selenium locate elements based on their relationship with other elements in the DOM.

This becomes useful when:

  • direct attributes are missing
  • IDs are dynamic
  • multiple similar elements exist
  • the surrounding UI structure is more stable than the element itself

Instead of locating elements only by attributes or text, XPath axes allow Selenium to navigate through:

  • parent elements
  • child elements
  • sibling elements
  • ancestor containers

This is one of the biggest advantages XPath has over CSS selectors.

Using following-sibling to Locate Related Elements

following-sibling is commonly used when an element appears next to a stable label or heading.

For example:

<label>Email</label>

<input type="text" />

XPath:

//label[normalize-space()='Email']/following-sibling::input

This locator identifies the input field relative to the label instead of depending on generated attributes or nested layout containers.

Relationship-based locators like this are usually more stable in dynamic forms and reusable UI components.

following sibling

Using ancestor to Scope Elements to a Specific Container

Large applications often contain repeated elements such as multiple buttons named “Edit” or “Save”.

The ancestor axis helps scope the element to the correct section of the page.

Example:

//button[normalize-space()='Edit']/ancestor::div[@class='user-card']

This allows Selenium to identify the correct container associated with the button.

Axes-based scoping becomes especially useful in:

  • dashboards
  • reusable cards
  • nested forms
  • component-heavy applications

where identical elements appear multiple times.

Using parent to Navigate Up the DOM

The parent axis helps locate the immediate parent of an element.

Example:

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

This is useful when:

  • validation messages appear inside parent containers
  • styling classes exist on wrappers instead of inputs
  • related elements are grouped together

Using descendant for Nested Elements

The descendant axis helps locate elements inside a specific container.

Example:

//form[@id='checkoutForm']/descendant::button[@type='submit']

This reduces ambiguity by restricting the search to a specific section of the page.

It is commonly used in:

  • forms
  • tables
  • modals
  • nested component structures

Handling Dynamic Elements and Flaky Locators with XPath in Selenium

Modern web applications rarely keep the DOM completely stable.

Frontend frameworks like React, Angular, and Vue frequently rerender components, generate dynamic attributes, and update parts of the UI without fully reloading the page. As a result, XPath locators that depend on unstable implementation details can quickly become flaky.

This is one of the biggest reasons Selenium tests fail after small frontend changes.

Most flaky XPath locators fail because they depend on:

  • generated class names
  • deeply nested layout wrappers
  • dynamic IDs
  • positional indexes
  • changing text values

For example:

//div[3]/div[2]/button

This locator assumes the DOM structure will always remain identical.

If a new wrapper container or banner is added, Selenium may:

  • fail to find the element
  • target the wrong element
  • interact with an outdated node

Modern frontend applications make this problem more common because components are frequently reused and rearranged during UI updates.

1. Avoid Dynamic Class Names and Generated IDs

Many frontend frameworks generate class names automatically during runtime or build compilation.

For example:

<button class="btn-13af7x primary-91ksl">

An XPath like:

//button[@class='btn-13af7x primary-91ksl']

is highly brittle because the class value may change after deployment.

Instead, prefer:

  • stable attributes
  • visible text
  • parent-child relationships
  • nearby labels
  • data-testid attributes

A more stable locator would be:

//button[normalize-space()='Checkout']

or:

//div[@data-testid='checkout-section']//button

2. Use Relationship-Based XPath for Dynamic UIs

When direct attributes are unstable, DOM relationships are often more reliable than exact structure.

For example:

//label[normalize-space()='Email']/following-sibling::input

This locator depends on the relationship between the label and input field instead of layout depth or generated wrappers.

Relationship-based XPath is especially useful in:

  • dynamic forms
  • reusable UI components
  • enterprise dashboards
  • nested frontend layouts

because functional relationships usually change less frequently than implementation details.

3. Be Careful with Text-Based XPath

Text-based XPath can improve readability, but it may become unstable in applications with:

  • localization
  • A/B testing
  • changing button labels
  • dynamic UI messaging

For example:

//button[text()='Submit']

may fail if the UI changes the label to:

  • “Continue”
  • “Save”
  • “Submit Order”

In these cases, combine text with stable containers or attributes instead of relying entirely on visible labels.

4. Reduce Locator Coupling to the DOM Structure

A stable XPath locator should identify the element semantically, not structurally.

Weak locator:

/html/body/div[2]/div[4]/form/div/input

Stronger locator:

//form[@id='signupForm']//input[@name='email']

The second locator depends on:

  • form identity
  • field purpose

instead of:

  • exact nesting depth
  • wrapper hierarchy
  • page layout structure

This makes the locator easier to maintain as the frontend evolves.

5. Validate XPath Stability Before Adding It to the Test Suite

Before finalizing a locator, ask:

  • Will this still work if a wrapper <div> is added?
  • Does this depend on generated values?
  • Would a small UI refactor break it?
  • Is the locator tied to layout instead of meaning?
  • Could another engineer understand this XPath quickly?

These checks help reduce flaky Selenium tests over time.

Experienced automation engineers usually spend more time thinking about locator stability than writing the XPath syntax itself. In large Selenium frameworks, stable locators directly affect debugging effort, CI reliability, and long-term maintenance cost.

XPath vs CSS Selectors in Selenium

XPath and CSS selectors are the two most commonly used locator strategies in Selenium.

Both can locate elements effectively, but they solve different problems and behave differently in modern web applications.

The goal is not to choose one universally. It is to use the locator strategy that creates the most stable and maintainable tests.

XPathCSS Selectors
Can navigate parent-child and sibling relationshipsCannot navigate upward in the DOM
Supports text-based matchingDoes not support direct text matching
Better for relationship-based locatorsBetter for simple attribute matching
More flexible for complex DOM traversalUsually shorter and easier to read
Commonly used for dynamic forms and tablesCommonly used for stable UI components

When XPath Works Better

XPath is usually more effective when the element needs to be located relative to another element.

For example:

//label[normalize-space()='Email']/following-sibling::input

This type of relationship-based locator is difficult to express cleanly using CSS selectors.

XPath is also useful for:

  • dynamic tables
  • nested forms
  • reusable components
  • locating elements by visible text
  • traversing complex DOM relationships

This flexibility is one of the main reasons XPath remains widely used in Selenium automation.

When CSS Selectors Work Better

CSS selectors are usually simpler when stable attributes already exist.

Example:

input[name='email']

For straightforward attribute matching, CSS selectors are often:

  • shorter
  • easier to read
  • easier to debug

Modern frontend applications that expose stable attributes such as:

  • data-testid
  • id
  • name

often work very well with CSS selectors.

When NOT to Use XPath in Selenium

XPath is powerful, but it is not always the best locator strategy in Selenium.

In many cases, XPath adds unnecessary complexity when a simpler and more stable locator already exists. Overusing XPath can also make tests harder to debug and maintain over time.

Avoid using XPath in these situations:

SituationBetter AlternativeWhy
Stable id or data-testid is availableCSS selector or ID locatorSimpler and easier to maintain
Simple attribute-based selectionCSS selectorsCleaner and more readable
Shadow DOM elementsShadow DOM APIs or framework-specific locatorsStandard XPath cannot cross Shadow DOM boundaries
Frequently changing UI textSemantic attributesText-based XPath becomes brittle
Generated frontend classesStable test attributesDynamic classes change across builds
Deeply nested layoutsRelationship-based locatorsStructure-dependent XPath breaks easily
Highly dynamic component treesScoped locators with stable anchorsBroad XPath becomes unreliable

Common XPath Mistakes That Break Selenium Tests

Most XPath-related Selenium failures are not caused by XPath itself. They happen because the locator depends on unstable implementation details that change as the frontend evolves.

These mistakes may work temporarily during local testing, but they often become flaky in large automation suites where UI changes happen frequently.

1. Using Deeply Nested Absolute XPath

One of the most common mistakes is relying on the full DOM hierarchy.

Example:

/html/body/div[2]/div[4]/form/div/input

This locator depends on the exact page structure remaining unchanged.

If a frontend developer adds:

  • a wrapper <div>
  • a new banner
  • a layout container
  • a responsive UI adjustment

the XPath may fail immediately.

Modern frontend frameworks make this especially risky because component structures change frequently during UI refactors.

A more stable approach is:

//form[@id='signupForm']//input[@name='email']

This identifies the element using stable meaning instead of DOM depth.

2. Depending on Dynamic Class Names

Many frontend frameworks generate class names automatically during runtime or build compilation.

Example:

<button class="btn-13af7x primary-91ksl">

Using:

//button[@class='btn-13af7x primary-91ksl']

creates a brittle locator because the class value may change after deployment.

This problem is common in:

  • React applications
  • CSS-in-JS systems
  • Tailwind-heavy UIs
  • component libraries

Instead, prefer:

  • stable attributes
  • visible text
  • parent-child relationships
  • data-testid
  • semantic attributes

3. Overusing Index-Based XPath

Index-based locators are another major source of flaky tests.

Example:

(//button[@type='submit'])[2]

This assumes the second matching button will always remain in the same position.

If another button is inserted earlier in the DOM, Selenium may:

  • click the wrong element
  • fail the test
  • interact with a hidden component

Indexes should usually be treated as a last resort, not a primary locator strategy.

4. Copy-Pasting XPath from Browser DevTools

Browser-generated XPath is often optimized for uniqueness, not stability.

For example, DevTools may generate locators containing:

  • nested containers
  • indexes
  • temporary attributes
  • long DOM paths

These locators usually work initially but become fragile after frontend changes.

Experienced automation engineers rarely use copied XPath directly in production frameworks. They typically rewrite it into:

  • shorter
  • relationship-based
  • attribute-focused
  • maintainable locators

5. Using Broad contains() Matches

contains() is useful for handling dynamic attributes, but overly broad matches can create unstable tests.

Weak locator:

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

This may unintentionally match multiple unrelated elements.

A more stable approach combines partial matches with additional context:

//div[@data-testid='checkout-section']//button[contains(@class,'primary')]

The locator becomes more predictable because it is scoped to a meaningful section of the page.

6. Ignoring Locator Readability

A technically valid XPath can still become difficult to maintain.

For example:

//div[@id='a1']//div[2]//span[contains(text(),'Submit')]

may work, but it does not clearly communicate intent.

Readable locators are easier to:

  • debug
  • review
  • update
  • maintain across teams

In large Selenium frameworks, locator readability directly affects long-term maintenance effort.

7. Treating XPath as Static Instead of Evolutionary

Frontend applications change continuously.

A locator that is stable today may become brittle after:

  • component refactoring
  • responsive layout updates
  • localization changes
  • UI redesigns

Experienced automation engineers regularly review and refactor XPath locators instead of assuming they will remain stable forever.

The most reliable Selenium frameworks treat locator maintenance as part of the automation lifecycle, not a one-time implementation task.

Conclusion

XPath remains one of the most effective Selenium locator strategies for handling dynamic elements, nested components, and relationship-based UI structures. While XPath is often considered fragile, most flaky locators fail because they depend on unstable DOM structure, generated classes, or positional indexes instead of stable UI meaning.

In modern Selenium frameworks, stable XPath locators are usually built around semantic attributes, visible text, and meaningful element relationships. The goal is not to write the shortest XPath possible, but to create locators that remain readable, maintainable, and resilient as frontend applications evolve.

Tags
Automation Testing Selenium Webdriver
Siddhi Rao
Siddhi Rao

Lead - Customer Engineering

I’ve worked on Selenium automation frameworks and large-scale UI test suites where locator stability directly affects debugging effort, CI reliability, and long-term maintenance.

FAQs

XPath locators depend on the DOM structure and attributes, so even small UI or attribute changes can cause them to fail if they are not written to be flexible.

Using relative XPath, stable attributes, functions like contains() or starts-with(), and avoiding absolute paths helps reduce failures caused by dynamic DOM changes.

Different browsers can interpret DOM rendering and timing differently. Testing on real browsers ensures XPath locators work consistently across actual user environments.

XPath locators breaking across browsers?
Validate XPath behavior on real browsers and devices using BrowserStack Automate