Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Playwright can handle multiple windows and tabs. The belief that it cannot is a misconception, usually carried over from Selenium. In Playwright, every tab and every browser window is represented by a Page object that lives inside a BrowserContext. When a link, button, or script opens a new tab or popup, Playwright fires an event that you capture to get the new Page, then you switch your reference to it and continue. There are no Selenium-style window handles to track, so the trick is knowing the event-driven model rather than fighting it.
The confusion almost always comes from a Selenium background. In Selenium you switch between tabs and windows with string identifiers using driver.getWindowHandles() and driver.switchTo().window(handle). Testers migrating to Playwright look for the same API, do not find it, and conclude that Playwright cannot handle multiple windows at all.
Playwright simply uses a different mechanism. It communicates with the browser over a native protocol rather than the WebDriver protocol, and it exposes each open tab or window as a live Page object you already hold a reference to. Instead of asking the browser for a list of handles, you listen for new pages as they appear. Once you understand that, multi-tab automation in Playwright is more direct than it ever was in Selenium.
Playwright organizes the browser into a clear three-level hierarchy. Understanding this hierarchy answers most multi-window questions before you write a line of code.
| Object | What it represents | Created with |
|---|---|---|
| Browser | A single browser instance (Chromium, Firefox, or WebKit). | chromium.launch() |
| BrowserContext | An isolated session with its own cookies, storage, and cache, like a fresh incognito profile. | browser.newContext() |
| Page | A single tab or window. Inside one context, a tab and an OS-level window are the same thing. | context.newPage() |
The key takeaways: a tab and a window are both just Page objects, multiple tabs that should share cookies and login state belong in the same BrowserContext, and a window that needs a completely separate session belongs in a new context.
When a click opens a popup, the page that triggered it emits a popup event. The reliable pattern is to start waiting for that event before you click, using Promise.all so the listener is armed first. Once you have the popup Page, wait for it to load and act on it directly.
// Arm the listener BEFORE the click, then capture the popup
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a#open-popup'),
]);
await popup.waitForLoadState();
console.log(await popup.title());
// Interact with the popup just like any other page
await popup.fill('#email', '[email protected]');
await popup.click('button[type="submit"]');If you need to react to every popup a page may open, attach a persistent listener instead:
page.on('popup', async (popup) => {
await popup.waitForLoadState();
console.log('New popup opened:', await popup.title());
});A click on a target="_blank" link, or any action that spawns a brand-new tab, surfaces as a page event on the BrowserContext. Use this when you do not know which page will trigger the new tab, or when the tab is opened by something other than the page you are driving.
// Capture a new tab opened anywhere in this context
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.click('a[target="_blank"]'),
]);
await newPage.waitForLoadState();
console.log('New tab URL:', newPage.url());Both pages stay open and independently actionable. A common slip is to keep running assertions on the original page variable after a new tab opened. You must switch to newPage to interact with the new tab.
You can also open tabs yourself, enumerate everything open, focus a specific tab, and close tabs cleanly. A useful detail: Playwright does not need a tab to be in the foreground to interact with it, so bringToFront() is only needed for focus-dependent behavior or visual debugging.
// Open a second tab in the same context (shares cookies/login)
const dashboard = await context.newPage();
await dashboard.goto('https://example.com/dashboard');
// List every open tab in this context
const pages = context.pages();
console.log('Open tabs:', pages.length);
// Find a tab by URL and bring it to front
const target = pages.find((p) => p.url().includes('/dashboard'));
await target.bringToFront();
// Close just this tab
await dashboard.close();When you need a second window that behaves like a completely separate browser session, for example to log in as two different users at once, do not open another tab. Create a new BrowserContext. Each context has its own cookies and storage, so the two sessions never interfere.
// Two isolated windows with independent sessions
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
await adminPage.goto('https://example.com/login');
const userContext = await browser.newContext();
const userPage = await userContext.newPage();
await userPage.goto('https://example.com/login');
// adminPage and userPage do not share cookies or storageThe same multi-tab and multi-window code runs unchanged on Chromium, Firefox, and WebKit, because Playwright keeps the Page and BrowserContext model identical across engines. The harder part is validating that popups, new-tab flows, and OAuth-style window handoffs behave the same on every browser and operating system your users rely on.
That is where a cloud grid helps. You can run your existing multi-tab Playwright scripts across a wide range of browser and OS combinations on TestMu AI's Playwright Testing cloud, capturing screenshots, video, and logs for each session without changing your test logic. It is a clean way to confirm that tab switching and popup handling stay reliable everywhere, not just on your local machine.
Yes. Playwright handles multiple tabs and windows natively. Each tab or window is a Page object inside a BrowserContext, and a new tab or popup arrives as a page or popup event that you capture and switch to. There is no separate tab versus window concept inside a context.
Inside a single BrowserContext there is no technical difference. A tab and an OS-level window are both represented by the same Page object. If you need a truly separate window with its own cookies and storage, create a new BrowserContext with browser.newContext() instead.
The most common cause is arming the listener after the click. The popup or page event fires the instant the action runs, so you must start waiting for it before you trigger it. Wrap the wait and the click in Promise.all so the listener is armed first.
No. Playwright does not use Selenium-style window handles or driver.switchTo().window(). It works over the browser's native protocol and exposes each tab as a Page object. You capture new pages through events and switch your page reference, rather than switching by a string handle.
Call context.pages(), which returns an array of every open Page in that BrowserContext. You can then filter by URL or title to find the tab you want, bring it to front with bringToFront(), or close it with close().
Yes. Multi-tab and multi-window scripts run unchanged on Chromium, Firefox, and WebKit, and you can scale them across browser and OS combinations on a cloud grid such as TestMu AI without changing your test logic.
KaneAI - Testing Assistant
World’s first AI-Native E2E testing agent.

TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance