Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsSome 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.
Recommended Free Tools
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.
#1 Best Overall
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.
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.
Rank #2
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.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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.
Rank #3
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.
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.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Rank #4
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.
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.
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.
Best Value
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_chromefor synchronous Rust automation that is specifically tied to Chrome/Chromium and benefits from CDP. - Use
fantocciniwhen 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.
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.
Quick Recap
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.

