Resource library

QA How-To

How to Use Selenium By xpath dynamic in Java (2026)

Learn selenium By xpath dynamic java patterns for changing attributes, text, tables, and waits, with safe Selenium 4 code and debugging guidance in CI.

22 min read | 3,203 words

TL;DR

For selenium By xpath dynamic java, identify the stable part of an element and express only the changing part with `contains()`, `starts-with()`, text normalization, or a relationship axis. Store the expression as a `By`, wait for the exact state, and avoid absolute paths, global indexes, and unescaped runtime values.

Key Takeaways

  • Anchor a dynamic XPath to stable meaning such as a label, role, container, or durable attribute.
  • Use `contains()` and `starts-with()` only on the changing attribute segment, not across the whole page.
  • Build locators as `By` values and find elements after the page reaches the required state.
  • Escape runtime text before inserting it into an XPath expression, or prefer a stable test attribute.
  • Use explicit waits with a locator so Selenium can re-find elements after DOM replacement.
  • Treat indexes and absolute paths as last resorts because ordinary layout changes invalidate them.
  • Debug XPath in the browser and inspect the application state before increasing timeouts.

The safest approach to selenium By xpath dynamic java is to locate an element by the stable meaning around it, then describe only the fragment that changes. In Java, create a By with By.xpath(...), pass that locator to an explicit wait, and let Selenium find the current DOM node when the condition is satisfied.

Dynamic does not mean random. IDs generated per build, changing table rows, localized text, and repeated cards usually still have a stable label, container, prefix, or data attribute. This guide turns those signals into readable XPath, shows runnable Selenium 4 code, and explains when CSS or a test ID is the better engineering choice.

TL;DR

Situation Recommended locator shape Avoid
ID has a stable prefix //button[starts-with(@id,'save-')] Copying the full generated ID
Class has a stable token //div[contains(concat(' ',normalize-space(@class),' '),' card ')] contains(@class,'card') false matches
Visible label identifies a control //label[normalize-space()='Email']/following::input[1] A page-wide input index
Repeated table rows //tr[td[normalize-space()='INV-42']]//button[@name='open'] //tr[7]//button
Runtime value is inserted Quote it safely or use a stable test attribute Raw string concatenation
DOM node is replaced Wait with By, then interact Caching a WebElement

A durable XPath answers three questions: what stable fact identifies the target, what scope limits the search, and what browser state must exist before the action.

1. Selenium By xpath dynamic Java: What It Actually Means

A dynamic XPath is an XPath expression designed for a DOM whose incidental values or structure can change. The Selenium API is not different. Java still calls By.xpath(String xpathExpression). The design work is choosing an expression that survives valid page changes while identifying exactly one intended element.

Consider a button rendered as <button id="save-94821" data-testid="save-profile">Save</button>. The numeric ID suffix may change on every render. The button's function does not. Possible strategies include the stable data-testid, accessible text, or the save- ID prefix. The strongest choice depends on the application's contract. If the team owns the app, By.cssSelector("[data-testid='save-profile']") is clearer than a dynamic XPath. XPath becomes valuable when the relationship between elements carries the stable meaning.

Dynamic XPath should not be confused with constructing a different locator after every failure. A good locator is deterministic. Given the same meaningful page state, it selects the same target. It may include a case ID or product name supplied by a test, but the expression shape remains intentional and reviewable.

Use a dynamic locator only after deciding which DOM changes are legitimate. A locator that matches any button containing Save may survive redesigns, but it can silently click Save as draft instead of Save profile. Resilience without precision creates false confidence.

2. Set Up a Runnable Selenium 4 Java Example

Modern Selenium 4 includes Selenium Manager. With a supported browser installed, new ChromeDriver() can discover or obtain a compatible driver when no driver is otherwise configured. You do not need to set webdriver.chrome.driver for the basic local case. Keep Selenium on a maintained 4.x release in your build and use the APIs shown here.

The following JUnit 5 test loads a self-contained page through a data URL, waits for a dynamically identified button, and clicks it. It uses only current Selenium WebDriver APIs.

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

