1#[macro_use]
12extern crate error_chain;
13
14pub mod error;
15mod protocol;
16
17use crate::error::*;
18pub use hyper::Method;
19use protocol::Client;
20use serde_json::Value;
21use std::time::Duration;
22use tokio::time::sleep;
23use webdriver::{
24 command::{SwitchToFrameParameters, SwitchToWindowParameters, WebDriverCommand},
25 common::{FrameId, WebElement, ELEMENT_KEY},
26 error::{ErrorStatus, WebDriverError},
27};
28
29#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
30pub enum Locator {
31 Css(String),
32 LinkText(String),
33 XPath(String),
34}
35
36impl Into<webdriver::command::LocatorParameters> for Locator {
37 fn into(self) -> webdriver::command::LocatorParameters {
38 match self {
39 Locator::Css(s) => webdriver::command::LocatorParameters {
40 using: webdriver::common::LocatorStrategy::CSSSelector,
41 value: s,
42 },
43 Locator::XPath(s) => webdriver::command::LocatorParameters {
44 using: webdriver::common::LocatorStrategy::XPath,
45 value: s,
46 },
47 Locator::LinkText(s) => webdriver::command::LocatorParameters {
48 using: webdriver::common::LocatorStrategy::LinkText,
49 value: s,
50 },
51 }
52 }
53}
54
55pub struct Driver(Client);
56
57macro_rules! generate_wait_for_find {
58 ($name:ident, $search_fn:ident, $return_typ:ty) => {
59 pub async fn $name(
61 &self,
62 search: Locator,
63 root: Option<WebElement>
64 ) -> Result<$return_typ> {
65 loop {
66 match self.$search_fn(search.clone(), root.clone()).await {
67 Ok(e) => break Ok(e),
68 Err(Error(ErrorKind::WebDriver(
69 WebDriverError {error: ErrorStatus::NoSuchElement, ..}
70 ), _)) => sleep(Duration::from_millis(100)).await,
71 Err(e) => break Err(e)
72 }
73 }
74 }
75 }
76}
77
78impl Driver {
79 pub async fn new(webdriver_url: &str, user_agent: Option<String>) -> Result<Self> {
81 Ok(Driver(Client::new(webdriver_url, user_agent).await?))
82 }
83
84 pub async fn goto<'a>(&'a self, url: &'a str) -> Result<()> {
86 let cmd = WebDriverCommand::Get(webdriver::command::GetParameters {
87 url: self.current_url().await?.join(url)?.into(),
88 });
89 self.0.issue_cmd(&cmd).await?;
90 Ok(())
91 }
92
93 pub async fn current_url(&self) -> Result<url::Url> {
95 match self.0.issue_cmd(&WebDriverCommand::GetCurrentUrl).await?.as_str() {
96 Some(url) => Ok(url.parse()?),
97 None => bail!(ErrorKind::NotW3C(Value::Null)),
98 }
99 }
100
101 pub async fn source(&self) -> Result<String> {
103 match self.0.issue_cmd(&WebDriverCommand::GetPageSource).await?.as_str() {
104 Some(src) => Ok(src.to_string()),
105 None => bail!(ErrorKind::NotW3C(Value::Null)),
106 }
107 }
108
109 pub async fn back(&self) -> Result<()> {
111 self.0.issue_cmd(&WebDriverCommand::GoBack).await?;
112 Ok(())
113 }
114
115 pub async fn refresh(&self) -> Result<()> {
117 self.0.issue_cmd(&WebDriverCommand::Refresh).await?;
118 Ok(())
119 }
120
121 pub async fn switch_to_frame(&self, frame: WebElement) -> Result<()> {
123 let p = SwitchToFrameParameters {
124 id: Some(FrameId::Element(frame)),
125 };
126 let cmd = WebDriverCommand::SwitchToFrame(p);
127 self.0.issue_cmd(&cmd).await?;
128 Ok(())
129 }
130
131 pub async fn switch_to_parent_frame(&self) -> Result<()> {
133 self.0.issue_cmd(&WebDriverCommand::SwitchToParentFrame).await?;
134 Ok(())
135 }
136
137 pub async fn switch_to_window(&self, window: String) -> Result<()> {
139 let p = SwitchToWindowParameters { handle: window };
140 let cmd = WebDriverCommand::SwitchToWindow(p);
141 self.0.issue_cmd(&cmd).await?;
142 Ok(())
143 }
144
145 pub async fn execute(&self, script: String, mut args: Vec<Value>) -> Result<Value> {
152 self.fixup_elements(&mut args);
153 let cmd = webdriver::command::JavascriptCommandParameters {
154 script: script,
155 args: Some(args),
156 };
157 let cmd = WebDriverCommand::ExecuteScript(cmd);
158 self.0.issue_cmd(&cmd).await
159 }
160
161 pub async fn wait_for_navigation(&self, current: Option<url::Url>) -> Result<()> {
168 let current = match current {
169 Some(current) => current,
170 None => self.current_url().await?,
171 };
172 loop {
173 if self.current_url().await? != current {
174 break Ok(());
175 }
176 sleep(Duration::from_millis(100)).await
177 }
178 }
179
180 pub async fn find(
183 &self,
184 locator: Locator,
185 root: Option<WebElement>,
186 ) -> Result<WebElement> {
187 let cmd = match root {
188 Option::None => WebDriverCommand::FindElement(locator.into()),
189 Option::Some(elt) => {
190 WebDriverCommand::FindElementElement(elt, locator.into())
191 }
192 };
193 let res = self.0.issue_cmd(&cmd).await?;
194 Ok(self.parse_lookup(res)?)
195 }
196
197 pub async fn find_all(
198 &self,
199 locator: Locator,
200 root: Option<WebElement>,
201 ) -> Result<Vec<WebElement>> {
202 let cmd = match root {
203 Option::None => WebDriverCommand::FindElements(locator.into()),
204 Option::Some(elt) => {
205 WebDriverCommand::FindElementElements(elt, locator.into())
206 }
207 };
208 match self.0.issue_cmd(&cmd).await? {
209 Value::Array(a) => Ok(a
210 .into_iter()
211 .map(|e| self.parse_lookup(e))
212 .collect::<Result<Vec<WebElement>>>()?),
213 r => bail!(ErrorKind::NotW3C(r)),
214 }
215 }
216
217 generate_wait_for_find!(wait_for_find, find, WebElement);
218 generate_wait_for_find!(wait_for_find_all, find_all, Vec<WebElement>);
219
220 fn parse_lookup(&self, mut res: Value) -> Result<WebElement> {
222 let key = if self.0.legacy {
223 "ELEMENT"
224 } else {
225 ELEMENT_KEY
226 };
227 let o = {
228 if let Some(o) = res.as_object_mut() {
229 o
230 } else {
231 bail!(ErrorKind::NotW3C(res))
232 }
233 };
234 match o.remove(key) {
235 None => bail!(ErrorKind::NotW3C(res)),
236 Some(Value::String(wei)) => Ok(webdriver::common::WebElement(wei)),
237 Some(v) => {
238 o.insert(key.to_string(), v);
239 bail!(ErrorKind::NotW3C(res))
240 }
241 }
242 }
243
244 fn fixup_elements(&self, args: &mut [Value]) {
245 if self.0.legacy {
246 for arg in args {
247 if let Value::Object(ref mut o) = *arg {
250 if let Some(wei) = o.remove(ELEMENT_KEY) {
251 o.insert("ELEMENT".to_string(), wei);
252 }
253 }
254 }
255 }
256 }
257
258 pub async fn attr(
260 &self,
261 eid: WebElement,
262 attribute: String,
263 ) -> Result<Option<String>> {
264 let cmd = WebDriverCommand::GetElementAttribute(eid, attribute);
265 match self.0.issue_cmd(&cmd).await? {
266 Value::String(v) => Ok(Some(v)),
267 Value::Null => Ok(None),
268 v => bail!(ErrorKind::NotW3C(v)),
269 }
270 }
271
272 pub async fn prop(&self, eid: WebElement, prop: String) -> Result<Option<String>> {
274 let cmd = WebDriverCommand::GetElementProperty(eid, prop);
275 match self.0.issue_cmd(&cmd).await? {
276 Value::String(v) => Ok(Some(v)),
277 Value::Null => Ok(None),
278 v => bail!(ErrorKind::NotW3C(v)),
279 }
280 }
281
282 pub async fn text(&self, eid: WebElement) -> Result<String> {
284 let cmd = WebDriverCommand::GetElementText(eid);
285 match self.0.issue_cmd(&cmd).await? {
286 Value::String(v) => Ok(v),
287 v => bail!(ErrorKind::NotW3C(v)),
288 }
289 }
290
291 pub async fn html(&self, eid: WebElement, inner: bool) -> Result<String> {
295 let prop = if inner { "innerHTML" } else { "outerHTML" };
296 self.prop(eid, prop.to_owned()).await?
297 .ok_or_else(|| Error::from(ErrorKind::NotW3C(Value::Null)))
298 }
299
300 pub async fn click(&self, eid: WebElement) -> Result<()> {
302 let cmd = WebDriverCommand::ElementClick(eid);
303 let r = self.0.issue_cmd(&cmd).await?;
304 if r.is_null() || r.as_object().map(|o| o.is_empty()).unwrap_or(false) {
305 Ok(())
307 } else {
308 bail!(ErrorKind::NotW3C(r))
309 }
310 }
311
312 pub async fn scroll_into_view(&self, eid: WebElement) -> Result<()> {
314 let args = vec![serde_json::to_value(eid)?];
315 let js = "arguments[0].scrollIntoView(true)".to_string();
316 self.clone().execute(js, args).await?;
317 Ok(())
318 }
319
320 pub async fn follow(&self, eid: WebElement) -> Result<()> {
323 match self.clone().attr(eid.clone(), String::from("href")).await? {
324 None => bail!("no href attribute"),
325 Some(href) => {
326 let current = self.current_url().await?.join(&href)?;
327 self.goto(current.as_str()).await
328 }
329 }
330 }
331
332 pub async fn set_by_name(
334 &self,
335 eid: WebElement,
336 name: String,
337 value: String,
338 ) -> Result<()> {
339 let locator = Locator::Css(format!("input[name='{}']", name));
340 let elt = self.clone().find(locator.into(), Some(eid)).await?;
341 let args = {
342 let mut a = vec![serde_json::to_value(elt)?, Value::String(value)];
343 self.fixup_elements(&mut a);
344 a
345 };
346 let js = "arguments[0].value = arguments[1]".to_string();
347 let res = self.clone().execute(js, args).await?;
348 if res.is_null() {
349 Ok(())
350 } else {
351 bail!(ErrorKind::NotW3C(res))
352 }
353 }
354
355 pub async fn submit(&self, eid: WebElement) -> Result<()> {
357 let l = Locator::Css("input[type=submit],button[type=submit]".into());
358 self.submit_with(eid, l).await
359 }
360
361 pub async fn submit_with(&self, eid: WebElement, button: Locator) -> Result<()> {
363 let elt = self.clone().find(button.into(), Some(eid)).await?;
364 Ok(self.clone().click(elt).await?)
365 }
366
367 pub async fn submit_using(&self, eid: WebElement, button_label: String) -> Result<()> {
370 let escaped = button_label.replace('\\', "\\\\").replace('"', "\\\"");
371 let btn = format!(
372 "input[type=submit][value=\"{}\" i],\
373 button[type=submit][value=\"{}\" i]",
374 escaped, escaped
375 );
376 Ok(self.submit_with(eid, Locator::Css(btn)).await?)
377 }
378
379 pub async fn submit_direct(&self, eid: WebElement) -> Result<()> {
390 let js = "document.createElement('form').submit.call(arguments[0])".to_string();
398 let args = {
399 let mut a = vec![serde_json::to_value(eid)?];
400 self.fixup_elements(&mut a);
401 a
402 };
403 self.clone().execute(js, args).await?;
404 Ok(())
405 }
406
407 pub async fn submit_sneaky(
417 &self,
418 eid: WebElement,
419 field: String,
420 value: String,
421 ) -> Result<()> {
422 let js = r#"
423 var h = document.createElement('input');
424 h.setAttribute('type', 'hidden');
425 h.setAttribute('name', arguments[1]);
426 h.value = arguments[2];
427 arguments[0].appendChild(h);
428 "#
429 .to_string();
430 let args = {
431 let mut a = vec![
432 serde_json::to_value(eid)?,
433 Value::String(field),
434 Value::String(value),
435 ];
436 self.fixup_elements(&mut a);
437 a
438 };
439 self.execute(js, args).await?;
440 Ok(())
441 }
442}