CLI tool
jonhoo/fantoccini avatar
jonhoo/fantoccini

fantoccini: async Rust WebDriver client for programmatic browser control

A high-level API for programmatically interacting with web pages through WebDriver.

2,027 stars136 forksRustApache-2.0

At a glance

What is it?
fantoccini is a Rust crate that drives a real browser through the WebDriver protocol, offering a high-level async API with CSS selectors, form manipulation, and raw HTTP access, while requiring a running geckodriver or chromedriver process. Version 0.22.1 requires Rust 1.67.0 and carries a dual MIT/Apache-2.0 licence.
Who is it for?
fantoccini suits Rust projects that need to drive a real browser for end-to-end testing or scraping JavaScript-heavy pages, and where adding a geckodriver or chromedriver process to the test setup is acceptable. Skip it if your need is simple HTTP page retrieval without JavaScript rendering: a plain HTTP client costs nothing in setup overhead.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

WebDriver protocol control from async Rust, without embedding a browser

fantoccini solves the problem of controlling a running browser from Rust code for end-to-end testing, automated form submission, and page scraping where JavaScript rendering matters. It communicates with any WebDriver-compatible browser process through the standard W3C WebDriver protocol, which means geckodriver for Firefox and chromedriver for Chrome are both supported out of the box.

High-level operations include navigating to URLs, finding elements by CSS selector or link text, clicking, submitting forms, reading the page source, and making raw HTTP requests that carry the browser's current cookies. No browser is embedded in the crate itself. A WebDriver-compatible process must already be running and reachable at a URL the caller supplies.

CSS selectors are the primary way to locate elements. Most WebDriver-compatible browsers are fairly recent, so more expressive CSS levels including pseudo-classes and attribute selectors are also available.

ClientBuilder::native().connect() opens a session, and Locator drives finding elements

Creating a client requires a running WebDriver process. Running geckodriver at the command line starts one, then ClientBuilder::native().connect() connects to it:

rust
use fantoccini::{ClientBuilder, Locator};

#[tokio::main]
async fn main() -> Result<(), fantoccini::error::CmdError> {
    let c = ClientBuilder::native().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");

    c.goto("https://en.wikipedia.org/wiki/Foobar").await?;
    let url = c.current_url().await?;
    assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");

    c.find(Locator::Css(".mw-disambig")).await?.click().await?;
    c.find(Locator::LinkText("Foo Lake")).await?.click().await?;

    let url = c.current_url().await?;
    assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foo_Lake");

    c.close().await
}

This example demonstrates two Locator variants: Locator::Css takes a CSS selector string, and Locator::LinkText matches by exact visible link text. Each find call returns an Element that can be clicked, read, or used to get attributes. Awaiting on find, click, and close makes the whole flow composable with other async Rust code.

Client::goto, Client::current_url, Client::find, and Client::close are all async methods on the same Client value. Port 4444 in the connect URL is the standard WebDriver port where geckodriver listens by default. Calling c.close() at the end closes the WebDriver session properly.

Form interaction uses Client::form and set_by_name before submit

Searching or filling out a form requires a different path than click-based navigation. Client::form locates the form element by a CSS selector and returns a Form handle, which then exposes methods for manipulating individual fields:

rust
c.goto("https://www.wikipedia.org/").await?;
let f = c.form(Locator::Css("#search-form")).await?;
f.set_by_name("search", "foobar").await?
 .submit().await?;

let url = c.current_url().await?;
assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");

set_by_name takes the HTML name attribute of the input field and the value to set, returning a reference to the Form so that submit can be chained directly. After submit, the browser navigates as if the user clicked the submit button, which means redirects and JavaScript form handlers fire normally.

For forms with multiple fields, set_by_name can be called once per field before calling submit. No separate type for individual fields is shown in the README examples; all field manipulation goes through the Form handle.

Client::raw_client_for bridges WebDriver sessions with direct HTTP requests

Occasionally, downloading a resource that the browser already has access to requires bypassing the browser's rendering engine entirely. Client::raw_client_for constructs a raw HTTP request to any URL while reusing the browser's current cookies:

rust
let img = c.find(Locator::Css("img.central-featured-logo")).await?;
let src = img.attr("src").await?.expect("image should have a src");
let raw = img.client().raw_client_for(http::Method::GET, &src).await?;

This returns a standard HTTP response body that can be consumed with a futures_util stream. Cookies accumulated during the browser session travel with the raw request, so authenticated resources are accessible without re-logging in through a separate HTTP client.

For pages where JavaScript is not needed, a plain async HTTP client like reqwest would avoid starting a browser process entirely. raw_client_for is most useful when the browser has already authenticated or navigated to a state that a standalone HTTP client cannot replicate without re-implementing the session logic. Client::source is the complementary method for reading the rendered page HTML as a string without making a separate HTTP request.

