ScreenshotNeo

BlogHow-to

How to Use Browserless Screenshots with Playwright

Connect Playwright to a Browserless browser, wait for the page state you need, and save a screenshot. Includes REST, setup, options, and troubleshooting.

By the ScreenshotNeo team4 October 202611 min read

To take a screenshot with Playwright while Browserless hosts the browser, connect to a Browserless browser endpoint, navigate to the page, wait for the content you need, then call page.screenshot(). Close the remote browser in a finally block so the session is released after success or failure. For a single capture that needs no page interaction, Browserless also offers a REST /screenshot endpoint that returns image bytes without Playwright or a WebSocket connection.

Use Playwright when your capture depends on clicks, page state, or custom readiness checks. Use REST when one stateless request is enough. Browserless documents both approaches in its screenshot guide and Screenshot API reference.

1. Choose the Browserless screenshot path

Path Use it when Tradeoff
Playwright over CDP Your script needs to control a Chromium page, interact with it, or wait for application state. You manage a remote browser session and must close it.
Playwright native protocol You need Playwright features such as page.route(), an APIRequestContext, or a browser other than Chromium. Choose the matching Browserless Playwright endpoint; its native mode is coupled to the Playwright version Browserless runs.
REST /screenshot You need one screenshot of a URL or supplied HTML and no multi-step interaction. It is stateless: it cannot keep session state across multiple actions.

Browserless’s default browser connection endpoint speaks CDP, so its Chromium workflow uses chromium.connectOverCDP(). Use a native /playwright endpoint when your required features or browser engine call for it. See Browserless’s connection URLs and endpoints for current endpoint formats and regions.

2. Connect Playwright to Browserless

Prerequisites

  1. Create a Browserless account and copy the API token from its dashboard.
  2. Use Node.js with a supported modern runtime that provides fetch.
  3. Install playwright-core. It is intended for connecting to a remote browser without downloading a local browser binary.
  4. Set the token in an environment variable. The token is a secret because it authenticates the connection.
npm install playwright-core
export BROWSERLESS_TOKEN="YOUR_API_TOKEN"
export BROWSERLESS_REGION="production-sfo"

Use the Browserless region nearest your application to reduce network distance. The endpoint shown below uses production-sfo; replace that hostname with the appropriate endpoint from your Browserless account or connection URL documentation. Avoid committing a real token to source control or printing a token-bearing WebSocket URL in logs.

Complete Node.js example using CDP

import { chromium } from 'playwright-core';

const token = process.env.BROWSERLESS_TOKEN;
const region = process.env.BROWSERLESS_REGION ?? 'production-sfo';

if (!token) {
  throw new Error('Set BROWSERLESS_TOKEN before running this script.');
}

const browser = await chromium.connectOverCDP(
  `wss://${region}.browserless.io?token=${encodeURIComponent(token)}`
);

try {
  const context = await browser.newContext({
    viewport: { width: 1440, height: 1000 },
    deviceScaleFactor: 1
  });
  const page = await context.newPage();

  const response = await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
    timeout: 45_000
  });

  if (!response || !response.ok()) {
    throw new Error(`Navigation failed: ${response?.status() ?? 'no response'}`);
  }

  // Prefer a page-specific readiness condition over assuming that navigation
  // means every dynamic component is ready.
  await page.locator('h1').waitFor({ state: 'visible', timeout: 15_000 });
  await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
  // Closing the browser releases the remote session even if navigation or
  // screenshot capture throws. Do not leave remote sessions open.
  await browser.close();
}

Save this as screenshot.mjs and run node screenshot.mjs. The example uses CDP and Chromium. The call to browser.close() is intentional: it closes the remote connection, so put it in finally rather than only on the successful path. This example follows Browserless’s documented connection pattern; it is not a claim that this article’s code was executed.

When native Playwright protocol is required

CDP is not the right connection mode for every Playwright workflow. If you need Playwright-only features such as network routing with page.route(), or you need Firefox or WebKit, use Browserless’s matching native Playwright endpoint and Playwright connection method. Consult the current endpoint list and Browserless’s BaaS documentation for the exact endpoint and protocol supported by your account. Do not assume a CDP connection supports every Playwright API.

3. Wait for the page content you intend to capture

A navigation event only describes a browser loading milestone. It does not guarantee that a client-rendered chart, image, or application component has finished. Choose the wait that matches the page:

  • waitUntil: 'domcontentloaded' waits for the initial document to be parsed. It can be a good starting point for pages whose relevant content appears afterward.
  • waitUntil: 'load' waits for the load event and its dependent resources. It may still precede later application work.
  • waitUntil: 'networkidle' waits for network activity to quiet down, but pages with polling or persistent connections may never become idle. Do not use it as a universal readiness test.
  • locator(...).waitFor() waits for a meaningful element or state in your page. This is often the clearest option when you know what content the screenshot must contain.
  • A deliberate timeout can handle a known animation or delayed widget, but arbitrary sleeps make captures slower and can still miss content.

