The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan 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 an explicit wait for the application state your test needs. Selenium’s driver.get() normally waits until the document’s readyState is complete, but that only establishes document and resource readiness. JavaScript applications can continue rendering, fetching data, or replacing elements afterward. In Python, navigate with driver.get(), then use WebDriverWait and an expected condition such as visibility, clickability, text, or element replacement.
What Selenium’s page load actually waits for
Each WebDriver session has a page_load_strategy. With Selenium’s default normal strategy, a navigation command waits for the browser’s document.readyState to reach complete before returning control to Python. That means the document and its normal subresources have reached the browser’s complete state.
It does not prove that a single-page application has finished its own work. A page can report complete while JavaScript is still making an API request, inserting dashboard cards, removing a loading overlay, or enabling a button. Treat ready state as a navigation milestone, not as a universal “the user can proceed” signal.
A reliable Python pattern
Navigate first, then wait for an observable condition that represents the next test action. This example waits for a dashboard to be visible and its submit button to be usable.
#1 Best Overall
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
from selenium.common.exceptions import TimeoutException
options = webdriver.ChromeOptions()
options.page_load_strategy = "normal" # "eager" or "none" are deliberate alternatives
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.test/dashboard")
wait = WebDriverWait(driver, 20)
dashboard = wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='dashboard']")
)
)
wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button.submit"))
)
dashboard.click() # continue with the test
except TimeoutException:
# Capture diagnostics here, then fail the test with useful context.
driver.save_screenshot("timeout.png")
raise
finally:
driver.quit()
WebDriverWait.until() repeatedly evaluates its condition until the return value is truthy or the timeout expires. Python’s documented default polling interval is 0.5 seconds. The 20-second value above is a bound for this example, not a guarantee that every site should use the same timeout.
Choose a wait that proves the right milestone
The best condition depends on what the next line of the test requires. Waiting for an unrelated element can make a test appear stable while it still races the real application state.
| Condition | What it proves | Typical use |
|---|---|---|
presence_of_element_located |
A matching node exists in the DOM. | Read an attribute or hand the element to code that does not require it to be visible. |
visibility_of_element_located |
The node exists and is displayed with a usable size. | Assert that rendered content is visible to the user. |
element_to_be_clickable |
The element is visible and enabled. | Click a button, link, checkbox, or other control. |
text_to_be_present_in_element |
A particular text value has appeared in an element. | Wait for “Loaded”, a status, a total, or a completed result. |
staleness_of |
An old element is no longer attached to the DOM. | Wait for a loading row or previous view to be replaced. |
For example, if a report initially shows “Loading…” and later changes to “42 results”, wait for the result text rather than sleeping for an assumed duration:
wait.until(
EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, "[data-testid='report-status']"),
"42 results"
)
)
If the application replaces a spinner node, retain a reference to that original node and wait for it to become stale:
spinner = driver.find_element(By.CSS_SELECTOR, ".spinner")
driver.find_element(By.CSS_SELECTOR, "button.load").click()
wait.until(EC.staleness_of(spinner))
Page-load strategies: normal, eager, and none
Set the strategy before creating the driver. The choice controls when navigation returns; it does not remove the need to synchronize application behavior.
| Strategy | Navigation returns when | Use it when | Required discipline |
|---|---|---|---|
normal |
readyState is complete. |
You want the safest ordinary navigation behavior and do not need to optimize the point at which get() returns. |
Still add explicit waits for AJAX, SPA rendering, and post-navigation controls. |
eager |
The document reaches interactive; some images and other subresources may continue loading. |
Your test can work with the DOM before every subresource finishes. | Wait explicitly for any image, font, widget, or application milestone you use. |
none |
Navigation does not block for page loading. | You need full control over synchronization or are driving a workflow where navigation itself should return immediately. | Every required readiness signal must be covered by explicit waits. |
options = webdriver.ChromeOptions()
options.page_load_strategy = "eager"
driver = webdriver.Chrome(options=options)
Changing to eager or none is not a fix for a missing readiness condition. It changes the timing of get(); your test must still wait for the state it will inspect or interact with.
Waiting after clicks, form submits, and SPA route changes
A navigation wait applies to a navigation command. It does not automatically wait for an in-place update caused by a click, an XMLHttpRequest, a fetch call, or a client-side route change. Put the wait immediately after the action that starts the asynchronous work.
Recommended Free Tools
Rank #2
Wait for a new view
driver.find_element(By.CSS_SELECTOR, "a.settings").click()
wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "main[data-page='settings']"))
)
Wait for a loading indicator to disappear
driver.find_element(By.CSS_SELECTOR, "button.refresh").click()
wait.until(
EC.invisibility_of_element_located((By.CSS_SELECTOR, ".loading-overlay"))
)
Wait for changed text
driver.find_element(By.CSS_SELECTOR, "button.search").click()
wait.until(
EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, "[data-testid='search-status']"),
"Complete"
)
)
Wait for replacement, then locate again
Do not continue using a reference to an element that the framework may replace. Wait for staleness and find the new element afterward.
old_table = driver.find_element(By.CSS_SELECTOR, "table.results")
driver.find_element(By.CSS_SELECTOR, "button.next-page").click()
wait.until(EC.staleness_of(old_table))
new_table = wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "table.results"))
)
Explicit waits versus implicit waits
An implicit wait is a driver-wide polling period used when Selenium tries to locate elements. An explicit wait targets one condition with its own timeout. Explicit waits make the synchronization point visible next to the action that needs it.
driver.implicitly_wait(5) # applies broadly to element lookups
Mixing a large implicit wait with explicit waits can make failures difficult to predict because a lookup inside an explicit condition may itself consume the implicit period. Prefer a small, intentional synchronization policy: use explicit waits for application milestones and avoid stacking long values unless you understand the resulting timing.
Never substitute an arbitrary time.sleep() for a state-based wait. A fixed sleep is either too short on a slow run or unnecessarily long on a fast one, and it provides no evidence that the page reached the required state.
Timeouts and diagnostics
A timeout is useful information: the condition did not become true within the bound. Catch TimeoutException where you can add diagnostics, then re-raise it so the test remains failed.
from pathlib import Path
from selenium.common.exceptions import TimeoutException
try:
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='dashboard']")
))
except TimeoutException:
Path("page.html").write_text(driver.page_source, encoding="utf-8")
driver.save_screenshot("page-timeout.png")
print("URL:", driver.current_url)
print("Title:", driver.title)
raise
Check the captured URL, title, screenshot, and HTML for a redirect, authentication page, consent dialog, bot check, JavaScript error, or a selector that no longer matches. If a condition is consistently close to succeeding, increase the bounded timeout only after confirming that the application is legitimately slower in your environment.
Common failures and precise fixes
driver.get() returns but data is missing
Cause: The document reached complete before the application’s asynchronous request and render finished.
Rank #3
Fix: Wait for the data container’s visibility, a known result string, the disappearance of its spinner, or another application-owned milestone.
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 minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallTimeoutException for an element that appears manually
Cause: The locator may be wrong, the element may be inside an iframe, the page may have redirected, or a modal may be blocking the workflow.
Fix: Inspect the saved URL and HTML. If the element is in an iframe, switch to the correct frame before waiting. Verify the selector in browser developer tools and handle authentication or consent flows explicitly.
Click fails even though the element exists
Cause: Presence only proves a DOM node exists. It may be hidden, disabled, covered by an overlay, or not yet positioned for interaction.
Fix: Use element_to_be_clickable, wait for an overlay to become invisible, and scroll or otherwise prepare the page only after the element is interactable.
StaleElementReferenceException appears after a wait
Cause: A framework replaced the node after you located it.
Fix: Wait for the old reference to become stale, then locate the replacement. Avoid caching references across known re-renders.
Rank #4
The test is slow after adding waits
Cause: A fixed sleep, an unnecessarily high timeout, or overlapping implicit and explicit waits may be delaying every path.
Fix: Use the narrowest condition, place it after the triggering action, keep timeouts bounded, and let successful conditions return immediately.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsThe browser hangs during navigation
Cause: A page may contain a slow or never-ending resource, or the selected strategy may not match the workflow.
Fix: Keep normal for ordinary flows, consider eager when the test only needs the interactive DOM, and use none only with explicit waits for every required state. Configure a WebDriver page-load timeout separately when you need navigation itself to fail within a known bound.
Performance and reliability practices
- Use stable application selectors such as dedicated
data-testidattributes instead of brittle generated class names. - Wait for the smallest meaningful milestone, not for a whole page when one component is sufficient.
- Use
normalunless you have a measured reason to return earlier; chooseeagerornonedeliberately. - Keep waits close to the action they synchronize and give each condition a bounded timeout.
- For repeated polling, return the value you need from a custom callable so the test can use the result directly.
def dashboard_total_is_ready(driver):
element = driver.find_element(By.CSS_SELECTOR, "[data-testid='total']")
value = element.text.strip()
return value if value.isdigit() else False
total = WebDriverWait(driver, 20).until(dashboard_total_is_ready)
This custom condition follows the same contract as built-in expected conditions: return a useful truthy value when ready, and False while polling should continue.
Or skip the browser setup
If your goal is a static image or PDF rather than browser interaction, ScreenshotNeo provides a website screenshot API and MCP server. It handles the capture wait on the service side, including options for a selector, delay, network idle, custom JavaScript, and lazy-loaded full-page images.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for the complete parameter list. The same request in Python is:
Best Value
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And in 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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets Claude, Cursor, and other MCP clients call screenshot, page-info, and PDF tools.
- The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan.
Create a free ScreenshotNeo account to try it without a card.
Which approach should you use?
Use Selenium when you must interact with controls, authenticate, submit forms, inspect state, or verify behavior inside a real browser session. Use explicit expected conditions to synchronize those interactions. Use ScreenshotNeo when the deliverable is a clean screenshot or PDF and you do not need to drive the page yourself.
Frequently Asked Questions
Does Selenium wait for network idle automatically?
No. The default navigation strategy waits for the document ready state, not for every network request. Wait for an application-specific element, text value, spinner transition, or other condition instead.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →What timeout should I choose for WebDriverWait?
Choose a bounded value based on the slowest legitimate environment and the importance of the operation. Start with a practical limit such as 20 seconds, then adjust from observed diagnostics rather than hiding a locator or application problem.
Can I wait on document.readyState directly?
You can poll it with JavaScript, but readyState alone does not establish that a JavaScript application finished rendering. A condition tied to the content or control your test uses is usually stronger.
The Bottom Line
driver.get() tells you that navigation reached its configured page-load milestone. WebDriverWait tells you that the application state your test needs is actually ready.
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