class DynamicXpathTest {
    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setUp() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new");
        driver = new ChromeDriver(options);
        wait = new WebDriverWait(driver, Duration.ofSeconds(5));
    }

    @AfterEach
    void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    void clicksButtonWithChangingIdSuffix() {
        String html = """
            <button id='save-94821' onclick="this.textContent='Saved'">Save</button>
            """;
        driver.get("data:text/html;charset=utf-8,"
                + URLEncoder.encode(html, StandardCharsets.UTF_8));

        By saveButton = By.xpath("//button[starts-with(@id,'save-')]");
        wait.until(ExpectedConditions.elementToBeClickable(saveButton)).click();

        assertEquals("Saved", driver.findElement(saveButton).getText());
    }
}

Run browser tests in an environment that permits Chrome startup. For a grid, create RemoteWebDriver with the grid URL and browser options, while keeping the locator and wait code unchanged.

3. Choose a Stable Anchor Before Writing XPath

Start from meaning, not syntax. Open the element's DOM and list candidate signals. Stable signals often include name, aria-label, role, an exact field label, a durable data-testid, a unique heading, or a container representing one domain object. Generated hashes, framework classes, inline styles, and child positions are weak signals.

Use the narrowest stable scope. Instead of searching the whole document for a button named Edit, first identify the customer card, invoice row, or dialog, then find its edit control. This turns an ambiguous global match into an intentional local relationship.

String customerName = "Avery Stone";
By editCustomer = By.xpath(
    "//section[@aria-label='Customers']"
  + "//article[.//h3[normalize-space()=" + xpathLiteral(customerName) + "]]"
  + "//button[@name='edit']"
);

This expression says more than //button[3]. It identifies the Customers region, a card whose heading exactly matches the customer, and the edit button inside that card. Reviewers can understand its business intent.

Before adopting XPath, compare it with a CSS selector. CSS is concise for stable attributes and descendant relationships. XPath is stronger for matching normalized text, moving from a label to a related input, selecting a row based on a cell, or navigating to an ancestor. The Selenium locator strategy guide provides a broader selection framework.

A locator contract is a team decision. Developers can expose stable test IDs or accessible labels, and test engineers can avoid coupling to layout. That conversation usually saves more maintenance than a clever expression.

4. Match Dynamic Attributes Without Overmatching

Use exact equality when the complete attribute is stable. Use starts-with() when a known prefix is stable and contains() when a meaningful substring can occur away from the start. XPath 1.0, which browsers expose to WebDriver, does not provide ends-with(). You can emulate a suffix with substring(), but a different attribute is often clearer.

By stablePrefix = By.xpath("//input[starts-with(@id,'billing-email-')]");
By stableFragment = By.xpath("//button[contains(@aria-label,'Delete invoice')]");
By exactName = By.xpath("//input[@name='email']");

Class matching needs special care. contains(@class,'card') also matches discarded and cardinal. Match a space-delimited token instead:

By activeCard = By.xpath(
    "//article["
  + "contains(concat(' ', normalize-space(@class), ' '), ' customer-card ')"
  + " and contains(concat(' ', normalize-space(@class), ' '), ' active ')"
  + "]"
);

Combine conditions with and only when each condition adds stable precision. An expression containing six partial attributes can be more fragile than one exact domain attribute. Use or carefully because it broadens the match and may hide a product change.

If an attribute looks generated, verify its behavior across reloads, builds, and representative records. Do not assume every number is unstable. An invoice ID can be the best domain key, while a React-generated suffix is incidental. Record why the chosen piece is stable in the page object name or a short comment when it is not obvious.

5. Match Text Safely With normalize-space()

Text is useful when it is visible, meaningful, and controlled. The text() node test matches direct text nodes only. A button containing nested markup such as <button><span>Save</span></button> may not match text()='Save'. A dot, ., evaluates the string value of the element including descendant text. normalize-space(.) trims leading and trailing whitespace and collapses internal whitespace sequences.

By save = By.xpath("//button[normalize-space(.)='Save profile']");
By invoiceHeading = By.xpath("//h2[contains(normalize-space(.),'Invoice INV-42')]");

Exact normalized text is safer than a partial match when copy is stable. Partial text is appropriate when a known dynamic value is embedded in a longer message. Scope it to the relevant dialog or notification region.

