Hero Background

Next-Gen App & Browser Testing Cloud

Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Next-Gen App & Browser Testing Cloud

Why Playwright is not able to handle multiple windows or tabs?

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.

Why People Think Playwright Cannot Handle Tabs or Windows

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.

How Playwright Models Tabs, Windows, and Contexts

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.

ObjectWhat it representsCreated with
BrowserA single browser instance (Chromium, Firefox, or WebKit).chromium.launch()
BrowserContextAn isolated session with its own cookies, storage, and cache, like a fresh incognito profile.browser.newContext()
PageA 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.

Capturing a New Tab or Popup

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());
});

Capturing Any New Page in the Context

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.

Switching Between, Listing, and Closing Tabs

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();

Opening Isolated Windows With New Browser Contexts

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 storage

Common Mistakes That Make It Look Like Playwright "Can't"

  • Cause: Arming the listener after the click. The popup or page event fires instantly, so a listener attached after the action misses it and the test hangs. Fix: wrap the wait and the click in Promise.all so the listener is armed before the action runs.
  • Cause: Acting on the old page reference. After a new tab opens, the original page variable still points at the first tab. Fix: capture the returned Page and run all new-tab actions against that reference.
  • Cause: Using the wrong event. A page-triggered popup uses page.waitForEvent('popup'), while a generic new tab uses context.waitForEvent('page'). Fix: match the event to the source of the new tab.
  • Cause: Confusing tabs with isolated windows. Opening a new tab in the same context shares cookies, which breaks multi-user scenarios. Fix: use browser.newContext() when each window needs its own session.
  • Cause: Selecting tabs by index. context.pages() order is not guaranteed to be stable. Fix: locate the tab by matching its URL or title instead of a numeric index.
  • Cause: Mistaking a native dialog for a popup. A JavaScript alert, confirm, or prompt is not a new page. Fix: handle those with page.on('dialog'), which is covered in the alerts and popups question linked below.

Running Multi-Tab Playwright Tests Across Browsers

The 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.

Frequently Asked Questions

Can Playwright actually handle multiple windows and tabs?

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.

What is the difference between a tab and a window in Playwright?

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.

Why does my Playwright test miss the new tab or popup?

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.

Does Playwright use window handles like Selenium?

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.

How do I get a list of all open tabs in Playwright?

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().

Can I run multi-tab Playwright tests across different browsers?

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.

Related Questions

Test Your Website on 3000+ Browsers

Get 100 minutes of automation test minutes FREE!!

Test Now...

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

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests