ScreenshotNeo

BlogHow-to

How to Manage Browser Automation Sessions

Learn when to create or reuse browser contexts, how to bound waits, handle failures, and close sessions cleanly in Playwright and Puppeteer.

By the ScreenshotNeo team4 October 20268 min read

Short answer: Treat the browser process, browser context, and page as separate lifecycle objects. Use a fresh context for each independent task or test; reuse a context only when the workflow intentionally needs shared state. Set bounded timeouts, handle failures at the task boundary, close explicitly created contexts, and then close the browser.

In Playwright, a BrowserContext is usually the right isolation boundary: it represents an independent browser session, can own multiple pages, and keeps its browsing data separate from other contexts. Playwright’s test runner creates a new context per test by default. [Playwright BrowserContext API · Playwright isolation guide]

1. Understand the lifecycle objects

Object What it represents Typical responsibility
Browser A running browser process, such as Chromium. Launch once for a worker or job group; close after its contexts.
Browser context An independent browser session with its own cookies and storage. Create per independent task, test, or user identity; close at the task boundary.
Page A tab or page within a context. Navigate, interact, and inspect. Popups opened from a page remain in that page’s parent context.

A browser can host multiple contexts, and a context can contain multiple pages. Non-persistent Playwright contexts do not write browsing data to disk. These are Playwright-specific API facts; other frameworks may use different terminology and defaults. [Playwright BrowserContext API]

2. Choose whether to isolate or reuse state

Use a fresh context when

  • Tests or jobs must not inherit another task’s cookies, local storage, permissions, or other browser state.
  • You simulate separate users, such as an administrator and a regular user.
  • Work runs in parallel and each task should behave independently.
  • You need the test runner’s usual isolation model: Playwright creates a context per test by default.

Reuse a context when

  • Several steps are one continuous user journey and must remain logged in.
  • Pages in the same task intentionally need to share session state.
  • You have an explicit state setup and cleanup strategy for the full workflow.

Starting fresh makes the boundary explicit. Cleaning selected state between unrelated tasks is easier to get wrong, and some browser state can be difficult to reset completely. Keep continuity inside a journey; start a separate context between independent journeys. [Playwright isolation guide]

3. Playwright: complete lifecycle example

This runnable Node.js example launches Chromium, creates a context and page, applies bounded navigation and selector waits, records failures, and closes resources in order. Install Playwright and its browser binaries in the project before running it.

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
let context;

try {
  context = await browser.newContext();
  context.setDefaultTimeout(10_000);
  context.setDefaultNavigationTimeout(20_000);

  const page = await context.newPage();
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.locator('h1').waitFor({ state: 'visible' });

  console.log(await page.title());
} catch (error) {
  console.error('Automation task failed:', error);
  process.exitCode = 1;
} finally {
  if (context) {
    await context.close();
  }
  await browser.close();
}

For projects that run on a pinned Playwright version, check that version’s API documentation. Page-level timeout settings take precedence over context defaults, so inspect local page configuration when a context timeout seems ineffective. [Playwright BrowserContext API]

Keep one login journey together

When steps need the same authenticated session, create one context for those steps and reuse it only for that journey. For separate users, create separate contexts:

const adminContext = await browser.newContext();
const userContext = await browser.newContext();

try {
  const adminPage = await adminContext.newPage();
  const userPage = await userContext.newPage();
  // Authenticate and perform each user's workflow independently.
} finally {
  await Promise.all([
    adminContext.close(),
    userContext.close(),
  ]);
}

4. Puppeteer: isolate tasks with browser contexts

Puppeteer also supports BrowserContexts for isolating automation tasks. This example creates a non-default context, opens a page, and closes the context before the browser. Verify names and behavior against the Puppeteer version installed in your project.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
let context;

try {
  context = await browser.createBrowserContext();
  const page = await context.newPage();
  page.setDefaultTimeout(10_000);
  page.setDefaultNavigationTimeout(20_000);

  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.waitForSelector('h1', { visible: true });
  console.log(await page.title());
} catch (error) {
  console.error('Automation task failed:', error);
  process.exitCode = 1;
} finally {
  if (context) {
    await context.close();
  }
  await browser.close();
}

Puppeteer describes non-default Chrome contexts as incognito, and notes that the default context may also be incognito when Chrome is launched with --incognito. Do not assume Playwright and Puppeteer share identical defaults or semantics. [Puppeteer BrowserContext API]

5. Set up state deliberately

Prefer creating the minimum state the task needs. Playwright exposes context-level cookie and permission APIs, including cookie clearing. Clearing cookies alone should not be treated as a complete reset of all browser state. If authenticated state is reused, make that reuse intentional, restrict access to any saved state or credentials, and define how the state is refreshed and invalidated. The framework documentation describes the APIs; storage security requirements depend on the application and deployment.

