skip to content

Session Isolation

What a single context owns - cookies, storage, permissions, cache - and how the pages inside it share that state. Probed because it is the cheap unit that makes a test start from a clean slate.

on this pageshow

explore

questions

10

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
open as a page

In Playwright, what state does a fresh browser.newContext() isolate from every other context?

level: juniorimportance: must knowfreq 80%

basics

~20 s

A new browser context is an incognito-like profile: its own cookies, localStorage, sessionStorage, IndexedDB, cache, service workers and permission grants. Contexts share only the running browser, so one test cannot leak client state into another.

open as a page

In Playwright, why must page.waitForEvent('popup') be created before the click that opens the tab?

level: middleimportance: must knowfreq 76%

basics

~20 s

Playwright emits the popup event the moment the new tab appears and never replays it. Awaiting the click first lets the event fire with nobody listening, so the later wait blocks on a second popup that never comes.

open as a page

In Playwright, why does context.addCookies() reject a cookie that has only a name and a value?

level: middleimportance: must knowfreq 58%

basics

~10 s

Because Playwright cannot place a cookie without knowing where it belongs. Every entry needs a url, or both domain and path, and passing neither throws: Cookie should have a url or a domain/path pair.

open as a page

In Playwright, how does the BrowserContext 'page' event differ from the Page 'popup' event?

level: middleimportance: should knowfreq 54%

basics

~20 s

The context's page event fires for every page created in that context, including ones your script opens. The page's popup event fires only for popups that particular page opened, and it fires in addition to the context event.

open as a page

In Playwright, how do you let a test use a permission-gated feature such as clipboard read?

level: middleimportance: should knowfreq 40%

basics

~10 s

Grant it on the context before the feature is used: context.grantPermissions(['clipboard-read']), or the permissions option on browser.newContext. Add an origin option to limit the grant, and clearPermissions to drop every override again.

open as a page

Why does a Playwright test that writes localStorage after page.goto() fail to change how the app boots?

level: middleimportance: should knowfreq 46%

basics

~10 s

Because the app has already started and read storage by the time the navigation resolves. Seeded values must exist before the document's own scripts run, which is what context.addInitScript() and page.addInitScript() are for.

open as a page

In a Playwright teardown, when do you call page.close(), context.close() and browser.close()?

level: seniorimportance: should knowfreq 47%

basics

~10 s

page.close() ends one tab and leaves its context running. context.close() closes the context and all its pages, flushing artifacts. browser.close() ends everything at once, like force-quitting, so close your contexts before it.

open as a page

Your Playwright suite tests a staging site behind HTTP Basic auth that also calls a partner API — how do you authenticate without leaking credentials?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Put httpCredentials on the context and pin it with the origin field, so Basic auth is answered only for the staging host. Without an origin, the same username and password answer any 401, including the partner's.

open as a page

How would you decide whether a Playwright suite may run every context with ignoreHTTPSErrors and bypassCSP enabled?

level: principalimportance: should knowfreq 28%

basics

~20 s

Playwright's ignoreHTTPSErrors and bypassCSP are deliberate deviations from a real browser. Default both off, enable them only for the tests or environments that need it, and keep one path running with the real certificate and policy.

open as a page