ScreenshotNeo

BlogHow-to

How to Capture Screenshots with Selenium Grid and Headless Chrome

Run headless Chrome on Selenium Grid, wait for the right page state, capture screenshots, and get the image back to your client or CI artifacts.

By the ScreenshotNeo team4 October 20269 min read

Selenium Grid can run a headless Chrome browser on a remote machine and route WebDriver commands to it. Create a remote session with the Grid URL and Chrome options, navigate to the page, wait for the state your capture needs, then call the binding’s screenshot API. Because the browser runs remotely, make an explicit plan for getting the screenshot bytes or file into the client or CI artifact store.

1. Start Selenium Grid

For a basic single-machine setup, Selenium’s Grid getting-started guide lists Java 11 or newer, a browser and driver, and the Selenium Server JAR as prerequisites. Selenium Manager can configure drivers automatically when enabled. Standalone mode runs the Grid components together in one process on one machine.

  1. Install Java 11 or newer and Chrome on the machine that will run the browser.
  2. Download the Selenium Server JAR from the Selenium downloads page.
  3. Start a local Standalone Grid with java -jar selenium-server-<version>.jar standalone.
  4. Point the client at http://localhost:4444.

The command-line example uses a placeholder JAR filename; substitute the file you downloaded. In a distributed or hosted Grid, use the reachable Grid URL provided by that deployment. The browser and driver run on a remote node, so the client and browser machine may not share a filesystem.

2. Create a headless remote Chrome session

Selenium’s Chrome documentation lists --headless=new as a commonly used Chrome argument. The Grid URL and browser options are both part of creating the remote session. Keep Chrome and ChromeDriver on matching major versions; Selenium Manager may help configure drivers depending on the binding and deployment.

from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

GRID_URL = "http://localhost:4444"
PAGE_URL = "https://example.com"
OUTPUT = Path("screenshot.png")

options = Options()
options.add_argument("--headless=new")

# The browser session runs on the Grid node; the Python client runs here.
driver = webdriver.Remote(
    command_executor=GRID_URL,
    options=options,
)
try:
    driver.set_window_size(1440, 1000)
    driver.get(PAGE_URL)

    # Add an explicit wait for the page state your test needs before capture.
    # This example captures after navigation; production pages often need a condition.
    saved = driver.save_screenshot(str(OUTPUT.resolve()))
    if not saved:
        raise OSError(f"Could not save screenshot to {OUTPUT.resolve()}")
finally:
    driver.quit()

print(f"Saved client-side screenshot to {OUTPUT.resolve()}")

Install the Python binding with python -m pip install selenium. Confirm the constructor and options against the installed Selenium version if you use an older binding. The save_screenshot call writes a PNG using the client binding; use an absolute path ending in .png and check its boolean result.

3. Wait for the state you intend to capture

A screenshot captures the browser’s current state. Navigation completion does not necessarily mean that an application’s asynchronous content, fonts, images, or transitions have finished. Wait for a test-relevant condition, such as a result selector becoming visible, before capturing.

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

# After driver.get(...):
WebDriverWait(driver, 20).until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "main .results"))
)
driver.save_screenshot("results.png")

Use the selector and timeout that make sense for the application. A fixed sleep can be useful for a known animation delay, but condition-based waits usually make the capture less sensitive to machine speed. Set the viewport deliberately when dimensions matter. Do not assume a regular WebDriver screenshot always means a full-page image: screenshot extent depends on the API and browser behavior.

4. Save or transfer the screenshot from a remote session

The WebDriver screenshot command returns base64-encoded image data at the protocol level. A binding may save that result to a file, expose the bytes, or expose base64. With RemoteWebDriver, determine which process owns the file path in your setup. Do not assume a path on the browser node automatically appears on the client.

For CI and distributed Grid deployments, use the Grid provider’s or CI platform’s supported artifact transfer or storage mechanism. Another option is to retrieve image data through the client binding and write it on the client side:

# Capture bytes from the current browser window and write on the client process.
image_bytes = driver.get_screenshot_as_png()
Path("client-artifacts").mkdir(parents=True, exist_ok=True)
Path("client-artifacts/page.png").write_bytes(image_bytes)

This writes through the Python client API, so the destination is the client process’s filesystem. Confirm the API behavior for your Selenium binding and version. Keep artifact paths unique when tests run in parallel to prevent one test from overwriting another’s image.

5. Capture one element instead of the window

When the desired artifact is a component, use the element screenshot API rather than capturing the window and cropping later. Selenium documents element screenshots in its windows and tabs guide.

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

element = WebDriverWait(driver, 20).until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "article.invoice"))
)
element.screenshot("invoice.png")

Wait until the element exists and is visible, and choose a selector that identifies the intended component. Element screenshots are useful for focused test evidence; window screenshots preserve surrounding page context.

6. cURL, Python, and Node.js notes

WebDriver is a session-based protocol: create a session with a client binding, issue browser commands, capture, and close the session. For the Grid workflow, Python provides the complete runnable example above. cURL is useful for checking whether the Grid endpoint responds, but it does not replace a WebDriver client for the full browser lifecycle.