For lazy-loaded content, scroll relevant sections into view before capture and allow the images or components to load. Check the resulting screenshot for missing sections; a full-page option does not by itself prove that every lazy resource was loaded. Browserless’s REST screenshot configuration also documents waits for events, functions, selectors, and timeouts, plus a scrollPage option for triggering lazy content.

4. Configure the Playwright screenshot

Playwright controls the image through page.screenshot(). The core choices are:

Need Playwright setting or approach
Save the visible viewport Omit fullPage; the screenshot uses the current viewport.
Capture the whole document Set fullPage: true. For long pages, first consider lazy loading and very large image dimensions.
Capture one component Use locator('...').screenshot({ path: 'element.png' }) to capture an element.
Choose image format Use a file extension or type: 'png', 'jpeg', or 'webp' where supported by the installed Playwright version. JPEG and WebP can take a quality value; PNG is lossless.
Set viewport dimensions Set viewport when creating the context. A viewport affects layout and responsive breakpoints.
Capture at higher pixel density Set deviceScaleFactor when creating the context. It increases output pixel dimensions and can increase memory and file size.
Capture a fixed rectangle Use Playwright’s screenshot clip option with an explicit rectangle.
Transparent background Use omitBackground: true when the page and chosen format support transparency; JPEG does not preserve transparency.
Hide a cursor or caret Use caret: 'hide'; disable animations where a stable frame matters.

Example: for a mobile layout, create the context with a mobile-sized viewport and an appropriate device scale factor before navigation. Changing screenshot dimensions after loading does not recreate the responsive layout; the viewport must be right before the page renders.

const context = await browser.newContext({
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 2,
  isMobile: true,
  hasTouch: true
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main').screenshot({ path: 'main.webp', type: 'webp', quality: 85 });

Use the screenshot options supported by the Playwright version installed in your project. For Browserless REST, the corresponding API documents Puppeteer-style options including format, quality, full-page mode, clip, viewport, device scale factor, and selectors; its selector is at the request body’s top level. The precise option shape differs between the Playwright client call and REST request.

5. Take one screenshot with Browserless REST

If there is no need to click, branch, or keep state between actions, a REST request is shorter than a remote Playwright session. The response body is the image, so save the bytes directly.

cURL

curl -X POST \
  "https://production-sfo.browserless.io/screenshot?token=$BROWSERLESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Cache-Control: no-cache' \
  -d '{
    "url": "https://example.com/",
    "options": {
      "fullPage": true,
      "type": "png"
    }
  }' \
  --output screenshot.png

Python

import os
import requests

 token = os.environ["BROWSERLESS_TOKEN"]
endpoint = "https://production-sfo.browserless.io/screenshot"
response = requests.post(
    endpoint,
    params={"token": token},
    json={
        "url": "https://example.com/",
        "options": {"fullPage": True, "type": "png"},
    },
    timeout=90,
)
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
    image_file.write(response.content)

Node.js

import { writeFile } from 'node:fs/promises';

const token = process.env.BROWSERLESS_TOKEN;
if (!token) throw new Error('Set BROWSERLESS_TOKEN first.');

const endpoint = new URL('https://production-sfo.browserless.io/screenshot');
endpoint.searchParams.set('token', token);
const response = await fetch(endpoint, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url: 'https://example.com/',
    options: { fullPage: true, type: 'png' }
  }),
  signal: AbortSignal.timeout(90_000)
});
if (!response.ok) {
  throw new Error(`Browserless returned HTTP ${response.status}: ${await response.text()}`);
}
await writeFile('screenshot.png', Buffer.from(await response.arrayBuffer()));

The Python snippet has a leading space before token in this displayed code? No: copy it with token starting at the left margin within the function-free top level. Supply url to capture a page, or html to render supplied markup. Browserless warns not to send both in the same request. To capture a single element, send selector at the top level alongside url; for a fixed crop, use options.clip. See the official request reference for the current schema and response formats.

6. Handle common capture requirements

Full page and lazy-loaded images

For a Playwright full-page image, use fullPage: true after waiting for the main page state. Lazy images commonly load only when scrolled into view, so scroll through the page before taking the screenshot if those images matter. Browserless REST documents scrollPage: true to scroll before capture; pair it with options.fullPage: true for long pages. Also check the final output because sites may load content only after additional interaction.

