Choosing Puppeteer Browser Contexts for Isolated Multi‑User Sessions
Use Puppeteer browser contexts to isolate multiple user sessions. Compare default vs. new contexts, trade‑offs, and a step‑by‑step script that sets and verifies cookies and localStorage in separate contexts.
02 Mar 2026, 17:43 UTC

Problem: Running Multiple User Sessions in One Browser
When automating tests, scraping, or running a service that needs to simulate several users at once, you want each session to have its own cookies, localStorage, and cache. Launching a fresh Chromium instance for every user is simple but expensive in terms of time and memory. Puppeteer’s browser.newContext() offers a middle ground: separate storage inside a single Chromium process.
Decision: Default Context vs. New Browser Context
Choose new browser contexts when you need true isolation between users while keeping startup overhead low. Stick with the default context only if you are certain that shared state is acceptable or if you cannot afford the extra memory cost of many contexts.
Constraints to Consider
- Memory Footprint – Each context adds a few megabytes. Hundreds of contexts can exhaust RAM.
- File Descriptors – Chromium opens a descriptor per context; OS limits may be hit in large tests.
- Network Sharing – Contexts share DNS and network sockets; they do not isolate traffic.
- Testing Parallelism – Contexts can run pages in parallel without blocking each other.
- Version Compatibility – Ensure Puppeteer ≥ 19.0.0 to avoid known localStorage leaks.
Comparison Table
| Feature | Default Context (no newContext) | New Browser Context |
|---|---|---|
| Cookie Isolation | Shared | Per‑context |
| LocalStorage Isolation | Shared | Per‑context |
| Cache Isolation | Shared | Per‑context |
| Network Traffic Sharing | Shared | Shared |
| Memory Consumption | Lowest | +≈10 MB per context |
| Startup Overhead | Zero (reuse existing browser) | ≤ 50 ms to create context |
| Parallelism | Limited by single context | Full parallel pages per context |
| API Simplicity | Single browser.newPage() | Use context.newPage() and browser.newContext() |
| Cleanup Required | No special cleanup | Must call context.close() |
Trade‑Off Summary
Using new contexts gives you the isolation that a true incognito mode provides, but at a cost: each context consumes memory and file descriptors. If your test suite runs dozens of concurrent sessions, monitor top or htop to avoid hitting system limits. Conversely, if you only need a few users or can tolerate shared state, staying in the default context keeps the process lean and simple.
Concrete Implementation: Validating Isolation
- Launch Puppeteer
Run this in a Node.js environment with Puppeteer installed (npm i puppeteer). The script starts one Chromium instance.
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: true }); /* ... */ })(); - Create Two Contexts
Each context will act as a separate user.
const ctxA = await browser.newContext(); const ctxB = await browser.newContext(); - Set Distinct Cookies
Use
context.addCookiesto set a cookie unique to each context.await ctxA.addCookies([{ name: 'user', value: 'alice', domain: 'example.com', path: '/' }]); await ctxB.addCookies([{ name: 'user', value: 'bob', domain: 'example.com', path: '/' }]); - Open Pages and Verify
Navigate to a page that reads the
usercookie and logs it. Replace<YOUR_URL>with a test page that prints the cookie value to the console.const pageA = await ctxA.newPage(); const pageB = await ctxB.newPage(); await pageA.goto('https://example.com/<YOUR_URL>'); await pageB.goto('https://example.com/<YOUR_URL>'); const userA = await pageA.evaluate(() => document.cookie); const userB = await pageB.evaluate(() => document.cookie); console.log('Context A cookie:', userA); console.log('Context B cookie:', userB); - Check LocalStorage Isolation
Set a value in each context and read it back.
await pageA.evaluate(() => localStorage.setItem('token', 'alpha')); await pageB.evaluate(() => localStorage.setItem('token', 'beta')); const tokenA = await pageA.evaluate(() => localStorage.getItem('token')); const tokenB = await pageB.evaluate(() => localStorage.getItem('token')); console.log('Context A token:', tokenA); // alpha console.log('Context B token:', tokenB); // beta - Cleanup
Close contexts to release memory. Failing to do so will leave Chromium processes holding resources.
await ctxA.close(); await ctxB.close(); await browser.close(); })();
Run the script with node isolation-test.js. You should see two separate cookie and localStorage values printed, confirming isolation.
Practical Checks After Running
- Use
ps aux | grep chrometo verify only one Chromium process is running. - Monitor memory with
top -p $(pgrep chrome)before and after closing contexts to see the drop. - Inspect open file descriptors:
lsof -p $(pgrep chrome) | wc -lshould reduce after closing contexts.
Limitations and Caveats
- Contexts share the same network stack; DNS caching or shared proxy settings can leak data between contexts.
- Large numbers of contexts can hit OS limits on file descriptors; consider batching or reusing contexts.
- Older Puppeteer releases (<19.0.0) had a bug leaking localStorage; always use a recent version.
- Third‑party extensions loaded in the browser process might share state across contexts; avoid extensions in automated runs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.