ScreenshotNeo

BlogHow-to

How to Handle Browser Tabs in Selenium

Learn to switch between Selenium tabs safely: wait for new windows, identify handles, create tabs, and return to a valid context.

By the ScreenshotNeo team4 October 20269 min read

Selenium handles browser tabs and windows the same way: each is a browsing context identified by a window handle. To work in a tab opened by a click, save the current handle, wait for the handle count to change, find the new handle by comparing it with the saved one, and explicitly switch to it. After closing a tab, switch to a handle that is still open.

1. Understand Selenium window handles

A window handle is Selenium’s identifier for a browser tab or window. WebDriver does not distinguish between those two kinds of browsing context. The browser may visually focus a newly opened tab, but Selenium commands still apply to the context selected by WebDriver; switch explicitly before reading or interacting with the new page. See Selenium’s Working with windows and tabs guide.

Keep the original handle when a test may need to return to its starting page. The set of handles can change as contexts open and close, so avoid relying on a fixed list position to identify a particular tab.

2. Switch to a tab opened by a click (Python)

This complete example assumes Selenium 4, Python, and a page with a link whose visible text is “Open new window.” Replace the URL and link text with those from your test site. Install the binding with python -m pip install selenium. The browser driver must also be available through Selenium Manager or your environment’s driver 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

start_url = "https://example.com/page-with-new-tab-link"
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)

try:
    driver.get(start_url)
    original_handle = driver.current_window_handle
    handles_before_click = set(driver.window_handles)

    driver.find_element(By.LINK_TEXT, "Open new window").click()

    # Wait until at least one context has been added.
    wait.until(lambda d: len(d.window_handles) > len(handles_before_click))

    new_handles = set(driver.window_handles) - handles_before_click
    if not new_handles:
        raise RuntimeError("The click did not open a new browser context")

    # If exactly one context should open, select it directly.
    new_handle = new_handles.pop()
    driver.switch_to.window(new_handle)

    # Wait for the destination page to finish reaching a useful state.
    wait.until(lambda d: d.title == "Expected destination title")
    print("New tab title:", driver.title)

    # Interact with the new tab here.
finally:
    # Close the current context only if it is not the original one.
    if driver.current_window_handle != original_handle:
        driver.close()
    # Return only to a context that remains open.
    if original_handle in driver.window_handles:
        driver.switch_to.window(original_handle)
    driver.quit()

The count wait confirms a context opened; the set difference identifies it without assuming its position. If the test can open several contexts, keep the full new_handles set and inspect each candidate after switching, for example by checking its title or URL. Do not use pop() when more than one candidate is possible unless the test has another way to identify the intended destination.

3. Use explicit waits and identify the intended page

Use a wait condition that matches the action’s expected result. Selenium’s Python expected conditions include number_of_windows_to_be(count) for an exact expected count. For example, when the test starts with one context and expects exactly one new one:

wait.until(EC.number_of_windows_to_be(2))
new_handle = (set(driver.window_handles) - {original_handle}).pop()
driver.switch_to.window(new_handle)
wait.until(EC.title_contains("Destination"))

When the starting count can vary, waiting for a count greater than the saved count is more appropriate. When multiple new tabs may open, wait for the expected count if known, then examine the new handles. Titles and URLs may initially be empty or temporary while navigation is underway, so wait for the destination property you actually need before asserting or interacting.

Official references: Selenium window and tab guide and the Python expected conditions API.

4. Create a blank tab or window with Selenium 4

If the test needs a fresh context rather than one opened by the site, Selenium 4 provides switch_to.new_window. It creates the requested context and switches WebDriver into it:

driver.switch_to.new_window("tab")
driver.get("https://example.com/")
print(driver.current_url)

Use "window" instead of "tab" to request a new browser window. This API is documented for Selenium 4 and later. For older binding versions, check that version’s API documentation rather than assuming the method exists.

5. Return to the original tab and close contexts safely

driver.close() closes the currently selected tab or window. It does not automatically select the previous context. Save the original handle before switching, close the working context, and explicitly switch back if the original remains open:

original_handle = driver.current_window_handle
# ... open and switch to another context ...
driver.close()

if original_handle in driver.window_handles:
    driver.switch_to.window(original_handle)
else:
    raise RuntimeError("The original browser context is no longer open")

Use driver.quit() to end the WebDriver session and close all associated contexts. Use close() when the session should continue with other tabs. Do not issue page commands while the selected handle refers to a context that has already been closed; that can raise a No Such Window exception.

6. JavaScript example

In Selenium’s JavaScript binding, driver operations are asynchronous. Await the handle reads, switching, and waits. This example uses the Selenium WebDriver package and expects one new context:

const { Builder, By, until } = require('selenium-webdriver');