Text locators become fragile when the product is localized, copy changes frequently, or the same wording appears in several components. Prefer an accessible name API or stable attribute where available. Selenium's XPath implementation does not offer Playwright-style role locators. You can locate @role and @aria-label, but remember that an HTML element's implicit role is not automatically materialized as a role attribute in the DOM.

Never lower-case text in Java and assume the XPath sees the altered value. For limited ASCII case-insensitive matching in XPath 1.0, translate() works, but it is verbose and incomplete for international text. A stable test hook or application-level normalization is usually more robust.

6. Use Axes for Labels, Containers, and Sibling Controls

XPath axes express relationships that CSS cannot always express directly. Common axes include ancestor, descendant, following-sibling, preceding-sibling, and following. Choose the narrowest relationship that reflects the component markup.

Suppose a field is wrapped with its label:

<div class="field">
  <label>Email address</label>
  <input name="contactEmail">
</div>

A relationship-based locator is clear:

By email = By.xpath(
    "//label[normalize-space(.)='Email address']"
  + "/following-sibling::input[@name='contactEmail']"
);

If the input is nested differently, move to the shared field container and search within it:

By emailByContainer = By.xpath(
    "//div[contains(concat(' ',normalize-space(@class),' '),' field ')"
  + " and .//label[normalize-space(.)='Email address']]//input"
);

Avoid following::input[1] unless the markup contract truly means the first input anywhere later in document order. It can cross container boundaries after a layout change. ancestor::form[1] or a named container often provides safer scope.

Axes should describe the UI component, not reverse engineer its current pixel arrangement. If the app already connects a label to an input with for and id, locate the input by that stable ID. The relationship locator is most useful when stable direct attributes are unavailable and the visible label is the intended contract.

7. Build Dynamic Table and List XPath Expressions

Tables are a strong XPath use case because one cell can identify a row and another descendant can provide the action. Select the row by a domain key, then select a named control within that row.

static By invoiceAction(String invoiceNumber, String actionName) {
    return By.xpath(
        "//table[@aria-label='Invoices']//tr["
      + "td[normalize-space(.)=" + xpathLiteral(invoiceNumber) + "]]"
      + "//button[@name=" + xpathLiteral(actionName) + "]"
    );
}

By openInvoice = invoiceAction("INV-42", "open");

For ARIA grids built from div elements, anchor by roles only if the markup sets those attributes reliably. A locator might select a role='row' containing a role='gridcell' with the target text. Always verify uniqueness with findElements(locator).size() during development, then enforce the intended condition in the test.

Indexes can be legitimate inside a well-defined component. For example, the second cell in a row may be a published table contract. A global expression such as (//button[@name='open'])[7] is not. Pagination, sorting, hidden template rows, and responsive layouts change global positions.

Virtualized lists add another constraint: an off-screen item may not exist in the DOM. No XPath can find a node that has not been rendered. Use the component's supported search, filter, pagination, or scrolling behavior, and wait for the intended row to appear. Keep row selection separate from navigation so failures reveal whether data was missing or the locator was wrong.

8. Escape Runtime Values Before Constructing XPath

Raw string concatenation breaks when a customer name contains an apostrophe or quotation mark. It can also turn test data into XPath syntax. Selenium does not provide a public XPath parameter-binding API, so either choose a stable non-XPath attribute, validate the input, or generate a correct XPath string literal.

This helper handles values containing single quotes, double quotes, or both:

static String xpathLiteral(String value) {
    if (!value.contains("'")) {
        return "'" + value + "'";
    }
    if (!value.contains("\"")) {
        return "\"" + value + "\"";
    }

    String[] parts = value.split("'", -1);
    StringBuilder expression = new StringBuilder("concat(");
    for (int i = 0; i < parts.length; i++) {
        if (i > 0) {
            expression.append(", \"'\", " );
        }
        expression.append("'").append(parts[i]).append("'");
    }
    return expression.append(")").toString();
}

Then insert the returned expression without adding another pair of quotes:

String person = "D'Angelo \"DJ\" Smith";
By row = By.xpath(
    "//tr[td[normalize-space(.)=" + xpathLiteral(person) + "]]"
);

This solves syntax, not locator design. Runtime values still need a defined source and meaning. Do not use unrestricted user input to create arbitrary selectors in a shared test service. Avoid logging sensitive values in locator failure messages. A database ID or safe test case key can be a better anchor than personal data.

Unit-test the helper with empty strings, each quote type, both quote types, whitespace, and Unicode. A tiny quoting defect can make hundreds of otherwise correct tests fail only for rare data.

9. Synchronize Dynamic XPath With Explicit Waits

A correct XPath can still fail when evaluated in the wrong state. Use WebDriverWait and an ExpectedConditions condition matching the next action. Presence means a node exists. Visibility adds displayed state. Clickability checks visible and enabled, but it cannot guarantee that an animation, overlay, or application rule will not intercept the click.

By status = By.xpath(
    "//section[@aria-label='Order status']"
  + "//*[normalize-space(.)='Ready']"
);

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.visibilityOfElementLocated(status));

