Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use an XPath text predicate to locate the cell, header, or row you need. For an exact table-cell value with insignificant whitespace ignored, start with //table//td[normalize-space(.)='Expected value']. In Java and Python, pass that XPath to Selenium’s findElement (or find_element). Scope it to the correct table or row whenever text can repeat, and wait for the element when the page is populated dynamically.

Basic text lookup with XPath

XPath is the practical choice when the locator itself must inspect displayed text. The dot in normalize-space(.) represents the element’s string value, including text in descendant elements such as a <span>. normalize-space removes leading and trailing whitespace and turns runs of whitespace into single spaces.

Java

WebElement cell = driver.findElement(
    By.xpath("//table//td[normalize-space(.)='Expected value']")
);

Python

from selenium.webdriver.common.by import By

cell = driver.find_element(
    By.XPATH,
    "//table//td[normalize-space(.)='Expected value']"
)

Change td to th for a header, or use a more specific element path when the value belongs to a button, link, or nested control. The expression is relative to the current browsing context, so you must first switch into the correct frame if the table is inside an iframe.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exact, partial, and scoped patterns

Exact value in a known table

//table[@id='orders']//td[normalize-space(.)='Paid']

This is an exact comparison after whitespace normalization. It will not match “Unpaid” or “Paid – refunded”. An ID is only an example; use the table’s stable ID, data attribute, class, or another identifying predicate that reflects the current DOM.

Find a row by one cell, then another cell in that row

//table[@id='orders']//tr[td[normalize-space(.)='Order 123']]//td[normalize-space(.)='Paid']

The first predicate selects rows containing the identifying order cell. The second part selects the status cell from that same row, preventing a “Paid” value in another row from becoming the match.

Substring matching

//table[@id='orders']//td[contains(normalize-space(.), 'Paid')]

Use contains only when a partial match is intentional. It can match “Unpaid” and any longer label containing “Paid”. For values with punctuation, variable suffixes, or a known prefix, make the substring rule explicit rather than using it as a substitute for an exact value.

Include nested text

Use the element’s dot string value when a cell wraps text in descendants:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
//table//td[normalize-space(.)='Ready']

A predicate based on text() can miss a value split across child elements, such as <span>Re</span><span>ady</span>. Inspect the actual markup before choosing between a descendant-aware expression and a direct text-node expression.

Scope to a section or row

Whole-document searches are easy to write but fragile when several tables contain the same label. Anchor the XPath to a unique table, a caption, a container, or a row key. The narrowest readable locator that still describes the target is usually easiest to maintain.

Reading the value Selenium sees

Selenium’s element text is rendered text: Java exposes it through getText(), and Python through .text. That is the appropriate value when your expected result is what a user can see.

// Java
String actual = cell.getText();

# Python
actual = cell.text

An input’s current value is not necessarily rendered element text. For an input, inspect its value property or attribute instead of assuming .text contains what the user typed. The same distinction applies to other runtime properties and attributes. Selenium’s element-information guidance explains these separate forms of element data in its official documentation.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing XPath versus CSS

Selenium recommends a unique, stable ID when one exists and generally favors a well-written CSS selector when an ID is unavailable. CSS is excellent for structural conditions, attributes, and classes, but standard CSS selectors do not express “element whose displayed text equals this value.” XPath directly expresses that text predicate and relationships such as “a row containing this cell.”

Therefore, use CSS or an ID when those selectors identify the element independently of its text. Use XPath when the displayed value is the requirement, when you must relate two cells in one row, or when nested descendant text matters. Keep the XPath compact; avoid coupling it to generated class names or unnecessary levels of markup.

One match or every match?

Singular lookup

findElement in Java and find_element in Python return the first matching element. That is convenient only when the locator is intended to be unique. A first result does not prove that the XPath identifies the correct cell.

Plural lookup and uniqueness checks

Use plural lookup when repeated values are valid or when you need to verify uniqueness.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Java
List<WebElement> matches = driver.findElements(
    By.xpath("//table//td[normalize-space(.)='Paid']")
);
for (WebElement match : matches) {
    System.out.println(match.getText());
}

# Python
matches = driver.find_elements(
    By.XPATH,
    "//table//td[normalize-space(.)='Paid']"
)
for match in matches:
    print(match.text)

After collecting the results, assert the count your test actually requires: zero for an optional value, one for a unique business key, or a known count for a repeated status. If order matters, verify the row identity as well as the text.

Waiting for dynamic tables

A correct XPath can still fail if the table has not appeared when Selenium searches. Selenium identifies looking too early as a common cause of NoSuchElementException. Prefer an explicit wait for the relevant condition rather than an arbitrary sleep.

Java explicit wait

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement paidCell = wait.until(
    ExpectedConditions.visibilityOfElementLocated(
        By.xpath("//table[@id='orders']//td[normalize-space(.)='Paid']")
    )
);

Python explicit wait

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

paid_cell = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((
        By.XPATH,
        "//table[@id='orders']//td[normalize-space(.)='Paid']"
    ))
)

Choose the condition that matches the test’s intent. Presence checks that the node exists; visibility additionally requires it to be displayed. If a table renders a placeholder and later replaces it, wait for the identifying row or value rather than merely waiting for the table tag.

Frames, shadow DOM, and changing markup

Iframe tables

XPath cannot cross a browsing-context boundary. Locate the iframe, switch to it, perform the table lookup, then switch back:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
driver.switchTo().frame(driver.findElement(By.cssSelector("iframe.orders")));
WebElement cell = driver.findElement(
    By.xpath("//table//td[normalize-space(.)='Paid']")
);
driver.switchTo().defaultContent();

Use the equivalent switch_to.frame and switch_to.default_content methods in Python. If the frame itself is inserted asynchronously, wait for it before switching.

Shadow DOM

A normal document XPath does not automatically traverse a shadow root. Access the shadow host, obtain its shadow root using Selenium’s supported shadow-DOM API, and search within that root. The exact code depends on your Selenium language binding and the component’s structure. Inspect the rendered DOM and keep each search inside the correct root.

Virtualized or replaced rows

Some grids render only visible rows or replace row nodes while scrolling. A text locator may return no result until the row is rendered. Scroll or use the component’s own pagination/filtering control, then wait for the target row. If a framework re-renders after every interaction, locate the element again instead of reusing a stale reference.

Troubleshooting failed text locators

NoSuchElementException or an empty plural result

  • Confirm the table and cell tags in the current DOM; the value may be in a th, a link, or a nested control rather than a td.
  • Check capitalization, punctuation, non-breaking spaces, and the exact value after normalization.
  • Scope the XPath to the table that is actually on the page; duplicate labels elsewhere can mislead both debugging and assertions.
  • Wait for the target row or cell if JavaScript fills the table after navigation.
  • Switch into the correct iframe or shadow root before searching.

Selenium’s common-errors guide covers wrong location and timing causes in more detail.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Invalid selector error

Check quotes, brackets, and parentheses in the XPath. A common mistake is passing an XPath expression with the CSS locator strategy, for example using By.cssSelector for a string beginning with //. Use By.xpath in Java or By.XPATH in Python. Selenium’s element-finding documentation describes the locator APIs.

The wrong cell is returned

Replace a whole-document expression with a table- or row-scoped one. Then use plural lookup to see how many candidates exist. If the same business value can legitimately occur twice, add a second condition such as an order ID, date, or row attribute instead of relying on document order.

Text appears visible but does not match

Print the value Selenium reads with getText() or .text and inspect the DOM. The screen may show an input value, a pseudo-element, or text rendered outside the cell. Retrieve an input’s value property, target the element that owns the text, or use the component’s accessible label when that is the real user-facing identifier.

Practical locator checklist

  • Prefer a stable ID or other unique attribute when it identifies the target without text.
  • When text is required, use normalize-space(.) for exact whitespace-normalized matching.
  • Use contains only for an intentional partial match.
  • Anchor repeated values to the intended table and identifying row.
  • Use plural lookup to test uniqueness rather than assuming the first result is correct.
  • Wait for dynamic content and use the correct frame or shadow root.
  • Read rendered text with getText() or .text; read runtime values through the appropriate property or attribute API.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a repeatable image or PDF of a table rather than an interactive WebDriver assertion, ScreenshotNeo returns a screenshot or PDF from one request. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo documentation for all options. A direct cURL request is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo supports full-page captures with lazy images loaded, CSS-selector element captures, 12 device presets plus custom viewports, retina scale, dark mode, PDF paper and page-range controls, custom CSS and JavaScript, click and wait actions, request and resource blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account to try it.

Further official references

Selenium’s locator strategies and locator guidance explain stable-selector practice. The Python binding’s By API reference lists the supported locator constants.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I locate a table cell by visible text with CSS alone?

Not with standard Selenium CSS selectors. Use XPath for a text predicate, or locate the cell by a stable attribute and then verify its rendered text.

Should I use text() or a dot in the XPath predicate?

Use the dot string value when text may be wrapped in descendant elements. A direct text-node predicate can be appropriate only when the markup guarantees one direct text node.

How do I prove a text locator is unique?

Use plural lookup, inspect the returned count, and assert the count expected by the test. Scope the XPath further if multiple legitimate matches remain.

Why does the browser show a value that Selenium’s text property cannot read?

The value may be an input property, pseudo-element content, or content in another browsing context. Inspect the DOM and retrieve the appropriate property, switch context, or target the element that owns the rendered text.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.