fantoccini/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
//! A medium-level API for programmatically interacting with web pages through WebDriver.
//!
//! This crate uses the [WebDriver protocol] to drive a conforming (potentially headless) browser
//! through relatively operations such as "click this element", "submit this form", etc.
//!
//! Most interactions are driven by using [CSS selectors]. With most WebDriver-compatible browser
//! being fairly recent, the more expressive levels of the CSS standard are also supported, giving
//! fairly [powerful] [operators].
//!
//! Forms are managed by first calling `Client::form`, and then using the methods on `Form` to
//! manipulate the form's fields and eventually submitting it.
//!
//! For low-level access to the page, `Client::source` can be used to fetch the full page HTML
//! source code, and `Client::raw_client_for` to build a raw HTTP request for a particular URL.
//!
//! # Feature flags
//!
//! The following feature flags exist for this crate.
//!
//! - `native-tls`: Enable [ergonomic https connection](ClientBuilder::native) using [`native-tls`](https://crates.io/crates/native-tls) (enabled by default).
//! - `rustls-tls`: Enable [ergonomic https connection](ClientBuilder::rustls) using Rusttls.
//!
//! # Examples
//!
//! These examples all assume that you have a [WebDriver compatible] process running on port 4444.
//! A quick way to get one is to run [`geckodriver`] at the command line. The code also has
//! partial support for the legacy WebDriver protocol used by `chromedriver` and `ghostdriver`.
//!
//! The examples will be using `panic!` or `unwrap` generously when errors occur (see `map_err`)
//! --- you should probably not do that in your code, and instead deal with errors when they occur.
//! This is particularly true for methods that you *expect* might fail, such as lookups by CSS
//! selector.
//!
//! Let's start out clicking around on Wikipedia:
//!
//! ```no_run
//! use fantoccini::{ClientBuilder, Locator};
//!
//! // let's set up the sequence of steps we want the browser to take
//! #[tokio::main]
//! async fn main() -> Result<(), fantoccini::error::CmdError> {
//! // Connecting using "native" TLS (with feature `native-tls`; on by default)
//! # #[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
//! let c = ClientBuilder::native().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! // Connecting using Rustls (with feature `rustls-tls`)
//! # #[cfg(feature = "rustls-tls")]
//! let c = ClientBuilder::rustls().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! # #[cfg(all(not(feature = "native-tls"), not(feature = "rustls-tls")))]
//! # let c: fantoccini::Client = unreachable!("no tls provider available");
//!
//! // first, go to the Wikipedia page for Foobar
//! c.goto("https://en.wikipedia.org/wiki/Foobar").await?;
//! let url = c.current_url().await?;
//! assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");
//!
//! // click "Foo (disambiguation)"
//! c.find(Locator::Css(".mw-disambig")).await?.click().await?;
//!
//! // click "Foo Lake"
//! c.find(Locator::LinkText("Foo Lake")).await?.click().await?;
//!
//! let url = c.current_url().await?;
//! assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foo_Lake");
//!
//! c.close().await
//! }
//! ```
//!
//! How did we get to the Foobar page in the first place? We did a search!
//! Let's make the program do that for us instead:
//!
//! ```no_run
//! # use fantoccini::{ClientBuilder, Locator};
//! # #[tokio::main]
//! # async fn main() -> Result<(), fantoccini::error::CmdError> {
//! # #[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
//! # let c = ClientBuilder::native().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! # #[cfg(feature = "rustls-tls")]
//! # let c = ClientBuilder::rustls().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! # #[cfg(all(not(feature = "native-tls"), not(feature = "rustls-tls")))]
//! # let c: fantoccini::Client = unreachable!("no tls provider available");
//! // -- snip wrapper code --
//! // go to the Wikipedia frontpage this time
//! c.goto("https://www.wikipedia.org/").await?;
//! // 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?;
//!
//! // we should now have ended up in the rigth place
//! let url = c.current_url().await?;
//! assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");
//!
//! // -- snip wrapper code --
//! # c.close().await
//! # }
//! ```
//!
//! What if we want to download a raw file? Fantoccini has you covered:
//!
//! ```no_run
//! # use fantoccini::{ClientBuilder, Locator};
//! # #[tokio::main]
//! # async fn main() -> Result<(), fantoccini::error::CmdError> {
//! # #[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
//! # let c = ClientBuilder::native().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! # #[cfg(feature = "rustls-tls")]
//! # let c = ClientBuilder::rustls().connect("http://localhost:4444").await.expect("failed to connect to WebDriver");
//! # #[cfg(all(not(feature = "native-tls"), not(feature = "rustls-tls")))]
//! # let c: fantoccini::Client = unreachable!("no tls provider available");
//! // -- snip wrapper code --
//! // go back to the frontpage
//! c.goto("https://www.wikipedia.org/").await?;
//! // find the source for the Wikipedia globe
//! let img = c.find(Locator::Css("img.central-featured-logo")).await?;
//! let src = img.attr("src").await?.expect("image should have a src");
//! // now build a raw HTTP client request (which also has all current cookies)
//! let raw = img.client().raw_client_for(hyper::Method::GET, &src).await?;
//!
//! // we then read out the image bytes
//! use futures_util::TryStreamExt;
//! let pixels = hyper::body::to_bytes(raw.into_body()).await.map_err(fantoccini::error::CmdError::from)?;
//! // and voilla, we now have the bytes for the Wikipedia logo!
//! assert!(pixels.len() > 0);
//! println!("Wikipedia logo is {}b", pixels.len());
//!
//! // -- snip wrapper code --
//! # c.close().await
//! # }
//! ```
//!
//! For more examples, take a look at the `examples/` directory.
//!
//! [WebDriver protocol]: https://www.w3.org/TR/webdriver/
//! [CSS selectors]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
//! [powerful]: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
//! [operators]: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
//! [WebDriver compatible]: https://github.com/Fyrd/caniuse/issues/2757#issuecomment-304529217
//! [`geckodriver`]: https://github.com/mozilla/geckodriver
#![deny(missing_docs)]
#![warn(missing_debug_implementations, rust_2018_idioms, rustdoc::all)]
#![allow(rustdoc::missing_doc_code_examples, rustdoc::private_doc_tests)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use crate::wd::Capabilities;
use hyper::client::connect;
macro_rules! via_json {
($x:expr) => {{
serde_json::from_str(&serde_json::to_string($x).unwrap()).unwrap()
}};
}
/// Error types.
pub mod error;
/// The long-running session future we spawn for multiplexing onto a running WebDriver instance.
mod session;
/// A [builder] for WebDriver [`Client`] instances.
///
/// You will likely want to use [`native`](ClientBuilder::native) or
/// [`rustls`](ClientBuilder::rustls) (depending on your preference) to start the builder. If you
/// want to supply your own connector, use [`new`](ClientBuilder::new).
///
/// To connect to the WebDriver instance, call [`connect`](ClientBuilder::connect).
///
/// [builder]: https://rust-lang.github.io/api-guidelines/type-safety.html#c-builder
#[derive(Default, Clone, Debug)]
pub struct ClientBuilder<C>
where
C: connect::Connect + Send + Sync + Clone + Unpin,
{
capabilities: Option<Capabilities>,
connector: C,
}
#[cfg(feature = "rustls-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
impl ClientBuilder<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> {
/// Build a [`Client`] that will connect using [Rustls](https://crates.io/crates/rustls).
pub fn rustls() -> Self {
Self::new(
hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.build(),
)
}
}
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
impl ClientBuilder<hyper_tls::HttpsConnector<hyper::client::HttpConnector>> {
/// Build a [`Client`] that will connect using [`native-tls`](https://crates.io/crates/native-tls).
pub fn native() -> Self {
Self::new(hyper_tls::HttpsConnector::new())
}
}
impl<C> ClientBuilder<C>
where
C: connect::Connect + Send + Sync + Clone + Unpin + 'static,
{
/// Build a [`Client`] that will connect using the given HTTP `connector`.
pub fn new(connector: C) -> Self {
Self {
capabilities: None,
connector,
}
}
/// Pass the given [WebDriver capabilities][1] to the browser.
///
/// The WebDriver specification has a list of [standard
/// capabilities](https://www.w3.org/TR/webdriver1/#capabilities), which are given below. In
/// addition, most browser vendors support a number of browser-specific capabilities stored
/// in an object under a prefixed key like
/// [`moz:firefoxOptions`](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions)
/// or
/// [`goog:chromeOptions`](https://sites.google.com/a/chromium.org/chromedriver/capabilities).
///
/// The standard options are given below. See the
/// [specification](https://www.w3.org/TR/webdriver1/#capabilities) for more details.
///
/// | Capability | Key | Value Type | Description |
/// |------------|-----|------------|-------------|
/// | Browser name | `"browserName"` | string | Identifies the user agent. |
/// | Browser version | `"browserVersion"` | string | Identifies the version of the user agent. |
/// | Platform name | `"platformName"` | string | Identifies the operating system of the endpoint node. |
/// | Accept insecure TLS certificates | `"acceptInsecureCerts"` | boolean | Indicates whether untrusted and self-signed TLS certificates are implicitly trusted on navigation for the duration of the session. |
/// | Page load strategy | `"pageLoadStrategy"` | string | Defines the current session’s page load strategy. |
/// | Proxy configuration | `"proxy"` | JSON Object | Defines the current session’s proxy configuration. |
/// | Window dimensioning/positioning | `"setWindowRect"` | boolean | Indicates whether the remote end supports all of the commands in Resizing and Positioning Windows. |
/// | Session timeouts configuration | `"timeouts"` | JSON Object | Describes the timeouts imposed on certain session operations. |
/// | Unhandled prompt behavior | `"unhandledPromptBehavior"` | string | Describes the current session’s user prompt handler. |
///
/// [1]: https://www.w3.org/TR/webdriver/#dfn-capability
pub fn capabilities(&mut self, cap: Capabilities) -> &mut Self {
self.capabilities = Some(cap);
self
}
/// Connect to the WebDriver session at the `webdriver` URL.
pub async fn connect(&self, webdriver: &str) -> Result<Client, error::NewSessionError> {
if let Some(ref cap) = self.capabilities {
Client::with_capabilities_and_connector(webdriver, cap, self.connector.clone()).await
} else {
Client::new_with_connector(webdriver, self.connector.clone()).await
}
}
}
pub mod client;
#[doc(inline)]
pub use client::Client;
pub mod actions;
pub mod cookies;
pub mod elements;
pub mod key;
pub mod wait;
pub mod wd;
#[doc(inline)]
pub use wd::Locator;