Passing a By lets the condition locate the current node on each poll. This matters when a framework replaces a loading skeleton with a new element. A cached WebElement references the old node and may throw StaleElementReferenceException. The Selenium stale element fix guide explains the lifecycle in depth.

Wait for the business-ready state, not merely the disappearance of a spinner. A spinner can vanish before data binding or reappear during a retry. Strong conditions include the expected row appearing, a button becoming enabled, a status changing to a terminal value, or a dialog receiving the expected heading.

Keep implicit wait at zero or a consciously small value when using explicit waits. Mixing large implicit and explicit waits can make timeout behavior harder to predict. Never add Thread.sleep() to repair an uncertain state. It always waits the full duration and still fails when the event takes longer.

10. Put Dynamic XPath in Page Components, Not Test Noise

Page objects and component objects should expose business operations and meaningful locators. They should not hide every call behind generic click(String xpath) methods. A generic string API removes type safety, spreads selector syntax into tests, and makes failure messages vague.

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

final class InvoiceTable {
    private final WebDriver driver;
    private final WebDriverWait wait;

    InvoiceTable(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    void openInvoice(String invoiceNumber) {
        By open = By.xpath(
            "//table[@aria-label='Invoices']//tr["
          + "td[normalize-space(.)=" + xpathLiteral(invoiceNumber) + "]]"
          + "//button[@name='open']"
        );
        wait.until(ExpectedConditions.elementToBeClickable(open)).click();
    }
}

A component can own a root locator and search within a root WebElement, but re-locate the root when the whole component is frequently replaced. Name methods after user intent, such as openInvoice, not mechanics, such as clickRowButton.

Centralization does not mean one universal locator utility. The owner closest to the component understands its stable relationships. Keep the quoting helper small and shared, while keeping invoice-specific XPath with the invoice component. Add a focused browser test for locator helpers and page components when their behavior is nontrivial.

11. Debug a Dynamic XPath Systematically

When a locator fails, preserve the page URL, screenshot, relevant DOM fragment, browser console output when useful, application correlation ID, and exact locator. Determine whether the intended element existed in the captured state. A timeout message alone cannot separate wrong page, missing data, slow response, hidden overlay, iframe, shadow DOM, and invalid XPath.

Evaluate the expression in browser DevTools with $x("...") where supported. Check the match count and inspect every result, not just the first. Then reload or repeat the action to see which attributes actually change. Do not edit the XPath until it matches one result and you can explain why.

XPath operates within the current browsing context. Switch to the correct iframe before locating inside it. Standard XPath does not pierce a shadow root. Selenium 4 exposes getShadowRoot() for open shadow DOM, after which you locate within that search context using supported selectors. Treat these as context problems, not as reasons to create a broader XPath.

If local tests pass and CI fails, compare browser version, viewport, feature flags, locale, data, authentication, worker count, and captured markup. The same locator can correctly expose a different application state. The Selenium explicit wait patterns guide helps diagnose state synchronization separately from selection.

Increase a timeout only when evidence shows the expected condition is valid but legitimately needs a wider service-level bound. A longer timeout cannot repair a zero-match locator, wrong frame, or never-rendered virtual row.

12. Selenium By xpath dynamic Java Decision Checklist

Before merging a locator, confirm that it starts from a stable user or domain signal, searches within the smallest meaningful container, and selects the intended number of elements. Verify it against multiple records, a reload, and any supported locale or responsive layout that can change markup.

Use this priority order as a practical default:

