If you’ve spent any time writing UI automation with Selenium and Java, you know the frustration: XPath is brittle, hard to read, and a nightmare to maintain. You spend precious minutes balancing quotes, checking for typos, and debugging flaky locators that break every time a developer changes a single CSS class.
We call this "String Soup"—a mess of long, error-prone strings that hide your automation intent.
But what if you could write locators that look like clean, fluent Java code? What if they could adapt automatically to whitespace, casing, and special characters?
Meet XPathy, a lightweight Java library designed to take the pain out of XPath.
🔍 The Problem: Why XPath Strings Fail Us
Consider a common scenario: you need to find a submit button that has a specific ID but is not currently disabled, and its text should be case-insensitive.
❌ The String Soup XPath
//button[contains(@id, 'submit-') and not(contains(translate(@class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'disabled')) and translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='login']
This single line is:
- Unreadable: Good luck figuring out the logic in six months.
-
Error-Prone: Missing a quote, forgetting a bracket, or mistyping
translate
is easy. - Untrustworthy: It relies on complex functions and manual string management.
âś… The Solution: Fluent and Intent-Driven Code
XPathy replaces raw strings with a fluent API that maps human intent directly to XPath logic. Instead of manually writing contains()
or translate()
, you simply chain methods.
âś… The XPathy Fluent Code
import static com.xpathy.Tag.*;
import static com.xpathy.Attribute.*;
import static com.xpathy.Case.*;
XPathy locator = button.byAttribute(id).contains("submit-")
.and()
.byAttribute(class_).not().withCase(IGNORED).contains("disabled")
.and()
.byText().withCase(IGNORED).equals("login");
Result:
//button[contains(@id, 'submit-') and not(contains(translate(@class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'disabled')) and translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='login']
The code is now:
- Readable: It reads like an English sentence.
- Type-Safe: The compiler catches typos.
- Maintainable: Add or remove conditions without breaking anything.
🔪 Three Pillars of Robust Locators
XPathy solves the three biggest problems with locators: brittleness, complexity, and ambiguity.
1. Bulletproof Locators with Transformations 🛡️
The most common reason for flaky tests is inconsistency in UI values (whitespace, case, or formatting). XPathy’s Transformations automatically wrap your value in the necessary XPath functions.
Flaky Scenario | XPathy Solution | Fluent Code |
---|---|---|
Case Inconsistency: Login vs LOGIN
|
withCase(IGNORED) |
byText().withCase(IGNORED).equals("login") |
Whitespace Padding: " Invalid Password "
|
withNormalizeSpace() |
byText().withNormalizeSpace().equals("Invalid Password") |
Special Characters: $1,999.00
|
withRemoveOnly(SPECIAL_CHARACTERS) |
byText().withRemoveOnly(SPECIAL_CHARACTERS).equals("199900") |
Accents/Diacritics: Café vs Cafe
|
withTranslate() |
byText().withTranslate("éà è", "eae").contains("Cafe") |
2. Mastering Complex Logic and Precedence đź§
When a simple .and()
isn’t enough, XPathy gives you explicit logical control.
A. Grouping Conditions with Union and Intersect
// Match a button that has one of three possible dynamic IDs
XPathy locator = button.byAttribute(id)
.union(
Or.equals("login-btn"),
Or.equals("signin-btn"),
Or.contains("auth-")
);
Result:
//button[@id='login-btn' or @id='signin-btn' or contains(@id, 'auth-')]
B. Nested Logic for Precision
import static com.xpathy.Condition.*;
XPathy locator = div.byCondition(
and(
text().contains("Order"),
or(
attribute(class_).equals("pending"),
attribute(class_).equals("processing")
)
)
);
Result:
//div[contains(text(), 'Order') and (@class='pending' or @class='processing')]
3. DOM Relationships and Having Operations 🔍
Defining relationships in XPath is powerful but hard to read. XPathy simplifies this with byHaving()
.
// Find a <div> product card that *has* a descendant button with "Add to Cart" text
XPathy locator = div.byAttribute(class_).equals("product-card")
.and()
.byHaving().descendant(
button.byText().contains("Add to Cart")
);
Result:
//div[@class='product-card' and ( .//button[contains(text(), 'Add to Cart')] )]
This makes it effortless to filter parent elements based on the content of their descendants.
🚀 From XPathy to Selenium
XPathy integrates directly with Selenium—no extra setup.
Get the Selenium Locator:
By seleniumLocator = xpathy.getLocator();
driver.findElement(seleniumLocator).click();
Get the Raw XPath String (for logging/debugging):
String rawXPath = xpathy.toString();
📊 Conclusion: Stop Writing Strings, Start Writing Code
XPathy is more than a utility—it’s a paradigm shift for locator management. It turns fragile XPath strings into expressive, fluent, and type-safe Java code.
If your tests constantly break because of small UI changes, it’s time to stop debugging String Soup and start building robust, readable, and maintainable locators with XPathy.
Ready to start? Check out the full documentation and repository.
XPathy provides a lot more: Read the Full Documentation:
The Repository:
https://github.com/Volta-Jebaprashanth/xpathy
Happy Testing! 🚀
Top comments (0)