Components allow you to wrap pieces of UI (buttons, forms, pages) in a Rust struct to avoid brittle, repetitive selector logic. This is similar to the Page Object Model.
Key characteristics:
- A Component is a struct that derives the
Component macro. - It must contain exactly one
base field of type WebElement which represents the outer element the component wraps. - Resolver fields (marked with
#[by(...)]) are ElementResolver types that query the DOM starting from the base element. - Resolvers are lazy: they don't query until
.resolve() is called, and they cache the result to avoid redundant WebDriver calls. - Scoping: Queries are scoped to the component's subtree. When using XPath, use
.// to stay within the component; using // will search from the document root.
use thirtyfour::prelude::*;
#[derive(Debug, Clone, Component)]
pub struct SearchForm {
base: WebElement, // The <form> itself.
#[by(id = "search-input")]
input: ElementResolver<WebElement>, // The <input>.
#[by(testid = "search-submit", description = "search submit button")]
submit: ElementResolver<WebElement>, // The <button>.
}
impl SearchForm {
pub async fn search(&self, term: &str) -> WebDriverResult<()> {
self.input.resolve().await?.send_keys(term).await?;
self.submit.resolve().await?.click().await?;
Ok(())
}
}
// Usage:
let form_el = driver.query(By::Id("search-form")).single().await?;
let form: SearchForm = form_el.into(); // From<WebElement> is derived.
form.search("Selenium").await?;