This page collects common pitfalls and their solutions. If you run into something not listed here, turn on debug logging and check the error category and hint:
Call init_tracing() and run with RUST_LOG=seleniumbase_rs=debug.
They read SB_LOG_LEVEL, for example SB_LOG_LEVEL=seleniumbase_rs=debug,info.
Without it (or with a blank or invalid one) they fall back to RUST_LOG, then
to info.
Start here#
Run sbrs doctor first. It reports which Chrome and chromedriver it finds and
whether their major versions match, and most start-up problems show up there. In
an AI-agent session the MCP tool diagnose_environment reports the same, plus the
platform and the redacted SB_* settings, without starting a browser.
| Symptom | Most likely cause | Go to |
|---|---|---|
| ”chromedriver keeps crashing”, “session not created” | chromedriver and Chrome are different major versions | chromedriver executable not found |
| The driver is “killed” or refused on a Mac | macOS quarantine or an overwritten signed binary | chromedriver is killed or refused on macOS |
| ”Chrome not found” | Chrome is not in a standard location | Chrome is not found |
| Chrome exits at once in Docker or as root | the sandbox cannot start | Chrome will not start in a container |
| A wait times out, “element not found” | wrong selector, or the page is not ready | An element is not found or a wait times out |
| A test passes alone but fails in the suite | shared state or too much parallelism | Passes alone, fails in the suite |
| Detected as a bot | layered detection | Detected as a bot despite UC mode |
| A type, function or macro is “not found” | an optional Cargo feature is off | Item not found |
| The MCP server lists no tools | wrong --server or no client config | MCP server not listing tools |
| A setting is ignored | wrong variable name or prefix | Environment variable not applied |
Detected as a bot despite UC mode#
UC mode removes many fingerprints, but detection is layered. Check each defense:
Patch the chromedriver binary
Use ChromedriverPatcher or
cargo run --bin sbrs -- patch-chromedriver --path /path/to/chromedriver.
Verify with needs_patch().
Add engine spoofing args
Pass engine_spoofing_args() through BrowserConfig::with_extra_args or
StealthOptions::extra_args.
Use a coherent fingerprint
A Windows user agent with a macOS WebGL vendor or a mobile screen on a desktop
platform is suspicious. Build a
Fingerprint, keep the values
consistent, and check it with Fingerprint::validate(), whose
CoherenceReport::is_coherent() is false when the values contradict each
other.
Check WebGL and geolocation consistency
Spoofed WebGL vendor/renderer and geolocation should match the IP address and timezone.
Run headed
A headless session already starts Chrome with --headless=new, but some sites
still tell headless from headed. See Headless detected.
Drop chromedriver altogether
The pure CDP engine (seleniumbase_rs::sb_cdp)
launches Chrome directly, so there is no driver binary to leak markers.
chromedriver executable not found#
BaseCase::new starts a chromedriver for you when the WebDriver URL is the
default (http://localhost:4444) and auto_start_driver is on (it is by
default).
How BaseCase::new finds or starts a chromedriver
BaseCase::new first tries to connect to a driver already listening there; if
none answers, it launches downloaded_drivers/chromedriver (chromedriver.exe
on Windows) on a free port. A chromedriver only drives the Chrome of its own
major version, so before launching it the crate runs --version on the driver
and on your Chrome. If the driver is missing, or is for another major version,
it downloads the driver of your Chrome’s milestoneA Chrome milestone is its major version number, such as 155. into downloaded_drivers/
first (the latest stable one when Chrome cannot be found). If both downloads
fail it keeps the old driver and logs a warning. The download needs network
access to the Chrome for Testing endpoint. To fetch the driver ahead of time:
sbrs install with no argument fetches chromedriver; sbrs install geckodriver
and sbrs install edgedriver fetch the drivers of Firefox and Edge (see the
CLI page).
For Chrome the auto-start does not look at your PATH or at CHROMEDRIVER_PATH;
those are consulted only by sbrs doctor, which prints which chromedriver it
finds (CHROMEDRIVER_PATH, then downloaded_drivers/, then PATH), its major
version next to Chrome’s (Version: 155 (matches Chrome 155), or
Version: 151, but Chrome is 155 with a hint to run sbrs install), and whether
it still has automation markers.
To use a driver you run yourself, start it and pass its URL. A URL other than the default turns auto-start off:
A driver you start yourself and pass with --webdriver is never replaced. If it
does not match your Chrome version, the session fails to start with a WebDriver
error; sbrs doctor shows both versions, and sbrs install or the
Chrome for Testing dashboard
has a matching one.
chromedriver is killed or refused on macOS#
macOS can refuse a driver that has the com.apple.quarantine flag or whose
code signature no longer matches its contents (which is what patching does).
The crate repairs this itself: after downloading, and again if the system
refuses to start the driver, it adds the execute permission, removes the
quarantine flag and, for a binary the system killed as it started, signs it
again ad hoc (codesign --force --sign -). ChromedriverPatcher::patch also
signs the patched file again on macOS. The same repair is available as
seleniumbase_rs::browser::driver_access::make_runnable(path, resign). If a
driver you supply still will not start, run the two steps by hand:
Headless detected#
BrowserConfig::default() is headless, while the global config file and the
sbrs command line default to headed. A headless session passes
--headless=new (and --disable-gpu) to Chrome. Switch to headed mode for
sites that still refuse it:
Combine this with a Fingerprint and binary patching. On a server with no
display, run the headed browser under a virtual display such as Xvfb. The
defaults of every entry point and what gives a headless browser away are in
Headless and headed browsers.
Item not found: a Cargo feature is off#
Nothing is enabled by default. These pieces exist only when their feature is on:
| Feature | What it adds |
|---|---|
tui | sbrs commander, sbrs caseplans and sbrs behave-gui (without it each command prints an error and exits with status 2) |
gui | Gui, api::dialog, api::gui and the dialog, dialogs and gui_automation examples (on Linux it links GTK and X11) |
playwright | PlaywrightSession, BaseCase::activate_playwright_mode |
mcp-server | the seleniumbase_rs::mcp module and the sbrs-mcp binary |
turso | seleniumbase_rs::storage (ResultStore, ProfileVault) and sbrs report |
test-util | the scripted mock browser for sb_cdp (Browser::new_mocked, MockCtrl) |
s3, azure, gcp | the cloud logging plugins |
full-tracing | a timing-histogram layer in the subscriber the init_tracing* functions install |
error-backtrace | nothing yet: the feature is declared, but no code reads it |
Enable one with --features, for example
cargo build --features tui,turso.
Playwright / rustwright feature not compiling#
The playwright feature pulls in the rustwright and rustwright-core
crates from crates.io, which bring a large Chromium automation core. It is off
by default, so a plain cargo build does not need them:
The project’s own quality gate (see ROADMAP.md) notes that building with
--all-features needs a libpython available because of this feature. If you
build with it on macOS and a test binary fails to start with a missing
libpython...dylib, point the loader at your Python install’s lib folder:
MCP server not listing tools#
The sbrs-mcp binary only exists when the mcp-server feature is
enabled:
The server communicates over stdio. Configure your MCP client with the absolute
path to the built binary. It serves the sb toolset unless you pass
--server cdp or --server driver. Listing tools does not require a running
browser, but no browser exists until the client calls the start_browser tool;
any other browser tool called first returns “no browser session is running; call
start_browser first”. See the MCP servers guide.
Macro not found#
Macros are #[macro_export] and live at the crate root, not under
seleniumbase_rs::macros:
Action macros expand .await internally, so they must be called inside an
async block or function. sb_test! generates a #[tokio::test] wrapper for
you. See the macros guide.
Chrome is not found#
The library looks in the usual install locations for your system (see Operating systems). If yours is elsewhere, or you want a specific build, name it:
Run sbrs doctor to confirm which one was picked. The pure CDP engine also takes
the path in LaunchOptions.
Chrome will not start in a container#
Chrome often cannot start its sandbox as root or inside Docker. Set
SB_NO_SANDBOX=1 (or LaunchOptionsBuilder::no_sandbox(true) in the pure CDP
engine) and run headless (SB_HEADLESS=true).
--no-sandbox weakens the browser’s isolation, so use it only in a container you
control.
If it crashes under load rather than at start, give the container more shared
memory (--shm-size=1g). See Deployment.
An element is not found or a wait times out#
Read the error before changing anything: it names the selector and carries a hint, and element errors point out a likely mistake in the selector.
Check the selector
Check the selector in the browser’s developer tools; a class that is generated on each build, or an element inside an iframe or a shadow root, will not match a plain selector.
Wait for the right condition
Wait for the right condition (wait_for_element_visible, wait_for_text)
instead of sleeping.
Remember the built-in wait
BaseCase actions already wait for their element for the default timeout
(10 seconds; set_timeout changes it). A timeout that long usually means the
selector is wrong, not that the page is slow.
Ask the agent tools
With an AI agent attached, diagnose_selector explains why a selector matches
nothing and suggest_selectors proposes ones that work; inspect_page shows
what is on the page.
Passes alone, fails in the suite#
The usual causes are shared state and too much parallelism. Give each test its
own browser context (the pool’s Lease isolates cookies and storage), do not
reuse a logged-in profile between tests, and lower SB_THREADS to see whether the
failure goes away. Do not add retries to hide it; see
Reliability.
Reading an error#
Every SeleniumBaseError has a hint: the likely cause and the fix
(error.hint()). Element errors name the selector, the strategy it was read as
(the same rules the lookup uses), and a likely mistake in it: a mistyped prefix
(lnk=Home for link=Home), a prefix from another tool (css=, xpath=,
id=, name=), XPath without its leading //, or unbalanced brackets.
sbrs prints a failure for people, on standard error, with colour only on a
terminal (and never with NO_COLOR set):
A cause the first line does not already say gets its own caused by: line. The
exit status says which kind of failure it was:
| Status | Meaning |
|---|---|
| 1 | a check or a test failed, or another error |
| 2 | the input was wrong (configuration, selector syntax, a value that does not parse); also what a mistyped option gives |
| 3 | an element could not be found or used, or a wait timed out |
| 4 | the browser or its driver failed |
| 5 | the network or a site failed |
| 6 | a local file or database failed |
| 7 | the MCP server failed |
| 130, 143 | stopped by Ctrl-C or SIGTERM |
sbrs test is the exception: it exits with the status of the cargo test it
ran.
In the HTML report of ReportPlugin, a failed test shows its error, the hint,
the page’s URL and title when it failed, and, with
ReportPlugin::html(path).with_evidence(), links to the screenshot and page
source saved next to the report. The MCP servers answer a failed call with a
report that includes the page’s state and the next tools to try; see the
MCP tutorial.
Errors are not descriptive enough#
SeleniumBaseError::log() emits a tracing error event with the error
category, whether it is transient, and a remediation hint; ResultExt::log_err
logs only the message. Make sure a subscriber is installed:
Or configure the log level before running sbrs / sbrs-mcp:
SeleniumBaseError provides helpers for programmatic handling:
To retry transient failures with backoff, see RetryPolicy in the
reliability guide.
Environment variable not applied#
Configuration uses the SB_ prefix, not SBASE_. For example:
Where a variable is read depends on how you build the configuration:
BrowserConfig::default()reads no variables.BrowserConfig::from_env()reads the runtime ones (SB_WEBDRIVER_URL,SB_CHROME_BIN,SB_REMOTE_WEBDRIVER_TIMEOUT, and so on).Settings::load(),Settings::load_from(path)andSettings::from_env()read the settings ones,SB_WEBDRIVER_URLandSB_CHROME_BINamong them (SB_BROWSER,SB_HEADLESS,SB_TIMEOUT, and the rest of the list in Customizing Test Runs).sbrs testpasses the flags you give it to the tests as such variables.- The
sbrsbinary reads the settings ones. Its browser commands (open,smoke, and the rest) start from the config file (--config FILE, else the global one), apply the settings variables on top, and then its own flags, which win. The WebDriver URL is--webdriver, elseSB_WEBDRIVER_URL, elsehttp://localhost:4444; the browser binary isSB_CHROME_BIN.
See Customizing Test Runs for the full list.
Frequently asked questions#
Short answers, with a pointer to the page that has the detail.
Choosing and getting started#
Is this the same as Python SeleniumBase?
It is a Rust port of it (tracking 4.55.2) and of its MCP server. Every upstream API has a recorded status in the parity table. It also adds things Python does not have; see Why Rust?.
Do I need Rust to use it?
To write tests in Rust, yes. The sbrs CLI and the MCP server are single
binaries you can use without writing Rust; sbrs can also convert Python
SeleniumBase scripts.
Which engine should I use?
Start with the pure CDP engine (sb_cdp): it drives Chrome directly, so there is
no chromedriver to match. Use the WebDriver engine (BaseCase) for Selenium Grid,
Firefox, or an existing SeleniumBase-style suite. See
Performance.
Is it on crates.io?
No. Build it from the repository (Building) or add it as a git dependency.
Does it run on Windows and Linux?
CI builds and tests on Linux, macOS and Windows. Development happens on macOS. See Operating systems.
How does it compare with Playwright, Selenium or Puppeteer?
See How it compares.
Using it#
Is it faster than Python SeleniumBase?
Not for end-to-end tests: Chrome, the network and the page dominate. It has low overhead of its own (Benchmarks), but there is no measured comparison with Python. See Performance.
A test fails with a wait timeout. What now?
Read the hint in the error: it names the selector and a likely mistake. Use
wait_for_element_visible/wait_for_text instead of sleeps. In an AI-agent
session, diagnose_selector and inspect_page show what the page really has.
See Reliability.
Why does sbrs exit with different statuses?
The exit status follows the error’s category (a bad selector differs from a missing browser), so a CI script can tell them apart.
Can an AI agent write my tests?
Yes, with the MCP servers and export_session_as_test; the result is a draft you
review. See Vibe coding.
Browsers and drivers#
"chromedriver keeps crashing" or "session not created".
The chromedriver and Chrome major versions differ. Run sbrs doctor, then
sbrs install. The pure CDP engine avoids the problem. See
Common problems.
Can it drive Firefox or Safari?
Firefox through the WebDriver engine (there are typed Firefox preferences); Safari
launch options exist for the WebDriver engine. The pure CDP engine is Chrome and
Chromium only. A playwright feature gives a Playwright-compatible mode.
Does it work headless?
Yes, and use it in containers and CI. How you turn it on depends on the entry
point: BrowserConfig::default() is already headless, sbrs and code that loads
Settings read SB_HEADLESS=true, and the pure CDP engine is headless on Linux
by default (LaunchOptionsBuilder::headless(true) anywhere). See
Headless and headed browsers.
Stealth and scraping#
Does it get past bot detection?
It reduces the obvious signs (no webdriver markers in the pure CDP engine, coherent fingerprints, human-paced input), but there is no guarantee against any particular service, and the randomised identities have not been tested against live detection.
TLS (JA3/JA4) and HTTP/2 impersonation for one measured browser and system (Chrome 155 on macOS), with an HTTP session, a proxy that applies it to a real Chrome, and a session that moves between the two, is the separate, experimental seleniumbase-rs-stealth project (it left this crate in 0.3.0). It was tested against local servers and a local Chrome, not live services.
See the stealth tutorial and Stealth scraping.
Is scraping with it allowed?
That depends on the site’s terms and the law where you are. The library does not make that decision for you.
Contributing and project status#
How complete is the port?
Every upstream API has a status (888 implemented, 383 composed, 94 not-applicable, none planned), and most behaviour is tested against a mock or unit tests rather than a real browser.
An audit of the entries marked
implemented found 26 that did less than the Python method, and a second audit of
the composed and not-applicable entries found 34 more that said more than the
Rust does; they were fixed or re-marked, and the planned ones are listed in
the parity table.
The project README has a “Project status” section.
How do I contribute?
Read CONTRIBUTING.md and CLAUDE.md. Run the checks listed in
Building before opening a pull request.