ScreenshotNeo

BlogEngineering

How to Benchmark Hosted Browsers for Screenshot and Automation Workloads

Build a reproducible hosted-browser benchmark that separates connection, navigation, screenshot, and end-to-end performance—and reports failures and tail latency.

By the ScreenshotNeo team4 October 202610 min read

To benchmark hosted browsers fairly, run the same browser workload against each service and time each stage separately: connection, page creation, navigation to a defined readiness condition, application actions, screenshot completion, and end-to-end success. Repeat at declared concurrency, keep screenshot settings and regions comparable, and report latency distributions, failures, timeouts, throughput, and image correctness. A connection-time result alone cannot tell you which service completes your production job fastest.

1. Define the workload before choosing a provider

A useful benchmark represents the jobs you actually run. A blank page or one public URL can be a smoke test, but cannot stand in for a diverse production workload.

  • Choose representative page types: static pages, JavaScript-rendered pages, and pages that require application-specific actions.
  • Use stable URLs and state where possible. Record a content version or fixture state if the target can change during the benchmark.
  • Include the screenshot modes you use: viewport, full page, selector, or clipped region.
  • Define expected output, such as successful navigation, a nonempty image, expected dimensions, and a recognizable page state.
  • Choose realistic concurrency and distinguish single-session responsiveness from behavior under load.

Keep a fixed smoke-test URL for quick comparisons, but base a purchase or architecture decision on your own representative workload.

2. Record the environment and controls

For every run, capture enough context to reproduce it. Hosted-browser results depend on geography, runtime, target-site behavior, and the exact work performed.

Control Record
Provider and deployment Provider, plan or deployment mode, endpoint, and region
Browser stack Browser engine and version when available; Playwright or Puppeteer version
Target URL, page type, test data or state, and any actions before capture
Viewport Width, height, device scale factor, and device preset if used
Readiness Navigation wait condition and any application-specific readiness signal
Capture Viewport/full-page/selector/clip, format, quality, image wait, background, and timeout
Load Concurrency, run schedule, retries, and whether the session was cold or reused
Context Timestamp, locale, timezone, and other settings that materially affect the page

Use a nearby region when that matches your real deployment. Browserless recommends choosing a nearby region to reduce latency and documents region-specific endpoints; region changes can affect measured times. Preserve each provider’s endpoint and region in the result so the comparison is interpretable.

3. Time the full lifecycle in separate stages

Use a monotonic clock and record timestamps around each operation. Keep the stages attributable so you can see whether delay comes from session startup, target rendering, capture, or output handling.

  1. Connection: start opening the remote browser connection and stop when the browser session is usable.
  2. Page creation: time creating a new page or tab.
  3. Navigation: time navigation until the declared readiness condition.
  4. Application work: time the actions needed to reach the target state.
  5. Screenshot: time the capture call through receipt of the image bytes.
  6. Persistence and validation: measure saving and validating the output separately if storage is part of the production path.
  7. End-to-end: record the total time and whether the job completed correctly.

Browserless’s published harness measures connection, newPage(), and page.goto(). For screenshot workloads, add capture and output validation. Browserbase’s hosted-session screenshot example navigates and then calls full-page capture. The readiness condition matters: DOM content loaded may happen before a page paints late content, so use and disclose an application-specific signal when the job requires one.

4. Make screenshot output comparable

Capture options are part of the workload, not incidental API details. Match them across providers where possible, and disclose any differences the APIs make unavoidable.

  • Scope: viewport, full page, CSS selector, or clip rectangle. Full-page output may involve more content and image work than viewport capture.
  • Readiness: choose whether to wait for images or another page condition; a fixed delay can be both wasteful and unreliable.
  • Format and quality: record PNG, JPEG, or other supported output, plus quality settings when applicable.
  • Background: state whether the page background is included or omitted.
  • Timeout: use the same job budget where possible, and count timed-out jobs as outcomes rather than silently dropping them.
  • Output checks: verify the file exists, has nonzero bytes, has expected dimensions, and reflects the intended page state.

