Your user clicks "Open in new tab." A new browser tab appears. Your test is still looking at the old tab. It has no idea a new tab even opened. This is one of the most common failures in automation. Playwright handles it beautifully -- but you need to know how.
In Playwright, every tab is a Page object. The browser context holds all pages. When a new tab opens, Playwright fires a "page" event on the context. You listen for it, grab the new page, and interact with it like any other page.
import { test, expect } from '@playwright/test';
test('handle link that opens in new tab', async ({ page, context }) => {
await page.goto('/topics/windows');
// Wait for the new tab AND click the link at the same time
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByRole('link', { name: 'Open in new tab' }).click(),
]);
// New tab may still be loading -- wait for it
await newPage.waitForLoadState();
// Now interact with the new tab
await expect(newPage).toHaveURL(/\/new-page/);
await expect(newPage.getByRole('heading', { level: 1 })).toBeVisible();
// Original page is still available
await expect(page).toHaveURL(/\/topics\/windows/);
});You must set up waitForEvent BEFORE the click that triggers the new tab. If you click first and then call waitForEvent, the tab might open before your listener is ready, and the event is missed. Always use Promise.all to run them together.
import { test, expect } from '@playwright/test';
test('handle popup window', async ({ page }) => {
await page.goto('/topics/windows');
// page.waitForEvent('popup') works for window.open() popups
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByRole('button', { name: 'Open Popup' }).click(),
]);
await popup.waitForLoadState();
await expect(popup.getByText('Popup Content')).toBeVisible();
// Fill a form in the popup
await popup.getByLabel('Name').fill('Shivam');
await popup.getByRole('button', { name: 'Submit' }).click();
// Close popup and verify result on original page
await popup.close();
await expect(page.getByText('Popup submitted')).toBeVisible();
});import { test, expect } from '@playwright/test';
test('create a new tab manually', async ({ context }) => {
// Create two tabs programmatically
const pageOne = await context.newPage();
const pageTwo = await context.newPage();
// Navigate each to a different URL
await pageOne.goto('/banking/login');
await pageTwo.goto('/shopping');
// Interact with each independently
await pageOne.getByLabel('Email').fill('john@test.com');
await pageTwo.getByRole('link', { name: 'Electronics' }).click();
// List all open pages
const allPages = context.pages();
console.log(`Open tabs: ${allPages.length}`);
});There is no "switch to tab" command in Playwright. Unlike Selenium where you call driver.switchTo().window(), in Playwright each tab is already a separate Page object. You just use the variable you saved. pageOne.click(), pageTwo.fill() -- you are already "switched."
Use page.bringToFront() if you need to visually bring a tab to the front in headed mode. It does not affect test logic -- just helps when debugging with --headed.
| Method | When to Use | Returns |
|---|---|---|
| context.waitForEvent('page') | Link with target="_blank" | New Page object |
| page.waitForEvent('popup') | window.open() calls | Popup Page object |
| context.newPage() | Create a tab programmatically | New Page object |
| context.pages() | List all open tabs | Page[] array |
| page.close() | Close a specific tab | void |
| page.bringToFront() | Focus a tab in headed mode | void |
Q: How do you handle multiple tabs in Playwright?
A: In Playwright, each tab is a separate Page object. When a link opens a new tab, I use Promise.all with context.waitForEvent('page') and the click action. For window.open popups, I use page.waitForEvent('popup'). There is no switchTo like Selenium -- I just use the Page variable directly. context.newPage() creates a new tab manually. context.pages() gives me all open tabs. The key rule: always set up the event listener BEFORE the action that opens the tab.
Key Point: Each tab is a separate Page object. Use context.waitForEvent('page') for new tabs, page.waitForEvent('popup') for popups. No switching needed -- just use the variable.