Skip to main content
Info

In short: pick stable selectors, let the methods wait instead of sleeping, always close the browser, run headless in CI, keep secrets out of the repository, and keep a browser identity consistent. Each habit below links to the page that explains it.

Writing tests#

Pick selectors that survive a redesign

Prefer an id or a test attribute (#checkout, [data-testid="total"]) over a long CSS path or a position (div > div:nth-child(3)). Use link text (link=Sign in) for links whose text is the contract. See Selectors.

Wait for the page, never sleep

Actions and assertions wait for their element, so a fixed sleep only makes a test slower, or flaky when the page is slower than the sleep. When you need to wait for something an action does not cover, use a wait method with a timeout (wait_for_element_visible(selector, timeout_secs)). See Waits and assertions.

Assert what the user sees

Check visible text and elements (assert_text_visible("Welcome!", "body")) rather than internals. Note the order: the expected text comes first, then the selector.

End every call with ?

Every call returns a Result. ? stops the test at the first failure with an error that names the selector; ignoring the result hides failures. See Taking advantage of Rust.

Always close the browser

A test that fails before sb.quit() leaves a Chrome running. sb_test! and run_browser_test always quit, even on failure. See Macros.

Write helpers against the traits

A helper written for BrowserApi and ElementApi runs on both engines, so you can switch a suite from WebDriver to pure CDP without rewriting it. See Capability traits.

Structuring tests#

One behaviour per test

A test that checks one thing fails for one reason, so its name says what broke. Keep tests independent: each signs in or loads a saved session itself, rather than relying on a test that ran before it.

Put page knowledge in one place

When several tests touch the same page, keep its selectors and steps in a small struct (a page object), so a changed selector is fixed once:

tests/pages/login.rs
Collect several checks, then fail once

When a page has many independent things to check, deferred assertions record each failure and report them all at the end, instead of stopping at the first:

tests/dashboard.rs

Running tests#

Run headless in CI and containers

There is no screen there. Set SB_HEADLESS=true for sbrs and code that loads Settings; BrowserConfig::default() is already headless. Debug headed on your own machine. See Headless and headed browsers.

Keep the driver matched to the browser

chromedriver must have the same major version as Chrome. sbrs doctor shows both; sbrs install fetches the matching driver. Or use the pure CDP engine, which needs no driver. See WebDriver and drivers.

Reuse browsers instead of starting one per task

Starting Chrome is the slowest step. Share browsers between tests with --reuse-session, or hand out pages from a BrowserPool in the pure CDP engine. See Pure CDP.

Keep evidence of a failure

Save a screenshot and the page source when a test fails (save_screenshot, save_page_source_to_logs), and turn on logs with SB_LOG_LEVEL=debug when you investigate. See Tracing and logging.

Staying safe and polite#

Keep credentials out of the repository

Read passwords and TOTP secrets from environment variables, and never commit saved cookie files: anyone with them is signed in as you. See Sessions, cookies and storage and MFA and TOTP.

Do not overload a site

Space out requests and retry with backoff instead of hammering a page; the crate has RetryPolicy and RateLimiter for this. Respect a site’s terms of use.

Keep one identity consistent

If you change the browser’s identity, change it as a whole: a Fingerprint draws random values together (operating system, region, locale), and .seed(..) makes a run repeatable. A proxy in one country with a time zone in another is a giveaway. See Detection and spoofing.

Choosing an engine#

UseWhen
Pure CDP (sb_cdp)Chrome only, no driver to manage, many browsers at once, network interception.
BaseCase (WebDriver)Firefox, Edge or Safari, a Selenium Grid, or the SeleniumBase-style method set.
BaseCase with UC modeSites that block ordinary automation.

Write helpers against the capability traits and you can move a suite between engines later.

In CI#

Pin the version

Depend on a tag, so a new commit on main cannot change your build:

Cargo.toml

Run headless

Terminal

Keep the evidence

Upload the screenshots and logs your failing tests saved as build artifacts, so a red build can be read without running it again.

Where to go next#