Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a stable locator, wait until the email link is visible and enabled, click it, and then assert the result your application promises. In Python, Selenium’s reliable pattern is an explicit WebDriverWait with expected_conditions.element_to_be_clickable; a successful click() call alone does not prove that verification, navigation, or another business action completed.
The basic pattern
This example clicks a uniquely named link after Selenium confirms that it is visible and enabled:
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)
email_link = wait.until(
EC.element_to_be_clickable((By.LINK_TEXT, "Verify your email"))
)
email_link.click()
driver must already be an active WebDriver instance pointed at the page containing the message. The 10-second value is an example timeout, not a guarantee that every page should use that number. Choose a limit that fits the application’s normal rendering time and your test environment.
Crashes, 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 minutePC 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 & 11Choose a locator that identifies one link
The locator is often the difference between a durable test and one that clicks the wrong control. Selenium supports link text, partial link text, CSS selectors, IDs and other strategies.
#1 Best Overall
Exact link text
By.LINK_TEXT is readable and appropriate when the complete label is stable and unique:
email_link = wait.until(
EC.element_to_be_clickable((By.LINK_TEXT, "Verify your email"))
)
Exact text is sensitive to capitalization, whitespace and copy changes. It can also fail when the application localizes the label.
Partial link text
By.PARTIAL_LINK_TEXT is useful only when the fragment still identifies exactly one target:
email_link = wait.until(
EC.element_to_be_clickable((By.PARTIAL_LINK_TEXT, "Verify"))
)
Do not use a short fragment such as “here” on a page with several links. A matching fragment that is duplicated can make the test ambiguous.
CSS or an intentional ID
When wording changes, localization is expected, or duplicate labels exist, tie the test to stable markup supplied by the application:
email_link = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "a[data-testid='verify-email']"))
)
email_link.click()
A deliberate test attribute, stable ID or narrowly scoped CSS selector is preferable to a long chain of presentation classes. If the link is inside a particular message card, include that container in the selector so another message cannot satisfy it.
Rank #2
Why an explicit wait is safer than an immediate click
Page-load completion does not mean that JavaScript has finished inserting, enabling or revealing every interactive element. An explicit wait repeatedly checks a specific condition and continues only when that condition is true; otherwise it ends with a timeout. This prevents a race in which the test runs before the email component is ready.
Recommended Free Tools
element_to_be_clickable checks that Selenium can find an element that is visible and enabled. It does not promise that a cookie banner, modal, animation or another overlay will not intercept the pointer, and it does not verify the business result after the click.
Do not replace the condition with a fixed sleep
A fixed delay may be too short on a slow run and unnecessarily long on a fast one. Use a condition that describes what the next action needs. If the link is present but intentionally disabled until a message finishes loading, wait for clickability rather than merely waiting for presence.
Do not casually mix implicit and explicit waits
An implicit wait changes the timing of element lookups globally, while an explicit wait polls its own condition. Selenium warns that combining the two can produce unpredictable wait times. Pick an explicit-wait strategy for this flow, or understand the interaction thoroughly before introducing a global implicit timeout.
Verify what the click was supposed to do
After click(), wait for an application-specific postcondition. The correct assertion depends on the email workflow.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Expected URL
from selenium.webdriver.support import expected_conditions as EC
email_link.click()
wait.until(EC.url_contains("/verify"))
assert "/verify" in driver.current_url
Use a URL condition only when the destination path is part of the contract. Avoid asserting an entire URL if query parameters or hostnames legitimately vary.
Confirmation element
email_link.click()
confirmation = wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='verification-success']")
)
)
assert confirmation.is_displayed()
This is usually stronger than checking that no exception was raised: it proves that the destination rendered the expected state.
Rank #3
New tab or window
Record the original window, click, wait for a second handle, switch, and then assert the new page:
original = driver.current_window_handle
handles_before = set(driver.window_handles)
email_link.click()
wait.until(lambda d: len(d.window_handles) > len(handles_before))
new_handle = (set(driver.window_handles) - handles_before).pop()
driver.switch_to.window(new_handle)
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "h1[data-testid='verification-page']")
))
assert "verify" in driver.current_url
# Return to the original page when the rest of the test needs it.
driver.switch_to.window(original)
If the application opens a new tab, asserting against the original window will produce a false failure. If it reuses the same tab, wait for the URL or destination element instead.
Application state rather than navigation
Some links trigger an in-page action, such as confirming an invitation or revealing a status message. In that case, wait for the state change or confirmation text. The absence of a WebDriver exception is not an assertion that the server accepted the token or completed the workflow.
Complete example with setup and cleanup
The following test uses Selenium’s Python binding and assumes the test page is already configured to contain the intended message:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def test_verify_email_link():
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 15)
try:
driver.get("https://example.test/inbox")
link = wait.until(EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a[data-testid='verify-email']")
))
link.click()
success = wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='verification-success']")
))
assert success.is_displayed()
finally:
driver.quit()
Replace the example URL and selectors with values from the application under test. Keep setup, action and assertion separate so a failure shows whether the problem was loading the inbox, finding the link, clicking it, or reaching the expected state.
Common failures and fixes
TimeoutException while locating the link
- Confirm that the test navigated to the correct page and that the email actually exists in the test data.
- Inspect the rendered DOM. The link may be inside an iframe, shadow root, or a component that appears only after another action.
- Check spelling, capitalization and localization when using link text.
- Use a stable ID, test attribute or CSS selector when the visible wording is dynamic.
Element is found but click is intercepted
An overlay, consent dialog, sticky header or animation may cover the target. Wait for the blocking element to disappear or handle the dialog through the application’s normal controls, then wait for the link to become clickable again. Scrolling the element into view can help with layout issues, but JavaScript-forced clicks should not hide a real user-facing defect.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesThe click runs but nothing changes
Check that the link is not disabled by application state, that the test account has permission, and that the expected result is the one your assertion describes. Add a wait for the destination URL, confirmation element or state change rather than increasing a sleep arbitrarily.
Rank #4
The wrong email link is clicked
Scope the locator to the intended message container and use a unique attribute. Exact link text alone is unsafe when an inbox displays several messages with the same call to action.
Stale element after rendering
A framework may replace the anchor after your first lookup. Locate it inside the wait and click the returned element immediately; do not retain a reference across a known re-render. If the page intentionally refreshes, wait for the new element after the refresh.
New-window assertion fails
Compare window handles before and after the click and switch to the newly added handle. If no handle appears, the link probably navigates in the current tab or the popup was blocked; assert the behavior the application actually specifies.
Timing, reliability and test design
- Use the narrowest condition: wait for visibility when you only need to read, and clickability when you need to interact.
- Keep timeouts intentional: a longer timeout can accommodate a slow environment, but it also delays diagnosis of a genuinely missing link.
- Keep locators close to the product contract: test IDs and semantic attributes generally survive cosmetic redesigns better than generated class names.
- Make test data deterministic: ensure the message, recipient and token are known before opening the inbox.
- Capture diagnostics on failure: save the current URL, page source, screenshot and relevant browser logs in the test runner so a timeout can be reproduced.
- Respect one-time tokens: verification links may expire or become invalid after one use; create fresh test data for each independent test.
Or skip the browser setup
If your goal is a rendered image of an email-link destination rather than an interaction test, ScreenshotNeo can capture the page through one request. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
For a direct capture, see the ScreenshotNeo documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo includes full-page and element capture, device presets and custom viewports, dark mode, retina scale, PDF controls, custom CSS and JavaScript, click-before-capture actions, selector hiding, selector/delay/network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
The free plan includes 1,000 screenshots each month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
Best Value
Python, cURL and Node.js alternatives
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}`);
FAQ
Does Selenium click links inside email clients?
Only if the client is exposed through a browser page that WebDriver controls. Desktop-native mail applications require a different automation interface.
Can I use a keyboard activation instead of click()?
Yes, when keyboard behavior is what you need to test; use the application’s accessibility contract and still wait for and assert the resulting state.
Which Selenium binding should I use?
Use the binding supported by your project. The same locator, explicit-wait and postcondition principles apply across Selenium’s language bindings, although syntax differs.
Frequently Asked Questions
Can Selenium click a mailto link?
It can activate the anchor in the browser, but handing the URL to a desktop mail application is outside normal WebDriver page control. Test the browser-visible behavior or use an OS-level tool for the native client.
Should I assert the link text after clicking?
No. Link text proves what was located, not that the workflow succeeded. Assert the destination URL, confirmation element or state transition that represents success.
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.

