A click opens a payment popup. A target="_blank" link launches a new tab. A help-link opens documentation in a separate window. Each one is a new browsing context, and Selenium tracks them via opaque string identifiers called window handles. This lesson covers the handle model, the canonical pattern for switching to a freshly-opened window, and the Selenium 4 shortcut for opening one programmatically. Master these and you can drive any flow that spans more than one tab without losing your place.
What a window handle is
Every window or tab Selenium controls has a unique opaque string ID — its handle. The handles look like UUIDs (CDwindow-D7B1A3E2...) and have no semantic meaning beyond "this string identifies this window." You collect handles, you switch to one by handle, and the driver routes future commands to that window's document.
Two methods do most of the work:
driver.getWindowHandle()— the handle of the current window.driver.getWindowHandles()— aSet<String>of every window the session controls.
The plural form returns a Set (not a List) because order is not guaranteed across calls or browsers. Don't reach for "the second one"; reach for "the one I haven't seen before."
The canonical "click opens new tab" pattern
import java.util.Set;
// 1. Remember where you started
String mainWindow = driver.getWindowHandle();
// 2. Trigger the action that opens the new window
driver.findElement(By.linkText("Open documentation")).click();
// 3. Wait for the new window to actually exist
wait.until(d -> d.getWindowHandles().size() > 1);
// 4. Switch to it — pick the handle that ISN'T the original
Set<String> all = driver.getWindowHandles();
for (String handle : all) {
if (!handle.equals(mainWindow)) {
driver.switchTo().window(handle);
break;
}
}
// 5. Do work in the new window
String docTitle = driver.getTitle();
Assert.assertTrue(docTitle.contains("Documentation"));
// 6. Close the new window when done
driver.close(); // close ONLY the current window — driver session stays alive
// 7. Switch back to the original
driver.switchTo().window(mainWindow);
// 8. Continue the test
driver.findElement(By.id("continue-checkout")).click();Eight steps for what feels conceptually simple — that's why every Selenium codebase ends up with a helper. We'll build one shortly.
The critical detail is step 6 vs step 7: driver.close() closes the current window. driver.quit() ends the entire driver session — every window. Don't mix them up.
A helper that hides the boilerplate
public static void switchToNewWindow(WebDriver driver, String currentHandle) {
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(d -> d.getWindowHandles().size() > 1);
for (String handle : driver.getWindowHandles()) {
if (!handle.equals(currentHandle)) {
driver.switchTo().window(handle);
return;
}
}
throw new IllegalStateException("Expected a new window but none appeared");
}
// Usage
String main = driver.getWindowHandle();
driver.findElement(By.linkText("Help")).click();
switchToNewWindow(driver, main);
// ... work in the popup ...
driver.close();
driver.switchTo().window(main);The same pattern as iframes: the verbose mechanics live in a helper, the test reads cleanly.
Selenium 4 — programmatic new window/tab
Selenium 4 added a clean API for opening windows yourself, no link-click required:
import org.openqa.selenium.WindowType;
// Open a new TAB and switch to it automatically
driver.switchTo().newWindow(WindowType.TAB);
driver.get("https://qa.codes/admin");
// Open a new WINDOW and switch to it
driver.switchTo().newWindow(WindowType.WINDOW);
driver.get("https://qa.codes/preview");Two reasons this matters:
- The driver auto-switches the focus, so you skip the four lines of "find the new handle and switch."
- It's deterministic — you know exactly when the window appears, no race condition.
Use it when the test wants to open a parallel session (a viewer in one tab, an editor in another) without depending on the app's UI to do it.
Window management — size, position, fullscreen
Once focused on a window, you can resize/reposition it:
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
// Maximise — typical at the start of a test
driver.manage().window().maximize();
// Set an exact size — useful for responsive testing
driver.manage().window().setSize(new Dimension(375, 667)); // iPhone-ish
// Move the window
driver.manage().window().setPosition(new Point(0, 0));
// Fullscreen (F11 equivalent)
driver.manage().window().fullscreen();
// Read current size and position
Dimension size = driver.manage().window().getSize();
Point pos = driver.manage().window().getPosition();For responsive testing across breakpoints, parameterise on a Dimension and run the same test at desktop, tablet, and mobile viewports.
The new-window flow, visualised
Step 1 of 6
Save main
String main = driver.getWindowHandle(); — capture the original handle so you can return later
A complete multi-window test
package com.mycompany.tests.tests;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WindowType;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.time.Duration;
import java.util.Set;
public class WindowsTest {
WebDriver driver;
WebDriverWait wait;
@BeforeMethod
public void setup() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.get("https://practice.expandtesting.com/windows");
}
@Test
public void shouldOpenAndSwitchToNewWindowFromLink() {
String main = driver.getWindowHandle();
// The practice site has a link that opens a new window
driver.findElement(By.linkText("Click Here")).click();
wait.until(d -> d.getWindowHandles().size() > 1);
for (String handle : driver.getWindowHandles()) {
if (!handle.equals(main)) {
driver.switchTo().window(handle);
break;
}
}
// The new window has its own heading
Assert.assertTrue(driver.getPageSource().contains("New Window"));
driver.close();
driver.switchTo().window(main);
// Still on the original page after returning
Assert.assertTrue(driver.getCurrentUrl().contains("/windows"));
}
@Test
public void shouldOpenNewTabProgrammatically() {
String main = driver.getWindowHandle();
// Selenium 4 — auto-switches to the new tab
driver.switchTo().newWindow(WindowType.TAB);
driver.get("https://qa.codes");
Assert.assertTrue(driver.getCurrentUrl().contains("qa.codes"));
// Close it, return to the original
driver.close();
driver.switchTo().window(main);
Assert.assertTrue(driver.getCurrentUrl().contains("/windows"));
}
@Test
public void shouldResizeWindow() {
driver.manage().window().setSize(new org.openqa.selenium.Dimension(800, 600));
Assert.assertEquals(driver.manage().window().getSize().getWidth(), 800);
Assert.assertEquals(driver.manage().window().getSize().getHeight(), 600);
}
@AfterMethod
public void teardown() {
if (driver != null) driver.quit();
}
}Three tests covering the three cases you'll meet: link-opens-window, programmatic-new-tab, and window resize.
How Cypress and Playwright handle this
// Cypress — single-tab limitation. Most workarounds remove target="_blank" first
cy.get("a").invoke("removeAttr", "target").click();
// Now the link opens in the same tab, which Cypress can follow
// Playwright — first-class new-page handling
const [newPage] = await Promise.all([
context.waitForEvent("page"),
page.click("a"),
]);
await newPage.waitForLoadState();
await expect(newPage).toHaveTitle(/Documentation/);Cypress's single-tab limitation is the biggest functional gap between it and Selenium/Playwright. Playwright's waitForEvent("page") is arguably the cleanest API of the three. Selenium's window-handle approach is the most verbose but the most explicit — and it works regardless of which browser you're driving.
The Selenium tool entry covers every switchTo() and manage().window() method.
⚠️ Common mistakes
driver.quit()when you meantdriver.close(). Quit ends the whole session — every window plus the driver process. If youquit()after a popup test, the test is over and your@AfterMethod's secondquit()does nothing.close()only closes the current window; the session lives on. Get this distinction right on day one.- Switching by index into the
Set.getWindowHandles()returns aSet<String>, which has no ordering guarantee. Code likewindows.toArray()[1]may pick a different window between runs and across browsers. Always pick by which handle isn't the one you started in. - Forgetting to switch back to the original window. Just like iframes, leaving the driver focused on a closed or background window means the next
findElementsearches the wrong document — or throws if the window was closed. Pair every "switch to new" with aswitchTo().window(originalHandle)once you're done.
🎯 Practice task
Drive multi-window flows. 30–40 minutes.
- Add
WindowsTestfrom this lesson to your project. Run all three tests; all should pass. - Build the helper. Add
WindowUtils.switchToNewWindow(driver, main)from earlier in the lesson to yourbase/package. RefactorshouldOpenAndSwitchToNewWindowFromLinkto use it. The test code drops to ~6 lines. - Three open at once. Use
driver.switchTo().newWindow(WindowType.TAB)three times in a row to openqa.codes,qa.codes/learn,qa.codes/tools. Then iterategetWindowHandles()andgetTitle()for each, switching between them. This is the test pattern for "log in once, then open multiple admin tools." - Cycle and close. From the same setup, write a method that closes every window except the original. Call
getWindowHandles(), iterate, skip the main handle, switch to each other andclose(). Verify only one window remains. - Quit vs close — feel the difference. Replace
driver.close()withdriver.quit()inshouldOpenAndSwitchToNewWindowFromLink. Run the test. Thedriver.switchTo().window(main)call now throws — the session is dead. Restoreclose()and watch the test pass again. - Stretch — responsive testing. Parameterise
shouldResizeWindowover three viewport sizes — 1920×1080 (desktop), 768×1024 (tablet), 375×667 (mobile). For each viewport, navigate to a real responsive site (Bootstrap docs, your own app) and assert that a known mobile-only or desktop-only element is/isn't visible. This is the foundation of cross-viewport testing — you'll formalise it via@DataProviderin chapter 5.
Chapter 4 is done. You can drive any complex interaction Selenium will throw at you — hover, drag-drop, alerts, iframes, multiple windows. Chapter 5 takes a step back from interactions and goes deep on TestNG itself: annotations, suites, groups, dependencies, data providers, listeners. The framework that organises all the test code you've been writing.