cdpfleet Docs Sign up free

Examples & benchmarks / #80 · 2026-11-03 · Benchmark · Easy · for web scraping, ai agents

How many tabs fit in one session, by plan, browser and "memory"

A long Wikipedia article opened in one new tab after another until the session ends: on Community (2 GB), a shared plan (4 GB) and a dedicated thread (6 GB), in Chromium, Firefox and WebKit, and with `"memory": 2`. How many tabs fit, what the client sees when the memory runs out, what a 51st tab gets, and what each run is billed.

Chromium Firefox WebKit

Run on production on 2026-11-04: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

A benchmark: the results below are what this setup measured on the dates shown, on our own test runs. Sites, networks and browsers change, so rerun the code before you rely on the numbers.

The problem

A crawler that keeps tabs open, an agent that never closes what it opened, a test that runs many pages in one browser: sooner or later the session runs into its limits. Every session can have up to 50 tabs, and the browser's memory is capped by the plan — 2 GB on Community, 4 GB on shared threads, 6 GB on a dedicated thread — unless you launch with "memory": 2, 3 or 4 for that many times the memory (and threads). This case measures how far those limits actually go with a real, mid-weight page, and what your code sees when it hits one.

What we used, and why

WhatWhy
A long Wikipedia articleA real, mid-weight page (text, images, a large DOM) that is fine to load repeatedly, one tab at a time.
One context, new tab per loadEach newPage() + goto() keeps the previous tabs open, as an agent or crawler that never closes tabs would.
"memory": 2Twice the plan's memory for this session; the launch response shows the effective memory_mb and weight.
The error the client getsWhen the memory cap is hit, the next call fails with the reason in its message.
GET /v1/me/sessionsThe session's end_reason, weight, peak memory and duration: what was billed.
GET /v1/meWhich plan each key is on, so the runs are labelled without guessing.

How it works

  1. For each run — Community Chromium; your plan with Chromium, Firefox, WebKit and Chromium with "memory": 2; a dedicated thread with Chromium — launch a headless session and note memory_mb and weight from the launch response.
  2. Open the article in a new tab, again and again, until a call fails or 50 tabs are open.
  3. At 50 tabs, try one more.
  4. Close the session and read its record: end reason, weight, peak memory, duration.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY (your plan), PROXY_URL; optional COMMUNITY_API_KEY, CDPFLEET_DEDICATED_API_KEY to compare plans
import { chromium, firefox, webkit } from 'playwright';

const STARTER = 'https://starter.cdpfleet.com';
const API = 'https://cdpfleet.com';
const PAGE = 'https://en.wikipedia.org/wiki/World_War_II'; // a long article: a mid-weight page
const MAX_TABS = 50; // the per-session tab cap on every plan
const PROXY = process.env.PROXY_URL;
const TYPES = { chromium, firefox, webkit };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// Each run: an account, an engine and a memory multiplier. Runs whose key isn't set are skipped.
const RUNS = [
  ['COMMUNITY_API_KEY', 'chromium', 1],
  ['CDPFLEET_API_KEY', 'chromium', 1],
  ['CDPFLEET_API_KEY', 'firefox', 1],
  ['CDPFLEET_API_KEY', 'webkit', 1],
  ['CDPFLEET_API_KEY', 'chromium', 2],
  ['CDPFLEET_DEDICATED_API_KEY', 'chromium', 1],
].filter(([env]) => process.env[env]);

const api = (key, path) => fetch(`${API}${path}`, { headers: { 'x-api-key': key } }).then((r) => r.json());

async function run([env, engine, memory]) {
  const key = process.env[env];
  const sub = (await api(key, '/v1/me')).subscription;
  const plan = sub.features?.community ? 'Community' : sub.kind === 'dedicated' ? 'Dedicated' : 'Shared';
  const t0 = Date.now();
  // Community launches use the plan's included proxy; paid plans bring their own.
  const body = { headless: true, ...(memory > 1 ? { memory } : {}), ...(plan === 'Community' ? {} : { proxy: PROXY }) };
  const res = await fetch(`${STARTER}/${engine}/session`, { method: 'POST', headers: { 'x-api-key': key, 'content-type': 'application/json' }, body: JSON.stringify(body) });
  if (!res.ok) return { plan, engine, memory, launch_error: `${res.status} ${await res.text()}` };
  const launch = await res.json();
  const browser = await TYPES[engine].connect(launch.wsUrl, { headers: { 'x-api-key': key } });
  const context = await browser.newContext();
  // Open the article in one new tab after another until the session ends or the tab cap is reached.
  let tabs = 0;
  let clientError = null;
  while (tabs < MAX_TABS) {
    try {
      const page = await context.newPage();
      // A slow load through a shared proxy gets one more try; a closed session doesn't.
      await page.goto(PAGE, { timeout: 60000 }).catch((err) => { if (/Timeout/.test(err.message)) return page.goto(PAGE, { timeout: 60000 }); throw err; });
      tabs++;
    } catch (err) {
      clientError = err.message.split('\n')[0];
      break;
    }
  }
  // At the cap with memory to spare: what does a 51st tab get?
  let tab51 = null;
  if (tabs === MAX_TABS) {
    try { await context.newPage(); tab51 = 'opened'; } catch (err) { tab51 = err.message.split('\n')[0]; }
  }
  await browser.close().catch(() => {});
  // The session's record: why it ended, its weight, peak memory and time.
  let record = null;
  for (let i = 0; i < 20 && !record?.ended_at; i++) {
    await sleep(1500);
    record = (await api(key, '/v1/me/sessions?limit=20')).sessions.find((s) => s.session_id === launch.sessionId);
  }
  return {
    plan, engine, memory, weight: launch.weight, memory_mb: launch.memory_mb, tabs,
    client_error: clientError?.replace(/^.*?(Session closed: .*)$/, '$1') ?? null, tab_51: tab51,
    end_reason: record?.end_reason ?? null, memory_peak_mb: record?.memory_peak_mb ?? null,
    seconds: record?.duration_seconds ?? Math.round((Date.now() - t0) / 1000),
    thread_seconds: record ? record.duration_seconds * record.weight : null,
  };
}