  1. A unique stable ID, name, or test attribute.
  2. A concise CSS selector for stable attributes and hierarchy.
  3. An XPath based on exact text or a meaningful relationship.
  4. A partial attribute only for the documented dynamic segment.
  5. A component-scoped index when position is truly part of the contract.

The order is guidance, not a universal law. A label-to-input XPath may convey more stable meaning than a styling-oriented CSS class. Accessibility-related attributes can be strong, but only if the application maintains them correctly.

Review the wait alongside the locator. Ask what state proves the element is ready, whether the DOM node can be replaced, and what evidence will exist on failure. Test data also matters. A locator that depends on a unique invoice number requires the test to create or reserve that number safely under parallel execution.

Finally, challenge complexity. If an XPath needs several axes, multiple partial conditions, and a runtime index, request a stable application hook. Testability is a product capability, and a small markup improvement is often cheaper than years of locator maintenance.

Interview Questions and Answers

Q: What is a dynamic XPath in Selenium Java?

It is an XPath expression designed around stable parts of a changing DOM, such as a label, attribute prefix, domain key, or element relationship. Selenium still uses the normal By.xpath() API. The important design goal is unique, meaningful selection across legitimate page changes.

Q: When would you use contains() instead of starts-with()?

I use starts-with() when the stable fragment is guaranteed at the beginning of an attribute. I use contains() when the fragment can occur elsewhere. Both require careful scoping because partial matching broadens the result set.

Q: Why use normalize-space(.) for element text?

It trims boundary whitespace, collapses internal whitespace sequences, and includes descendant text through the dot string value. That handles formatted buttons and headings more reliably than an exact direct text() node comparison. It does not solve localization or frequently changing copy.

Q: How do you handle an apostrophe in a runtime XPath value?

I do not insert it between single quotes blindly. I generate a valid XPath literal, using the other quote type where possible and concat() when the value contains both types. If a safe stable ID exists, I prefer that over inserting personal or unrestricted text.

Q: Why pass a By to WebDriverWait instead of caching a WebElement?

A locator-based condition can find the current DOM node on every poll. If the application replaces a node during rendering, a cached reference can become stale. I wait for the exact state required by the next action and interact with the returned current element.

Q: Are absolute XPath expressions always wrong?

They are valid syntax, but they usually encode incidental page hierarchy. A wrapper or layout change can invalidate the whole path. I reserve positional paths for a small, stable component contract and prefer semantic anchors elsewhere.

Q: Can XPath locate an element inside shadow DOM?

A document-level XPath does not cross a shadow root. With an open shadow root, Selenium 4 can retrieve the shadow root as a search context, then locate inside it. I diagnose the boundary explicitly instead of making the document XPath broader.

Q: How do you test whether a dynamic XPath is robust?

I verify uniqueness, repeat the relevant render transitions, exercise representative data including quote characters, and check supported locales or layouts. I also capture failure artifacts and review whether a simpler stable attribute can replace the expression.

Common Mistakes

