[![Crates.io](https://img.shields.io/crates/v/thirtyfour_sync.svg?style=for-the-badge)](https://crates.io/crates/thirtyfour_sync)
[![docs.rs](https://img.shields.io/badge/docs.rs-thirtyfour_sync-blue?style=for-the-badge)](https://docs.rs/thirtyfour_sync)
[![Build Status](https://img.shields.io/github/workflow/status/stevepryde/thirtyfour_sync/build-check/main?style=for-the-badge)](https://github.com/stevepryde/thirtyfour_sync/actions)

Thirtyfour is a Selenium / WebDriver library for Rust, for automated website UI testing.

It supports the full W3C WebDriver spec. Tested with Chrome and Firefox although any W3C-compatible WebDriver should work.

This crate provides a synchronous (i.e. not async) interface for `thirtyfour`.
For async, see the [thirtyfour](https://docs.rs/thirtyfour) crate instead.

# Status: Looking for maintainer
![maintenance-status](https://img.shields.io/badge/maintenance-looking--for--maintainer-orange.svg)

Upcoming changes to the `thirtyfour` project will cause it to diverge from this
synchronous version. As such this project will no longer be actively maintained.
Apologies for any inconvenience. If you need more information or if you're
interested in taking over this project, please get in touch.

## Features

- All W3C WebDriver and WebElement methods supported
- Create new browser session directly via WebDriver (e.g. chromedriver)
- Create new browser session via Selenium Standalone or Grid
- Automatically close browser session on drop
- Find elements (via all common selectors e.g. Id, Class, CSS, Tag, XPath)
- Send keys to elements, including key-combinations
- Execute Javascript
- Action Chains
- Get and set cookies
- Switch to frame/window/element/alert
- Shadow DOM support
- Alert support
- Capture / Save screenshot of browser or individual element as PNG
- Chrome DevTools Protocol support

## Why 'thirtyfour' ?

It is named after the atomic number for the Selenium chemical element (Se).

## Examples

The example assumes you have a WebDriver running at localhost:4444.

You can use chromedriver directly by downloading the chromedriver that matches your Chrome version,
from here: [https://chromedriver.chromium.org/downloads](https://chromedriver.chromium.org/downloads)

Then run it like this:

    chromedriver --port=4444

### Example:

To run this example:

    cargo run --example sync

```rust
use thirtyfour_sync::prelude::*;

fn main() -> WebDriverResult<()> {
     let caps = DesiredCapabilities::chrome();
     let driver = WebDriver::new("http://localhost:4444", &caps)?;

     // Navigate to https://wikipedia.org.
     driver.get("https://wikipedia.org")?;
     let elem_form = driver.find_element(By::Id("search-form"))?;

     // Find element from element.
     let elem_text = elem_form.find_element(By::Id("searchInput"))?;

     // Type in the search terms.
     elem_text.send_keys("selenium")?;

     // Click the search button.
     let elem_button = elem_form.find_element(By::Css("button[type='submit']"))?;
     elem_button.click()?;

     // Look for header to implicitly wait for the page to load.
     driver.find_element(By::ClassName("firstHeading"))?;
     assert_eq!(driver.title()?, "Selenium - Wikipedia");
    
     // Close the browser.
     driver.quit()?;

     Ok(())
}
```

### Advanced element queries

#### ElementQuery

The `WebDriver::query()` and `WebElement::query()` methods return an `ElementQuery` struct.

Using `ElementQuery`, you can do things like:

```rust
let elem_text =
    driver.query(By::Css("match.this")).or(By::Id("orThis")).first()?;
```

This will execute both queries once per poll iteration and return the first one that matches.
You can also filter on one or both query branches like this:

```rust
driver.query(By::Css("branch.one")).with_text("testing")
    .or(By::Id("branchTwo")).with_class("search").and_not_enabled()
    .first()?;
```

The `all()` method will return an empty Vec if no elements were found.
In order to return an error in this scenario, use the `all_required()` method instead.

`ElementQuery` also allows the use of custom predicates that take a `&WebElement` argument
and return a `WebDriverResult<bool>`.

As noted above, the `query()` method is also available on `WebElement` structs as well for querying elements
in relation to a particular element in the DOM.

#### ElementWaiter

The `WebElement::wait_until()` method returns an `ElementWaiter` struct.

Using `ElementWaiter` you can do things like this:

```rust
elem.wait_until().displayed()?;
// You can optionally provide a nicer error message like this.
elem.wait_until().error("Timed out waiting for element to disappear").not_displayed()?;

elem.wait_until().enabled()?;
elem.wait_until().clickable()?;
```

And so on. See the `ElementWaiter` docs for the full list of predicates available.

`ElementWaiter` also allows the use of custom predicates that take a `&WebElement` argument
and return a `WebDriverResult<bool>`.

A range of pre-defined predicates are also supplied for convenience in the
`thirtyfour_sync::query::conditions` module.

```rust
use thirtyfour_sync::query::conditions;

elem.wait_until().conditions(vec![
    conditions::element_is_displayed(true),
    conditions::element_is_clickable(true)
])?;
```

These predicates (or your own) can also be supplied as filters to `ElementQuery`.

## Running the tests for `thirtyfour_sync`, including doctests

You generally only need to run the tests if you plan on contributing to the development of `thirtyfour_sync`. If you just want to use the crate in your own project, you can skip this section.

Just like the examples above, the tests in this crate require a running instance of Selenium at `http://localhost:4444`.

The tests also require a small web app called `thirtyfour_testapp` that was purpose-built for testing the `thirtyfour` crate.

This can be run using docker and docker-compose.

To install docker, see [https://docs.docker.com/install/](https://docs.docker.com/install/) (follow the SERVER section if you're on Linux, then look for the Community Edition)

To install docker-compose, see [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)

Once you have docker-compose installed, you can start the required containers, as follows:

    docker-compose up -d --build

Then, to run the tests:

    cargo test -- --test-threads=1

We need to limit the tests to a single thread because the selenium server only supports 1 browser instance at a time.
(You can increase this limit in the `docker-compose.yml` file if you want. Remember to restart the containers afterwards)

If you need to restart the docker containers:

    docker-compose restart 

And finally, to remove them:

    docker-compose down

## LICENSE

This work is dual-licensed under MIT or Apache 2.0.
You can choose either license if you use this work.

See the NOTICE file for more details.

`SPDX-License-Identifier: MIT OR Apache-2.0`
