HasData
Back to Q&A

How do you use XPath to find an element that contains specific text?

To match an element by partial text in XPath, use contains(). //button[contains(text(), 'Submit')] selects any button whose direct text node includes “Submit”. When the element has nested tags (a <span> inside the button, an icon, bold text), replace text() with .:

//button[contains(., 'Submit')]

This matches against the full string value including descendants.

Matching by text() vs dot

text() returns the direct text children of an element, not its full visible string value. Once markup splits the label, the predicate misses the nested part:

//button[contains(text(), 'Buy now')]

That fails on <button>Buy <span>now</span></button> because the direct text node is just "Buy ". Switch to ., which is the concatenated string of the element and all descendants:

//button[contains(., 'Buy now')]

If whitespace or line breaks split the label, wrap it: contains(normalize-space(.), 'Buy now'). Default to . (or normalize-space(.)) unless you know the element has a single clean text node.

Matching by attribute

The same contains() shape works against any attribute:

//input[contains(@class, 'user-input')]

Swap @class for @id, @href, @aria-label, or any @data-* attribute. Visible text changes with copy edits, A/B tests, and localization, so attribute selectors usually survive deploys longer than text selectors.

Combining conditions

When one contains() matches too many nodes, narrow with and:

//div[contains(@class, 'alert') and contains(., 'successfully')]

Use or as a fallback when the same label can live in either visible text or an aria-label: //button[contains(., 'Submit') or contains(@aria-label, 'Submit')].

Run XPath against pages that actually loaded

HasData fetches blocked, rendered, or protected pages, so XPath has something to query.