Using Selenium with Rust
Tags: rust, selenium, webdriver
Published: 2020-11-29 01:57:21

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](https://www.rust-lang.org/community)

## Required tools for selenium

To use selenium, we first need to download the selenium server from [here](https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar).

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](https://chromedriver.chromium.org/downloads).

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
```

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.*

```rust
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(())
}
```

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](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html).

* If you prefer not to use async, there is a sync version of the crate called [thirtyfour_sync](https://docs.rs/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:
```rust
caps.add_chrome_arg("--enable-automation")?;
```

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):
```rust
tokio::time::sleep(Duration::from_secs(5)).await;
```
(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:
```rust
let result = WebDriver::new("http://localhost:4444/wd/hub", &caps).await;
let driver = match result {
    Ok(value) => value,
    Err(error) => return Err(error)
};
```
But this would get very tedious so instead Rust provides the ? operator so we can just do this instead:
```rust
let driver = WebDriver::new("http://localhost:4444/wd/hub", &caps).await?;
```

### 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.

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

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:

```rust
let elem_form = driver.find_element(By::Id("search-form")).await?;
let elem_search = elem_form.find_element(By::Id("searchInput")).await?;
```

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:
```rust
println!("Font size: {}", elem_search.get_css_property("font-size").await?);
```

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

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

```rust
let elem_button = elem_form.find_element(By::Css("button[type='submit']")).await?;
elem_button.click().await?;
```
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:
```rust
elem_form.find_element(By::Css("button[type='submit']")).await?.click().await?;
```

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.

```rust
driver.find_element(By::ClassName("firstHeading")).await?;
```

By default, `thirtyfour` sets a 30 second implicit wait for all element searches. For more advanced polling, see [thirtyfour_query](https://docs.rs/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:
```rust
println!("Title = {}", driver.title().await?);
```

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:
```rust
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?;
```

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](https://docs.rs/thirtyfour)