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 glitchesSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The reliable way to wait for a background XHR in Selenium is to wait for the page state that the request is meant to produce. After a click or navigation, use an explicit wait for a result element, changed text, an attribute, or another application condition. Use execute_async_script only when you deliberately need to coordinate with a browser-side callback or return the result of an injected asynchronous request.
Selenium’s navigation wait follows document readiness, not every later XMLHttpRequest or fetch. The Selenium Project explains that readyState covers assets defined in the HTML, while JavaScript can continue changing the page afterward (Waiting Strategies).
Why Selenium moves on before an XHR finishes
A page can report that navigation is complete while its application is still loading data. A typical sequence is:
- Selenium opens a page and waits for the selected page-load strategy.
- The page’s JavaScript starts an XHR or
fetch()request. - The browser receives the response and JavaScript updates a results panel.
- Your next Selenium command runs before that panel is ready.
If the test immediately reads text, clicks a newly rendered control, or asserts a count, it can race step three. This is not evidence that Selenium ignored the request; document readiness and application readiness are different events.
#1 Best Overall
The correct synchronization point is the state your next test action needs. If you need a table row, wait for that row. If you need a spinner to disappear, wait for invisibility. If you need a value to change, wait for the expected value rather than guessing how long the server will take.
Preferred pattern: wait for the rendered result
Python example with Selenium 4
This example clicks a control that starts an XHR and waits until the result container contains the expected order number. Replace the selector and text with your application’s actual contract.
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
with webdriver.Chrome() as driver:
driver.get("https://example.test/orders")
wait = WebDriverWait(driver, 30, poll_frequency=0.2)
driver.find_element(By.CSS_SELECTOR, "button[data-action='load-order']").click()
result = wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "#order-result"))
)
wait.until(
lambda d: "Order #12345" in result.text
)
assert "Order #12345" in result.text
visibility_of_element_located waits for a usable element; the second condition waits for the application data. If the element exists before the request, wait for a meaningful change instead:
Recommended Free Tools
old_text = driver.find_element(By.CSS_SELECTOR, "#results").text
driver.find_element(By.ID, "refresh").click()
WebDriverWait(driver, 30).until(
lambda d: d.find_element(By.CSS_SELECTOR, "#results").text != old_text
)
Useful condition choices
- Element appears:
presence_of_element_locatedwhen existence in the DOM is enough. - Element is usable:
visibility_of_element_locatedorelement_to_be_clickable. - Loading indicator ends:
invisibility_of_element_located. - Text or attribute changes: a lambda that checks
text,value,aria-busy, or a data attribute. - Several conditions: a lambda that returns the element or a truthy value only when all required state is present.
Prefer a stable application signal such as data-testid="results-ready" over a brittle class generated by a UI framework. The wait should describe readiness for the next action, not merely the absence of one request.
Rank #2
Waiting for a known browser-side callback
Sometimes the test owns the asynchronous operation: for example, it injects an XHR and needs the response body, or it can call a page function that accepts a completion callback. Selenium’s asynchronous executor appends a callback as the final argument. Your script must invoke that callback; otherwise Selenium waits until the script timeout. The Selenium Java API documents this requirement and provides AJAX/XHR examples (JavascriptExecutor API).
Python: execute_async_script with success and error paths
from selenium import webdriver
with webdriver.Chrome() as driver:
driver.set_script_timeout(30)
driver.get("https://example.test")
response_text = driver.execute_async_script("""
const done = arguments[arguments.length - 1];
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/status');
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
done({ok: true, status: xhr.status, body: xhr.responseText});
} else {
done({ok: false, status: xhr.status, body: xhr.responseText});
}
};
xhr.onerror = () => done({ok: false, error: 'network error'});
xhr.ontimeout = () => done({ok: false, error: 'request timeout'});
xhr.timeout = 25000;
xhr.send();
""")
if not response_text["ok"]:
raise RuntimeError(response_text)
print(response_text["body"])
Python’s set_script_timeout limits how long the asynchronous script may run. It is separate from the implicit element-wait timeout and the page-load timeout. The Python WebDriver API documents both methods for Selenium 4.49.0 (Python WebDriver API).
Java: callback coordination
WebDriver driver = new ChromeDriver();
try {
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(30));
driver.get("https://example.test");
Object result = ((JavascriptExecutor) driver).executeAsyncScript(
"var done = arguments[arguments.length - 1];" +
"var xhr = new XMLHttpRequest();" +
"xhr.open('GET', '/api/status');" +
"xhr.onload = function () { done({status: xhr.status, body: xhr.responseText}); };" +
"xhr.onerror = function () { done({error: 'network error'}); };" +
"xhr.send();"
);
System.out.println(result);
} finally {
driver.quit();
}
The injected function runs in the page context. Keep it self-contained: Selenium converts a function passed as a script to text, so it cannot depend on local JavaScript symbols that are not defined in the page.
Free tools Windows power users keep installed
One-click scans. No signup required.
Choosing between a DOM wait and an asynchronous script
| Test need | Best approach | Reason |
|---|---|---|
| Click, assert, or read the rendered result | Explicit wait on a DOM/application condition | Synchronizes with the behavior the test actually verifies. |
| Retrieve a response from an XHR created by the test | execute_async_script |
The injected callback provides an unambiguous completion signal and result. |
| Application starts an unknown number of requests | Wait for a stable UI state | A global request counter is app-specific and can finish before rendering does. |
| Need global network-idle semantics | Use a browser/protocol-specific implementation | There is no portable Selenium condition established by these APIs for all network activity. |
Timeouts, polling, and failure behavior
Set a bounded explicit wait
Choose a timeout that covers normal server and rendering variation, then fail with a useful condition message. A 30-second example is shown above; the correct value depends on your environment and service-level expectations. A shorter polling interval can make a fast UI responsive, but it does not make a slow request finish sooner.
Rank #3
Always complete asynchronous scripts
Call the callback on success, HTTP errors, network errors, and application-level failures. If an error path omits the callback, Selenium reports a script timeout instead of the real cause. Return status and response details so the test failure is diagnosable.
Do not use fixed sleeps as the default
time.sleep(5) may be too short on a busy run and wastes time when the response arrives quickly. Selenium’s waiting guidance also warns that mixing implicit and explicit waits can produce unpredictable timing. Use one explicit strategy for the operation, and keep implicit waits disabled or consistently managed.
Common failures and fixes
“The element is present but still empty”
Presence only proves that a node exists. Wait for visibility, non-empty text, a specific attribute, or a ready marker set by the application.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows 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 reinstall“The wait times out although the API returned 200”
The response may be successful while rendering fails, the selector may be wrong, or the expected text may differ. Capture the current DOM state, inspect browser console logs, and verify the selector and data contract independently.
Rank #4
“The asynchronous script always times out”
Check that the script uses arguments[arguments.length - 1] as the callback and invokes it on every path. Also verify that the request URL is same-origin or otherwise permitted by the page’s CORS policy.
“It works locally but fails in CI”
CI can have slower CPUs, different network access, or a different viewport that changes rendering. Wait on the same user-visible condition, use a bounded timeout, and record the URL, selector, and diagnostic response when a timeout occurs.
“A spinner disappears too early”
A spinner can be removed before the final DOM update. Combine its disappearance with a positive condition such as a result row, expected text, or a ready attribute.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Reliability and performance checklist
- Trigger the request and establish the wait immediately afterward, unless the application can start it before the trigger.
- Use stable IDs, ARIA attributes, or test IDs rather than positional XPath.
- Wait for the smallest state that proves the next action is safe.
- Return structured error information from injected scripts.
- Keep script, page-load, implicit, and explicit timeouts conceptually separate.
- Do not infer that all network activity is complete from one finished XHR.
- When debugging, log the condition’s observed value at timeout.
Or skip the browser setup
If your goal is a clean screenshot after a page’s background requests settle, ScreenshotNeo makes the capture a single API call instead of maintaining Selenium drivers. Before capture it accepts cookie/consent banners and removes 60+ known consent platforms, newsletter popups, and chat widgets. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
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 documentation for all capture options, including waits, full-page images, custom JavaScript, and signed webhooks. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Best Value
FAQ
Does Selenium have a built-in “wait for every AJAX request” switch?
Not as a portable WebDriver condition. A DOM condition is usually more reliable, while global network-idle behavior requires browser- or protocol-specific code.
Can I wait on document.readyState?
It can confirm document loading, but it does not confirm that later JavaScript requests have rendered their results.
Should I increase the implicit wait?
Increasing an implicit wait does not synchronize an existing element with changing application data. Use an explicit condition for the state you need.
Frequently Asked Questions
Can Selenium intercept an XHR response directly?
The portable approach shown here is to wait for the resulting DOM state or use an injected asynchronous script when the test owns the request. Direct interception depends on browser-specific tooling rather than the basic WebDriver wait API.
What should a timeout failure include?
Record the URL, selector or condition, observed text or attribute, elapsed timeout, and any returned HTTP or JavaScript error so the failure identifies whether the request, rendering, or locator was responsible.
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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →

