How to Handle Tabs and Windows in Selenium and Playwright
Practice browser tab and window automation — open new tabs, switch between windows, close tabs, handle popups, and assert URL and title in Playwright, Selenium & Cypress.
Interactive Scenarios
"_blank"Hint attributes: data-expected-url-contains, data-expected-title-contains
Uses window.open() with explicit size — Playwright captures via page.waitForEvent('popup')
Tab Launcher Panel
No data-testid on rows or buttons. Use XPath by cell text or data-tab-id.
| Tab Name | URL | Status | Action |
|---|---|---|---|
| Tab A | / | Open | |
| Tab B | /practice | Open | |
| Tab C | /practice/links | Open |
| ID | Scenario | Type | Priority | |
|---|---|---|---|---|
| TW_001 | Verify clicking a link opens a new browser tab Expected: context.pages().length increases by 1 after the click. ✅ positivehigh | positive | high | |
| TW_002 | Verify new tab URL matches expected destination Expected: newPage.url() equals the link's href. ✅ positivehigh | positive | high | |
| TW_003 | Verify new tab title matches expected value Expected: newPage.title() returns the correct page title. ✅ positivemedium | positive | medium | |
| TW_004 | Verify switching back to the original tab restores context Expected: The original page URL is still correct after switching back. ✅ positivehigh | positive | high | |
| TW_005 | Verify closing a tab reduces the open tab count Expected: context.pages().length decrements by 1 after close(). ✅ positivehigh | positive | high | |
| TW_006 | Verify multiple tabs can be opened simultaneously Expected: All three tab buttons produce independent page contexts. ✅ positivemedium | positive | medium | |
| TW_007 | Verify window.open popup is captured in Playwright Expected: page.waitForEvent('popup') resolves to the popup page. ✅ positivehigh | positive | high | |
| TW_008 | Verify Selenium can switch to a new window via handle Expected: driver.switchTo().window(newHandle) changes the active context. ✅ positivehigh | positive | high | |
| TW_009 | Verify sibling-located tab button can be clicked via XPath Expected: Button located by structural XPath fires the tab-open action. ✅ positivemedium | positive | medium | |
| TW_010 | Verify dynamic tab registry row located by cell text Expected: Focus button inside the Tab C row is clicked successfully. ✅ positivemedium | positive | medium | |
| TW_011 | Verify tab with target=_blank has correct attribute Expected: The anchor element has target="_blank" and rel="noopener noreferrer". ✅ positivemedium | positive | medium | |
| TW_012 | Verify Cypress can interact with a same-origin new tab by removing target Expected: After removing target attribute, the link navigates in the same tab. ✅ positivemedium | positive | medium | |
| TW_013 | Verify that interacting with a closed tab throws an error Expected: Calling methods on a closed page context raises a Target closed error. ❌ negativemedium | negative | medium | |
| TW_014 | Verify no extra tabs remain open after the test cleans up Expected: context.pages().length equals 1 after all new tabs are closed. ⚠️ edgelow | edge | low |
Browser tabs and windows are separate browsing contexts. Each new tab opened by a link or window.open() creates an independent page with its own URL, DOM, and navigation history. Automation frameworks provide context-level APIs to enumerate, switch between, and close these contexts.
The key technique is to register the event listener before the action that opens the tab, then await the resolved page context. In Playwright this is waitForEvent('page') on the browser context. In Selenium you collect window handles before and after the click, then switch to the new handle.
// Open a new tab and capture its context
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByTestId('tw-open-new-tab').click(),
]);
await newPage.waitForLoadState();
// Assert URL and title
expect(newPage.url()).toContain('qaplayground');
expect(await newPage.title()).toBeTruthy();
// Assert tab count
expect(context.pages().length).toBe(2);After opening a new tab, the automation is 'inside' that new context. To interact with the original page again you must explicitly switch back — in Playwright via page.bringToFront(), in Selenium via driver.switchTo().window(originalHandle).
// Open a new tab
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByTestId('tw-open-and-return').click(),
]);
await newPage.waitForLoadState();
// Do work in new tab
await expect(newPage).toHaveURL(/qaplayground/);
// Switch back to the original tab
await page.bringToFront();
await expect(page).toHaveURL(/practice/tabs-windows/);window.open() calls produce popup contexts. In Playwright, listen with page.waitForEvent('popup') before the triggering action. In Selenium, popup windows appear in getWindowHandles() like any other window. Cypress cannot handle true multi-tab scenarios natively — the standard workaround is to remove the target attribute so navigation stays in the same tab.
// Capture a window.open() popup
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByTestId('tw-popup-btn').click(),
]);
await popup.waitForLoadState();
// Assert popup URL
expect(popup.url()).toContain('qaplayground');
// Close the popup
await popup.close();Call page.close() (Playwright) or driver.close() (Selenium) to close the active tab. After closing, always switch back to a remaining open page before continuing assertions. Verify context.pages().length to confirm the count decremented.
// Open a new tab
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByTestId('tw-close-tab-btn').click(),
]);
await newPage.waitForLoadState();
expect(context.pages().length).toBe(2);
// Close the new tab
await newPage.close();
expect(context.pages().length).toBe(1);
// Bring original back to front
await page.bringToFront();| Action | Selenium | Playwright JS | Playwright PY | Cypress |
|---|---|---|---|---|
| Capture new tab | getWindowHandles() diff | context.waitForEvent('page') | context.wait_for_event('page') | invoke('removeAttr','target') |
| Capture popup | getWindowHandles() diff | page.waitForEvent('popup') | page.wait_for_event('popup') | cy.stub(win, 'open') |
| Switch to tab | switchTo().window(handle) | page.bringToFront() | page.bring_to_front() | N/A (single tab) |
| List all pages | getWindowHandles() | context.pages() | context.pages | N/A |
| Close tab | close() | page.close() | page.close() | N/A |