Browserless’s screenshot API documents full-page capture, clipping, selector capture, image waiting, output type, quality, timeout, and speed/size behavior. Its settings are useful examples of variables to control; do not assume another provider implements identical behavior.

5. Run repeated trials at declared load

Repeat runs and publish the run count and schedule. There is no neutral, universal sample-count or concurrency standard established by the sources for this benchmark, so choose enough runs to reveal variability and explain your choice.

  • Run cold connections separately from reused sessions if both occur in production.
  • Run a single-session profile for interactive latency and a separate stated-concurrency profile for load behavior.
  • Keep concurrency steady within each profile; do not combine low-load and saturation runs into one average.
  • Record retries and their policy. Report initial attempts and final job outcomes clearly.
  • Track completed jobs, failures, timeouts, and throughput alongside latency.

6. Calculate and report results

For each provider and workload, report median and high-percentile end-to-end latency, plus the stage timings that explain it. Include the sample count and spread. A mean can hide slow tails; a fastest observation says little about what a typical job experiences.

Result What it answers
Connection, page creation, navigation, capture Where time is spent
End-to-end median and tail percentile Typical and slow-job experience
Success, failure, timeout, retry counts Whether the work completes reliably
Throughput at stated concurrency How many successful jobs finish over time at that load
Output validation rate Whether completed captures are usable

Include test date, region, browser/runtime, library, target, run count, concurrency, readiness condition, and image settings beside the numbers. Keep failed and timed-out work in the report; excluding it can make a slow or unreliable system look artificially fast.

7. Interpret published comparisons carefully

Browserless published a Puppeteer comparison dated January 1, 2026, reporting repeated-run averages for connection, page creation, and navigation across Browserless, Anchor Browser, Browserbase, and Hyperbrowser. In that configuration, it reported connection averages of 692.5 ms for Hyperbrowser, 936.4 ms for Browserless, 1,929.9 ms for Browserbase, and 5,582.4 ms for Anchor Browser. It reported navigation averages of 166.2 ms for Browserless, 251.1 ms for Hyperbrowser, 317 ms for Browserbase, and 401.6 ms for Anchor Browser.

These are Browserless’s own reported results, published by a vendor participating in the comparison. The article describes one configuration and notes that geography and workload affect results. It does not establish screenshot capture speed, performance at concurrency saturation, behavior in other regions, or performance on every plan. Treat the figures as a scoped example, then rerun the harness with your own URLs, settings, and regions before drawing a buying conclusion.

8. Compare deployment and operating requirements

Hosted and self-hosted browser infrastructure solve different operational problems. Compare cold startup, session reuse, region placement, browser version control, debugging and persistence needs, concurrency behavior, and the operational work your team must own. Browserless documents hosted endpoints and a self-hosted Docker image with Puppeteer/Playwright connectivity and screenshot APIs. This research does not establish current comparable pricing, plan limits, or neutral reliability data across providers; verify current terms directly and calculate cost from your measured successful workload rather than inventing a universal cost-per-job figure.

9. Reproducible Playwright example

The following Node.js script runs a remote Playwright browser over a WebSocket endpoint, records lifecycle timings, saves a screenshot, and emits a JSON result. Set BROWSER_WS_ENDPOINT to the provider’s documented endpoint and credentials. The script uses domcontentloaded as an explicit example; replace it with the readiness signal your page actually requires. Provider connection syntax and authentication differ, so use the provider’s official endpoint format.

import { chromium } from 'playwright';
import { performance } from 'node:perf_hooks';
import { writeFile } from 'node:fs/promises';

const endpoint = process.env.BROWSER_WS_ENDPOINT;
const target = process.env.TARGET_URL ?? 'https://example.com';
if (!endpoint) throw new Error('Set BROWSER_WS_ENDPOINT');

const marks = {};
const stamp = () => performance.now();
let browser;
let outcome = 'ok';
let error = null;