  • Copying an absolute XPath from DevTools and treating the current hierarchy as a contract.
  • Using a global index such as (//button)[5] for an element identified by business data.
  • Matching a class with contains(@class,'card'), which can select unrelated class names.
  • Concatenating runtime text without generating a valid XPath literal.
  • Caching WebElement fields on pages that replace nodes during rendering.
  • Combining a weak locator with a long timeout and calling the result stable.
  • Using text without considering duplicate wording, nested markup, or localization.
  • Searching the document when a dialog, table, row, or card can provide scope.
  • Assuming XPath can cross iframe or shadow-root boundaries automatically.
  • Retrying stale or missing-element failures without identifying the state transition.
  • Hiding domain operations behind generic clickXpath(String) helpers.
  • Choosing a clever expression when the application can expose a stable test attribute.

Conclusion

Reliable selenium By xpath dynamic java code starts with a stable semantic anchor, limits the search to a meaningful component, and handles only the documented changing fragment. Use By.xpath() with exact attributes, carefully scoped partial functions, normalized text, or axes, then synchronize through an explicit wait that can re-find the current node.

Apply the decision checklist to one flaky locator in your suite. Capture its real DOM states, replace positional details with a domain relationship, add safe runtime quoting if required, and verify the resulting locator across repeated renders before expanding the pattern.

Interview Questions and Answers

What is a dynamic XPath in Selenium Java?

A dynamic XPath uses stable semantic fragments or relationships to locate an element whose incidental attributes or position can change. Java creates it with the standard `By.xpath()` method. I scope it to a meaningful container and verify uniqueness across legitimate page states.

How do contains() and starts-with() differ in XPath?

`starts-with()` requires the fragment at the beginning of the string, while `contains()` matches it anywhere. A prefix is usually more restrictive when the application guarantees it. Both can overmatch, so I combine them with stable element type and component scope.

Why is normalize-space(.) useful in Selenium XPath?

It removes leading and trailing whitespace, collapses whitespace sequences, and reads descendant text through the element string value. That makes it useful for nested, formatted labels. I still avoid text coupling when copy or locale is unstable.

How would you locate an action in a dynamic table row?

I identify the row with a unique domain key in a cell, then search for the named action inside that row. I escape the runtime key correctly and wait for the row or control state. I avoid global indexes because sorting and pagination change them.

How do you prevent XPath injection or quote errors?

I prefer a stable attribute and controlled identifier. When runtime text is necessary, I generate a valid XPath literal, using single quotes, double quotes, or `concat()` as required. I also keep sensitive values out of diagnostic output.

How do explicit waits improve dynamic locator reliability?

A locator-based explicit wait polls for a defined state and can re-find the element if the DOM replaces it. It separates selection from readiness. I choose a condition that represents the next action, not a generic delay.

When should you request a data-testid instead of writing XPath?

I request a stable test hook when the available DOM signals are generated, positional, duplicated, or require a complex chain of axes. The hook should represent a durable component contract. A small product change can be much cheaper than maintaining a fragile selector.

Can Selenium XPath cross iframes and shadow roots?

No document XPath automatically crosses those boundaries. I switch to the correct frame before searching. For an open shadow root, I obtain its Selenium search context and locate within it.

Frequently Asked Questions

How do I write a dynamic XPath in Selenium Java?

Identify the stable part of the element or its container, then use `By.xpath()` with exact predicates, `starts-with()`, `contains()`, `normalize-space()`, or an axis. Scope the expression so it selects only the intended element and pass the locator to an explicit wait.

Is XPath or CSS better for dynamic elements?

CSS is usually simpler for stable attributes and descendant relationships. XPath is useful when visible text, a label relationship, an ancestor, or a table cell identifies the target. A stable test attribute is often better than either complex expression.

How can I match a dynamic ID in Selenium?

Use an exact stable attribute when possible. If only part of the ID is stable, use an expression such as `//button[starts-with(@id,'save-')]`, then add a meaningful container or second condition if the prefix is not unique.

Why does my XPath work in DevTools but fail in Selenium?

Selenium may evaluate it before the required state exists, in the wrong iframe, or against different data and markup. Capture the failing DOM, confirm the browsing context, check match count, and wait for the business-ready condition rather than adding a fixed sleep.

How do I use a variable inside XPath in Java?

Construct the expression from controlled values and convert each value to a valid XPath literal. Values containing both quote types require `concat()`. Prefer stable identifiers and never insert unrestricted sensitive input into locators or logs.

What is the best XPath for a dynamic table row?

Select the row by a unique domain cell, then locate the named action inside that row. For example, `//tr[td[normalize-space(.)='INV-42']]//button[@name='open']` is clearer and safer than a global row index.

Should I use Thread.sleep with a dynamic XPath?

No. A fixed sleep does not improve the locator and may be either unnecessarily slow or too short. Use `WebDriverWait` with a condition such as visibility, clickability, or expected text that describes the required state.

Related Guides