Reliable AI-Generated Tests
Use this checklist when asking a coding agent to write or review thirtyfour
automation. It is intentionally short enough to paste into project-level agent
instructions:
- Use WebDriver::managed for local browsers; use WebDriver::new or builder only
when connecting to a remote Selenium or Grid endpoint.
- Use query() for normal lookup. Use find() only for an intentional one-shot lookup.
- End unique queries with single(), and add desc(...) to important queries.
- Prefer By::Testid or another stable app-owned selector. Avoid generated classes,
DOM-position chains, and XPath when stable CSS can express the target.
- Never use a fixed sleep for page readiness. Wait with query() or wait_until().
- Scope queries through a container element; use Components for repeated UI areas.
- Prefer run_browser_test(...) in tests; otherwise explicitly call
driver.quit().await? when the session is finished.
- On failure, capture bounded diagnostics before cleanup with
FailureArtifactCollector rather than dumping unbounded page source.
- Do not share one WebDriver session across independent concurrent flows. A shared
WebDriverManager may launch separate sessions for parallel tests.
- Keep CDP and BiDi code isolated from portable WebDriver flows. Gate optional
cdp-events and bidi APIs with their Cargo features, and opt BiDi sessions in.
Why These Rules
WebDriver::manageddownloads, launches, and lifetime-manages a local driver. Use the normal constructor orWebDriver::builderwhen an external Selenium service owns that lifecycle. Explicitquit()reports shutdown errors and does not rely on asynchronous cleanup duringDrop.- Element queries poll for page state and provide
filters, descriptions, and cardinality checks. See the guidance for
stable selectors,
choosing
single()orfirst(), describing important queries, and intentional one-shot lookup. - Element waits express the state an existing element must reach. A fixed sleep only guesses how long the page needs; a query or wait completes as soon as the required state exists and produces a useful timeout when it does not.
- Element-scoped queries prevent unrelated matches. For recurring UI areas, Components keep selectors private and expose user-intent methods while their resolvers handle polling and stale elements.
- A cloned
WebDrivercontrols the same browser session, so independent flows can race over tabs, navigation, and browser state. The manager’s shared configuration can instead launch a separate session for each test or worker. - CDP is Chromium-specific, while BiDi is the cross-browser W3C protocol. Keep
either behind a small boundary so the rest of the automation remains portable,
and follow the feature flag and
BiDi opt-in requirements. The
cdpfeature is enabled by default;cdp-eventsandbidiare opt-in.
Treat these as review rules, not merely generation hints: generated code should not be accepted until its selectors, waits, cleanup, concurrency, and feature gates satisfy the same checklist.
Cleanup That Survives Test Failures
Use run_browser_test
as the default test shape:
#![allow(unused)]
fn main() {
use thirtyfour::{
prelude::*,
testing::{BrowserTestError, run_browser_test},
};
#[tokio::test]
async fn page_has_heading() -> Result<(), BrowserTestError> {
run_browser_test(
WebDriver::managed(DesiredCapabilities::chrome()),
|driver| async move {
driver.goto("https://example.com").await?;
driver.query(By::Css("h1")).single().await?;
Ok(())
},
)
.await
}
}
The runner keeps its own session handle, passes a clone to the test body, and
awaits quit() after success, an early ? return, or a panicking assertion.
After a panic it resumes the original panic; if cleanup also fails, the cleanup
error is written through tracing. panic = "abort" cannot run cleanup.
The runner future must be allowed to finish: cancelling or aborting its task can
interrupt asynchronous cleanup.
Error precedence is explicit:
| Test body | quit() | Result |
|---|---|---|
| succeeds | succeeds | body value |
| fails | succeeds | BrowserTestError::Body |
| succeeds | fails | BrowserTestError::Cleanup |
| fails | fails | BrowserTestError::BodyAndCleanup with both errors |
The first argument accepts any future that creates a WebDriver, so the same
runner works with WebDriver::managed(...), WebDriver::builder(...), and
WebDriver::new(...).
For CI diagnostics, attach
FailureArtifactCollector
before the test body and call capture() on the error path before the
runner cleans up. Each artifact succeeds or fails independently, source and log
text are hard-bounded by default, and the display report never prints PNG bytes.
See Failure Artifacts And Logs for the complete
pattern and browser/feature limitations.