selenium_rs/
webdriver.rs

1/*!
2
3    This provides the primary point of interaction with the Selenium WebDriver API. We
4    can use it to create and manage sessions, as well as use it to spawn elements from the
5    current browsing context.
6
7    # Example - Navigating to a web page
8
9    ```rust
10    use selenium_rs::webdriver::{Browser,WebDriver};
11
12    let mut driver= WebDriver::new(Browser::Chrome);
13    driver.start_session();
14    driver.navigate("https://www.rust-lang.org");
15    assert_eq!(driver.get_current_url().unwrap(), String::from("https://www.rust-lang.org/"));
16    ```
17*/
18
19use element::Element;
20use element_structs::{ElementResponse, ElementsResponse, ExecuteScriptResponse};
21use reqwest;
22use session_structs::{NewSessionRequest, NewSessionResponse, TitleResponse};
23use std::collections::HashMap;
24use utils::*;
25
26#[derive(Clone, Copy, Debug)]
27pub enum Browser {
28    Chrome,
29    Firefox,
30}
31
32#[derive(Clone, Copy, Debug)]
33pub enum Selector {
34    CSS,
35    LinkText,
36    PartialLinkText,
37    TagName,
38    XPath,
39    ID,
40}
41
42#[derive(Serialize, Deserialize)]
43struct ElementRequest {
44    using: String,
45    value: String,
46}
47
48impl ElementRequest {
49    pub fn new(using: String, value: String) -> ElementRequest {
50        ElementRequest { using, value }
51    }
52}
53
54#[derive(Serialize)]
55struct ExecuteScriptRequest {
56    script: String,
57    args: Vec<serde_json::Value>,
58}
59
60impl ExecuteScriptRequest {
61    pub fn new(script: String, args: Vec<serde_json::Value>) -> ExecuteScriptRequest {
62        ExecuteScriptRequest { script, args }
63    }
64}
65
66/// The WebDriver is the primary way by which interaction
67/// is managed between the Selenium-Webdriver Server, and
68/// our client.
69pub struct WebDriver {
70    browser: String,
71    client: reqwest::Client,
72    session_id: Option<String>,
73}
74
75// Contains Creation Methods
76impl WebDriver {
77    /// Constructs a new Webdriver with the specific browser.
78    /// TODO: Make sure and add testing to verify that it supports
79    /// firefox properly
80    pub fn new(browser: Browser) -> WebDriver {
81        let browser = get_browser_string(browser);
82        WebDriver {
83            browser,
84            client: reqwest::Client::new(),
85            session_id: None,
86        }
87    }
88}
89
90// Contains Session Handling
91impl WebDriver {
92    /// Actually starts and creates a session on the server,
93    /// collecting the session ID on success, and returning an error
94    /// on failure
95    pub fn start_session(&mut self) -> reqwest::Result<()> {
96        let body = NewSessionRequest::new(&self.browser);
97        let url = construct_url(vec!["session/"]);
98
99        let response: NewSessionResponse = self.client
100            .post(url)
101            .json(&body)
102            .send()?
103            .error_for_status()?
104            .json()?;
105
106        self.session_id = Some(response.get_session_id());
107        Ok(())
108    }
109    /// Returns the current url of the browsing context. See examples for
110    /// more details on how this is used
111    pub fn get_current_url(&self) -> reqwest::Result<String> {
112        let url = construct_url(vec![
113            "session/",
114            &(self.session_id.clone().unwrap() + "/"),
115            "url",
116        ]);
117        let response: TitleResponse = self.client.get(url).send()?.error_for_status()?.json()?;
118
119        Ok(response.get_title())
120    }
121
122    pub fn get_title(&self) -> reqwest::Result<String> {
123        let url = construct_url(vec![
124            "session/",
125            &(self.session_id.clone().unwrap() + "/"),
126            "title",
127        ]);
128
129        let response: TitleResponse = self.client.get(url).send()?.error_for_status()?.json()?;
130
131        Ok(response.get_title())
132    }
133}
134
135// Contains Navigation Handling
136impl WebDriver {
137    /// Navigates the current browsing session to the reqwested url. This will
138    /// also wait until the browser has finished executing the request, meaning that
139    /// future calls may assume we've reached the appropriate url
140    pub fn navigate(&self, url: &str) -> reqwest::Result<()> {
141        let sess_id = self.session_id.clone().unwrap();
142        let nav_url = construct_url(vec!["session/", &(sess_id + "/"), "url"]);
143        let mut payload = HashMap::new();
144        payload.insert("url", url);
145        self.client
146            .post(nav_url)
147            .json(&payload)
148            .send()?
149            .error_for_status()?;
150        Ok(())
151    }
152
153    pub fn forward(&self) -> reqwest::Result<()> {
154        let sess_id = self.session_id.clone().unwrap();
155        let nav_url = construct_url(vec!["session/", &(sess_id + "/"), "forward"]);
156        self.client.post(nav_url).send()?.error_for_status()?;
157        Ok(())
158    }
159
160    pub fn back(&self) -> reqwest::Result<()> {
161        let sess_id = self.session_id.clone().unwrap();
162        let nav_url = construct_url(vec!["session/", &(sess_id + "/"), "back"]);
163        self.client.post(nav_url).send()?.error_for_status()?;
164        Ok(())
165    }
166}
167
168// Contains Element Handling
169impl WebDriver {
170    /// Requests an elements from the webpage, given the specified selector and query string
171    #[deprecated(since = "0.1.2", note = "query_element does not follow WebDriver naming convention, use find_element")]
172    pub fn query_element(&self, selector: Selector, query: &str) -> reqwest::Result<Element> {
173        self.find_element(selector, query)
174    }
175
176    /// Requests an elements from the webpage, given the specified selector and query string
177    pub fn find_element(&self, selector: Selector, query: &str) -> reqwest::Result<Element> {
178        let sess_id = self.session_id.clone().unwrap();
179        let url = construct_url(vec!["session/", &(sess_id + "/"), "element"]);
180        let payload = ElementRequest::new(str_for_selector(selector), query_string_for_selector(selector, query));
181        let response: ElementResponse = self.client
182            .post(url)
183            .json(&payload)
184            .send()?
185            .error_for_status()?
186            .json()?;
187        let element = response.parse_into_element(&self.client);
188        Ok(element)
189    }
190
191    /// Requests a list of elements from the webpage, given the specified selector and query string
192    #[deprecated(since = "0.1.2", note = "query_elements does not follow WebDriver naming convention, use find_elements")]
193    pub fn query_elements(&self, selector: Selector, query: &str) -> reqwest::Result<Vec<Element>> {
194        self.find_elements(selector, query)
195    }
196
197    /// Requests a list of elements from the webpage, given the specified selector and query string
198    pub fn find_elements(&self, selector: Selector, query: &str) -> reqwest::Result<Vec<Element>> {
199        let sess_id = self.session_id.clone().unwrap();
200        let url = construct_url(vec!["session/", &(sess_id + "/"), "elements"]);
201        let payload = ElementRequest::new(str_for_selector(selector), query.to_string());
202        let response: ElementsResponse = self.client
203            .post(url)
204            .json(&payload)
205            .send()?
206            .error_for_status()?
207            .json()?;
208        let elements = response.parse_into_elements(&self.client);
209        Ok(elements)
210    }
211}
212
213// Contains Document Handling
214impl WebDriver {
215    /// Executes the given script synchronously and returns the result
216    pub fn execute_script<T: serde::de::DeserializeOwned>(&self, script: &str, args: &[serde_json::Value]) -> reqwest::Result<T> {
217        let sess_id = self.session_id.clone().unwrap();
218        let url = construct_url(vec!["session/", &(sess_id + "/"), "execute/sync"]);
219        let payload = ExecuteScriptRequest::new(script.to_string(), args.to_owned());
220        let response: ExecuteScriptResponse<T> = self.client
221            .post(url)
222            .json(&payload)
223            .send()?
224            .error_for_status()?
225            .json()?;
226        Ok(response.value)
227    }
228}
229
230impl Drop for WebDriver {
231    fn drop(&mut self) {
232        if let Some(ref id) = self.session_id {
233            let url = construct_url(vec!["session/", id]);
234            let _ = self.client.delete(url).send();
235        }
236    }
237}