ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team4 October 20268 min read

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 a finally block.

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.

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.