// Playwright: inspect or clear cookies at the context boundary.
const cookies = await context.cookies();
console.log(`Cookie count: ${cookies.length}`);
await context.clearCookies();

6. Bound waits and navigation

Unbounded waits can leave a worker occupied indefinitely. Use a finite default for ordinary actions and an appropriate navigation timeout. Wait for the condition the task actually needs, rather than assuming every page reaches full network idle; some sites keep long-lived connections open. A timeout is a task failure to handle, not a reason to disable timeouts globally.

  • Set context defaults for the workflow’s common action and navigation limits.
  • Use a page-specific timeout only where that page needs a different bound.
  • Check page-level overrides when a context-level default appears to have no effect.
  • Prefer waiting for a required selector or state over a fixed sleep when possible.

Playwright provides context-level default timeouts and navigation timeouts, with page-level settings taking precedence. [Playwright BrowserContext API]

7. Handle failure and close cleanly

Put cleanup in a finally block so it runs after success, a navigation timeout, or an interaction error. Close contexts you created before closing the browser. Closing a context closes its pages; Playwright recommends this order so artifacts such as HAR files and videos can be flushed and saved. A context close event may also occur because the browser closes or crashes, so orchestration should treat unexpected closure as a failed task and decide whether retrying is safe. [Playwright BrowserContext API · Playwright Browser API]

  1. Catch task-level failures and record enough context to diagnose them.
  2. Close each explicit context, even when a page operation failed.
  3. Close the browser after its contexts.
  4. Retry only when the operation is safe to repeat; avoid blindly repeating actions that submit payments, create records, or otherwise have side effects.

8. cURL, Python, and Node.js for screenshot jobs

When the task is to capture a page image rather than interact with a multi-step browser workflow, use the ScreenshotNeo API. The examples below use the API’s documented endpoint and parameters. See the ScreenshotNeo API documentation for available options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. A single GET request returns a PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted like a visitor and removed before capture; newsletter popups and chat widgets are removed too, and each of those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, with response headers reporting the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

There are 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Sign up for free and capture 1,000 screenshots a month with no card.

Performance, reliability, and cost

  • Context reuse: Reusing a browser process for multiple contexts can organize related work, while fresh contexts preserve session boundaries. The reviewed sources do not provide a measured speed or resource comparison, so benchmark your own deployment before making capacity claims.
  • Parallelism: Separate contexts are useful for independent users or jobs, but choose concurrency based on the limits of your runtime and deployment rather than assuming a universal safe number.
  • Reliability: Bound waits, close resources in order, detect unexpected closure, and retry only idempotent operations or operations with an explicit deduplication strategy.
  • Cost: Browser automation cost depends on the infrastructure and workload you operate; the research sources do not establish a universal per-session cost. For screenshot-only work, ScreenshotNeo’s stated plans range from free (1,000 per month) to paid tiers at $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000; yearly billing gives two months free.

Troubleshooting

Symptom Likely cause Fix
A test sees another task’s login or preferences. Unrelated tasks reused one context. Create a fresh context per independent task or test.
Clearing cookies did not reset the session. Other state remains, or the workflow intentionally shares a context. Use a fresh context for an independent session; clear only the state your workflow explicitly manages.
A timeout setting appears ignored. A page-level timeout overrides the context default. Inspect page-level timeout configuration and set an explicit bound at the level that owns the operation.
The process hangs after work finishes. A context or browser remains open, or a wait is unbounded. Use finite waits and close contexts in finally, then close the browser.
HAR or video output is missing or incomplete. The browser was closed before contexts had a chance to close gracefully. Close explicitly created contexts before closing the browser.
A context closes unexpectedly. The browser may have closed or crashed. Handle closure in orchestration, mark the task failed, capture diagnostics, and retry only if safe.
Puppeteer context behavior differs from Playwright. The APIs and defaults are framework- and browser-specific. Check the documentation for the exact installed framework version and browser launch configuration.

Design checklist

  • Have you chosen process, context, and page responsibilities explicitly?
  • Does every independent task get a fresh context?
  • Is state reuse limited to a single intentional workflow?
  • Are action and navigation waits bounded, with page overrides accounted for?
  • Are context closure and browser shutdown handled after both success and failure?
  • Can your job distinguish retryable failures from actions with side effects?
  • Have you checked API behavior against the project’s pinned framework version?

FAQ

Can one browser context contain multiple tabs?

Yes. A context can own multiple pages. Those pages share the context’s session boundary; use separate contexts for separate users or independent sessions.

Does closing a context close its pages?

Yes. Closing a Playwright context closes its pages, which is why context cleanup is a useful task boundary.

Should each test launch a new browser?

Not necessarily. Playwright’s test runner creates a new context per test by default. The browser process and the isolated session are separate lifecycle objects.

Is a Puppeteer BrowserContext exactly the same as a Playwright BrowserContext?

Both support isolated sessions, but terminology and defaults can differ. Follow the API documentation for your framework and browser version.