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:
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:
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#
| Use | When |
|---|---|
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 mode | Sites 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:
Run headless
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.