How to Use Browserless with Playwright to Screenshot a Website
Connect Playwright to Browserless, capture a website as an image, and choose the right endpoint, wait condition, and screenshot options for your workflow.
To screenshot a website with Browserless and Playwright, connect Playwright to Browserless’s remote Chromium browser, navigate to the page, wait until the content you need is ready, then call page.screenshot(). For Browserless’s default Chromium endpoint, use Playwright’s chromium.connectOverCDP(). Close the remote browser in a finally block so the session is released if navigation or capture fails.
This guide uses JavaScript. Browserless also works with Playwright’s Python package. If you only need a URL-to-image result and do not need to interact with the page, Browserless offers a REST screenshot endpoint as an alternative to opening a WebSocket session.
1. Set up the Browserless connection
- Get an API token from your Browserless account dashboard. Treat it as a secret: the connection URL includes it as a
tokenquery parameter. - Install Playwright’s core package, which Browserless recommends for remote-browser connections because it does not download local browser binaries.
- Store the token in the
BROWSERLESS_TOKENenvironment variable. Do not commit it to source control or print the connection URL in logs.
npm install playwright-core
export BROWSERLESS_TOKEN="your-browserless-token"
The example uses Browserless’s production SFO endpoint. Use the endpoint assigned to your account if it differs. See Browserless’s Playwright connection guide and connection URL documentation for endpoint details.
2. Capture a full-page screenshot with JavaScript
Save this as screenshot.mjs and run it with node screenshot.mjs. It waits for network activity to settle, captures the full page, and closes the remote session even when an operation throws.
import { chromium } from 'playwright-core';
const token = process.env.BROWSERLESS_TOKEN;
if (!token) {
throw new Error('Set BROWSERLESS_TOKEN before running this script.');
}
const browser = await chromium.connectOverCDP(
`wss://production-sfo.browserless.io?token=${encodeURIComponent(token)}`
);
try {
const page = await browser.newPage();
await page.goto('https://example.com', {
waitUntil: 'networkidle',
timeout: 60_000,
});
await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
await browser.close();
}
networkidle is useful for pages that finish loading their requests, but it is not a guarantee that every visual element is ready. Sites with analytics, live updates, or persistent connections may never become idle. For those pages, wait for a meaningful selector or use an explicit delay instead.
3. Wait for the page state you actually need
Navigation completion and visual readiness are different. Choose a condition tied to the content in the image. Playwright’s page.goto() supports load-state choices such as domcontentloaded, load, and networkidle; after navigation, you can wait for a selector or a known delay.
await page.goto('https://example.com/products', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
// Prefer a page-specific readiness condition when possible.
await page.locator('[data-page-ready="true"]').waitFor({
state: 'visible',
timeout: 20_000,
});
await page.screenshot({ path: 'products.png', fullPage: true });
If the site has no stable readiness selector, a short delay can help with animations or delayed rendering:
await page.waitForTimeout(1500);
await page.screenshot({ path: 'products.png', fullPage: true });
Use delays sparingly: they add time to every capture and can still be too short or unnecessarily long. A selector that represents the content you need is usually more reliable.
4. Capture an element or viewport instead of the whole page
Use fullPage: true for a screenshot of the entire document. Omit it for the current viewport. To capture one element, wait for it and call screenshot() on its locator:
const card = page.locator('.product-card').first();
await card.waitFor({ state: 'visible' });
await card.screenshot({ path: 'product-card.png' });
Element capture is useful for a chart, card, or component and avoids producing a very tall full-page image. Make sure the selector uniquely identifies the intended content; use first(), nth(), or a more specific locator when the page has repeated matches.
5. Use Python with Browserless
Install Playwright for Python. Because the browser runs remotely, you do not need to run playwright install to download a local browser for this workflow.
python -m pip install playwright
export BROWSERLESS_TOKEN="your-browserless-token"
Save as screenshot.py and run python screenshot.py:
import os
from playwright.sync_api import sync_playwright
TOKEN = os.environ.get("BROWSERLESS_TOKEN")
if not TOKEN:
raise RuntimeError("Set BROWSERLESS_TOKEN before running this script.")
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(
f"wss://production-sfo.browserless.io?token={TOKEN}"
)
try:
page = browser.new_page()
page.goto(
"https://example.com",
wait_until="networkidle",
timeout=60_000,
)
page.screenshot(path="screenshot.png", full_page=True)
finally:
browser.close()
As in JavaScript, replace networkidle with a page-specific wait when a site never settles or when navigation finishing is not enough to make the target content visible.
6. Choose CDP, Playwright protocol, or REST
| What you need | Connection | Tradeoff |
|---|---|---|
| Typical Chromium navigation and screenshots | chromium.connectOverCDP() on the default Browserless endpoint |
The default endpoint speaks Chrome DevTools Protocol (CDP), which is the straightforward option for remote Chromium screenshots. |
Playwright-native features such as page.route() or a non-Chromium browser |
connect() using a protocol-specific path such as /chromium/playwright, /firefox/playwright, or /webkit/playwright |
The Playwright protocol endpoint is more sensitive to client and server Playwright version compatibility. |
| A URL-to-image capture without interactive browser work | Browserless POST /screenshot REST API |
No WebSocket session is needed; send a URL and options and receive image bytes. |
Do not use Playwright’s connect() against the default endpoint as if it were the Playwright protocol endpoint. Browserless documents the default Chromium endpoint as CDP, which pairs with connectOverCDP(). For Playwright-native features, follow its protocol endpoint instructions and use a supported client/server version combination.
7. Browserless REST screenshot API with cURL
If you only need a screenshot from a URL, the REST endpoint is simpler than connecting Playwright. The exact request parameters depend on the Browserless screenshot API; consult its current endpoint documentation for required authentication and supported options. The endpoint accepts screenshot configuration such as output format, full-page capture, quality, viewport, device scale factor, clipping, and element selection.
For example, a request shape for a URL and full-page capture is:
curl -X POST "https://production-sfo.browserless.io/screenshot?token=$BROWSERLESS_TOKEN" \
-H "Content-Type: application/json" \
--data '{"url":"https://example.com","options":{"fullPage":true,"type":"png"}}' \
--output screenshot.png
Check the API documentation for the token placement and request schema supported by your Browserless endpoint before deploying this request. REST capture is a good fit when there is no need to click through the site, inspect page state, or run custom Playwright logic.
8. Handle lazy-loaded content and long pages
Full-page screenshots can miss images or sections that load only after scrolling. Browserless’s REST screenshot API documents scrollPage: true to trigger lazy-loaded content before a full-page capture. In a Playwright workflow, explicitly scroll through the page before taking the screenshot, then wait for the content that should appear.
await page.goto('https://example.com/article', { waitUntil: 'domcontentloaded' });
await page.evaluate(async () => {
const step = Math.max(300, window.innerHeight);
for (let y = 0; y < document.body.scrollHeight; y += step) {
window.scrollTo(0, y);
await new Promise(resolve => setTimeout(resolve, 150));
}
window.scrollTo(0, 0);
});
await page.locator('footer').waitFor({ state: 'attached' });
await page.screenshot({ path: 'long-page.png', fullPage: true });
For pages that continuously add content, a scrolling loop needs a practical stopping condition or maximum duration. The sample uses the document height observed at the start; infinite-scroll pages may need a page-specific strategy to load the desired number of items.
9. Use Playwright Test in CI
Browserless sessions consume plan concurrency. If Playwright Test runs multiple workers, each worker that opens a remote browser session contributes to concurrent usage. Browserless recommends creating the remote connection in a worker-scoped fixture so a worker can reuse its session appropriately, and closing it when the fixture ends. Bound the worker count to the concurrency available on your account.
Remote browser launch options do not work like local chromium.launch() options. Browserless documents supported launch settings through connection URL parameters; consult its connection documentation for the options available to your endpoint. Avoid assuming that arbitrary local launch flags can be passed to a remote browser.
10. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection fails or authentication is rejected | The token is missing, invalid, or placed in the wrong endpoint URL. | Confirm BROWSERLESS_TOKEN is set, check the endpoint assigned to the account, and follow Browserless’s connection URL format. Keep the token out of public code and logs. |
| Playwright reports a protocol or version error | The connection method and endpoint protocol do not match, or the native Playwright endpoint does not support the client version. | For the default Chromium endpoint, use connectOverCDP(). For native protocol features, use the documented /playwright path and a compatible Playwright version. |
| Navigation times out | The site is slow, blocked, or keeps network activity open; networkidle may be too strict. |
Try domcontentloaded and then wait for the specific selector needed in the screenshot. Set a deliberate timeout and investigate whether the target site is reachable from the remote browser. |
| Screenshot is blank or content is missing | The capture happened before rendering, lazy content was not triggered, or the selector/viewport does not cover the intended area. | Wait for a visible target, scroll to trigger lazy loading, check the capture mode, and confirm the element selector matches the page. |
| Remote sessions are rejected under parallel load | Concurrent workers may exceed the account’s current concurrency limit, or sessions are not being closed. | Close each browser in cleanup code, bound parallel workers, and check the concurrency limit for the applicable plan. |
| Local launch settings appear to have no effect | The browser is remote; local process launch options do not automatically configure it. | Use Browserless’s documented connection URL parameters for supported remote launch settings. |
11. Performance, reliability, and cost considerations
- Keep the session lifecycle short. Navigate, wait for the needed state, capture, and close. Each parallel remote session uses concurrency capacity.
- Wait precisely. A relevant selector often avoids the unnecessary delay of waiting for every network request to stop, while a broad fixed delay can be unreliable.
- Limit image size when practical. Full-page images from long pages can be large and take longer to capture and transfer. Capture the viewport or a target element if that satisfies the job.
- Reuse sessions carefully. In test workers, a worker-scoped connection can reduce repeated setup, while unbounded workers can exceed concurrency. Always close sessions at fixture teardown.
- Check current plan details. Browserless plan-specific concurrency and pricing can change; consult the account and plan information rather than relying on a hard-coded limit.
- Protect credentials. Since the token is in the WebSocket URL, avoid logging full connection strings and configure secrets in CI or the runtime environment.
Or skip the browser setup
If you need a direct URL-to-image request, ScreenshotNeo is a website screenshot API and MCP server. Its one-call GET API returns a PNG, JPEG, WebP, or PDF; see the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.
Start free with 1,000 screenshots a month and no card.
FAQ
Can I use Browserless without installing a local browser?
Yes. The browser runs remotely. Use playwright-core in JavaScript, or install Python Playwright without downloading a local browser for this remote connection workflow.
Should I use CDP or the Playwright protocol?
Use CDP for ordinary Chromium navigation and screenshots on Browserless’s default endpoint. Choose a protocol-specific Playwright endpoint when you need Playwright-native features or another browser engine.
Can the REST API capture a full page?
Yes. Browserless documents full-page capture, element selection, and lazy-load scrolling controls for its screenshot REST API. Refer to the API docs for the request schema supported by your endpoint.
Why is my screenshot missing images that appear after scrolling?
Those images may load lazily. Scroll through the page before capture or use the REST API’s documented scrolling option, then wait for the required content to appear.