curl -i http://localhost:4444/status

For Node.js, the Selenium WebDriver package provides the client binding. This example creates the remote session, navigates, waits for document readiness, captures PNG bytes, and saves them on the Node client:

import { Builder, Browser } from "selenium-webdriver";
import chrome from "selenium-webdriver/chrome.js";
import { writeFile } from "node:fs/promises";

const gridUrl = "http://localhost:4444";
const options = new chrome.Options().addArguments("--headless=new");
const driver = await new Builder()
  .forBrowser(Browser.CHROME)
  .usingServer(gridUrl)
  .setChromeOptions(options)
  .build();

try {
  await driver.manage().window().setRect({ width: 1440, height: 1000, x: 0, y: 0 });
  await driver.get("https://example.com");
  await driver.wait(async () => {
    return (await driver.executeScript("return document.readyState")) === "complete";
  }, 20000);
  const pngBase64 = await driver.takeScreenshot();
  await writeFile("screenshot.png", Buffer.from(pngBase64, "base64"));
} finally {
  await driver.quit();
}

Install the package with npm install selenium-webdriver. Ensure the Grid has a Chrome node available and use the Grid URL for your environment. If your Selenium JavaScript package version exposes different builder methods, follow that version’s API documentation.

7. Configure reliable and reproducible captures

Need What to configure Why it matters
Consistent layout Set the browser window size before navigation or capture. Viewport changes can alter responsive layout and screenshot dimensions.
Dynamic content Wait for a meaningful selector, state, or application condition. Prevents capturing before the desired content appears.
Component evidence Use an element screenshot where the binding supports it. Captures the selected element without requiring a later crop.
Parallel CI runs Give each test a unique artifact filename and ensure client-side directories exist. Avoids collisions and missing-path errors.
Remote storage Use the Grid provider or CI artifact mechanism, or transfer bytes through the client. Browser-node disk and client disk may be separate.

Headless mode removes the need for a visible desktop window on the browser node, but it does not remove the need for a compatible Chrome installation, an available Grid slot, or network access to the target page. For parallel execution, Grid can route commands to remote browser instances; capacity and session limits depend on the Grid deployment.

8. Troubleshooting

Symptom Likely cause Fix
Remote session cannot be created Wrong Grid URL, unreachable endpoint, no available Chrome node, or options the node cannot satisfy. Check the Grid address and status endpoint, confirm a Chrome node is registered and available, and simplify options to isolate the issue.
Chrome fails to start or reports a driver error Chrome and ChromeDriver major versions do not match, or the node lacks a usable browser/driver. Align major versions and check the node’s browser installation and driver configuration. Review how Selenium Manager is enabled in that deployment.
Screenshot is missing from the local workspace The browser-side and client-side filesystems are separate, or the save path belongs to a different process. Write returned screenshot bytes from the client or use the Grid/CI provider’s supported artifact transfer.
Image is blank or content is missing The page or asynchronous component was not ready when capture ran, or navigation produced an error page. Wait for a relevant element or application state; inspect the current URL and page state before capture.
PNG file is not created Destination directory does not exist, the path is invalid, or the binding reported an I/O failure. Create the directory first, use an absolute .png path, and check the return value from save_screenshot.
Element screenshot fails The selector did not match, the element is not ready or visible, or the binding cannot capture its current state. Wait for the element, verify the selector, and use a window screenshot if element capture is unsupported in the selected binding.
Capture dimensions differ between runs Window size or responsive page state differs across nodes or sessions. Set a deliberate window size and keep the target page state consistent before capture.

Check Selenium’s current documentation and downloads before pinning a version: release numbers change over time. The research snapshot listed Selenium Server 4.49.0 as stable, released September 9, 2026; treat that as a dated observation rather than a current-version guarantee.

9. Performance, reliability, and cost

Capture time includes session allocation, browser startup when a session is new, page loading, readiness waits, screenshot capture, and artifact transfer. Reuse sessions only when the test design safely allows it; otherwise, isolate state to avoid cookies or page data leaking between cases. Set reasonable page and wait timeouts so a stalled site does not hold a Grid slot indefinitely, and always quit the driver in a finally block.

Grid enables parallel browser execution, but parallelism consumes node capacity and the target website may impose its own rate limits. Keep screenshots only as long as needed, particularly in high-volume suites. Selenium Grid and WebDriver are software; hosting cost depends on the machines, hosted Grid provider, storage, and CI environment you choose. The Selenium documentation cited here does not define a universal price or artifact-transfer service.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.

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)
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}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Does headless Chrome change the WebDriver screenshot call?

No special screenshot call is needed for headless mode. Enable Chrome’s headless argument when creating the session, then use the screenshot API from your binding.

Does a standard screenshot always capture the full page?

No. Do not assume a regular window screenshot is a full-page capture. Check the behavior supported by your browser and binding, or use an approach designed for the required extent.

Where is Selenium Server’s current version listed?

Check the official Selenium downloads page when setting up; the release number in this guide’s research was a dated snapshot.

Sources