Fix Blank Website Screenshots in Selenium with Window and Wait Settings
A blank Selenium screenshot often means the wrong page context, viewport, or render condition. Check the active tab, inspect window dimensions, and wait for page-specific content before capture.
If a Selenium screenshot is blank, first confirm that the intended page is open in the active tab, set and verify a useful browser window size, then wait for a page-specific element to become visible before capturing. A navigation call returning does not prove that a JavaScript-driven page has finished rendering.
This guide uses Selenium with Python. The same diagnostic order applies in other Selenium language bindings: verify the browser context, inspect the viewport, synchronize with the page’s actual ready condition, then compare browser and driver behavior if the problem remains.
1. Confirm Selenium is on the page you expect
Selenium captures the current browser context. Before changing waits, inspect the current URL, available window handles, and active handle. If the site opened a new tab or window, switch to it before taking the screenshot.
print("Current URL:", driver.current_url)
print("Window handles:", driver.window_handles)
print("Active handle:", driver.current_window_handle)
# If you know the target handle, switch to it:
# driver.switch_to.window(target_handle)
Also check whether navigation reached the intended route, including redirects and trailing slashes. A screenshot of a blank tab, an intermediate route, or an error page can look like a rendering failure.
2. Set and read back the window size
Window dimensions affect responsive layouts and can change what the application renders. Set the intended size explicitly, then read back the dimensions from the running session. Do not assume that the requested dimensions were applied.
driver.set_window_size(1365, 900)
size = driver.get_window_size()
print("Observed window size:", size)
Choose a size suitable for the page layout you need to capture. If the site behaves differently at mobile and desktop breakpoints, try a deliberate viewport for each layout and verify the resulting content. Selenium documents the window sizing and current-context screenshot behavior in its windows and tabs documentation and window interactions documentation.
3. Wait for rendered content, not just navigation
Modern pages often render or update content after the browser reports that navigation is ready. Wait for a representative element that proves the content you want is present and visible. Prefer a stable page-specific selector over a generic delay.
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
wait = WebDriverWait(driver, 20)
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main")))
# Replace "main" with a stable selector for the content to capture.
driver.save_screenshot("page.png")
The selector and timeout are examples; use a condition and duration appropriate for the target site and environment. Selenium’s expected conditions include checks for presence, visibility, text, and title. For a more specific readiness signal, wait for a known heading or a piece of text:
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "article h1")
))
wait.until(EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, "article h1"),
"Expected page heading"
))
driver.save_screenshot("page.png")
Use the condition that matches the page. Presence in the DOM does not necessarily mean an element is visible, and visibility does not prove that every image or below-the-fold component has loaded. If the capture includes lazy-loaded content, scroll or otherwise trigger that content using a page-appropriate approach, then wait for the relevant elements before capturing.
4. Choose a page-load strategy with a separate render wait
Selenium supports three page-load strategies. They change when navigation returns; none guarantees that a single-page application has completed later asynchronous rendering.
| Strategy | Navigation waits for | What to do before capture |
|---|---|---|
normal |
The load event | Still wait for the page-specific content you need. |
eager |
DOMContentLoaded |
Wait for the application content after navigation. |
none |
No ready-state condition | Immediately apply explicit waits for the required page state. |
For most captures, keep the default strategy and add an explicit wait for the content. Consider another strategy only when its navigation behavior fits the application and your automation flow. Selenium describes these settings in its browser options documentation.
5. Complete Python example
This runnable example opens a page, checks the destination, sets and reports the window size, waits for visible content, and saves a PNG. Install Selenium and ensure a compatible browser is available; current Selenium releases can manage drivers through Selenium Manager in supported setups.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
url = "https://example.com"
options = webdriver.ChromeOptions()
# For headless runs, configure a deliberate viewport as well.
options.add_argument("--headless=new")
# Optional: choose how navigation waits. The default is normally sufficient.
# options.page_load_strategy = "normal" # also: "eager" or "none"
driver = webdriver.Chrome(options=options)
try:
driver.set_window_size(1365, 900)
driver.get(url)
print("Current URL:", driver.current_url)
print("Window size:", driver.get_window_size())
wait = WebDriverWait(driver, 20)
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "main")
))
driver.save_screenshot("page.png")
finally:
driver.quit()
Replace https://example.com and the main selector with the target page and a meaningful readiness condition. If the page has no main element, wait for a stable heading, article, or other visible content marker instead. Selenium’s screenshot API captures the current context; see the official windows and tabs documentation.
6. Avoid conflicting waits
Use explicit waits for page-specific conditions. Avoid setting an implicit wait and also using explicit waits in the same session: Selenium warns that combining them can produce unpredictable timeout durations. A fixed sleep can be too short on a slow run and waste time on a fast one. Selenium explains these tradeoffs in its waiting strategies documentation.
# Prefer one explicit, page-specific wait strategy:
wait = WebDriverWait(driver, 20)
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main")))
# Avoid pairing driver.implicitly_wait(...) with WebDriverWait(...).
7. Troubleshooting common blank captures
| Symptom | Likely cause | Fix |
|---|---|---|
| The image is entirely white or empty | The browser is on the wrong tab, route, or intermediate page; content has not rendered. | Print the URL and handles, switch to the intended window, and wait for a visible page-specific element. |
| The screenshot has the wrong layout or missing columns | The effective window size differs from the intended viewport, or the page selected a responsive breakpoint. | Set dimensions explicitly, read them back, and choose a viewport that matches the desired layout. |
| The wait succeeds but the relevant region is still blank | The waited-for element is generic or appears before the target content. | Wait for a more representative element or expected text within the content region you need. |
| It works locally but fails in headless or CI runs | The browser environment, viewport, timing, or driver behavior differs. | Set a deliberate window size, log the URL and observed size, and compare browser and driver versions and behavior. |
| Navigation returns before the page is usable | The selected page-load strategy only controls navigation readiness, while the application continues rendering asynchronously. | Keep navigation strategy and application readiness separate; add an explicit wait after navigation. |
| Timeouts vary unpredictably | Implicit and explicit waits are combined, or the condition does not match the page reliably. | Remove the implicit wait, use a stable condition, and set an explicit timeout appropriate to the environment. |
| Only one browser fails | Browser-specific behavior or a driver issue may be involved. | Compare the same flow in another supported browser and inspect the corresponding driver and version. |
| The page shows a challenge, access-denied screen, or CAPTCHA | The destination is presenting a bot check or access restriction rather than the expected page. | Confirm the page state and site access policy. Selenium’s window and wait settings cannot establish that the intended content loaded. |
Selenium notes that some reported failures originate in browser drivers. Its troubleshooting guidance and common errors reference are useful when context, sizing, and waits look correct.
8. Performance, reliability, and cost considerations
- Wait for a signal, not an arbitrary long delay. An explicit condition can return as soon as the needed content is ready, while a fixed sleep always waits its full duration and may still be too short.
- Make the capture repeatable. Log the current URL, window handle, observed dimensions, and which readiness condition timed out. These details narrow down whether the issue is context, viewport, rendering, or driver behavior.
- Keep timeouts bounded. A timeout should allow the expected environment enough time while letting failures surface. No single timeout fits every site or run environment.
- Account for browser resources. Headless browser sessions still consume compute and need browser startup, navigation, and page rendering time. Reuse a session when appropriate, and always close it in a
finallyblock or equivalent cleanup path. - Budget for failed attempts. With a DIY Selenium setup, browser infrastructure and retries are part of the operating cost. Distinguish a genuine page failure from a capture taken too early before automatically retrying.
Or skip the browser setup
If your goal is a screenshot rather than managing a browser session, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. Its documentation covers the API parameters and options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture, along with 60+ known consent platforms, newsletter popups, and chat widgets. Each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- 1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does document.readyState being complete mean the page is ready for a screenshot?
No. JavaScript can add or change content after navigation readiness. Wait for a condition tied to the content you need.
Should I use a longer timeout to fix every blank screenshot?
No. First verify the active context and dimensions, then wait for a representative page condition. Increase a timeout only when the correct condition sometimes takes longer in the target environment.
Will changing from normal to eager fix a single-page application?
Not by itself. The strategy changes when navigation returns; an application-specific wait is still needed for content rendered afterward.
What if the screenshot is still blank after these checks?
Record the URL, active handle, observed dimensions, and wait condition; then compare browser and driver behavior. Those observations help locate the failing stage without assuming a root cause.


