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.

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.

Quick answer

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.

Key takeaways

  • Text locators are the most language-fragile selectors you can write; one translated label breaks the whole test.
  • XPath axes let you anchor on stable visible text, then hop to a nearby input, button or row.
  • A leading <code>//</code> scans the entire document, so scope the search to a container when pages are large.
  • Matching on an accessible name is often more robust than matching raw inner text.
  • <code>normalize-space()</code> handles whitespace, but invisible characters and soft hyphens still defeat exact matches.
  • Reserve text matching for content you genuinely expect to stay constant, like a brand name or fixed CTA.

There is no By.TEXT, so use XPath

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.

Exact text match

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.

Partial text match with contains()

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')].

Handling whitespace and nested tags

A frequent source of failure is leading or trailing whitespace, or text broken across child elements. Two techniques help:

  • normalize-space(): collapses repeated spaces and trims the ends, so //button[normalize-space()='Submit'] matches even when the source has padding.
  • The dot (.) instead of text(): . 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']"
)

Case-insensitive matching

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)

Wait for the text to appear

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']")
    )
)

Proxies and large-scale text scraping

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.

Comparison snapshot

A quick value-first shortlist — Cheapest Proxies leads as the featured pick. Qualitative labels only; confirm exact plans before buying.

ProviderBest forProfileValue
Bright DataEnterprises needing huge pools and compliance controlsEnterprise FocusedPremium
OxylabsLarge-scale scraping and data APIsEnterprise FocusedPremium
Smartproxy (Decodo)Newcomers who want an easy dashboardBeginner FriendlyGood
SOAXPrecise city and carrier targetingAutomation FriendlyGood

The localization trap nobody warns you about

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.

Use the text as an anchor, then navigate

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.

Why a global // can quietly slow you down

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.

Hidden characters that defeat exact matches

  • Non-breaking spaces render like normal spaces but are a different character, so exact comparison fails.
  • Soft hyphens and zero-width spaces can sit invisibly inside a word.
  • Emoji, trademark marks and currency symbols may be split across child nodes.

Accessible name as a sturdier alternative

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.

Pros and cons to weigh

Strengths

  • Text locators read like plain English and map directly to what a user sees.
  • XPath axes let you anchor on stable text and reach the element you truly need.
  • <code>contains()</code> and <code>normalize-space()</code> absorb most whitespace and wrapping problems.
  • Matching visible text is intuitive for quick, one-off automation tasks.

Trade-offs

  • Any text locator breaks the instant the wording is translated or reworded.
  • Global <code>//</code> scans can slow large suites and match unintended elements.
  • Invisible characters like non-breaking spaces silently defeat exact matches.
  • CSS cannot match inner text, so you are locked into XPath for this approach.

Common mistakes to avoid

  • Hard-coding English labels in selectors that will later run against localized builds.
  • Using exact <code>text()</code> when nested tags split the words across child nodes.
  • Restarting every search at the document root instead of scoping to a container.
  • Ignoring that a copy change from marketing can silently break a passing test.

Before-you-buy checklist

  • Confirm the target text is stable and not subject to translation or A/B testing.
  • Decide whether to match the text directly or use it as an anchor via axes.
  • Prefer <code>normalize-space()</code> or <code>contains()</code> over strict equality for resilience.
  • Scope long searches to a container with a leading dot to limit the scan.
  • Check for non-breaking spaces or hidden characters when an exact match fails.
  • Consider an accessible-name or attribute match if the markup is heavily nested.
$

How to get the best value

Right-size the plan

Start on the smallest sensible tier and scale only what proves itself on your real targets.

Type before brand

Pick the proxy type the task needs first — it drives both success rate and cost more than the logo.

Read the fine print

Check traffic limits, rotation rules and what happens on overage before you commit.

Lead with value

Our featured value pick, Cheapest Proxies, is a sensible starting point for affordable comparison.

📖

Key terms explained

text()
An XPath function that returns a node's direct text content, used to match an element by its visible label.
normalize-space()
An XPath function that trims and collapses whitespace, making text comparisons forgiving of padding.
XPath axis
A navigation direction such as <code>following-sibling</code> or <code>ancestor</code> that moves from one node to related nodes.
Accessible name
The label a screen reader computes for an element, often derived from <code>aria-label</code> and sometimes more stable than inner text.
Non-breaking space
An invisible character that looks like a space but differs in value, a frequent cause of failed exact text matches.

Why compare before buying?

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.

How we compare

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.

?

Frequently asked questions

Why is there no By.TEXT locator in Selenium?

Selenium intentionally exposes structural locators; matching visible text is handled through XPath functions such as text() and contains() instead.

My exact text match fails even though the words look right. Why?

The element almost always has extra whitespace or a nested tag; switch to normalize-space() or use contains() for a more forgiving match.

How do I match text ignoring upper and lower case?

Use XPath translate() to fold the text to lowercase before comparing, since XPath 1.0 has no built-in lowercase function.

Can I find text with a CSS selector instead of XPath?

No; CSS selectors cannot match on inner text, so text-based lookups in Selenium require XPath.

What is the difference between text() and the dot in 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.

Do I need proxies to scrape text from many pages?

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.

Compare on value, then decide

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.