Using Selenium with Rust

stevepryde

Steve Pryde

Posted on November 29, 2020

Using Selenium with Rust

Selenium is a tool for automating web browsers, and is often used for testing websites, especially for end-to-end testing. Rust is commonly known as a Systems Programming language and might not be your first choice when it comes to browser testing but it can actually work pretty well.

First, let's introduce Rust.

For those who may not be familiar with the Rust language, let me give you a small taste of what its strengths are. This won't be a comprehensive introduction, but I hope to give you a sense of why this language exists and what it can offer you.

  • Performance
    Rust code often performs very well due to it being a statically compiled language and also due to it encouraging patterns that naturally lead to good optimizations.

  • Safety
    Rust prevents entire classes of common bugs and mistakes, which means your code is more likely to work correctly. Safety-by-default is a very common theme in Rust.

  • Rich type system
    Rust has a very expressive type system which allows you to more accurately model your data objects and the relationships between them. It borrows many of the best features from other languages including its familiar syntax.

  • Awesome and welcoming community
    Rust has a fantastic community behind it, willing to help you learn and succeed with the language. Click here for more info

Required tools for selenium

To use selenium, we first need to download the selenium server from here.

Selenium server is written in Java so you will also need Java installed.

We also need a webdriver to interact with your browser of choice. We will use Chrome, so you will need chromedriver which you can download from here.

Check your version of Chrome (open Chrome and select menu > Help > About Google Chrome) and then download the version of chromedriver that corresponds to your version of Chrome.

Unzip the downloaded file and place the chromedriver executable somewhere in your PATH. You can put it in the same directory as the selenium jar file if you want.

Now start selenium like this:

java -jar selenium-server-standalone-3.141.59.jar
Enter fullscreen mode Exit fullscreen mode

This should start selenium server on port 4444.

Let's see the code

In order to interact with a web browser from Rust we will use the thirtyfour library (or "crate" in Rust lingo).

It is called "thirtyfour" because 34 is the atomic number for the Selenium chemical element.

Full Disclosure: I wrote the thirtyfour crate, with contributions from several other volunteers as well.

use thirtyfour::prelude::*;
use tokio;

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
    color_eyre::install()?;

    let mut caps = DesiredCapabilities::chrome();
    caps.add_chrome_arg("--enable-automation")?;
    let driver = WebDriver::new("http://localhost:4444/wd/hub", &caps).await?;

    driver.get("https://wikipedia.org").await?;

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Some brief notes about other crates we are using:

  • We are using tokio for async/await. If you want to learn more about async in Rust, click here.

  • If you prefer not to use async, there is a sync version of the crate called thirtyfour_sync.

  • We are also using the awesome color_eyre crate for error reporting. It makes any error messages much clearer.

Ok, so firstly we set up a DesiredCapabilities struct. In this case we want to use the Chrome presets. There are also presets for other browsers (to use other browsers you need the webdriver for that browser in your PATH).

We want to enable the automation flags for Chrome, via this line:

caps.add_chrome_arg("--enable-automation")?;
Enter fullscreen mode Exit fullscreen mode

And then we start the web browser and return a WebDriver struct for us to interact with it.

If you run the above code you should see a Chrome browser open and automatically navigate to https://wikipedia.org. Note that selenium will automatically wait for the page to load. This can all happen fairly quickly so if you need it to wait at the end you can add this line (requires the time feature of tokio):

tokio::time::sleep(Duration::from_secs(5)).await;
Enter fullscreen mode Exit fullscreen mode

(In previous versions of tokio, sleep was named delay_for).

Aside: The ? symbol in Rust

You may be wondering what the ? symbol is for at the end of each line. This is an error-handling feature of Rust. You can see that the main() function returns a Result. This means it can either return Ok(value) or Err(error_value). The ? is a shorthand way of handling the error without having to type out all of the boilerplate code.

It is the equivalent of doing something like this:

let result = WebDriver::new("http://localhost:4444/wd/hub", &caps).await;
let driver = match result {
    Ok(value) => value,
    Err(error) => return Err(error)
};
Enter fullscreen mode Exit fullscreen mode

But this would get very tedious so instead Rust provides the ? operator so we can just do this instead:

let driver = WebDriver::new("http://localhost:4444/wd/hub", &caps).await?;
Enter fullscreen mode Exit fullscreen mode