try {
  let t = stamp();
  browser = await chromium.connectOverCDP(endpoint);
  marks.connection_ms = stamp() - t;

  t = stamp();
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
  const page = await context.newPage();
  marks.page_creation_ms = stamp() - t;

  t = stamp();
  await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 45000 });
  marks.navigation_ms = stamp() - t;

  // Replace with a stable, application-specific condition when needed.
  t = stamp();
  await page.screenshot({ path: 'benchmark.png', fullPage: true, type: 'png', timeout: 30000 });
  marks.screenshot_ms = stamp() - t;
  const image = await (await import('node:fs/promises')).readFile('benchmark.png');
  if (image.length === 0) throw new Error('Screenshot output is empty');
  marks.image_bytes = image.length;
  await context.close();
} catch (e) {
  outcome = 'failed';
  error = String(e?.message ?? e);
} finally {
  await browser?.close().catch(() => {});
}

console.log(JSON.stringify({
  target,
  outcome,
  error,
  stages: marks,
  end_to_end_ms: Object.values(marks).filter(Number.isFinite).reduce((a, b) => a + b, 0),
  timestamp: new Date().toISOString()
}));
if (outcome !== 'ok') process.exitCode = 1;

For a benchmark harness, wrap the job in a loop, write one JSON record per attempt, and run separate processes or workers for each declared concurrency level. The sample’s stage sum excludes file persistence and some local bookkeeping; if saving is part of the measured job, time it as its own stage and use a single end-to-end timer around the complete operation.

10. cURL, Python, and Node.js with ScreenshotNeo

For a screenshot-only job, a browser automation harness may be more infrastructure than you need. ScreenshotNeo is a website screenshot API and MCP server. Its one-call API can return a screenshot or PDF; the example below captures a fixed target as WebP. See the ScreenshotNeo API documentation for request 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()
with open("shot.webp", "wb") as f:
    f.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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

Or skip the browser setup

ScreenshotNeo accepts a URL and returns the capture. Cookie banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. All features are on every plan.

Sign up for 1,000 free screenshots a month, with no card.

Troubleshooting

Symptom Likely cause What to do
Connection timing varies widely Cold starts, region distance, or shared load are mixed together Separate cold and reused sessions, record region, and repeat under a fixed load profile.
Navigation is fast but the screenshot is incomplete The wait condition fires before late content paints Wait for an application-specific selector or readiness signal; record that condition consistently.
Providers return different dimensions Viewport, device scale, full-page behavior, or clipping differs Align those settings and validate image dimensions before comparing timings.
Many jobs time out Timeout budgets are too short, target pages are unstable, or one provider is overloaded Keep the budget declared, preserve timeout outcomes, inspect stage timings, and rerun against stable targets.
Benchmark results cannot be reproduced Environment, target state, schedule, or runtime versions were not recorded Log the environment table fields and pin versions where the provider permits.
High throughput but poor success rate Only completed jobs are counted or retries obscure initial failures Report attempts, successes, retries, timeouts, and successful throughput separately.
Remote connection is rejected Endpoint format, credentials, or browser protocol is incorrect Use the provider’s documented WebSocket URL and authentication format; avoid printing secrets in logs.

Performance, reliability, and cost notes

  • Performance: measure end-to-end latency and stages. Region and workload influence timings; a faster connection does not guarantee a faster completed screenshot.
  • Reliability: count failed loads, timeouts, invalid images, and retries. A successful response should mean the expected page and image passed checks.
  • Throughput: declare concurrency and report completed valid jobs over time. Do not infer saturation capacity from serial trials.
  • Cost: verify current vendor pricing and plan constraints directly. Estimate your cost using representative successful jobs and the retry/failure behavior you measured.
  • Fairness: disclose unavoidable API differences and do not describe one URL, region, or run as a universal winner.

FAQ

Is connection time enough to choose a hosted browser?

No. It omits page creation, navigation, rendering readiness, screenshot capture, and whether the output is valid.

Should I use a public website or my own pages?

Use a stable public page for a quick smoke check, then use representative production pages for a decision.

Should I compare hosted browsers with self-hosted infrastructure?

You can, if you report deployment mode and include the operational requirements and work your team owns. Treat them as distinct deployment options.

Can a provider’s published benchmark settle the decision?

Use it as a scoped observation. Reproduce the workload with your URLs, region, browser configuration, and capture settings before choosing.