skip to content

In Playwright, how do you open a second tab in the same browser context?

level: juniorimportance: must knowfreq 68%

answer

  1. The session lives on the context
  2. A page is just one tab
  3. Ask the context for a new one
  4. pages() is synchronous, not awaited
  5. browser.newPage() is not the same call

basics

~10 s

Calling context.newPage() creates another tab inside the same BrowserContext. Both tabs share that context's cookies, storage and emulation, and context.pages() lists every page currently open in it.

solid answer

~40 s

`context.newPage()` returns a new `Page` — a fresh tab living inside the same `BrowserContext`. Because the context is the session boundary, the new tab shares its cookies, storage, permissions, routes and emulation, so a hotel-booking guest signed in on the search tab is already signed in on the room-detail tab. `context.pages()` is synchronous and returns an array of every page currently open in that context, in creation order, including tabs the site opened itself. A tab you no longer need goes away with `await page.close()`; the context and its other pages stay alive even if that was the last one. Watch out for the convenience call `browser.newPage()` — it quietly creates a *new* context per page, so two calls give you two isolated sessions rather than two tabs of one.

code

typescript · 18 lines
typescript
import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext();

const searchTab = await context.newPage();
await searchTab.goto('https://hotels.example.com/search?city=lisbon');

const roomTab = await context.newPage();
await roomTab.goto('https://hotels.example.com/rooms/deluxe-suite');

console.log(context.pages().length); // 2, both signed in as the same guest

await roomTab.close();
console.log(context.pages().length); // 1, the context is still open

await context.close();
await browser.close();

go deeper

for a junior

Remember the one call, context.newPage(), and remember that the new tab is already logged in because the context holds the session.

for a middle

Be able to explain that the context owns cookies, storage and emulation while a page owns only the tab, and why browser.newPage() creates a context behind your back.

for a senior

Show judgment about when a second page is right and when it is wrong: two tabs are one guest, so two guests or two tenants need two contexts rather than two pages.

for a principal

Own the cost model. Extra pages are cheap and share a session; extra contexts are the unit of isolation and of parallel-run memory, so the page-versus-context choice sets both fidelity and suite footprint.

## A page is a tab; the context is the session In Playwright the isolation boundary is the `BrowserContext`, not the `Page`. A context owns the cookie jar, the storage partition, granted permissions, emulation settings and any network routes registered on it. A `Page` is one tab or popup window living **inside** a context; it has no session of its own and borrows the context's. That is why the call reads `context.newPage()` rather than something on the browser: you are adding a tab to a session that already exists, not starting a fresh one. ## Opening the tab `const roomTab = await context.newPage();` resolves to a `Page` you can drive immediately. - The new page starts on `about:blank`; navigate it yourself, for example `await roomTab.goto('/rooms/deluxe-suite')`. - It inherits everything the context carries: cookies set before the tab existed, the storage state the context was built with, viewport, locale and routes. - It is fully active. Playwright drives background tabs directly, so you do not have to focus a tab before interacting with it. `page.bringToFront()` exists, but is only needed when the application itself reacts to focus. - It appears in `context.pages()` from the moment it is created. On a multi-tenant hotel-booking site this is what makes side-by-side flows cheap: sign the guest in once, then open the search results in one tab and a room-detail view in another, both already authenticated as the same guest of the same tenant. ## Listing what is open `context.pages()` is a **synchronous** getter — no `await` — returning an array of every page currently open in that context, in creation order. It includes pages the site opened itself, such as a `target="_blank"` room-preview window, not only the ones your script created. Reading it is a snapshot, not a subscription: if you want to be told about pages as they appear, listen to the context's `page` event instead of polling the array. ## Four calls that look alike | Call | Returns | Creates a context? | Shares the session? | |---|---|---|---| | `context.newPage()` | `Promise<Page>` | no | yes, with the context's other pages | | `context.pages()` | `Page[]`, synchronously | no | reads the current tabs | | `browser.newPage()` | `Promise<Page>` | yes, one per call | no | | `browser.contexts()` | `BrowserContext[]` | no | lists the open contexts | `browser.newPage()` is the trap in that table. It is a convenience for single-page snippets: it creates a brand-new context **and** one page inside it, and closing that page closes its context too. Two `browser.newPage()` calls therefore give you two isolated guests, not two tabs belonging to one guest — the opposite of what the name suggests. Anything longer than a snippet should create the context explicitly and then call `context.newPage()` on it, so the lifetimes are yours to control. ## Closing one tab 1. `await roomTab.close()` closes that tab only. Its siblings keep working and the context stays alive, even when the tab you closed was the last one in it. 2. `roomTab.isClosed()` reports the state synchronously afterwards, which is what you check inside a long-lived event handler before touching a page it captured earlier. 3. Acting on a closed page rejects with a "Target page, context or browser has been closed" error rather than hanging, so a stale reference fails loudly. ## When a second tab is actually the right tool - A `target="_blank"` link the test has to follow, such as the room-preview window opened from a search result. - Comparing two rooms side by side without losing the first tab's filters or scroll position. - Checking that a change made in one tab is visible in another — the guest edits their profile in tab A, and tab B re-reads it. And the case where it is the **wrong** tool: two different guests, or two different tenants. Pages in one context share its cookies and storage, so both tabs are always the same signed-in user. Separate identities need separate contexts, not separate pages.

  • What is the difference between browser.newPage() and context.newPage()?
    `context.newPage()` adds a tab to an existing session. `browser.newPage()` is a shortcut that creates a brand-new context and one page in it, so each call is an isolated session, and closing that page closes its context too. Use it only for single-page snippets.
  • Does context.pages() include tabs the site opened itself, or only ones you created?
    It includes every page currently open in the context, whatever opened it — script-created tabs, `target="_blank"` windows and popups alike. It is a synchronous snapshot, so to be notified as pages appear you listen to the context's `page` event instead.

A context is one signed-in browser profile; pages are the tabs open inside it, all sharing the same login.

saying these in an interview costs you the question

  • Thinking each page has its own cookies and login
  • Calling browser.newPage() twice to get two tabs
  • Awaiting context.pages(), which is synchronous
  • Believing a background tab must be focused before use
  • Expecting the context to close with its last page