Capture a Webpage Screenshot After JavaScript Finishes Loading in Selenium
Wait for the page state your screenshot needs, then capture it with Selenium. Learn reliable waits, screenshot scope, troubleshooting, and a one-call alternative.
To capture a Selenium screenshot after JavaScript has rendered the content you need, navigate to the page, wait for a page-specific condition such as a results element becoming visible, and take the screenshot only after that condition succeeds. A completed navigation is not proof that later JavaScript updates have finished.
The example below uses Python, Chrome, and an explicit wait. Replace the example URL and CSS selector with the page and state that matter to your capture.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
URL = "https://example.com"
READY_SELECTOR = "main .results" # Replace with a real readiness signal.
OUTPUT = "page.png"
options = webdriver.ChromeOptions()
# Uncomment to run without a visible browser window:
# options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.set_page_load_timeout(60)
driver.get(URL)
wait = WebDriverWait(driver, 20, poll_frequency=0.25)
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, READY_SELECTOR)))
driver.save_screenshot(OUTPUT)
print(f"Saved screenshot to {OUTPUT}")
finally:
driver.quit()
Install Selenium with python -m pip install selenium. Selenium Manager can obtain a suitable browser driver for supported setups; Chrome itself must also be installed. The 20-second wait is an example timeout, not a universal recommendation. Selenium’s waiting strategies documentation explains why navigation readiness and application readiness differ, and its screenshot documentation covers page and element captures.
1. Choose what “JavaScript finished loading” means
There is no universal signal that every script on every site has finished. Pages can load analytics, polling, animations, advertisements, and other work indefinitely. Define readiness as the state required for this screenshot.
| What you need | Wait for | Typical condition |
|---|---|---|
| A result or page section | That element to appear or become visible | visibility_of_element_located |
| A loading indicator to finish | The spinner or loading marker to disappear | invisibility_of_element_located |
| Text updated by the app | The expected text to appear | text_to_be_present_in_element |
| A client-side route change | The route-specific heading or content to appear | Visibility or a custom URL and content condition |
| A specific element only | That element’s ready state | Wait for it, then call its screenshot method |
Prefer a condition connected to the actual content over a generic delay. For example, if the page initially displays a loading marker and later replaces it with a chart, waiting for the chart to be visible is more meaningful than waiting a fixed number of seconds.
2. Use an explicit wait
An explicit wait repeatedly checks one condition until it succeeds or its timeout expires. Selenium provides expected conditions for presence, visibility, text, and other common states. You can also supply a callable for application-specific logic. See Selenium’s expected conditions guide.
Wait for visible content
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main .results")))
Use visibility when the screenshot must show the content. Presence only means the element exists in the DOM; it may still be hidden or have no visible content.
Wait for a loading marker to disappear
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".loading-spinner")))
Choose this when a real marker reliably represents loading. If the marker disappears before the important content is ready, combine its disappearance with a wait for the final content.
Wait for updated text
wait.until(EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, "[data-testid='status']"),
"Complete"
))
Use a custom readiness condition
For a condition that Selenium’s built-ins do not express, return a truthy value only when the page is ready. For example, this waits for a chart container to have at least one child:
def chart_has_content(driver):
chart = driver.find_element(By.CSS_SELECTOR, "#chart")
return chart if chart.is_displayed() and chart.find_elements(By.CSS_SELECTOR, "*") else False
wait.until(chart_has_content)
Keep custom checks small and repeatable: Selenium evaluates them many times. If an element may not exist yet, handle that expected intermediate state rather than letting a missing-element exception end the wait.
3. Capture the right area
driver.save_screenshot("page.png") captures the current browser context according to the driver’s screenshot behavior. If only one component matters, wait for it and capture that element instead:
chart = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "#chart")))
chart.screenshot("chart.png")
An element screenshot covers the visible region inside the element’s bounding rectangle. A normal driver screenshot is generally a viewport or current-context capture; do not assume it always means a full, arbitrarily long page. If the required output is a full-page image, confirm the behavior supported by your browser and driver or use a tool that explicitly provides full-page capture.
4. Handle pages with more complicated readiness
Wait for a particular number of results
If the page renders results in batches, wait for the expected minimum count rather than the first result:
def at_least_five_results(driver):
results = driver.find_elements(By.CSS_SELECTOR, ".result-card")
return results if len(results) >= 5 else False
wait.until(at_least_five_results)
Use a count that represents the screenshot you want. A page may legitimately return fewer results, so account for empty or short result sets if they are valid outcomes.
Wait for a route or state change
For a single-page application, wait for the expected route and the new content. A URL change alone can happen before rendering finishes; pair it with a page-specific element condition when the screenshot depends on that content.
Use async script execution only for a page-provided hook
If the application exposes a documented readiness promise or callback, Selenium can run asynchronous JavaScript in the current frame. The script must call Selenium’s completion callback, and the script timeout must allow that work to finish. Do not treat window.onload or document.readyState as proof that a later app update has completed.
driver.set_script_timeout(15)
driver.execute_async_script("""
const done = arguments[arguments.length - 1];
Promise.resolve(window.appReady)
.then(() => done(true))
.catch(error => done({error: String(error)}));
""")
This pattern applies only if the page actually provides window.appReady (or another known hook). Selenium’s Python WebDriver API documents the script timeout, and its JavaScript WebDriver API describes async scripts and their required completion callback.
5. Relevant timing and browser options
| Setting or method | What it controls | Guidance |
|---|---|---|
WebDriverWait timeout |
How long a specific condition may take | Set it to the maximum reasonable time for that page and operation. |
poll_frequency |
How often the condition is checked | The example uses 0.25 seconds. A shorter interval checks more often; it does not make the page render faster. |
set_page_load_timeout |
How long navigation may wait for the selected page-load strategy | Set it separately from the explicit wait. A navigation timeout and a readiness timeout are different failures. |
set_script_timeout |
How long asynchronous script execution may take | Set this when using execute_async_script. |
| Page-load strategy | Which document readiness state navigation waits for | Changing it can return control earlier, but it does not replace a page-specific wait. |
| Implicit wait | Global wait applied to element lookups | Prefer explicit waits for this task. Selenium warns against mixing implicit and explicit waits because elapsed times can become unpredictable. |
| Headless browser option | Whether the browser displays a window | Useful for automation environments. Keep viewport and browser settings consistent if comparing captures. |
Selenium’s default navigation readiness is complete, but scripts can continue to change the page after that state. A fixed time.sleep() can be useful as a short diagnostic, but it is a poor readiness rule: it may be too short on a slow run and needlessly long on a fast one. Selenium describes these timing races in its official waits documentation.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
TimeoutException waiting for an element |
The selector is wrong, the element never appears, or the condition does not match the page’s actual state. | Inspect the rendered DOM, verify the selector, and choose a condition the page reliably reaches. Check whether the content is inside an iframe. |
| Screenshot shows a spinner or incomplete content | The wait condition succeeded too early or tracks the wrong state. | Wait for the final content or expected text, and if appropriate also wait for the loading marker to disappear. |
| Element is present but screenshot looks empty | Presence was mistaken for visibility or meaningful content. | Wait for visibility and, for dynamically populated containers, wait for a child, text, or other app-specific signal. |
| Element cannot be found even though it appears in the browser | It may be in a different frame, window, or shadow DOM. | Switch to the correct frame or window before waiting. Use the page’s supported shadow-root access where applicable. |
StaleElementReferenceException |
The app replaced the element during rendering after Selenium found it. | Wait using a locator so Selenium can locate the current element again, then capture after the replacement settles. |
| Async script times out | The script did not call its callback, a promise rejected without completing, or the configured script timeout is too short. | Ensure every success and error path calls the callback, and set an appropriate script timeout. |
| Navigation times out before the explicit wait | The navigation load strategy did not complete within the page-load timeout, perhaps because the page keeps loading resources. | Review the page-load timeout and strategy, then still use a separate explicit wait for the content needed in the screenshot. |
| Screenshot is clipped or captures the wrong content | The desired scope is an element or full page, but the driver screenshot captures a different current context or viewport. | Switch to the intended window/frame; use an element screenshot for a component; verify full-page support for your browser and driver. |
| Works locally but fails in automation | Differences in browser version, viewport, network speed, headless settings, or available fonts change rendering timing or layout. | Make browser, viewport, and options consistent; wait for content rather than elapsed time; retain browser logs and failure screenshots for diagnosis. |
7. Performance, reliability, and cost
An explicit wait proceeds as soon as its condition is true, so it avoids waiting out the full timeout on fast pages. The timeout is a ceiling, not a sleep duration. Polling more often adds condition checks and does not accelerate JavaScript or network work. Avoid overly broad conditions that require unrelated page activity to stop, since analytics and background requests may never become idle.
For reliable captures, make the browser environment repeatable: use a consistent viewport, wait for the actual subject, and write screenshots to distinct paths when capturing multiple pages. Close the driver in a finally block so browser processes are cleaned up after a timeout or other error. Selenium is a good fit when the task requires browser interaction, custom application state, or a test environment you control. Running browsers also requires managing browser binaries, drivers, execution time, and infrastructure; costs depend on where and how you run them.
8. Or skip the browser setup
For a screenshot without managing a Selenium browser, ScreenshotNeo returns an image or PDF from one GET request. See the ScreenshotNeo API docs for its request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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(`Screenshot request failed: ${res.status}`);
await Bun.write("shot.webp", res);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. AI agents can use its MCP server tools to take screenshots, get page information, and capture PDFs. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
9. FAQ
Does Selenium wait for every JavaScript task to finish?
No. There is no universal “all JavaScript is finished” state for pages with ongoing or background activity. Wait for the specific rendered state your screenshot needs.
Should I use document.readyState?
It can tell you about document loading, but it does not prove that a later JavaScript-driven update has rendered. Pair it with an app-specific condition.
Can Selenium save a screenshot of one element?
Yes. Find and wait for the element, then call element.screenshot("element.png"). That captures its visible region.
Why not just increase the timeout?
A longer timeout helps only when the correct condition eventually becomes true. It cannot fix a wrong selector, a missing state, or a page that never reaches the expected condition.
Can I use this with another Selenium language?
Yes. The pattern is the same: navigate, wait for an observable condition using that language’s Selenium binding, then use its driver or element screenshot API. API names and file handling vary by binding.


