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

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.

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

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.

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

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
XPath 2.0 Programmer's Reference
  • 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.

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

Match 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.

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

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():

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
//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.

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

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.

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

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

  1. Inspect the live DOM in developer tools, not only the original HTML response. Client-side rendering can add, remove, or alter attributes.
  2. Copy the exact attribute name, capitalization, and whitespace. Check whether the value is stable across sessions.
  3. 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.
  4. Add a semantic element name and stable ancestor to reduce accidental matches.
  5. Test the expression in the browser’s XPath search or with Selenium’s plural lookup, then assert the expected count and key attributes.
  6. 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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}`);

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.

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

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.

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.