Two TLS feature flags: native-tls brings OpenSSL, rustls-tls avoids it

fantoccini ships two TLS backends as Cargo feature flags. native-tls is the default and pulls in hyper-tls and openssl. For projects that need to avoid the OpenSSL build dependency, switching to rustls requires two edits in Cargo.toml: set default-features = false on the fantoccini entry, then add rustls-tls to the features list. Leaving default features enabled means both backends compile, since native-tls is part of the default set.

native-tls and rustls-tls are mutually exclusive backends. Using both in the same build is redundant and increases compilation time without benefit.

One detail in the keywords field of Cargo.toml: the list includes "phantomjs" alongside "webdriver", "chromedriver", and "geckodriver". PhantomJS was discontinued before this crate reached its current version and is no longer maintained, but the keyword remains, which may cause confusion for users searching for PhantomJS-specific automation. No compatibility notes for PhantomJS exist in the crate documentation.

Cargo.toml sets rust-version to 1.67.0, which the README does not state, and tests need two browser drivers

Cargo.toml sets rust-version = "1.67.0", meaning the crate will not compile on earlier Rust toolchains. This constraint is absent from the README, so a project that checks only the README before adding the dependency may encounter a compiler error if the installed toolchain predates 1.67.0.

Running cargo test requires chromedriver and geckodriver running as separate processes before the command executes. Starting them in separate terminal tabs is the documented approach, and they stay running until stopped manually. Without both drivers active, the test suite fails on connection errors. There is no automated driver setup script or Docker Compose file visible in the top-level repository entries.

Crate version 0.22.1 is on crates.io. To find what changed between versions, reading commit history directly is the only option available from the repository.

Where the raw webdriver crate ends and fantoccini begins

fantoccini sits on top of the webdriver crate (version 0.54 in its Cargo.toml), which provides the low-level WebDriver protocol types and JSON serialization. Using the webdriver crate directly means constructing session commands, managing the session ID across requests, building HTTP requests to the WebDriver endpoint, and parsing response bodies manually. fantoccini wraps all of that in a typed Client API with async methods.

For use cases that require only a subset of WebDriver, this layering has a cost: the full fantoccini Client is the entry point, and there is no published way to use parts of it independently. If a project needs only element inspection with no form interaction and no raw HTTP access, fantoccini still brings in the full dependency tree including hyper 1.1.0, cookie 0.18.0, base64 0.22, and the TLS backend.

For Rust-based browser automation, fantoccini covers the common operations well. For testing that does not require a real browser, the correct comparison is against crates that mock HTTP responses or drive a headless server, not WebDriver at all. fantoccini is the wrong choice if no geckodriver or chromedriver can be added to the test environment, such as in a minimal CI container with no browser binaries.

Editorial conclusion

fantoccini suits Rust projects that need to drive a real browser for end-to-end testing or scraping JavaScript-heavy pages, and where adding a geckodriver or chromedriver process to the test setup is acceptable. Skip it if your need is simple HTTP page retrieval without JavaScript rendering: a plain HTTP client costs nothing in setup overhead. Before adopting, note that Rust 1.67.0 is the minimum version required, that the default native-tls feature brings in OpenSSL, and that switching to rustls requires adding rustls-tls to the features list and disabling the default feature in Cargo.toml.

Frequently asked questions

What is fantoccini?

fantoccini is a Rust crate that controls a web browser through the WebDriver protocol using async Rust. It provides a high-level Client API with methods for navigation, element finding, form submission, and raw HTTP access, built on top of tokio and hyper.

How do I add fantoccini to my Rust project?

Add fantoccini to Cargo.toml under [dependencies]. The crate is published at version 0.22.1 on crates.io. Running it requires a WebDriver-compatible process such as geckodriver or chromedriver already listening on a port.

Does fantoccini support both Firefox and Chrome?

fantoccini communicates through the standard WebDriver protocol, so any WebDriver-compatible browser process works. Running geckodriver connects to Firefox, and running chromedriver connects to Chrome.

What Rust version does fantoccini require?

Cargo.toml sets rust-version = "1.67.0", so Rust 1.67.0 or newer is required. The README does not mention this constraint.

Does fantoccini use OpenSSL?

By default, yes. The native-tls feature is enabled by default and pulls in hyper-tls and openssl. Switching to the rustls-tls feature and setting default-features = false in Cargo.toml avoids the OpenSSL dependency.

Official sources

  1. Issues
  2. jonhoo/fantoccini on GitHub
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jonhoo-fantoccini.svg)](https://hysenlabs.com/projects/jonhoo-fantoccini)