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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

If Selenium cannot find one link by its href, first check that you are using an attribute locator—not By.LINK_TEXT—then verify the anchor’s actual DOM value, the number of matches, the page state, and the browsing context. A reliable starting point in Python is (By.CSS_SELECTOR, 'a[href="https://example.test/path"]'). The exact fix depends on the error and the rendered page; without those details, no single root cause can be assumed.

First, make sure the locator is searching the right thing

Selenium’s By.LINK_TEXT and By.PARTIAL_LINK_TEXT strategies match an anchor’s visible text. They do not match its href attribute. If you pass a URL to By.LINK_TEXT, Selenium looks for a link whose displayed text is that URL, not a link that navigates to it. [Selenium locator strategies]

For an exact href match, use a CSS attribute selector or an XPath attribute predicate:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • a[href="https://example.test/path"] in CSS
  • //a[@href="https://example.test/path"] in XPath

Use the actual attribute value found in the page’s DOM. A URL you expect to be present may differ from the one rendered by the site, for example because of a redirect target, query string, trailing slash, or relative URL. The selector only matches what the DOM contains.

Python: exact match with a wait

This example waits for the anchor to be present in the DOM, then verifies the attribute value before relying on it:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

href = "https://example.test/path"
locator = (By.CSS_SELECTOR, f'a[href="{href}"]')

link = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located(locator)
)
assert link.get_attribute("href") == href

The example assumes driver is already configured and the relevant page has been opened. If the target URL contains quotes or other characters that affect selector syntax, do not paste it into a selector blindly; use correct CSS or XPath escaping for your binding, or locate the link with a more stable attribute and verify its href separately.

Identify the failure before changing the selector

Different Selenium errors point to different problems. A lookup failure is not the same as a malformed selector, an outdated element reference, or a failed click. Note the exception type and the line that raised it before editing the locator.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Symptom What it tells you First check
NoSuchElementException No matching element was found in the current search context at lookup time. Verify the DOM value, page state, selector, and browsing context.
Invalid selector error The selector string is not valid for the strategy you chose. Check CSS versus XPath syntax, quoting, and escaping.
StaleElementReferenceException A previously located element reference no longer corresponds to an accessible DOM element. Re-run the locator after the page or component updates.
Click or interaction error The element may have been found but may not be ready or usable for the intended action. Check visibility, enabled state, overlays, and whether the click target is correct.

Selenium’s troubleshooting guidance recommends checking that the expected page loaded, prior actions completed, the wait strategy fits the situation, and the locator still describes the current page. [Selenium troubleshooting errors]

Inspect the anchor’s actual DOM value

Open the page in browser developer tools and inspect the specific element. Confirm that it is an <a> element and read the exact href attribute—not merely the link text or the URL you expected to see. Check for capitalization, path differences, query parameters, trailing slashes, and whether the link is present only after an interaction.

In Selenium, inspect the candidates with find_elements before settling on a locator. It returns a list, including an empty list when nothing matches, so you can examine all candidates without allowing a first match to hide ambiguity:

from selenium.webdriver.common.by import By

href = "https://example.test/path"
locator = (By.CSS_SELECTOR, f'a[href="{href}"]')
matches = driver.find_elements(*locator)

print("matches:", len(matches))
for match in matches:
    print(
        "tag:", match.tag_name,
        "href:", match.get_attribute("href"),
        "text:", match.text,
    )

find_element returns the first matching element. A successful call therefore does not prove that Selenium selected the one you intended. If more than one anchor has the same href, narrow the selector using a stable parent, ID, or other distinguishing attribute, then inspect the match count again. [Selenium finding elements]

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

Choose a locator that is both accurate and maintainable

Use the most stable locator that identifies the intended element uniquely. An ID is usually a good choice when the page provides a stable, unique one. If the attribute itself is the meaningful target, a compact CSS selector is readable and direct. Use XPath when you need a relationship to another element or an expression CSS cannot conveniently express; Selenium notes that XPath can be harder to debug. [Selenium locator strategies]

Strategy Example Best fit Watch for
Stable ID By.ID, "account-link" The target has a unique, stable ID. An ID that is generated or changes between renders is not a stable hook.
CSS href attribute By.CSS_SELECTOR, 'a[href="https://example.test/path"]' The href value identifies the desired anchor. Exact values and selector quoting must match the DOM.
XPath href predicate By.XPATH, '//a[@href="https://example.test/path"]' You need an attribute predicate or a relationship to nearby elements. Long or deeply nested XPath expressions can be brittle and harder to diagnose.
Visible link text By.LINK_TEXT, "Open account" The visible anchor wording is the intended target. This matches text, not the URL in href.