(async function handleNewTab() {
  const driver = await new Builder().forBrowser('chrome').build();
  let originalHandle;

  try {
    await driver.get('https://example.com/page-with-new-tab-link');
    originalHandle = await driver.getWindowHandle();
    const handlesBefore = new Set(await driver.getAllWindowHandles());

    await driver.findElement(By.linkText('Open new window')).click();
    await driver.wait(async () => {
      const handles = await driver.getAllWindowHandles();
      return handles.length > handlesBefore.size;
    }, 10000);

    const handlesAfter = await driver.getAllWindowHandles();
    const newHandle = handlesAfter.find(handle => !handlesBefore.has(handle));
    if (!newHandle) throw new Error('No new browser context was opened');

    await driver.switchTo().window(newHandle);
    await driver.wait(until.titleIs('Expected destination title'), 10000);
    console.log(await driver.getTitle());
  } finally {
    const handles = await driver.getAllWindowHandles();
    const current = await driver.getWindowHandle().catch(() => null);
    if (current && current !== originalHandle) await driver.close();
    if (originalHandle && (await driver.getAllWindowHandles()).includes(originalHandle)) {
      await driver.switchTo().window(originalHandle);
    }
    await driver.quit();
  }
})();

Install the package with npm install selenium-webdriver and configure the browser and driver for your environment. The official Selenium guide has examples for additional bindings; use the method names and asynchronous conventions for the binding installed in your project.

7. cURL, Python, and Node.js alternatives for a screenshot

If the goal is to save a rendered page image rather than drive browser interactions, a screenshot API can return the capture directly. These examples use ScreenshotNeo’s documented endpoint and parameter style; see the ScreenshotNeo API documentation for the available 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,
)
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}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);

8. Waits, reliability, performance, and cost

  • Wait for state, not elapsed time. Explicit waits for a handle count, title, URL, or element are less brittle than fixed sleeps. Use a timeout long enough for your environment and keep it bounded so failures are reported promptly.
  • Handle races deliberately. A click can fail to open a tab, open more than one, or trigger a delayed navigation. Capture the pre-action handle set, wait for a meaningful change, and validate the destination before continuing.
  • Keep cleanup in a finally block. Tests can fail after switching. Cleanup should close only the intended context, check whether the original handle is still open, and quit the driver at the end of the test session.
  • Limit unnecessary contexts. Each extra tab adds browser work and can make tests slower or harder to reason about. Close temporary contexts when their assertions are complete.
  • Set a cost boundary. Selenium itself is browser automation; infrastructure cost depends on where and how the browser runs. This dossier provides no benchmark or cost figure for Selenium execution. For repeated image-only capture, compare the cost of maintaining browser infrastructure with a screenshot API’s plan and request pattern.

9. Troubleshooting

Symptom Likely cause Fix
Commands still operate on the first tab The new context opened visually, but WebDriver did not switch to its handle. Wait for the handle change, identify the new handle, then call switch_to.window(handle).
No new handle appears before timeout The click did not open a context, the locator/action was wrong, or the timeout is too short for the environment. Confirm the click target and expected site behavior. Save the handle set before the action and inspect the current count when diagnosing.
The wrong tab is selected Code assumed an index or selected arbitrarily when several contexts opened. Compare before and after handle sets, then check each candidate’s title or URL after switching.
NoSuchWindowException or “no such window” The selected handle was closed, or the session has no remaining valid context. Read the live handle list and switch to a handle still present. Do not switch back to a closed handle.
Title or page content is empty immediately after switching The context exists but its navigation is still in progress. Wait for a specific title, URL, or element that indicates the needed page state.
new_window is unavailable The installed Selenium binding/version may predate the Selenium 4 API. Upgrade to a Selenium 4 release or use the site action to open a context and handle it with the window-handle workflow.
Cleanup raises another error Earlier test code may already have closed the current or original context. Check handles before closing or switching, and always end the session with quit() in final cleanup.

10. Or skip the browser setup

For a screenshot rather than browser interaction, ScreenshotNeo takes a URL in one GET request and returns an image or PDF. Before capture, it accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

See the API docs for the request options. Sign up for 1,000 free screenshots a month, with no card required.

11. FAQ

Does Selenium use a different API for tabs and windows?

No. In this workflow both are browsing contexts managed through window handles.

Why save the original handle before clicking?

It gives the test a stable reference for finding the new context and returning to the starting page after the work is done.

Does closing a tab return to the previous one?

No. Explicitly switch to a remaining handle after calling close().

Yes. Selenium 4 and later provide switch_to.new_window("tab"), which switches into the new context.

When should I use a screenshot API instead?

Use browser automation when the task needs interaction or assertions across contexts. For a rendered screenshot or PDF without browser control, an API request may be simpler.