When a test or a command fails, the cause is usually one of three things: the page, the browser or driver, or the code. This page lists the tools the crate has for each, from turning on the logs to reproducing a failure without Chrome.
What you will learn
- How to turn on the logs, and which variables control them.
- Which build features help with debugging, and which do nothing yet.
- How to watch the browser, and slow a run down.
- How to keep a screenshot and the page’s HTML when a test fails.
- How to reproduce a failure with a mocked browser, or record the network.
- How to check the setup, the driver and the MCP server.
- How to run one test, and the known traps.
Turn on the logs#
The crate logs with tracing. Logs go to standard error, never to standard
output: standard output carries what a program produces, such as an sbrs
command’s answer or the protocol messages of sbrs-mcp.
sbrs and sbrs-mcp install the log subscriber themselves, with
init_tracing_from_runtime. These variables control it:
The filter, such as debug or seleniumbase_rs=debug,info. Unset, blank or
invalid, the next one is used.
Used when SB_LOG_LEVEL is unset, blank or invalid. When neither gives a
filter, it is info.
json writes one JSON object per line. Any other value makes
RuntimeConfig::from_env fail; sbrs warns about it and keeps the default.
Default: "pretty"
Turns colour off. Colour is used only when standard error is a terminal.
In your own program or test#
A program, a test or an example has no subscriber until it installs one. Install it once, at the start; a second call is ignored.
| Function | Filter | Format |
|---|---|---|
init_tracing() | RUST_LOG, else info | pretty |
init_tracing_json() | RUST_LOG, else info | JSON |
init_tracing_with_filter("seleniumbase_rs=debug,info") | the string, else RUST_LOG, else info | pretty |
init_tracing_json_with_filter(...) | as above | JSON |
init_tracing_from_runtime(&config) | SB_LOG_LEVEL, else RUST_LOG, else info | SB_LOG_FORMAT |
All five are at the crate root. In a test, call seleniumbase_rs::init_tracing()
first, then run it with RUST_LOG=seleniumbase_rs=debug.
Errors are logged as structured events with error.category, error.transient
and error.hint. Log one yourself with .log() on a SeleniumBaseError or
.log_err() on a result, and add where it happened with
.sb_context("while loading profile"). See
Tracing and Logging.
Features for a debugging build#
tokio-console: watch every async taskFor hangs: which tasks wait, for how long, and on what.
Build with the instrumentation
A different RUSTFLAGS makes cargo rebuild every crate, so expect a long first
build.
Start the console
In another terminal:
It connects to 127.0.0.1:6669.
Your own program gets the same when it installs logging through
seleniumbase_rs::tracing_util with the feature on.
full-tracing: timing histograms
Adds a tracing-timing layer to the subscriber the crate installs, which
records timing histograms of spans and events. The crate itself has no code
that reads or prints them.
error-backtrace: declared, not used
The feature is declared in Cargo.toml, but no code reads it, so turning it on
changes nothing, and SeleniumBaseError carries no backtrace. For a panic, set
RUST_BACKTRACE=1 (or full). For a returned error, add context with
.sb_context(...) and read its hint().
Watch the browser#
Headless is the default. To see the window, turn it off:
--headed and --headless go before the subcommand and beat SB_HEADLESS.
On Linux without a display, run under a virtual display such as Xvfb; see Operating systems.
Slow the run down#
WebDriver engine onlyDemo mode outlines each element before an interactive call and pauses after it; slow mode pauses without the outline. Both go in the configuration:
Never leave demo or slow mode on in CI: the run waits on purpose. Which calls pause, and for how long, is in Run settings.
Keep a browser open to inspect#
sbrs devtools starts Chrome with the pure CDP engine and a DevTools endpoint
on 127.0.0.1:9222, and keeps it running until you press Ctrl+C. Attach a
DevTools client to it; see DevTools MCP servers.
Keep evidence when a test fails#
Plugins runs a test like run_browser_test and lets plugins look at the page
when it fails, before the browser is closed. Two plugins come with the crate:
- On a failure they save
login_works_<date>_<time>.pngand.htmlin the folder you name. An existing file is never replaced. - A plugin that fails (a full disk, a browser that has gone) is logged and does not change the test’s result. A test that panics is a failure for the plugins too, and the panic is raised again afterwards.
- For the pure CDP engine,
plugins.run_cdp_test(name, launch_options, async |page| ...)does the same with aPage.
To save evidence by hand: save_screenshot("login.png") and
save_page_source("login.html") write into the logs folder, latest_logs. The
name must be a bare file name; one with a directory part or .. is refused.
js_errors() on a pure CDP Page lists the page’s uncaught script errors, and
assert_no_js_errors() fails on the first, on either engine.
Reproduce without Chrome#
feature test-util no browserBrowser::new_mocked() returns a pure CDP browser that never launches Chrome.
Every protocol command it receives is recorded and answered by the handler you
register. It has one blank tab, and every navigation finishes at once. Any other
command succeeds with an empty result until you say otherwise.
Scripts and inspects the mocked browser. Exported from seleniumbase_rs::sb_cdp.
- Run the existing mock tests with
cargo test --features test-util --test sb_cdp_mock. In this repository a test file that uses the mock starts with#![cfg(feature = "test-util")]. page.with_timeout(Duration::from_millis(150))makes the failure paths quick.- The crate’s own unit tests have a WebDriver counterpart,
MockWebDriverinsrc/api/base_case_impls/base_case_impl_common.rs. It is#[cfg(test)], so only those unit tests can use it.
More in Testing without Chrome.
Record the network#
A HARHTTP Archive 1.2: a JSON file of every request and response. shows every request a tab made, with headers, status and timings. Start the recorder before the navigation you want to see:
Chrome’s and Firefox’s developer tools and HAR viewers open the file.
start_har_with(HarOptions::new().content(true)) keeps the response bodies too.
A HAR holds cookies, so treat it like the session it came from. See
Recording a HAR.
Check the setup#
sbrs doctor prints the SB_* settings (and any it could not read), the Chrome
it found, the patch cache, and the chromedriver it found with its major version
next to Chrome’s. Each problem comes with a hint. An AI agent gets the same
from the MCP tool diagnose_environment.
Driver problems#
WebDriver engine onlyThe pure CDP engine needs no driver, so a failure that only happens with the WebDriver engine points at chromedriver.
See what was found
Run sbrs doctor. The Chromedriver part says which driver it found and
whether its major version matches Chrome’s.
Fetch the matching driver
sbrs install downloads the chromedriver for your Chrome into
downloaded_drivers/. The WebDriver engine uses that file when its major
version fits the browser.
Or name one
Set CHROMEDRIVER_PATH to a driver you already have.
Rule the driver out
Run the same steps with the pure CDP engine (sb_cdp). If they pass there, the
problem is the driver or the WebDriver session.
Before it starts a driver, the launcher makes the file runnable
(browser::driver_access::make_runnable): it sets a missing execute bit,
removes the macOS quarantine flag and, after a patch on macOS, signs the binary
again ad hoc. Firefox and Edge use geckodriver and msedgedriver; see
Operating systems and
Common problems.
The MCP server’s logs#
sbrs-mcp speaks the protocol on standard output and logs to standard error.
Set the log variables in the client’s configuration; where the client shows the
server’s standard error depends on the client.
- An invalid
SB_*variable is logged as a warning when the server starts, and its default is used. - A failed tool call is answered with a report: a stable
kind, the call that was tried, the page’s state, a hint and up to three next tools to try, such asget_console_messagesanddiagnose_environment. See When a call fails. - From a checkout,
SB_LOG_LEVEL=debug cargo run --bin sbrs-mcp -- --server cdpstarts the server by hand; it reads the protocol on standard input and stops when that closes, or on Ctrl+C.
Debug one test#
- The name after
--testis the file intests/(orcrates/cli/tests/, with-p seleniumbase-rs-cli). A word after it filters the tests by name. - Tests that need a real Chrome are
#[ignore]d: add--ignored, and run one binary at a time. --nocaptureshows what the test prints as it runs. Logs appear only if the test installs a subscriber (see In your own program or test).
rust-analyzer#
Use rust-analyzer, or cargo check, for types and references before a full
build. The repository’s rust-analyzer.toml:
- turns on the
mcp-serverandtest-utilfeatures, without whichsrc/mcpand the test helpers get no diagnostics; - checks into a target directory of its own (
targetDir = true), so analysis never waits on the lock that builds and test runs hold; - keeps two threads and no cache priming, for a small machine that is also compiling.
Code behind other features, such as base_case_impl_gui.rs (feature gui),
gets diagnostics only when you add that feature to your editor’s rust-analyzer
settings.
Known traps#
Headless and headed runs time differently. A real Chrome behaves
differently in the two modes, so check which mode a test uses before you trust
a timing. BrowserConfig is headless by
default; the MCP server’s start_browser and sbrs devtools are headed except
on Linux.
macOS is slow the first time it runs a new executable
macOS scans a freshly written or replaced program on its first run, which can
take seconds. Tests that fake java or another script run it once with
--warm before they time it.
Chrome will not start as root or in a container
It needs --no-sandbox there. The WebDriver engine always passes it on Linux.
For the pure CDP engine, set SB_NO_SANDBOX (any value, 0 included) or call
LaunchOptionsBuilder::no_sandbox(true). The real-Chrome tests of this
repository pass it when CI is set. In Docker, also start the container with
--shm-size=1g.
Doctests fail with extern location does not exist
Two checkouts share one CARGO_TARGET_DIR. Give each its own; see
Build from source.
sbrs commander exits with status 2
The binary was built without the tui feature. It prints a hint; build with
--features tui.