Element versus viewport

Use an element screenshot when the output should contain a chart, card, or other specific region. It avoids manual crop coordinates and the surrounding page. If the target element is absent, wait for it explicitly and make the missing-element case visible in your script rather than saving an unintended full-page image.

HTML instead of a public URL

For REST, provide an html field to render supplied markup. Do not include url in the same request. Use this when the HTML is the actual input; it is not a way to make a target website’s authenticated application state appear automatically.

Request and launch configuration

Browserless REST supports shared request configuration for waits, navigation behavior, blocking resource types or request patterns, and best-effort continuation when asynchronous events fail or time out. Launch parameters configure the browser environment; Browserless documents passing individual query parameters or a JSON launch payload, with individual parameters taking precedence when both methods set the same value. Only add options needed for the capture, and consult the current launch parameters reference for valid settings.

7. Troubleshoot blank or incomplete screenshots

Symptom Likely cause What to check
Blank page, CAPTCHA, 403, or access denied in the image The destination may block automated browsing or return a challenge to the remote browser. Check the page in a normal browser and inspect the response or captured page. Browserless documents an /unblock API for some protections, but sophisticated fingerprinting or interactive challenges can still prevent access. Do not assume every site can be unblocked.
Image is missing or a component is half rendered The screenshot ran before the relevant app state or image was ready. Wait for a meaningful selector or application condition. Scroll lazy-loaded sections into view; inspect the saved output.
Navigation times out on a live page The page may keep network connections open or take longer than the chosen timeout. Wait for a narrower condition such as DOM readiness plus a visible target selector instead of requiring network idle. Increase the timeout only if the page genuinely needs more time.
Playwright reports an unsupported operation The workflow may use a Playwright feature that the CDP connection does not implement. Check the feature requirements and use Browserless’s native Playwright endpoint when necessary.
WebSocket connection fails The token, endpoint hostname, URL encoding, account settings, or network access may be wrong. Confirm the current endpoint in the account documentation, verify the token is present, and URL-encode it. Do not paste a token-bearing URL into shared logs.
REST call returns JSON or an HTTP error instead of an image The request may be malformed, unauthorized, or rejected before capture. Check the HTTP status and error body before writing image bytes. Confirm the token, JSON content type, target field, and option placement.
REST element selector capture fails The selector may be missing or placed inside options instead of at the top level. Use a selector that exists after page readiness and place it alongside url, as Browserless documents.
Legacy example behaves differently The example may target Browserless BaaS v1, which is no longer actively supported. Use the current BaaS v2 or BrowserQL documentation instead of relying on the legacy screenshot page.

8. Performance, reliability, and cost

  • Keep the capture narrow. A viewport or element capture generally produces less image data than a very long full-page capture. Large pages and higher device scale factors produce larger images and can require more browser memory.
  • Wait for a condition, not a guess. A specific selector can avoid both premature captures and long fixed sleeps. Use a timeout around navigation and readiness waits so a stuck page fails in a bounded time.
  • Always release sessions. Close the browser connection in finally, including when navigation or screenshot writing fails.
  • Retry selectively. A retry may help with transient navigation or network failures, but repeating a capture will not fix a persistent CAPTCHA, bad selector, or incorrect endpoint. Bound retries and avoid retry storms.
  • Account for output storage and transfer. PNG preserves detail but is often larger; JPEG or WebP can reduce bytes when lossy output is acceptable. Choose dimensions and format based on the consumer of the screenshot.
  • Check your Browserless plan and usage terms. This article does not state Browserless prices, quotas, concurrency, or reliability figures; consult your current account plan and official commercial documentation for those details.

9. Or skip the browser setup

If the job is simply “give me a screenshot of this URL,” ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and the docs are at ScreenshotNeo API documentation.

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

Cookie banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed, and each response reports its verdict and billing status. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

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

10. FAQ

Does Browserless take the screenshot, or does Playwright?

Browserless provides the remote browser. Your Playwright script controls the page and calls the screenshot method; for a one-shot REST capture, Browserless handles the request and returns the image.

Can I take a screenshot without installing Playwright?

Yes. Use Browserless’s REST /screenshot endpoint for a single URL or HTML capture.

Should I use CDP or the native Playwright endpoint?

Use CDP for the documented Chromium connection workflow. Use native Playwright when your needed Playwright APIs or browser engine require it, and verify the endpoint and version compatibility in Browserless’s current docs.

Why does a successful navigation still produce the wrong image?

Navigation completion does not necessarily mean the page’s important dynamic content, lazy images, or challenge response is ready. Wait for the content you expect and inspect the resulting image.

Primary documentation