Skip to main content

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.

Info

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:

SB_LOG_LEVELfilter#

The filter, such as debug or seleniumbase_rs=debug,info. Unset, blank or invalid, the next one is used.

RUST_LOGfilter#

Used when SB_LOG_LEVEL is unset, blank or invalid. When neither gives a filter, it is info.

SB_LOG_FORMATpretty | json#

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"

NO_COLORany value#

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.

src/main.rs
FunctionFilterFormat
init_tracing()RUST_LOG, else infopretty
init_tracing_json()RUST_LOG, else infoJSON
init_tracing_with_filter("seleniumbase_rs=debug,info")the string, else RUST_LOG, else infopretty
init_tracing_json_with_filter(...)as aboveJSON
init_tracing_from_runtime(&config)SB_LOG_LEVEL, else RUST_LOG, else infoSB_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

Terminal

A different RUSTFLAGS makes cargo rebuild every crate, so expect a long first build.

Start the console

In another terminal:

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:

On Linux without a display, run under a virtual display such as Xvfb; see Operating systems.

Slow the run down#

WebDriver engine only

Demo mode outlines each element before an interactive call and pauses after it; slow mode pauses without the outline. Both go in the configuration:

tests/watch.rs
Warning

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:

tests/login.rs
  • On a failure they save login_works_<date>_<time>.png and .html in 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 a Page.

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 browser

Browser::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.

tests/title.rs
MockCtrlstruct#

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, MockWebDriver in src/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 HAR 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 only

The 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.

.mcp.json
  • 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 as get_console_messages and diagnose_environment. See When a call fails.
  • From a checkout, SB_LOG_LEVEL=debug cargo run --bin sbrs-mcp -- --server cdp starts 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 --test is the file in tests/ (or crates/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.
  • --nocapture shows 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-server and test-util features, without which src/mcp and 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.

Next steps#