const runs = [];
for (const r of RUNS) runs.push(await run(r)); // one at a time: one site, one session at a time
console.log(JSON.stringify({ language: 'node', page: PAGE, max_tabs: MAX_TABS, measured_at: new Date().toISOString().slice(0, 10), runs }, null, 2));

What we got

Plan · browser · memoryMemory cap · weightTabs openedWhat ended itBilledMeasured
Community · chromium2048 MB · weight 120session_memory_limit — the client saw "Session closed: session_memory_limit (browser ran out of memory: plan limit 2048 MB)"90 s × 1 = 90 thread-seconds2026-11-04
Shared · chromium4096 MB · weight 137session_memory_limit — the client saw "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)"40 s × 1 = 40 thread-seconds2026-11-04
Shared · firefox4096 MB · weight 126session_memory_limit — the client saw "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)"40 s × 1 = 40 thread-seconds2026-11-04
Shared · webkit4096 MB · weight 118session_memory_limit — the client saw "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)"127 s × 1 = 127 thread-seconds2026-11-04
Shared · chromium · "memory": 28192 MB · weight 250 (the cap; 51st: refused)client_closed (closed by the client), peak 5298 MB53 s × 2 = 106 thread-seconds2026-11-04
Dedicated · chromium6144 MB · weight 150 (the cap; 51st: refused)client_closed (closed by the client), peak 5294 MB51 s × 1 = 51 thread-seconds2026-11-04

From the Node.js run on 2026-11-04. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "language": "node",
  "page": "https://en.wikipedia.org/wiki/World_War_II",
  "max_tabs": 50,
  "measured_at": "2026-11-04",
  "runs": [
    {
      "plan": "Community",
      "engine": "chromium",
      "memory": 1,
      "weight": 1,
      "memory_mb": 2048,
      "tabs": 20,
      "client_error": "Session closed: session_memory_limit (browser ran out of memory: plan limit 2048 MB)",
      "tab_51": null,
      "end_reason": "session_memory_limit",
      "memory_peak_mb": 2048,
      "seconds": 90,
      "thread_seconds": 90
    },
    {
      "plan": "Shared",
      "engine": "chromium",
      "memory": 1,
      "weight": 1,
      "memory_mb": 4096,
      "tabs": 37,
      "client_error": "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)",
      "tab_51": null,
      "end_reason": "session_memory_limit",
      "memory_peak_mb": 4096,
      "seconds": 40,
      "thread_seconds": 40
    },
    {
      "plan": "Shared",
      "engine": "firefox",
      "memory": 1,
      "weight": 1,
      "memory_mb": 4096,
      "tabs": 26,
      "client_error": "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)",
      "tab_51": null,
      "end_reason": "session_memory_limit",
      "memory_peak_mb": 4096,
      "seconds": 40,
      "thread_seconds": 40
    },
    {
      "plan": "Shared",
      "engine": "webkit",
      "memory": 1,
      "weight": 1,
      "memory_mb": 4096,
      "tabs": 18,
      "client_error": "Session closed: session_memory_limit (browser ran out of memory: plan limit 4096 MB)",
      "tab_51": null,
      "end_reason": "session_memory_limit",
      "memory_peak_mb": 4096,
      "seconds": 127,
      "thread_seconds": 127
    },
    {
      "plan": "Shared",
      "engine": "chromium",
      "memory": 2,
      "weight": 2,
      "memory_mb": 8192,
      "tabs": 50,
      "client_error": null,
      "tab_51": "browserContext.newPage: page_limit: this session already has 50 pages open (plan limit), so the new page was closed; the session continues",
      "end_reason": "client_closed",
      "memory_peak_mb": 5298,
      "seconds": 53,
      "thread_seconds": 106
    },
    {
      "plan": "Dedicated",
      "engine": "chromium",
      "memory": 1,
      "weight": 1,
      "memory_mb": 6144,
      "tabs": 50,
      "client_error": null,
      "tab_51": "browserContext.newPage: page_limit: this session already has 50 pages open (plan limit), so the new page was closed; the session continues",
      "end_reason": "client_closed",
      "memory_peak_mb": 5294,
      "seconds": 51,
      "thread_seconds": 51
    }
  ]
}

Takeaways