How to Switch Tabs in Selenium with Python
Switch to tabs opened by a click, create tabs with Selenium 4, and return safely to the original page using window handles and explicit waits.
Selenium switches between tabs with window handles. Save the current handle, wait for the new tab to open, find the handle that was not present before, and call driver.switch_to.window(handle). Selenium uses the same mechanism for tabs and windows; a click that opens a tab does not automatically switch WebDriver to it. See the official Selenium windows and tabs guide.
1. Install Selenium and start a browser
Install Selenium in the Python environment used by your test:
python -m pip install selenium
This runnable example uses Chrome. Selenium Manager can handle driver setup for supported local configurations when you create the driver this way. If your environment manages drivers differently, replace the driver setup with your existing configuration.
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
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
try:
driver.get("https://www.selenium.dev/selenium/web/window_switching.html")
print("Current handle:", driver.current_window_handle)
print("Open handles:", driver.window_handles)
finally:
driver.quit()
driver.current_window_handle identifies the selected browsing context. driver.window_handles returns the handles for the contexts currently open in the session. A handle is an identifier for the session, not a stable tab number or page title.
2. Switch to a tab opened by a click
Save the handles before the click. Then wait until there are two contexts and select the new handle by set difference. This example uses Selenium’s documented window-switching demo page and its “Open new window” link.
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
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
try:
driver.get("https://www.selenium.dev/selenium/web/window_switching.html")
original_handle = driver.current_window_handle
handles_before_click = set(driver.window_handles)
driver.find_element(By.LINK_TEXT, "Open new window").click()
# This count assumes the click opens exactly one additional context.
wait.until(EC.number_of_windows_to_be(len(handles_before_click) + 1))
new_handles = set(driver.window_handles) - handles_before_click
if len(new_handles) != 1:
raise RuntimeError(f"Expected one new tab, found {len(new_handles)}")
new_handle = new_handles.pop()
driver.switch_to.window(new_handle)
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
print("Now on:", driver.current_url)
print("Title:", driver.title)
finally:
driver.quit()
In a test that starts with one browser context and opens exactly one more, the wait can simply be EC.number_of_windows_to_be(2). Taking the difference from the saved handle set is safer than assuming that the new tab is always at index 1. Selenium documents both the count condition and new_window_is_opened in its Python expected-conditions API.
When more than one tab may open
If one action can open multiple contexts, wait for an expected count and inspect the set difference. Do not use pop() unless you have confirmed there is exactly one new handle: set order does not identify which page you want.
before = set(driver.window_handles)
# Perform the action that may open several tabs.
wait.until(EC.number_of_windows_to_be(len(before) + 2))
opened = set(driver.window_handles) - before
for handle in opened:
driver.switch_to.window(handle)
print(handle, driver.current_url, driver.title)
Choose the right handle using a page-specific signal, such as its URL or title, then switch back to that handle to continue. If the number of new tabs is variable, use a wait predicate that checks for a new handle rather than waiting for a guessed exact count:
before = set(driver.window_handles)
# Perform the action that opens a new context.
wait.until(lambda d: bool(set(d.window_handles) - before))
opened = set(driver.window_handles) - before
# Inspect candidates and select the one matching the page you expect.
for handle in opened:
driver.switch_to.window(handle)
if "expected-path" in driver.current_url:
break
else:
raise RuntimeError("The expected tab did not open")
3. Create a tab directly with Selenium 4
If the test itself should create a fresh tab, Selenium 4 and later provide driver.switch_to.new_window("tab"). Selenium creates the context and switches to it. Use "window" when you want to request a separate window instead. The API accepts "tab" or "window"; if the type is omitted, the browser may choose. See the Python switch-to API reference.
from selenium import webdriver
driver = webdriver.Chrome()
try:
original_handle = driver.current_window_handle
driver.switch_to.new_window("tab")
new_handle = driver.current_window_handle
driver.get("https://example.com")
print("Original:", original_handle)
print("New tab:", new_handle)
print("Page title:", driver.title)
finally:
driver.quit()
This differs from switching to a page-opened tab: new_window creates a context, while switch_to.window(handle) selects an existing one.
4. Return to the original tab and close safely
After closing the selected tab, switch to a handle that is still open before issuing more browser commands. Commands directed at a closed context can raise NoSuchWindowException. driver.close() closes the current context; driver.quit() ends the WebDriver session and closes all its contexts. See Selenium’s browser windows documentation.
original_handle = driver.current_window_handle
# Open another tab, then switch to its handle.
# ...
driver.close() # Closes the currently selected tab.
driver.switch_to.window(original_handle)
print("Back on:", driver.current_url)
For cleanup in a test, keep driver.quit() in a finally block so the session is ended even if an assertion or navigation fails.
5. Wait for the right event
Opening a tab is asynchronous from the test’s perspective. Read the handles only after waiting for the expected change. Selenium’s explicit waits poll a condition until it succeeds or times out. Common choices include:
| Situation | Wait condition | Use it when |
|---|---|---|
| Known total number of contexts | EC.number_of_windows_to_be(count) |
The test knows the exact count after the action. |
| At least one context added | EC.new_window_is_opened(before_handles) |
A new context should appear, but you will identify the handle afterward. |
| Known page loaded in selected context | A wait for a URL, title, selector, or application-specific condition | The handle exists, but the target page still needs to become usable. |
For example, wait for any new window using the original handle list:
before = driver.window_handles
# Click the control that opens another tab.
wait.until(EC.new_window_is_opened(before))
new_handle = (set(driver.window_handles) - set(before)).pop()
driver.switch_to.window(new_handle)
Use a condition tied to the actual next operation when possible. Waiting for a tab count only confirms that a context exists; it does not prove that a particular element or application state is ready.
6. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
NoSuchWindowException |
The code is targeting a context that was closed, or the handle is stale. | Check driver.window_handles, then switch to a handle that remains open. Save the original handle before opening another tab. |
TimeoutException waiting for a new window |
The click did not open a tab, the link was blocked, or the expected count is wrong. | Confirm the click locator matched the intended control. Log handles before and after the click. Wait for the actual expected count, or use new_window_is_opened when the exact count is not known. |
| Switches to the wrong tab | The code assumed a handle index or selected arbitrarily when several tabs opened. | Compute the set difference from the pre-action handles, then inspect each new page’s URL or title to choose the right one. |
| New handle appears, but page content is missing | The browsing context opened before navigation or the target element finished loading. | After switching, explicitly wait for the expected URL, title, selector, or application state. |
InvalidSessionIdException or browser commands fail after cleanup |
driver.quit() already ended the whole session. |
Create a new driver session; do not attempt to switch handles after quitting. |
| Click does not open a tab in automation | The page behavior, popup policy, or click target differs in the test environment. | Verify the element is interactable and that the page’s own action opens a context. If the test needs a fresh context regardless of page behavior, use Selenium 4’s new_window("tab"). |
7. Reliability, performance, and test design
- Keep handle state explicit. Capture the original handle and the pre-action handle set before every action that may open a tab. This avoids confusing existing contexts with newly opened ones.
- Wait for outcomes, not arbitrary time. A fixed sleep can be too short on a slow run and waste time on a fast one. Prefer explicit conditions and choose a timeout that fits the test environment.
- Use a page-specific readiness check. A new handle only means the context exists. Wait for the URL, title, or element that proves the destination is ready for the next step.
- Close only what the test owns. Close the current tab when finished and restore a still-open handle. Quit the driver once the full test is done.
- Keep tests isolated. A test that depends on leftover tabs from another test can select the wrong context. Start with a known session state and assert the expected number of handles around tab operations.
Tab switching itself is a small WebDriver operation. In practice, time is usually spent waiting for the page and its resources, so reliable readiness conditions matter more than repeatedly switching handles. Selenium’s official documentation describes the shared window-handle mechanism; browser and remote-execution details can vary, so validate the behavior in the actual driver setup used by your suite.
8. Or skip the browser setup
If your goal is a screenshot rather than an interactive browser test, ScreenshotNeo can return an image or PDF from one GET request. Its API supports PNG, JPEG, and WebP screenshots, plus PDF output. The API documentation describes the 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}`);
- Cookie and consent banners are accepted or removed before capture; newsletter popups and chat widgets are removed too. Each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Responses include
X-Page-VerdictandX-Billedheaders. - An MCP server gives AI agents tools named
take_screenshot,get_page_info, andcapture_pdf. - The Free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.
9. FAQ
Can I use a tab index such as 1?
Use a window handle instead. Handles identify open browsing contexts; their list position should not be treated as a durable tab identity.
Does switching tabs navigate to the page?
No. Switching selects an already open context. Use driver.get(url) to navigate the selected context.
Does new_window("tab") work in Selenium 3?
The documented creation method is for Selenium 4 and later. With older versions, upgrade to Selenium 4 to use this API.
Does closing a tab end the WebDriver session?
No. close() closes the selected context. quit() ends the session and closes its contexts.


