Free tools Windows power users keep installed
One-click scans. No signup required.
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 //*[contains(., '_')] to find elements whose complete string-value contains an underscore. The dot includes text in descendant elements. Use //*[contains(text(), '_')] only when the underscore must be in a direct text child, and use equality such as //*[. = '_ready_'] for an exact complete value.
The correct XPath for most cases
The general-purpose locator is:
//*[contains(., '_')]
contains() performs a substring test. The first argument, ., is the context element’s string-value: the text contributed by that element and its descendants. The underscore is an ordinary character inside the quoted XPath string, so it needs no special escaping.
For example, this markup is matched by the expression above:
<button>file_<strong>name</strong></button>
The button’s complete string-value is file_name, even though the word is split by a nested <strong> element.
#1 Best Overall
Choose the expression that matches your requirement
| Requirement | XPath | What it examines |
|---|---|---|
| Underscore anywhere in the element’s complete text | //*[contains(., '_')] |
The element string-value, including descendant text |
| Underscore in a direct text child | //*[contains(text(), '_')] |
Text-node children immediately under the element |
| Exact complete text | //*[. = '_ready_'] |
The whole element string-value must equal _ready_ |
| Underscore in an attribute | //*[@data-label and contains(@data-label, '_')] |
The value of the data-label attribute |
Substring matching versus exact matching
Find any occurrence
//*[contains(., '_')] matches user_name, _leading, trailing_, and __. It also matches an ancestor whose descendant contains an underscore, because that ancestor’s string-value includes descendant text.
Require one exact value
Use equality when the complete string must be one value:
//*[. = '_ready_']
This does not mean “contains the phrase.” An element with string-value status: _ready_ will not match. Equality also evaluates the element’s complete string-value, so nested markup is still included.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Match a known element type
Replace the wildcard with a name when the page has many matching nodes:
Rank #2
- Used Book in Good Condition
//button[contains(., '_')]
For namespaces or a larger document, add an identifying attribute or another structural predicate so that the result set describes the target, not every ancestor containing the same character.
Why text() can miss an underscore
text() selects direct text-node children. Consider:
<button>file_<strong>name</strong></button>
The button has a direct text node containing file_, so it happens to match. But if the underscore appears only inside the nested element, as in:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
<button>file<strong>_name</strong></button>
contains(text(), '_') checks the button’s direct text child (file) and can miss the underscore. contains(., '_') sees the combined descendant text and is the safer default when visual text may be wrapped in spans, emphasis tags, or other inline markup.
There is another XPath 1.0 subtlety: when a node-set such as text() is converted to a string, the conversion uses the first node in document order. An element with several direct text nodes therefore should not be treated as though text() automatically tests every one of them. Use the dot form for the complete string-value, or make the structure explicit if direct-child matching is intentional.
Text, attributes, and element boundaries
When the underscore is in an attribute
Element-text predicates do not inspect attributes. For a label stored in data-label, use:
//*[@data-label and contains(@data-label, '_')]
The first condition excludes elements without that attribute; the second tests the attribute value. Change @data-label to the actual attribute name, such as @id or @aria-label, when that is where the underscore lives.
When ancestors are unwanted
A broad descendant-aware predicate can return both the target and its parents. Scope by element name, class, or another relationship:
//section//button[contains(., '_')]
If a parent must not match merely because a child does, target the expected node type (for example, //button) or add a structural condition that identifies the leaf or control you need. The right scope depends on the page’s markup.
Using the locator in automation code
Pass the XPath as a string to your framework; XPath quoting and host-language quoting are separate layers. The following examples show the same locator in common environments.
Python with Selenium
from selenium.webdriver.common.by import By
matches = driver.find_elements(By.XPATH, "//*[contains(., '_')]
")
for element in matches:
print(element.text)
If your Python string uses single quotes outside, use double quotes around the underscore in XPath, or escape the outer quote. Keep the XPath valid first, then satisfy the programming language’s string rules.
JavaScript DOM evaluation
const result = document.evaluate(
"//*[contains(., '_')]",
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < result.snapshotLength; i++) {
console.log(result.snapshotItem(i));
}
In browser automation libraries that accept a locator string directly, provide //*[contains(., '_')] to the library’s XPath locator method rather than evaluating it as CSS.
Best Value
Version, case, and collation
XPath 3.1 defines contains() as a substring test that takes collations into account. XPath 3.1 expressions are case-sensitive by default. Case is irrelevant when the only character of interest is the underscore, but it matters if the same predicate also tests surrounding letters.
Do not assume that every browser, XML processor, or automation framework implements the same XPath version or exposes configurable collations. Check the host application’s XPath support before relying on version-specific behavior. The basic underscore expressions above use long-established syntax, while collation-sensitive or case-insensitive requirements may need a host-specific solution.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Practical troubleshooting
- No result when text is visibly present: inspect the markup. If the underscore is inside a descendant element, replace
text()with.. - Too many results: remember that a parent can match because a descendant contains the character. Replace
*with the expected element name or add an identifying predicate. - The whole value does not match:
contains()is substring matching. Use[. = 'value']for exact equality. - The underscore is not in text: select the attribute explicitly, for example
contains(@data-label, '_'). - Only some direct text nodes are considered: XPath 1.0 string conversion of a node-set uses the first node in document order. Use the element dot form when you need the combined string-value.
- Invalid expression in code: separate XPath quoting from host-language quoting. A quote that is valid in XPath may still terminate a Python, JavaScript, Java, or shell string.
- Unexpected case behavior: verify the XPath version and collation supplied by the host. Do not assume a browser’s evaluator supports every XPath 3.1 feature.
- Matches differ between tools: confirm the context node. An expression beginning with
//searches descendants of the context, and a framework may set that context to a document, frame, or component root.
A repeatable selection workflow
- Inspect whether the underscore is in element text or an attribute.
- Start with
//*[contains(., '_')]for complete descendant-aware text. - If only direct text is valid, test
contains(text(), '_')and verify that multiple text nodes are not being overlooked. - Switch to equality when the entire string must match.
- Reduce false positives by naming the element or adding a structural condition.
- Run the expression in the same frame, document, and XPath implementation used by your automation code.
Or skip the browser setup
If your next step is to obtain a clean visual capture of the page rather than inspect nodes interactively, ScreenshotNeo returns a screenshot or PDF from one GET request. It is separate from XPath selection, so use the expressions above when you need element locators; use this API when you need the rendered page asset.
Its API documentation is at https://screenshotneo.com/docs/. A 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
The same request in 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)
And 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}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and whether it was billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.
Create a free ScreenshotNeo account to try the 1,000 monthly screenshots without a card.
Quick decision guide
| Question | Use |
|---|---|
| Can the underscore appear inside nested markup? | contains(., '_') |
| Must it be a direct text child? | contains(text(), '_'), with the text-node caveat |
| Must the entire value be one exact string? | . = 'your_value' |
| Is the value stored in an attribute? | contains(@attribute, '_') |
| Are ancestors being returned accidentally? | Scope the path to the expected element or structure |
For ordinary element-text searches, begin with //*[contains(., '_')], then narrow the path only when the page structure requires it.
Quick Recap
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.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →

