Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

headless_chrome gives Rust programs a synchronous, high-level way to control Chrome or Chromium through the Chrome DevTools Protocol (CDP). Add the crate, provide a browser binary (or enable its documented download feature), launch a Browser, navigate a tab, wait for an element, and then click, inspect, run JavaScript, capture screenshots, or create PDFs. The current docs.rs listing identifies version 1.0.22. This guide shows a complete local workflow, explains the crate’s boundaries, and helps you decide when WebDriver or a hosted browser is a better fit.

What headless_chrome is—and what it is not

The crate is a Rust API for controlling headless Chrome or Chromium over CDP. The project describes it as a Rust equivalent of Puppeteer, while warning that it is not fully feature-compatible. Its API is synchronous and thread-based rather than built around Tokio futures.

That design suits command-line tools, browser tests, crawlers, screenshot jobs, and automation where Chrome-specific controls matter. It is less suitable when your application must share one asynchronous Tokio architecture, drive non-Chrome browsers, or depend on CDP areas the crate does not expose.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and project setup

Install Rust and Chrome or Chromium

Create a normal Cargo binary or library and make sure a usable Chrome/Chromium executable is available on the machine running the program. The documented quick start uses the system browser. If you want the crate to fetch a known-good browser binary, enable its documented fetch feature; the project documents fetching for Linux, macOS, and Windows.

Add the dependency

[dependencies]
headless_chrome = "1.0.22"

If your selected release has moved on, use the current version shown by docs.rs and adjust examples for any API changes. With the browser-download feature, the dependency declaration is:

[dependencies]
headless_chrome = { version = "1.0.22", features = ["fetch"] }

Choose one approach: either install and expose Chrome/Chromium yourself, or let the crate’s documented feature obtain a compatible binary. In CI, pin the browser image or downloaded version so a browser update does not silently change test behavior.

Launch Chrome, open a tab, and capture a page

The following program follows the official quick-start flow: launch a browser, obtain a tab, navigate, wait for a target element, capture a screenshot, and execute JavaScript in the element’s context.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
use headless_chrome::{Browser, protocol::page::ScreenshotFormat};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let browser = Browser::default()?;
    let tab = browser.wait_for_initial_tab()?;

    tab.navigate_to("https://example.com")?;
    tab.wait_until_navigated()?;

    let heading = tab.wait_for_element("h1")?;
    println!("heading: {}", heading.get_description()?);

    heading
        .call_js_fn("function () { return this.textContent; }", vec![], false)?;

    tab.capture_screenshot(
        ScreenshotFormat::PNG,
        None,
        true,
    )?;

    Ok(())
}

Browser::default() is the shortest documented launch path. wait_for_initial_tab() returns the first tab, navigate_to starts navigation, and wait_until_navigated prevents the next operation from racing the document load. Waiting for h1 is more useful than sleeping for an arbitrary number of milliseconds because it expresses the condition your automation actually needs.

For production code, save the returned screenshot bytes to a file, check the result of every operation, and use selectors that are stable in your application. A selector for a semantic attribute such as [data-testid="checkout"] is generally less fragile than a long CSS path.

Configure launch behavior with LaunchOptions

Use LaunchOptions or LaunchOptionsBuilder when the default browser location, headless mode, arguments, window size, or other launch details do not match your environment. The exact builder fields can vary by crate release, so consult the API documentation for the version in your Cargo.lock.

use headless_chrome::{Browser, LaunchOptionsBuilder};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let options = LaunchOptionsBuilder::default()
        .headless(true)
        .build()?;
    let browser = Browser::new(options)?;
    let tab = browser.wait_for_initial_tab()?;
    tab.navigate_to("https://example.com")?;
    tab.wait_until_navigated()?;
    println!("title: {}", tab.get_title()?);
    Ok(())
}

Keep launch configuration in one place. Pass explicit arguments only when required by your container or security policy; every extra flag can change Chrome’s behavior and complicate debugging.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common browser-automation operations

Find, inspect, and interact with elements

Tabs can wait for selectors and return element handles. From a handle you can inspect attributes or text, click, type, and execute JavaScript in the element’s context. Always wait for the element that proves the page is ready, and account for navigation or asynchronous content after a click.

let search = tab.wait_for_element("input[name='q']")?;
search.click()?;
search.type_into("rust browser automation")?;
let button = tab.wait_for_element("button[type='submit']")?;
button.click()?;
tab.wait_until_navigated()?;

Run JavaScript

Use the tab or element JavaScript APIs for DOM state that is awkward to express with selectors. Treat page data as untrusted input and keep scripts short; a script that assumes one exact page structure will fail when the site changes.

Take screenshots and PDFs

The project documents element and full-page screenshots and PDF output. Screenshot format and capture options are exposed through the page protocol types. For a long page, verify whether your chosen method captures the full document or only the viewport, and wait until lazy content has appeared before capturing.

Other documented capabilities

  • Network request interception.
  • JavaScript coverage monitoring.
  • Incognito windows.
  • Headful browsing for local debugging.
  • Extension preloading.
  • Known-good browser binary fetching on Linux, macOS, and Windows.

Documented CDP gaps to plan around

The README lists areas that the crate does not implement. They include frame handling, file-chooser interactions, touchscreen tapping, network-condition emulation, network-request timing, SSL-certificate reading, XHR replay, HTTP Basic Authentication, EventSource inspection, and WebSocket inspection. This is a documented limitation list, not a promise that every CDP feature absent from it is available through another API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If your workflow depends on one of these operations, test a small proof of concept before committing to the crate. You may need direct CDP access, a different Rust client, or a browser-automation tool with the required abstraction.

headless_chrome versus fantoccini

Concern headless_chrome fantoccini
Browser protocol Chrome DevTools Protocol WebDriver
Rust execution model Synchronous, thread-based Asynchronous on Tokio
Browser scope Chrome/Chromium focused Can work with browsers beyond Chrome through WebDriver
Chrome-specific features Exposes CDP-oriented functionality such as JavaScript coverage Does not expose that CDP-specific functionality in the project comparison
Project maturity claim The README presents it as less feature-compatible than Puppeteer The README characterizes fantoccini as more battle-tested

Choose headless_chrome when CDP features, Chrome fidelity, or a synchronous design are the priority. Choose fantoccini when Tokio integration and cross-browser WebDriver support matter more. Neither choice removes the need to manage browser versions, selectors, authentication, and test data.

Reliability, performance, and cost considerations

Make waits state-based

Prefer waiting for navigation, a selector, or a known page condition over fixed sleeps. A sleep that passes on a fast laptop can fail in a busy CI runner, while an unnecessarily long sleep wastes every run.

Reuse a browser carefully

Launching Chrome is more expensive than opening another tab. A long-running worker can keep one browser process and create isolated tabs or incognito contexts, but clean up tabs and contexts so cookies and page state do not leak between jobs. For parallel work, measure the memory and CPU impact of your chosen tab count rather than assuming unlimited concurrency.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Control artifacts and logs

Save screenshots, HTML, console output, and backtraces only when a failure occurs or a test needs them. Enable verbose logging temporarily; trace logs can be large and may contain URLs or page data.

Troubleshooting launch and page failures

Chrome never starts or times out

A timeout can indicate that sandboxing is not enabled in the kernel or that a usable setuid sandbox is unavailable. This is environment-dependent: check the container or host security configuration instead of copying one universal Chrome flag. Also verify that the executable is installed, executable by the current user, and compatible with the crate’s expectations.

Turn on diagnostics

RUST_BACKTRACE=1 RUST_LOG=headless_chrome=trace cargo test

The project README recommends these variables for diagnosing tests. Remove trace logging after diagnosis and redact captured URLs or page content before publishing CI logs.

Selector or navigation errors

  • Element not found: confirm the selector in the same URL and wait for the page state that creates it.
  • Click has no effect: check overlays, disabled state, and whether the click triggers a new tab or navigation.
  • Content is missing from a screenshot: wait for the lazy-loaded element, scroll if the site requires it, and verify that the capture mode includes the full page.
  • Authentication fails: inspect the site’s login flow; HTTP Basic Auth and some related CDP areas are listed as unsupported.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you only need a reliable screenshot or PDF rather than an interactive local browser, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A single request is enough:

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

See the ScreenshotNeo API documentation for parameters and response handling. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes features such as full-page capture, CSS-selector element capture, device presets, custom JavaScript and CSS, request blocking, cookies and headers, PDF controls, caching, signed links, asynchronous webhooks, bulk capture, and a usage API.

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account to try the API without adding a card.

When a hosted browser is the better fit

Local Chrome is appropriate when you control the runtime and need interactive CDP operations. If you need a remotely hosted browser, Steel publishes a recipe for automating a cloud browser with headless_chrome. That documentation establishes an integration pattern; check the provider’s current terms, availability, limits, and pricing separately before designing around it.

Practical decision checklist

  • Use headless_chrome for synchronous Rust automation that is specifically tied to Chrome/Chromium and benefits from CDP.
  • Use fantoccini when Tokio, WebDriver, or broader browser coverage is the primary requirement.
  • Prototype workflows that need any item on the crate’s documented unsupported list.
  • Make browser, crate, and OS versions explicit in CI.
  • Wait on page conditions, isolate tabs, and enable trace logs only while diagnosing failures.

Frequently Asked Questions

Does headless_chrome require Google Chrome specifically?

No. It controls Chrome or Chromium; provide a usable installation or enable the documented fetch feature for a known-good browser binary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is headless_chrome asynchronous?

The project describes its API as synchronous and thread-based. It is not a Tokio-native async API like fantoccini.

Can it automate Firefox?

The crate is Chrome/Chromium-focused through CDP. Use a WebDriver-oriented tool when non-Chrome browser coverage is required.

Where can I find the current API details?

Use the docs.rs page and repository examples for the exact release in your Cargo.lock; the version identified for this article is 1.0.22.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.