Selenium Screenshot Is Blank After Switching to a New Window: Fix
Wait for the new window handle, switch WebDriver to it, and verify the page is ready before capturing. Here’s how to diagnose blank or wrong-tab screenshots.
A Selenium screenshot is taken from WebDriver’s current top-level browsing context. After a click opens another tab or window, wait for its handle, switch to that handle, then wait for a page-specific readiness condition before capturing. A tab looking active on screen does not prove WebDriver selected it. If the capture is still blank, log the current handle, URL, title, and a known page element immediately before taking the screenshot; then check whether the code uses Selenium’s regular WebDriver screenshot command or a separate DevTools screenshot path.
This sequence fixes the common context-selection and timing mistakes. The title alone does not identify a universal browser or driver defect.
1. The reliable fix: wait, switch, verify, capture
Selenium represents browser tabs and windows with window handles. Its documentation explains that a link can visually focus a new tab while WebDriver still does not know which operating-system window is active; code must explicitly switch to the new handle. The WebDriver specification likewise associates commands such as taking a screenshot with the session’s selected top-level browsing context. Selenium: Working with windows and tabs · W3C WebDriver specification
Use this Python pattern when an action opens one additional window. Replace the title and element selector with conditions that identify your actual page.
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
# Selenium Manager can locate a compatible driver for supported setups.
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 20)
try:
driver.get("https://www.selenium.dev/")
original_handle = driver.current_window_handle
original_handles = set(driver.window_handles)
# Replace this with the action in your test that opens the new tab/window.
driver.find_element(By.LINK_TEXT, "Documentation").click()
# Wait for a handle that was not present before the click.
wait.until(lambda d: len(set(d.window_handles) - original_handles) == 1)
new_handle = (set(driver.window_handles) - original_handles).pop()
driver.switch_to.window(new_handle)
# Wait for meaningful page readiness. A handle can exist before the
# expected application content is ready to render.
wait.until(EC.presence_of_element_located((By.TAG_NAME, "h1")))
print("handle:", driver.current_window_handle)
print("url:", driver.current_url)
print("title:", driver.title)
assert driver.current_window_handle == new_handle
assert driver.save_screenshot("target.png"), "Could not write target.png"
finally:
driver.quit()
The selector and expected content are examples: use a stable element or state that proves the target application is ready for your screenshot. Selenium’s documented Python example uses a window-count wait and title condition; the Python screenshot API saves the current window as PNG. Selenium Python WebDriver API
Why each step matters
- Save the original handle before the click. This gives you a reliable way to identify a newly created context even if several tabs already exist.
- Wait for the handle. A click returning does not mean the new browsing context is already available to WebDriver.
- Switch explicitly. Use
switch_to.window(new_handle); do not rely on visual focus or handle ordering. - Wait for application readiness. A handle can exist while navigation or client-side rendering is still in progress. Prefer a known page element or state over a fixed sleep.
- Inspect and capture in the same context. Log the current handle, URL, title, and expected element immediately before the screenshot.
2. Choosing and identifying the right window
Do not assume that the new page is always window_handles[1]. That can work in a minimal two-window example but becomes fragile when the session already has tabs or opens more than one. Take the set difference between handles before and after the action.
before = set(driver.window_handles)
# Trigger the action that opens a page.
wait.until(lambda d: bool(set(d.window_handles) - before))
new_handles = set(driver.window_handles) - before
if len(new_handles) != 1:
raise RuntimeError(f"Expected one new window; found {len(new_handles)}")
driver.switch_to.window(new_handles.pop())
If the site may open multiple windows, identify the intended one by URL or title rather than selecting an arbitrary new handle:
def target_window_is_ready(d):
for handle in d.window_handles:
d.switch_to.window(handle)
if d.current_url.startswith("https://example.com/report"):
return True
return False
wait.until(target_window_is_ready)
print(driver.current_window_handle, driver.current_url)
Keep in mind that this predicate switches contexts as it searches. When it returns, the matching context is selected. Adapt URL matching to your application, including redirects and query strings.
When your code creates the window
In Selenium 4, switch_to.new_window("tab") or switch_to.new_window("window") creates a context and switches to it. That is different from clicking a site link that opens a context: for the latter, wait for and select the handle created by the site. Selenium window and tab documentation
3. Check the page before blaming the screenshot
Run these checks immediately before capture:
print("handles:", driver.window_handles)
print("selected:", driver.current_window_handle)
print("url:", driver.current_url)
print("title:", driver.title)
print("readyState:", driver.execute_script("return document.readyState"))
# Replace with an element that should exist on the target page.
print("body text:", driver.find_element(By.TAG_NAME, "body").text[:300])
assert driver.save_screenshot("debug.png")
| What you observe | Likely area to investigate |
|---|---|
| Wrong handle, URL, or title | Handle selection or navigation. Recompute the new handle and switch explicitly. |
| Expected URL but missing page element | Page readiness, application error, redirect, authentication, or content rendered in a different frame. |
| Expected handle and page content, but regular Selenium screenshot is blank | Capture behavior in the specific browser/driver/session. Reproduce with a minimal script and record versions and headless/remote mode. |
| Regular screenshot works, DevTools screenshot does not | Investigate the distinct DevTools capture route; do not assume it behaves like WebDriver’s screenshot command. |
document.readyState can reach complete before a single-page application has finished rendering its useful content. Conversely, some pages intentionally continue loading resources after the state changes. A page-specific element or application readiness signal is generally a better capture gate.
4. Python, JavaScript, and other binding patterns
Python: complete minimal switching pattern
Install the binding with python -m pip install selenium. Provide a real link or button locator that opens a new context and a readiness condition specific to the destination.
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
options = webdriver.ChromeOptions()
# Uncomment for a headless run when appropriate:
# options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 20)
try:
driver.get("https://example.com/start")
before = set(driver.window_handles)
driver.find_element(By.CSS_SELECTOR, "a.opens-report").click()
wait.until(lambda d: len(set(d.window_handles) - before) == 1)
target = (set(driver.window_handles) - before).pop()
driver.switch_to.window(target)
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main h1")))
print({"handle": driver.current_window_handle,
"url": driver.current_url,
"title": driver.title})
if not driver.save_screenshot("report.png"):
raise OSError("Selenium could not save report.png")
finally:
driver.quit()
JavaScript: Selenium WebDriver
For the JavaScript binding, install selenium-webdriver and use the same before/after handle comparison. This example expects a link opening one new window and waits for a destination title.
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
(async () => {
const options = new chrome.Options();
// Uncomment for a headless run when appropriate:
// options.addArguments('--headless');
const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();
try {
await driver.get('https://example.com/start');
const before = new Set(await driver.getAllWindowHandles());
await driver.findElement(By.css('a.opens-report')).click();
await driver.wait(async d => {
const handles = await d.getAllWindowHandles();
return handles.filter(h => !before.has(h)).length === 1;
}, 20000);
const handles = await driver.getAllWindowHandles();
const target = handles.find(h => !before.has(h));
await driver.switchTo().window(target);
await driver.wait(until.titleIs('Expected report'), 20000);
console.log({handle: await driver.getWindowHandle(),
url: await driver.getCurrentUrl(),
title: await driver.getTitle()});
await driver.takeScreenshot().then(data =>
require('node:fs').writeFileSync('report.png', data, 'base64'));
} finally {
await driver.quit();
}
})().catch(err => { console.error(err); process.exitCode = 1; });
Java and C#
The same diagnostic order applies in every binding: save existing handles, wait for a new handle, switch to it, wait for a page-specific condition, inspect the selected context, and capture. Method names differ by language. Follow the official Selenium binding documentation for the installed version rather than copying Python method names into Java or C#.
5. Distinguish WebDriver screenshots, DevTools, and BiDi
A normal Selenium screenshot command captures the selected WebDriver context. The W3C WebDriver screenshot command describes a screenshot of the top-level browsing context’s visual viewport; if that context has closed, WebDriver returns a “no such window” error. W3C WebDriver: screenshots and browsing contexts
A separate Chrome DevTools Protocol call such as Page.captureScreenshot is a different capture route. Selenium issue #12529 records a historical report in which a DevTools screenshot continued to show the main window after a popup switch. That report is evidence about the reporter’s DevTools path and setup; it does not prove ordinary WebDriver screenshots have the same issue or establish behavior in current versions. Reproduce the exact API used by your application. SeleniumHQ issue #12529
WebDriver BiDi also defines browsingContext.captureScreenshot, with an explicit context ID and options such as viewport versus document capture and image format. Browser and active-session support varies; the API can report an unsupported operation when capture is unavailable. Consider it only after confirming the correct page is selected and ready. MDN: browsingContext.captureScreenshot
6. Troubleshooting common blank screenshot failures
| Symptom or error | Cause to check | Fix |
|---|---|---|
| Screenshot shows the previous tab | WebDriver remained on the original handle, or the code used a separate capture session/API. | Log current_window_handle and switch to the handle discovered after the action. If using DevTools, verify its target/session is attached to the intended context. |
| Screenshot is white or mostly empty but no exception occurs | Capture occurred before the target page rendered, or the loaded document itself is empty/erroring. | Wait for a stable, visible application element and inspect the URL, title, body text, and console/application state where available. |
| Timeout waiting for a new window | The click did not open a context, popup policy blocked it, the locator triggered a same-tab navigation, or the timeout is too short. | Check the action and resulting handle list. If it navigates the same tab, wait for URL or page content instead of a new handle. Choose a timeout based on observed system behavior. |
NoSuchWindowException / “no such window” |
The selected context was closed, or code closed it and continued issuing commands without switching back. | Inspect current handles, switch to a handle that still exists, or end the session if none remain. Selenium documents that closing a window does not automatically restore another context. |
| Screenshot save returns false or file is missing | Filesystem path or write permissions problem; this is separate from a blank image. | Use a writable absolute path, ensure the parent directory exists, check the screenshot method’s return value, and inspect the output file independently. |
| BiDi returns unsupported operation | The browser or current session does not support that capture command/context. | Check implementation support for the exact browser/session, or use standard WebDriver screenshot capture after selecting the correct context. |
| Only remote/headless execution is blank | An environment-specific difference remains possible; the supplied facts do not establish one universal cause. | Compare a minimal reproduction across the exact Selenium binding, browser, driver, headless/headed mode, remote/local session, and capture route. Keep the page and readiness condition identical. |
7. Reliability, speed, and cost considerations
- Prefer conditions over sleeps. Polling for a new handle and meaningful page state avoids adding a fixed delay to every run while still waiting when navigation is slow. Set finite, realistic timeouts and report which condition timed out.
- Make handle selection deterministic. Compare against the pre-action handle set and verify the URL or title when several windows can open.
- Capture only after content is stable. For animated pages or asynchronous widgets, wait for the exact state your test needs. A screenshot can faithfully capture a page that is technically loaded but visually unfinished.
- Close deliberately. If closing the new page, switch to a surviving handle before subsequent commands. Use
driver.quit()to end the whole session during cleanup. - Account for browser infrastructure. Selenium requires a running browser session and driver or remote WebDriver service. A screenshot-only API can reduce browser setup for simple URL-to-image jobs, but it does not replace Selenium when you need to interact with application state or validate browser behavior.
For reproducible diagnosis, record Selenium binding/version, browser/version, driver/version, operating mode, local versus remote execution, capture API, window dimensions, and the page readiness condition. The Python API documents screenshots as PNG for its standard save method; output dimensions and behavior may depend on the browser/session, so inspect the generated file rather than assuming a particular viewport or full-page result.
8. Or skip the browser setup
If you need a clean screenshot of a public URL rather than a Selenium interaction test, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. See the ScreenshotNeo API documentation.
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}`);
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page info, and PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
9. FAQ
Does switching windows guarantee the page is ready?
No. It selects the browsing context. Wait separately for the destination content your application requires.
Should I use a fixed sleep after switching?
Usually prefer an explicit wait for a handle and a meaningful page condition. A fixed sleep can be too short on a slow run and waste time on a fast one.
Does Selenium automatically follow the tab that appears in front?
No. Visual operating-system focus and WebDriver’s selected handle are separate; switch explicitly.
Is the DevTools popup report proof that Selenium screenshots are broken?
No. It concerns a reported DevTools capture path. Test the same API your code uses and avoid generalizing that report to standard WebDriver screenshots.
Can I use ScreenshotNeo for a test that must click through a login flow?
ScreenshotNeo is suited to capturing a URL through its screenshot API. Use Selenium when the task depends on browser interaction and session state.


