Use an attribute predicate: //element[@attribute='value']. For example, //input[@value='f'] selects every input whose value attribute is exactly f. In Selenium, pass the same expression to By.xpath() (or the equivalent XPath locator in another binding). Choose equality, contains(), starts-with(), or a token-safe class expression according to the match you actually need.
The basic attribute-value expression
An XPath predicate is the bracketed test after an element name. The abbreviated @attribute notation refers to that element’s attribute axis. Thus:
//input[@name='email']
means “find input elements whose name attribute equals email.” The attribute axis contains the attributes of the context element; it is empty for non-element context nodes. A predicate evaluates once for each candidate node and keeps the candidates whose test is true.
//button[@aria-label='Save']
//*[@data-testid='save']
//form[@id='signup']//input[@name='email']
// searches descendants throughout the document. A constrained ancestor, such as the signup form in the last example, prevents an unrelated field elsewhere from matching.
#1 Best Overall
Exact, combined, and existence tests
Exact value
Use quoted equality when the complete attribute value is known:
//input[@type='email']
//div[@role='dialog']
XPath string comparisons are case-sensitive in XPath 1.0. Whitespace and capitalization must match the attribute value in the live document.
Attribute exists
Leaving out the equality test checks for presence, regardless of the value:
//button[@disabled]
//input[@required]
Several conditions
Combine predicates with and, or, and not():
//input[@type='text' and @name='email']
//input[@type='email' or @type='text']
//input[not(@type='hidden')]
Every condition in an and expression must pass; at least one must pass for or.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Substring, prefix, suffix, and class-token matching
Contains a substring
contains() returns true when its first string contains its second string:
//a[contains(@href, '/docs/')]
//div[contains(@id, 'checkout')]
This is deliberately broad. contains(@class, 'card') also matches postcard and cardigan, so do not use it for a whitespace-separated class list.
Rank #2
- Used Book in Good Condition
Starts with a prefix
//div[starts-with(@id, 'item-')]
//input[starts-with(@name, 'shipping_')]
Ends with a suffix in XPath 1.0
XPath 1.0 has no ends-with(). Compare the final characters with substring() and string-length():
//tr[substring(@id, string-length(@id)-string-length('-row')+1)='-row']
The arithmetic starts the substring at the position where the -row suffix begins.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteMatch one class token, not a substring
Classes are separated by whitespace. Pad the normalized value with spaces, then search for a padded token:
//*[contains(concat(' ', normalize-space(@class), ' '), ' card ')]
This matches class="card featured" but not class="postcard". normalize-space() collapses repeated whitespace and removes leading or trailing spaces before padding.
Case-insensitive matching that remains portable
XPath 1.0 comparisons are case-sensitive. For portable ASCII case folding, translate uppercase letters in the attribute to lowercase before comparing:
//div[translate(@role, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='dialog']
This handles English letters only. Newer XPath implementations may provide other functions, but browser automation commonly exposes XPath 1.0 behavior; confirm the engine before relying on extensions.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Attribute tests versus element text
An attribute predicate and a text predicate inspect different data:
//button[@aria-label='Save']
//button[contains(., 'Save')]
The first checks the aria-label attribute. The second checks the element’s string-value, including descendant text. If a visible label is rendered as nested markup, the text expression may be appropriate; if the accessible name is stored in an attribute, test that attribute directly.
Quotes and values containing quotes
Use single quotes around a value containing double quotes, or double quotes around a value containing single quotes:
//div[@data-note="Sam's panel"]
//div[@data-note='A "quoted" panel']
If the value contains both quote types, construct it with concat():
//div[@data-note=concat('He said "', 'open', '" and it', "'s ready")]
When generating XPath in a programming language, escape the host-language string as well as the XPath literal. Printing the final expression before evaluating it often exposes a quoting error.
Scope, document context, frames, and shadow roots
Make the path specific
Global paths such as //input[@name='email'] are easy to write but can match more than one component. Anchor to a stable semantic ancestor:
//form[@data-testid='billing']//input[@name='email']
Prefer data-testid, name, or aria-label supplied for semantics. Generated CSS-module or framework class names can change between builds, and absolute paths such as /html/body/div[2]/div[1] are brittle.
Frames
XPath is evaluated in the current document. In Selenium, switch into an iframe before locating its descendants, then switch back to the default content when finished. A syntactically correct expression returns nothing if evaluated in the parent document.
Recommended Free Tools
Shadow DOM
Browser automation treats a shadow root as a separate tree boundary. Locate the host, obtain its shadow root using the automation API, and evaluate the XPath (or a shadow-root-supported locator) inside that root. An XPath run against the outer document cannot cross the boundary.
XML namespaces
For namespaced XML, bind a prefix in the XPath host and use that prefix in element and attribute names. An unprefixed QName is in no namespace, so an expression that looks right can return zero nodes when the source vocabulary is namespaced. The prefix you bind is a host-side alias; it need not equal the document’s prefix.
Selenium examples
Java
Selenium’s official locator strategy accepts the XPath string directly:
WebElement element = driver.findElement(
By.xpath("//input[@value='f']")
);
assertEquals("radio", element.getAttribute("type"));
assertEquals("f", element.getAttribute("value"));
Python
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
try:
driver.get("https://example.test/form")
email = driver.find_element(By.XPATH, "//input[@name='email']")
email.send_keys("[email protected]")
finally:
driver.quit()
Use find_elements while exploring a locator: it returns an empty list instead of immediately raising a no-such-element exception, allowing you to inspect how many nodes matched.
Best Value
JavaScript
const element = await driver.findElement(
By.xpath("//button[@aria-label='Save']")
);
Equivalent XPath locator APIs in other Selenium bindings use the same expression; only the host-language call changes.
A practical selection workflow
- Inspect the live DOM in developer tools, not only the original HTML response. Client-side rendering can add, remove, or alter attributes.
- Copy the exact attribute name, capitalization, and whitespace. Check whether the value is stable across sessions.
- Choose the narrowest matching operation: equality for a whole value,
contains()for a substring,starts-with()for a prefix, or the padded expression for a class token. - Add a semantic element name and stable ancestor to reduce accidental matches.
- Test the expression in the browser’s XPath search or with Selenium’s plural lookup, then assert the expected count and key attributes.
- If no node is found, verify frame selection, shadow-root boundaries, namespace bindings, and whether the element appears only after JavaScript or a wait condition runs.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No matches | Wrong attribute spelling, case, or live value | Inspect the live DOM and copy the attribute exactly. |
| Too many matches | Global // path or broad substring test |
Anchor to a stable ancestor; replace contains() with equality where possible. |
| Class locator matches unintended elements | Substring collision such as postcard |
Use contains(concat(' ', normalize-space(@class), ' '), ' token '). |
| Element appears later | Asynchronous rendering | Wait for the element or a stable attribute before evaluating XPath. |
| Works outside an iframe but not inside | Wrong document context | Switch into the frame first. |
| XML query returns zero nodes | Namespace not bound | Register a prefix and use it in the XPath. |
| Single lookup throws immediately | No node matched | Use a plural lookup during diagnosis, then restore a single-element assertion once stable. |
| Generated locator breaks after a release | Framework-generated class or index changed | Target a semantic data-testid, name, or ARIA attribute. |
Or skip the browser setup
If your goal is a screenshot rather than interaction or element assertions, ScreenshotNeo returns a rendered image or PDF with one request. Its cleanup step accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
It also offers an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools. Every plan includes the full feature set, including selectors, custom JavaScript and CSS, waits, device presets, PDFs, signed links, asynchronous webhooks, bulk capture, and caching.
See the ScreenshotNeo API documentation for parameter details. A direct call is:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemscurl -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}`);
The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; the service bills only clean shots, not failed or blocked captures. Create a free ScreenshotNeo account.
XPath choices at a glance
| Need | Expression | Trade-off |
|---|---|---|
| Whole attribute value | //input[@name='email'] |
Most exact and readable. |
| Any value, attribute present | //button[@disabled] |
Does not constrain the value. |
| Substring | //a[contains(@href, '/docs/')] |
Can match unintended text. |
| Prefix | //div[starts-with(@id, 'item-')] |
Useful for predictable generated prefixes. |
| Suffix in XPath 1.0 | substring(...)='-row' |
More verbose because no native ends-with(). |
| Class token | contains(concat(' ', normalize-space(@class), ' '), ' card ') |
Avoids substring false positives. |
Frequently Asked Questions
Does @value refer to a property or an HTML attribute?
In an XPath expression it addresses the element’s attribute node. Browser automation may expose a separate DOM property API, so use the API appropriate to the value you intend to verify.
Can XPath select an element when the attribute is missing?
An equality or function test on a missing attribute evaluates as an empty string and normally fails; use a separate presence test such as [@data-state] when existence itself is the requirement.
Which locator is better when both XPath and CSS selectors can express the same attribute test?
Compare exactness, stability, scope, portability, and readability for your team. XPath is useful for relationships, text, and functions; CSS is often shorter for straightforward attribute selectors.
The Bottom Line
Start with //element[@attribute='value'], then choose the narrowest function that matches your data. Scope the path to a stable ancestor, treat classes as tokens, and verify frame, shadow-root, namespace, and timing context before changing a working expression.
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.

