fantoccini

repository·main·Indexed 24 days ago

https://github.com/jonhoo/fantoccini

A high-level Rust library for web automation using the WebDriver protocol. It provides an ergonomic API for interacting with web elements, managing forms, and performing raw HTTP requests within a browser session. Key features include support for CSS selectors, complex input simulation via KeyActions and PointerActions, JavaScript execution, and session management for conforming browsers.

Tokens
9.3K
Snippets
12
Records
60
Agent score
84%

What's inside fantoccini

  1. What is fantoccini?

    main
    fantoccini is a high-level Rust API for programmatically interacting with web pages via the WebDriver protocol. It allows you to drive conforming browsers (including headless ones) through high-level operations like clicking elements, submitting forms, and navigating URLs. Most interactions are performed using CSS selectors.
  2. Connect to a WebDriver instance

    main

    To use fantoccini, you must have a WebDriver-compatible process (like geckodriver or chromedriver) running. You connect to it using ClientBuilder.

    By default, you can use ClientBuilder::native() to establish a connection to a WebDriver server running on a specific URL (e.g., http://localhost:4444).

    use fantoccini::{ClientBuilder, Locator};
    
    #[tokio::main]
    async fn main() -> Result<(), fantoccini::error::CmdError> {
        let c = ClientBuilder::native()
            .connect("http://localhost:4444")
            .await
            .expect("failed to connect to WebDriver");
    
        // ... use client ...
    
        c.close().await
    }
  3. Understand WebDriverStatus and Capabilities

    main

    WebDriverStatus

    Returned by Client::status(), this struct indicates if the WebDriver is ready. It contains:

    • ready: A boolean indicating if the driver can start a new session.
    • message: A status message string.

    Note: Some drivers like Geckodriver may return ready: false if a session is already active.

    Capabilities

    Capabilities is a type alias for serde_json::Map<String, serde_json::Value>, representing the dynamic set of WebDriver capabilities used during session creation.

  4. Error handling for WebDriver commands

    main

    When using issue_cmd, errors returned from the WebDriver server are encapsulated in error::CmdError.

    Fantoccini follows the W3C WebDriver error handling specification:

    • Successful Responses: The value field of the JSON response is extracted and returned as the result.
    • Error Responses: If the HTTP status is not successful, the library parses the JSON body for error and message fields. It converts these into a error::WebDriver error, which includes the error status, the message, and optional stacktrace or data fields if provided by the server.
  5. Wait for conditions using `client.wait()`

    main

    Instead of using deprecated methods like wait_for or wait_for_find, use the wait() method to build a structured wait operation. This allows you to wait for specific conditions, such as an element appearing on the page.

    Example of waiting for an element:

    let button = client.wait().for_element(Locator::Css("#my-button")).await?;
    # use fantoccini::{ClientBuilder, Locator};
    # #[tokio::main]
    # async fn main() -> Result<(), fantoccini::error::CmdError> {
    # let client = ClientBuilder::native().connect("http://localhost:4444").await.unwrap();
    
    let button = client.wait().for_element(Locator::Css(
        r#"a.button-download[href="/learn/get-started"]"#,
    )).await?;
    
    # Ok(())
    # }
  6. Synchronize with browser state using Wait utilities

    main

    To avoid flaky tests caused by static delays, use fantoccini's asynchronous wait operations. These operations periodically check for a specific condition (like an element appearing or a URL changing) and retry until the condition is met or a timeout occurs.

    Default Behavior

    • Timeout: 30 seconds.
    • Polling Period: 250 milliseconds.

    Configuration

    You can customize the wait behavior using a builder pattern on the Wait object:

    • at_most(Duration): Sets a maximum time to wait before returning CmdError::WaitTimeout.
    • forever(): Disables the timeout, waiting indefinitely until the condition is met.
    • every(Duration): Sets the interval between polling attempts.

    Error Handling

    • If the timeout is reached, the operation returns CmdError::WaitTimeout.
    • If the condition check itself returns an error (other than the specific error being waited for, such as NoSuchElement in for_element), the wait operation is aborted and that error is returned.
  7. How multiple action sequences work together (Ticks)

    main

    In WebDriver, Actions can contain multiple ActionSequence objects. These sequences are executed in parallel using a concept called "ticks".

    Each row in the Actions object represents a different input source (e.g., one row for KeyActions, one for MouseActions). Each column represents a "tick" of time.

    • At each tick, all actions scheduled for that tick across all sequences are triggered simultaneously.
    • A tick lasts until the longest duration of any individual action in that tick is completed (including Pause actions).
    • The next tick only begins after the current tick's longest action has finished.

    This allows you to synchronize complex interactions, such as pressing a key and clicking a mouse button at the exact same moment.

  8. How the WebDriver command lifecycle works

    main

    Fantoccini manages WebDriver interactions through a background Session task that processes commands sent via a Client.

    1. Command Issuance: When you call issue_cmd, the Client wraps the command into a Task and sends it through an unbounded MPSC channel to the background Session loop.
    2. Task Processing: The Session loop receives the task and determines if it is a local housekeeping command (like GetSessionId or SetUa) or a remote WebDriver command.
    3. Remote Execution: For remote commands, the Session uses a hyper client to perform an asynchronous HTTP request to the WebDriver server. It resolves the correct URL endpoint and HTTP method (GET, POST, DELETE, etc.) based on the command type.
    4. Response Handling: The background task waits for the HTTP response, parses the JSON body, extracts the value field for successful responses, or converts error bodies into error::CmdError types for failed requests. It then sends the result back to the caller via a oneshot channel.
  9. Initialize a WebDriver Client with ClientBuilder

    main

    To interact with a browser, you must first create a Client using a ClientBuilder. You can choose between different TLS providers depending on your feature flags:

    • Use ClientBuilder::native() if the native-tls feature is enabled (default).
    • Use ClientBuilder::rustls() if the rustls-tls feature is enabled.
    • Use ClientBuilder::new(connector) to provide a custom HTTP connector.

    After configuring the builder, call .connect(webdriver_url) to establish the session.

    Note for geckodriver users: geckodriver does not support multiple simultaneous instances. Ensure you explicitly call client.close().await to end the session, even if an error occurs.

    use fantoccini::{ClientBuilder, Locator};
    
    #[tokio::main]
    async fn main() -> Result<(), fantoccini::error::CmdError> {
        // Connecting using "native" TLS (with feature `native-tls`; on by default)
        let c = ClientBuilder::native()
            .connect("http://localhost:4444")
            .await
            .expect("failed to connect to WebDriver");
    
        // ... use the client ...
    
        c.close().await
    }
  10. Find and interact with elements using Locators

    main

    You can find elements on a page using the find method on a Client instance, passing a Locator. Common locators include Locator::Css for CSS selectors and Locator::LinkText for finding elements by their visible text.

    Once an element is found, you can perform actions like .click().await? or retrieve attributes using .attr("attribute_name").await?.

  11. Perform raw HTTP requests with current session context

    main

    If you need to perform low-level operations, such as downloading a file while preserving the current browser session's cookies, you can use Client::raw_client_for.

    This method allows you to build a raw HTTP request for a specific URL. You can then consume the response body as a stream to read the raw bytes.

    // Build a raw HTTP client request (which also has all current cookies)
    let raw = img.client().raw_client_for(http::Method::GET, &src).await?;
    
    // Read out the bytes using futures_util::TryStreamExt
    use futures_util::TryStreamExt;
    let pixels = raw
        .into_body()
        .try_fold(Vec::new(), |mut data, chunk| async move {
            data.extend_from_slice(&chunk);
            Ok(data)
        })
        .await
        .map_err(fantoccini::error::CmdError::from)?;
    assert!(pixels.len() > 0);
  12. Manage and submit forms

    main

    fantoccini provides a specialized Form abstraction to simplify form manipulation. You first obtain a Form object by calling Client::form(locator), then use methods on that object to populate fields and submit the form.

    Common methods on Form include:

    • set_by_name(name, value): Sets the value of a field identified by its name attribute.
    • submit(): Submits the form.
    // Find the search form, fill it out, and submit it
    let f = c.form(Locator::Css("#search-form")).await?;
    f.set_by_name("search", "foobar").await?
     .submit().await?;