For years, UI automation engineers have struggled with the same nemesis: raw XPath strings. They're brittle, hard to read, and prone to silent errors caused by a misplaced bracket, quote, or function. Balancing string concatenation, single quotes, and double quotes to build complex locators is a maintenance headache that slows down test development and makes debugging a nightmare.
XPathy is a lightweight Java library built to solve this problem. It replaces manual string manipulation with a fluent, object-oriented API, allowing developers to build sophisticated Selenium locators using clear, chainable methods. XPathy makes raw XPath obsolete by turning the process of locator creation into a declarative, business-readable task.
Why XPathy? The Core Problem Solved
The goal of a locator is to express a test's intent. Instead of writing the syntax for an element with an ID that starts with menu-, XPathy lets you express the intent directly:
| Traditional, Brittle XPath | XPathy Fluent API (Declarative Intent) |
|---|---|
//div[starts-with(@data-testid, 'menu-') and contains(text(), 'Item')] |
div.byAttribute(data_testid).startsWith("menu-").and().byText().contains("Item") |
The result is a locator that is dramatically more readable, maintainable, and scalable across different environments. The final XPathy object seamlessly converts to a Selenium By object or a standard XPath string via .getLocator() or .toString().
The XPathy Architecture: Context and Flow
XPathy operates on a clear, layered architecture that guarantees valid XPath generation at every step. This context-building flow is what enables the fluent API.
- Starting Point: Begin with a generic attribute (id, class_, data_testid) or a specific HTML tag (div, button, span).
-
Context Selection: Use a
by...()method to define what you are filtering on:.byAttribute(id).byText().byNumber().byStyle(backgroundColor) -
Condition Finalization: Apply the final predicate to generate the XPath condition:
.equals("value").contains("text").greaterThan(50)
1. Basic Operations
All standard XPath operations are immediately accessible:
| XPathy Code | Generated XPath | Function |
|---|---|---|
id.contains("login") |
//*[contains(@id, 'login')] |
Target any tag with an ID containing "login". |
h2.byText().startsWith("Chapter") |
//h2[starts-with(text(), 'Chapter')] |
Target an <h2> tag whose text begins with "Chapter". |
value.lessThan(50) |
//*[@value < 50] |
Target any tag where the numeric attribute value is less than 50. |
2. Robustness Through Value Transformations
One of XPathy's most powerful features is its ability to apply transformations (like case-insensitivity or whitespace cleanup) before comparison. This creates locators resilient to UI variations.
You use the .with...() methods to apply transformations to the current context:
| Scenario | XPathy Code | Function |
|---|---|---|
| Ignore Case | div.byAttribute(class_).withCase(IGNORED).equals("active") |
Matches class="active", class="Active", or class="ACTIVE". |
| Normalize Space | p.byText().withNormalizeSpace().equals("Error message") |
Ignores leading/trailing/extra spaces in the text node. |
| Remove Symbols | span.byText().withRemoveOnly(SPECIAL_CHARACTERS).contains("1999") |
Removes $, €, ,, or . from price text before comparison. |
These transformations—complex in raw XPath—become simple, chainable methods in XPathy.
3. Logical Composition and Grouping
XPathy eliminates the risk of incorrect operator precedence by managing parentheses automatically when grouping conditions.
| Feature | XPathy Example | Generated XPath |
|---|---|---|
| Simple AND | div.byAttribute(id).equals("form").and().byText().contains("Login") |
//div[@id='form' and contains(text(), 'Login')] |
| Union (OR Group) | button.byAttribute(id).union(Or.equals("btn1"), Or.equals("btn2")) |
//button[@id='btn1' or @id='btn2'] |
| Nested Logic | div.byCondition(and(text()..., or(attr()...))) |
Generates fully parenthesized complex logic. |
This fluent logical API enables modeling of complex business rules (e.g., Must be a featured product OR a high-rated product, BUT NOT expired) in clear Java code.
4. Advanced Relationship Testing with Having
The Having Operation allows you to filter a target element based on related elements (child, ancestor, or sibling) without leaving the target context.
| Use Case | XPathy Code | Generated XPath |
|---|---|---|
| Check Descendant | div.byAttribute(class_).equals("card").and().byHaving().descendant(span.byText().contains("In Stock")) |
//div[@class='card' and (.//span[contains(text(), 'In Stock')])] |
| Check Preceding Sibling | input.byAttribute(name).equals("user").and().byHaving().precedingSibling(label.byText().equals("Username")) |
//input[@name='user' and (preceding-sibling::label[text()='Username'])] |
This allows you to say: "Find the DIV only if it has a descendant SPAN that says 'In Stock'." It's essential for locating containers based on dynamic content.
Conclusion
XPathy is more than just a convenience wrapper; it's a paradigm shift in how web element locators are created. By moving from fragile string-based syntax to a resilient, object-oriented, fluent API, XPathy ensures locators are:
- Clear: Intent is expressed explicitly.
- Robust: Built-in transformations neutralize common UI quirks.
- Maintainable: Code is self-documenting and easy to update.
If your team struggles with flaky tests, complex XPath, and slow debugging cycles, XPathy offers a definitive Java-native solution that makes raw XPath truly obsolete.
XPathy provides a lot more: Read the Full Documentation:
The Repository:
https://github.com/Volta-Jebaprashanth/xpathy
Happy Testing! 🚀
Top comments (0)