Great! Now let's make it do something

First we want to find a particular element. To do this we use an element selector. We can search by Id, Name, Class name, Tag, and we can use CSS or XPath selectors as well.

To keep things simple, let's search by Id.

let elem_search = driver.find_element(By::Id("searchInput")).await?;
elem_search.send_keys("selenium").await?;
Enter fullscreen mode Exit fullscreen mode

This will find the element with the id searchInput and then type the word selenium into it.

Note that we can also search for an element that is nested below another element. Another way to locate the searchInput element is as follows:

let elem_form = driver.find_element(By::Id("search-form")).await?;
let elem_search = elem_form.find_element(By::Id("searchInput")).await?;
Enter fullscreen mode Exit fullscreen mode

This will first find the element with id search-form and then it will look for an element with id searchInput that is nested below the first element. This can be useful for performing more complex element searches, however you should also check out CSS and XPath selectors which can often do the same thing in one single query.

If you want to get more information about this element, you can get CSS properties like this:

println!("Font size: {}", elem_search.get_css_property("font-size").await?);
Enter fullscreen mode Exit fullscreen mode

This will display the font size of the search input element.

Ok, now let's find a button and click it.

let elem_button = elem_form.find_element(By::Css("button[type='submit']")).await?;
elem_button.click().await?;
Enter fullscreen mode Exit fullscreen mode

Here you can see the CSS selector being used to locate the button element.

If preferred you can shorten the code by simply chaining these calls together, like this:

elem_form.find_element(By::Css("button[type='submit']")).await?.click().await?;
Enter fullscreen mode Exit fullscreen mode

At this point we could "sleep" for a few seconds to wait for the page to load, but using "sleep" in selenium tests is often very unreliable and can result in flaky tests.

Instead, it is better to locate some element on the page that indicates the page has finished loading. So let's wait for the page heading.

driver.find_element(By::ClassName("firstHeading")).await?;
Enter fullscreen mode Exit fullscreen mode

By default, thirtyfour sets a 30 second implicit wait for all element searches. For more advanced polling, see thirtyfour_query, but the implicit wait will work fine for this particular example.

If for some reason the element was not found within 30 seconds, the above call would return an error (WebDriverError::NoSuchElement). That error would be propagated up by the ? operator and would return the error from the main() function, exiting the program.

Now we can display the page title, like this:

println!("Title = {}", driver.title().await?);
Enter fullscreen mode Exit fullscreen mode

And this should display "Selenium - Wikipedia" if everything has worked correctly.

So hopefully this has given you a taste of how you can automate a web browser in Rust. This is also useful for testing your web applications, especially if those applications are also written in Rust.

A little "Rusty" feature

There is one more thing I want to point out, and it's something perhaps unique to Rust.

The thirtyfour crate uses a feature of Rust called lifetimes to guarantee at compile-time that no references to the selenium session can be used after the browser has been instructed to close. It does this by sharing a reference to the session between the WebDriver and all WebElement structs related to it. When you call WebDriver::quit() (or the WebDriver struct goes out of scope) this will consume the struct and destroy the session, and this means Rust knows that all of the other references to it are now invalid and cannot be used.

The following code will not compile:

let driver = WebDriver::new("http://localhost:4444/wd/hub", &caps).await?;
driver.get("https://wikipedia.org").await?;

let elem_search = driver.find_element(By::Id("searchInput")).await?;

// The session is "moved" here, preventing further use of either the driver or elements.
driver.quit().await?; 

// This attempts to "borrow" the session after it has been moved, which Rust will not allow.
elem_search.send_keys("this won't work").await?;
Enter fullscreen mode Exit fullscreen mode

The Rust compiler tells us that the call to driver.quit() here is not allowed because the driver is also "borrowed" later in the call to elem_search.send_keys().

While other selenium libraries may be able to perform such checks at runtime, Rust is able to do the check at compile-time, saving you time in debugging and making your tests a little bit more reliable from the start.

Thank you for reading. Hopefully I have shown you that interacting with a web browser in Rust can be quite easy, especially if you are already familiar with Selenium :)

For more documentation on the thirtyfour crate, click here

💖 💪 🙅 🚩
stevepryde
Steve Pryde

Posted on November 29, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Using Selenium with Rust
rust Using Selenium with Rust

November 29, 2020