If the complete URL changes but a stable part of the page identifies the link, a less exact selector may be useful; confirm the resulting match set rather than weakening the selector until it returns something. If no stable distinguishing attribute exists, use the closest stable container and verify that the selected element’s attributes and text are the expected ones.

Wait for the condition your next action needs

A link created by client-side rendering may not exist when an immediate lookup runs. For a lookup, wait for presence. If you need to read or interact with a visible link, wait for visibility. Before clicking, use a clickable condition, which accounts for visibility and enabled state. Waiting longer will not repair a wrong selector or an incorrect browsing context.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

locator = (By.CSS_SELECTOR, 'a[href="https://example.test/path"]')
wait = WebDriverWait(driver, 10)

# Suitable when the element only needs to exist in the DOM:
link = wait.until(EC.presence_of_element_located(locator))

# Use this instead when you need to click it:
clickable_link = wait.until(EC.element_to_be_clickable(locator))
clickable_link.click()

Choose a timeout appropriate to your application rather than adding a fixed sleep everywhere. A condition-based wait can proceed as soon as its condition is true. Selenium’s Python expected-conditions reference documents these wait conditions and frame switching. [Selenium Python expected conditions]

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

Check whether the element is in another context

A driver-level lookup searches the current browsing context. If the link is inside an iframe, switch to that frame before locating it. If it is inside a shadow tree, find the relevant shadow root and search from that root; its descendants should not be treated as ordinary children of the document.

Iframe

When you know a frame locator, wait for it and switch as part of the condition. Then find the link within the frame:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
wait.until(EC.frame_to_be_available_and_switch_to_it(
    (By.CSS_SELECTOR, "iframe#content-frame")
))
link = wait.until(EC.presence_of_element_located(
    (By.CSS_SELECTOR, 'a[href="https://example.test/path"]')
))

Replace the example frame selector with one that identifies the actual iframe. To return to the top-level document later, switch to the default content with driver.switch_to.default_content().

Shadow DOM

Find the host element, obtain its shadow root, and search from that root:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
host = driver.find_element(By.CSS_SELECTOR, "site-navigation")
shadow_root = host.shadow_root
link = shadow_root.find_element(
    By.CSS_SELECTOR,
    'a[href="https://example.test/path"]'
)

The host selector and shadow-root support depend on the page and Selenium binding. Selenium’s finding-elements guidance shows searching from a shadow root rather than querying its descendants as if they were in the regular document. [Selenium finding elements]

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Re-locate elements after a DOM update

A saved WebElement is a reference to a particular DOM element, not a live query. If the application replaces that element during navigation, rerendering, or another update, the old reference can become stale. Selenium does not automatically locate a replacement. Run the locator again after the update, and verify that it still selects the intended link. [Selenium troubleshooting errors]

# After an action that may replace or rerender the link:
link = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located(locator)
)
print(link.get_attribute("href"))

Troubleshoot the common causes in order

  • The selector uses link text for a URL. Change to a CSS href attribute selector or an XPath href predicate. Keep By.LINK_TEXT only when matching visible wording.
  • The DOM value differs from the expected URL. Inspect the rendered anchor in developer tools and use its actual attribute value. Check the full path, query, slash, and any escaping required by the selector.
  • The page has not reached the required state. Ensure navigation and earlier actions completed, then wait for presence, visibility, or clickability according to what comes next.
  • The lookup succeeds but the wrong link is selected. Use find_elements, inspect every candidate, and constrain the selector with a stable parent or attribute. Do not treat the first result as proof of uniqueness.
  • The link is inside an iframe. Switch into the correct frame before looking it up; return to default content when finished.
  • The link is inside a shadow root. Locate the host and search from its shadow root.
  • A previously found element is stale. Re-run the locator after the DOM change instead of reusing the old WebElement.
  • The selector is rejected as invalid. Confirm that CSS syntax is paired with By.CSS_SELECTOR and XPath syntax with By.XPATH; escape literal values for the selected syntax.
  • The lookup works but the click fails. Wait for the link to be clickable and inspect whether another element or page state is preventing the intended interaction.

Or skip the browser setup

If your actual goal is to capture a page screenshot rather than interact with its link, ScreenshotNeo offers a one-request screenshot API. A URL returns an image or PDF; its capture process accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets, with each cleanup step switchable. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents and MCP clients. Details and parameters are in the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.test/path -o shot.webp

ScreenshotNeo’s free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. See ScreenshotNeo or sign up for the free plan.

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

Frequently Asked Questions

Does an href selector match the text shown for a link?

No. An href selector matches the anchor’s attribute value; use a link-text strategy to match its visible wording.

Why does find_element return the wrong link?

It returns the first element that matches. Inspect all matches and narrow the locator if the result is ambiguous.

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.