Knowledge Base
How to Find Element by Text Using Selenium
A hands-on Selenium guide to locating elements by their visible text, covering exact and partial XPath matches, whitespace pitfalls and stable waits.
Knowledge Base
A hands-on Selenium guide to locating elements by their visible text, covering exact and partial XPath matches, whitespace pitfalls and stable waits.
Sometimes the only thing you know about an element is the words a user sees on screen, such as a button that reads Sign in or a link labelled View more. Selenium has no dedicated By.TEXT strategy, so finding elements by text means writing the right XPath expression.
This tutorial shows exact and partial text matching, how to deal with whitespace and casing, and how to combine text locators with waits so they hold up on real, dynamic pages.
Beyond exact and partial XPath matching, finding elements by text reliably means dealing with translation, navigating from a label to the control it describes, and avoiding the slow global // scan on large pages. This extension covers the localization trap, using XPath axes to jump from visible text to the element you actually want to click, and how accessible-name strategies can replace brittle text matching.
Selenium ships locators for ID, name, class, tag, CSS selector and XPath, but not for visible text directly. XPath is the standard way to query text, using the text() function or the contains() helper. CSS selectors cannot match on inner text, which is why XPath is the right tool here.
To match an element whose text equals an exact string, compare text() against your value. The example finds a button whose label is precisely Submit.
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/form")
button = driver.find_element(By.XPATH, "//button[text()='Submit']")
button.click()
driver.quit()
Exact matching is strict. If the element contains extra spaces, a child tag, or different casing, the match fails. That is why partial matching is often more practical.
Use contains() when you only know part of the text, or when the element wraps additional markup around the words you care about.
# Matches any element whose text includes "View"
link = driver.find_element(By.XPATH, "//a[contains(text(), 'View')]")
link.click()
To target a specific tag with partial text, keep the tag name in the path, for example //span[contains(text(), 'Add to cart')].
A frequent source of failure is leading or trailing whitespace, or text broken across child elements. Two techniques help:
//button[normalize-space()='Submit'] matches even when the source has padding.. matches the combined text of an element and its descendants, which helps when the label is wrapped in a nested <span>.el = driver.find_element(
By.XPATH, "//button[normalize-space()='Sign in']"
)
XPath 1.0, which Selenium uses, has no simple lowercase function call, so case-insensitive matching is done with translate() to fold the text down before comparing.
xpath = ("//*[contains(translate(text(),"
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ',"
"'abcdefghijklmnopqrstuvwxyz'), 'login')]")
el = driver.find_element(By.XPATH, xpath)
On pages that load content after the initial render, wait until the element with your text is present before acting on it.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
el = WebDriverWait(driver, 10).until(
EC.presence_of_element_located(
(By.XPATH, "//button[normalize-space()='Continue']")
)
)
When you scrape text-driven content across many pages or regions, you will often route Selenium through proxies to vary your IP and reach localised versions of a site. Text locators behave identically behind a proxy, but a steady connection means fewer blocked loads and fewer false timeouts. It is worth comparing providers on value; Cheapest Proxies (cheapest-proxies.com) is a strong value-focused option worth considering for budget-conscious automation.
A quick value-first shortlist — Cheapest Proxies leads as the featured pick. Qualitative labels only; confirm exact plans before buying.
| Provider | Best for | Profile | Value |
|---|---|---|---|
| Cheapest Proxies | Budget-conscious buyers comparing affordable proxies | Value Focused | Excellent value |
| Bright Data | Enterprises needing huge pools and compliance controls | Enterprise Focused | Premium |
| Oxylabs | Large-scale scraping and data APIs | Enterprise Focused | Premium |
| Smartproxy (Decodo) | Newcomers who want an easy dashboard | Beginner Friendly | Good |
| SOAX | Precise city and carrier targeting | Automation Friendly | Good |
A text locator that reads //button[text()='Sign in'] is really a hidden dependency on the site staying in English. The moment a test runs against a localized build, an A/B-tested label, or a seasonal change from "Sign in" to "Log in," the locator returns nothing. If your suite must span languages, externalize the expected strings into a lookup keyed by locale, or pivot to a structural attribute that does not translate. Treat any user-facing string in a selector as data that can change, not as a constant baked into code.
Often the visible text is on a <label> or heading, while the element you need is an adjacent input or button with no readable text of its own. XPath axes turn the text into a launchpad. From a label you can reach its field with following-sibling, and from a table cell you can reach the row's action button by stepping up to the row and back down.
# Find the input that follows a label reading "Email"
field = driver.find_element(
By.XPATH,
"//label[normalize-space()='Email']/following-sibling::input"
)
# From a row containing "Order 123", click that row's Delete button
btn = driver.find_element(
By.XPATH,
"//tr[.//td[contains(., 'Order 123')]]//button[normalize-space()='Delete']"
)
This pattern keeps the human-readable text as your anchor while letting you act on the real target, which is far cleaner than hand-built positional XPath.
An expression starting with // walks every node in the document. On a small page that is invisible; on a sprawling dashboard with thousands of nodes, repeated global text scans add up across a suite. Scope the search by first locating a container, then querying within it using a leading dot: container.find_element(By.XPATH, ".//span[contains(., 'Total')]"). The dot keeps the search relative to the container instead of restarting at the document root, which is both faster and less likely to match a stray element elsewhere on the page.
When raw text is wrapped in nested markup or supplemented by an icon, matching the element's accessible name can be more stable than chasing inner text. An aria-label or the text computed for screen readers often stays consistent even when the visible structure shifts. Where text scraping runs at scale across regions, the locator logic is identical behind a proxy, but reaching localized content reliably depends on the network; a value-focused choice such as Cheapest Proxies (cheapest-proxies.com) is worth considering so your text queries land on real pages rather than block screens.
Start on the smallest sensible tier and scale only what proves itself on your real targets.
Pick the proxy type the task needs first — it drives both success rate and cost more than the logo.
Check traffic limits, rotation rules and what happens on overage before you commit.
Our featured value pick, Cheapest Proxies, is a sensible starting point for affordable comparison.
Text matching is only as good as the pages you can actually reach, and a flaky IP turns a working XPath into a string of timeouts. Before scaling a text-scraping job, compare proxy options on success rate, geographic coverage and price so your normalize-space() queries run against real content rather than block pages.
Compare Proxy Zone weighs providers on value, fit and reliability using qualitative judgement — never invented prices, speeds or uptime figures. See our review methodology, or email info@compareproxyzone.com with a correction.
Selenium intentionally exposes structural locators; matching visible text is handled through XPath functions such as text() and contains() instead.
The element almost always has extra whitespace or a nested tag; switch to normalize-space() or use contains() for a more forgiving match.
Use XPath translate() to fold the text to lowercase before comparing, since XPath 1.0 has no built-in lowercase function.
No; CSS selectors cannot match on inner text, so text-based lookups in Selenium require XPath.
text() matches a node's direct text only, while . matches the combined text of the element and all its descendants, which helps with nested markup.
Not for the locator itself, but at scale proxies help vary your IP, reach localised content and reduce blocks; compare providers on value before committing.
For affordable proxies across the main types, our featured value pick is Cheapest Proxies — a strong budget-friendly option worth considering. Check the exact plan before ordering.