How to Handle Multiple Windows in Selenium with Python
Save window handles, wait for new tabs, switch reliably, and close or restore browser contexts in Selenium with Python.
To handle multiple windows or tabs in Selenium with Python, save the current window handle, trigger the new context, explicitly wait for it to appear, identify its handle, and call driver.switch_to.window(handle). After closing that context with driver.close(), switch to a handle that is still open. Selenium uses window handles for both tabs and windows; the operating system’s visual focus does not change WebDriver’s selected context. See Selenium’s official windows and tabs guide.
1. Open a window or tab from a page action
Install Selenium with python -m pip install selenium. This complete example opens the new window from Selenium’s demo page, waits for it, switches to it, checks its title, closes it, returns to the original context, and always ends the browser session.
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://www.selenium.dev/selenium/web/window_switching_tests/page_with_frame.html"
def main():
driver = webdriver.Chrome()
try:
driver.get(URL)
wait = WebDriverWait(driver, 10)
original = driver.current_window_handle
handles_before = set(driver.window_handles)
driver.find_element(By.LINK_TEXT, "Open new window").click()
wait.until(EC.new_window_is_opened(list(handles_before)))
added = set(driver.window_handles) - handles_before
if len(added) != 1:
raise RuntimeError(f"Expected one new window; found {len(added)}")
new_window = added.pop()
driver.switch_to.window(new_window)
wait.until(EC.title_is("Simple Page"))
print("New context title:", driver.title)
driver.close()
driver.switch_to.window(original)
print("Returned to:", driver.current_url)
finally:
driver.quit()
if __name__ == "__main__":
main()
Selenium Manager can manage the browser driver for supported setups when you instantiate webdriver.Chrome(). The browser itself must still be installed and available. If your environment uses a remote Selenium server, construct a webdriver.Remote session with that server’s configured URL and options; the handle and switching pattern is the same.
2. Wait for and identify the right handle
WebDriver handles are identifiers for open browsing contexts. Save handles before an action, then compare the post-action handle set with the saved set. This avoids assuming the new context is always at index 1 and works even if the session already had multiple contexts.
| Situation | Wait or selection pattern |
|---|---|
| Known total after action | wait.until(EC.number_of_windows_to_be(expected_count)) |
| Unknown initial total, expect one or more new contexts | wait.until(EC.new_window_is_opened(handles_before)) |
| Find a single new context | set(driver.window_handles) - handles_before, then validate the result count |
| Several contexts may open | Inspect every added handle and identify by URL, title, or expected page content |
new_window_is_opened only waits for the number of handles to increase. If the action can open more than one, do not blindly take pop(); inspect the difference. If the new handle appears before the destination page finishes loading, switch to it and then wait for a meaningful condition such as the expected title or element.
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(driver, 10)
before = set(driver.window_handles)
trigger_that_may_open_a_tab()
wait.until(EC.new_window_is_opened(list(before)))
added_handles = set(driver.window_handles) - before
for handle in added_handles:
driver.switch_to.window(handle)
try:
wait.until(lambda d: d.current_url != "about:blank")
if "expected.example" in driver.current_url:
target_handle = handle
break
except Exception:
continue
else:
raise RuntimeError("The expected destination did not open")
driver.switch_to.window(target_handle)
For a stable test, prefer a specific expected title, URL, or element over the illustrative about:blank condition above. The handle set is unordered: do not use a permanent list index to infer which context is new.
3. Create a new tab or window directly (Selenium 4+)
When the test needs a fresh context rather than responding to a link or popup, Selenium 4 provides driver.switch_to.new_window("tab") and driver.switch_to.new_window("window"). The command creates the context and selects it, so an immediate second switch is unnecessary.
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
try:
driver.get("https://example.com")
original = driver.current_window_handle
driver.switch_to.new_window("tab")
driver.get("https://www.selenium.dev/")
WebDriverWait(driver, 10).until(lambda d: d.title != "")
print("Tab title:", driver.title)
driver.close()
driver.switch_to.window(original)
finally:
driver.quit()
Remove the leading space before driver = webdriver.Chrome() if copying the code as a top-level Python file; it is shown here only as a visual continuation. The two creation choices differ in browser presentation, not in how Selenium addresses them afterward.
4. Close one context, return, or quit the session
driver.close()closes only the currently selected tab or window. It does not choose another handle for subsequent commands.driver.switch_to.window(saved_handle)selects a still-open context.driver.quit()ends the WebDriver session and closes all of its windows. Use it for teardown, commonly in afinallyblock.
Before returning, check that the saved handle remains present if the page might have closed it:
driver.close()
remaining = set(driver.window_handles)
if original_handle not in remaining:
raise RuntimeError("The original window was closed")
driver.switch_to.window(original_handle)
Closing the last open context may end the session or leave no usable window. Keep a live handle open until your work is complete. Cleanup with quit() even when assertions or interactions fail.
5. Handle common variations and edge cases
More than two contexts
Snapshot the full handle set immediately before the action. After waiting, calculate the added set and validate how many appeared. If an application opens multiple tabs, use an attribute of each destination—such as its title, URL, or a page-specific element—to select the intended context.
Popup opened asynchronously
Use an explicit wait after the click or other trigger. Do not rely on a fixed time.sleep(): it may be too short on a slow run and needlessly long on a fast one. Set the wait timeout to match the expected application behavior and let Selenium poll for the condition.
Blank or intermediate page
A newly created tab may initially have an empty title or about:blank. A window-count change means the context exists, not that navigation and page rendering have completed. After switching, wait for the relevant URL, title, or element.
Frames are not windows
An iframe is inside a page and does not get a separate top-level window handle. Use Selenium’s frame switching commands for frames; use window handles for tabs and windows.
Popups blocked by the browser or application
If no new handle appears before the wait expires, confirm the action is supposed to open a separate context and that the test environment allows it. For a test-owned context, create one with Selenium 4’s new_window method rather than depending on a site popup.
6. Troubleshooting
| Error or symptom | Likely cause | Fix |
|---|---|---|
NoSuchWindowException |
The selected handle was closed, or the saved handle is no longer valid. | Read driver.window_handles, choose a live handle, and switch before issuing more commands. Do not continue after closing the last context. |
| Timeout waiting for another window | The click did not open a context, the popup was blocked, or the wait expected the wrong count. | Verify the trigger and use new_window_is_opened for an increase or number_of_windows_to_be for a known total. |
| Commands affect the old page | WebDriver remained on its previous selected context even if another tab became visually active. | Call switch_to.window(new_handle) before locating elements or reading page state. |
| Wrong new tab selected | Code assumed a stable handle index or multiple contexts opened. | Compare before and after sets, then inspect candidate URLs, titles, or page elements. |
| Element not found immediately after switching | The context exists but its navigation or rendering is still in progress. | Wait for the expected element or other page-ready condition after switching. |
InvalidSessionIdException or commands fail after teardown |
quit() ended the WebDriver session. |
Create a new WebDriver session; a quit session cannot be resumed. |
| Cannot find the link locator | Link text differs, content is inside a frame, or the page has not loaded the target element. | Use a locator that matches the page, wait for it, and switch into the correct frame if appropriate. |
7. Reliability, performance, and cost
Window-handle operations themselves are lightweight, but browser startup, navigation, scripts, and remote WebDriver round trips dominate elapsed time. Reuse one driver session for related contexts within a test, avoid opening contexts the test does not need, and close temporary contexts promptly. Always use condition-based waits so slow and fast runs follow the same logic.
For reliability, keep each test’s expected handle count explicit, snapshot before the triggering action, assert the expected number of new handles, and use try/finally teardown. A test should not depend on whichever tab happens to look active on screen. Selenium has no multiple-window usage statistic relevant to this workflow, so no adoption or speed figure is needed to apply the pattern.
For a screenshot-only task, launching and maintaining a browser session can be more setup than the capture requires. ScreenshotNeo offers a screenshot API and MCP server; its listed plans range from a free 1,000 shots per month to paid options. See ScreenshotNeo for the product and the API documentation.
Or skip the browser setup
If the goal is a screenshot rather than interacting with a second window, call ScreenshotNeo’s API directly. Replace the example URL with the page you want to capture and provide your API key:
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,
)
r.raise_for_status()
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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the API documentation, then sign up for 1,000 free screenshots a month, with no card.
FAQ
Are tabs and windows handled differently in Selenium?
No. WebDriver addresses each as a window handle, regardless of whether the browser displays it as a tab or a separate window.
Does switching a handle wait for the page to finish loading?
No. Switching selects the context. Wait separately for the destination state your automation needs.
Can I switch back by using handle index zero?
It may appear to work in a simple session, but storing the original handle is clearer and avoids relying on collection ordering.
What should I use for an iframe?
Use Selenium’s frame switching API. An iframe does not add an item to driver.window_handles.


