Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stevepryde committed Jan 24, 2022
1 parent 66a698a commit 8fd8ce0
Show file tree
Hide file tree
Showing 10 changed files with 763 additions and 19 deletions.
37 changes: 27 additions & 10 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ pub enum CmdError {
/// ["no such window"]: https://www.w3.org/TR/webdriver/#dfn-no-such-window
NoSuchWindow(WebDriver),

/// The requested alert does not exist.
///
/// This variant lifts the ["no such alert"] error variant from `Standard` to simplify
/// checking for it in user code.
///
/// ["no such alert"]: https://www.w3.org/TR/webdriver/#dfn-no-such-alert
NoSuchAlert(WebDriver),

/// A bad URL was encountered during parsing.
///
/// This normally happens if a link is clicked or the current URL is requested, but the URL in
Expand Down Expand Up @@ -150,14 +158,20 @@ impl CmdError {
}

pub(crate) fn from_webdriver_error(e: webdriver::WebDriverError) -> Self {
if let webdriver::WebDriverError {
error: webdriver::ErrorStatus::NoSuchElement,
..
} = e
{
CmdError::NoSuchElement(WebDriver::from_upstream_error(e))
} else {
CmdError::Standard(WebDriver::from_upstream_error(e))
match e {
webdriver::WebDriverError {
error: webdriver::ErrorStatus::NoSuchElement,
..
} => CmdError::NoSuchElement(WebDriver::from_upstream_error(e)),
webdriver::WebDriverError {
error: webdriver::ErrorStatus::NoSuchWindow,
..
} => CmdError::NoSuchWindow(WebDriver::from_upstream_error(e)),
webdriver::WebDriverError {
error: webdriver::ErrorStatus::NoSuchAlert,
..
} => CmdError::NoSuchAlert(WebDriver::from_upstream_error(e)),
_ => CmdError::Standard(WebDriver::from_upstream_error(e)),
}
}
}
Expand All @@ -168,6 +182,7 @@ impl Error for CmdError {
CmdError::Standard(..) => "webdriver returned error",
CmdError::NoSuchElement(..) => "no element found matching selector",
CmdError::NoSuchWindow(..) => "no window is currently selected",
CmdError::NoSuchAlert(..) => "no alert is currently visible",
CmdError::BadUrl(..) => "bad url provided",
CmdError::Failed(..) => "webdriver could not be reached",
CmdError::Lost(..) => "webdriver connection lost",
Expand All @@ -184,7 +199,8 @@ impl Error for CmdError {
match *self {
CmdError::Standard(ref e)
| CmdError::NoSuchElement(ref e)
| CmdError::NoSuchWindow(ref e) => Some(e),
| CmdError::NoSuchWindow(ref e)
| CmdError::NoSuchAlert(ref e) => Some(e),
CmdError::BadUrl(ref e) => Some(e),
CmdError::Failed(ref e) => Some(e),
CmdError::Lost(ref e) => Some(e),
Expand All @@ -205,7 +221,8 @@ impl fmt::Display for CmdError {
match *self {
CmdError::Standard(ref e)
| CmdError::NoSuchElement(ref e)
| CmdError::NoSuchWindow(ref e) => write!(f, "{}", e),
| CmdError::NoSuchWindow(ref e)
| CmdError::NoSuchAlert(ref e) => write!(f, "{}", e),
CmdError::BadUrl(ref e) => write!(f, "{}", e),
CmdError::Failed(ref e) => write!(f, "{}", e),
CmdError::Lost(ref e) => write!(f, "{}", e),
Expand Down
9 changes: 8 additions & 1 deletion src/keys.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Key codes for use with Actions.

use std::fmt::{Display, Formatter};
use std::ops::{Add, AddAssign};

/// Key codes for use with Actions.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Key {
/// Null
Null,
Expand Down Expand Up @@ -182,6 +183,12 @@ impl From<Key> for char {
}
}

impl Display for Key {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", char::from(self.clone()))
}
}

impl Add<Key> for String {
type Output = String;

Expand Down
1 change: 1 addition & 0 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ where
"invalid session id" => ErrorStatus::InvalidSessionId,
"no such element" => ErrorStatus::NoSuchElement,
"no such window" => ErrorStatus::NoSuchWindow,
"no such alert" => ErrorStatus::NoSuchAlert,
"stale element reference" => ErrorStatus::NoSuchElement,
_ => unreachable!(
"received unknown error ({}) for NOT_FOUND status code",
Expand Down
11 changes: 8 additions & 3 deletions src/wd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,19 @@ impl<'a> Locator<'a> {
/// See [8.3 Status](https://www.w3.org/TR/webdriver1/#status) of the WebDriver standard.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebDriverStatus {
ready: bool,
message: String,
/// True if the webdriver is ready to start a new session.
///
/// NOTE: Geckodriver will return `false` if a session has already started, since it
/// only supports a single session.
pub ready: bool,
/// The current status message.
pub message: String,
}

/// Timeout configuration, for various timeout settings.
///
/// Used by [`Client::get_timeouts()`] and [`Client::set_timeouts()`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct TimeoutConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
script: Option<u64>,
Expand Down
235 changes: 235 additions & 0 deletions tests/actions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
//! Alert tests
#[macro_use]
extern crate serial_test;
extern crate fantoccini;
extern crate futures_util;

use crate::common::sample_page_url;
use fantoccini::actions::{
Actions, InputSource, KeyAction, KeyActions, MouseActions, NullActions, PointerAction,
MOUSE_BUTTON_LEFT,
};
use fantoccini::keys::Key;
use fantoccini::{error, Client, Locator};
use std::time::Duration;
use time::Instant;

mod common;

async fn actions_null(mut c: Client, port: u16) -> Result<(), error::CmdError> {
let sample_url = sample_page_url(port);
c.goto(&sample_url).await?;
let null_actions = NullActions::new("null".to_string()).pause(Duration::from_secs(1));
let now = Instant::now();
c.perform_actions(vec![null_actions].into()).await?;
assert!(now.elapsed().as_seconds_f64() >= 1.0);
Ok(())
}

async fn actions_key(mut c: Client, port: u16) -> Result<(), error::CmdError> {
let sample_url = sample_page_url(port);
c.goto(&sample_url).await?;

// Test pause.
let key_pause = KeyActions::new("key".to_string()).pause(Duration::from_secs(1));
let now = Instant::now();
c.perform_actions(vec![key_pause].into()).await?;
assert!(now.elapsed().as_seconds_f64() >= 1.0);

// Test key down/up.
let mut elem = c.find(Locator::Id("text-input")).await?;
assert_eq!(elem.prop("value").await?.unwrap(), "");

let key_actions = KeyActions::new("key".to_string())
.then(KeyAction::Down { value: 'a' })
.then(KeyAction::Up { value: 'a' });
elem.click().await?;
c.perform_actions(vec![key_actions].into()).await?;
let mut elem = c.find(Locator::Id("text-input")).await?;
assert_eq!(elem.prop("value").await?.unwrap(), "a");
Ok(())
}

async fn actions_mouse(mut c: Client, port: u16) -> Result<(), error::CmdError> {
let sample_url = sample_page_url(port);
c.goto(&sample_url).await?;

// Test pause.
let mouse_pause = MouseActions::new("mouse".to_string()).pause(Duration::from_secs(1));
let now = Instant::now();
c.perform_actions(vec![mouse_pause].into()).await?;
assert!(now.elapsed().as_seconds_f64() >= 1.0);

let elem = c.find(Locator::Id("button-alert")).await?;

// Test mouse down/up.
let mouse_actions = MouseActions::new("mouse".to_string())
.then(PointerAction::MoveToElement {
element: elem,
duration: None,
x: 0,
y: 0,
})
.then(PointerAction::Down {
button: MOUSE_BUTTON_LEFT,
})
.then(PointerAction::Up {
button: MOUSE_BUTTON_LEFT,
});

c.perform_actions(vec![mouse_actions].into()).await?;
assert_eq!(c.get_alert_text().await?, "This is an alert");
c.dismiss_alert().await?;
Ok(())
}

async fn actions_mouse_move(mut c: Client, port: u16) -> Result<(), error::CmdError> {
let sample_url = sample_page_url(port);
c.goto(&sample_url).await?;

let mut elem = c.find(Locator::Id("text-input")).await?;
let rect = elem.rectangle().await?;
let elem_center_y = rect.1 + (rect.3 / 2.0);

elem.send_keys("fantoccini").await?;
assert_eq!(elem.prop("value").await?.unwrap(), "fantoccini");

// Test mouse MoveTo and MoveBy, by implementing drag-and-drop to select text
// in the text input element.
let mouse_actions = MouseActions::new("mouse".to_string())
// Move to the left edge of the input element.
// Offset by 1 pixel to ensure we click inside the element.
.then(PointerAction::MoveTo {
duration: None,
x: (rect.0 as i64) + 1,
y: elem_center_y as i64,
})
// Press left mouse button down.
.then(PointerAction::Down {
button: MOUSE_BUTTON_LEFT,
})
// Drag mouse to the right edge of the input element.
// Reduce width by 2 pixels to ensure we stop dragging just inside the right edge.
.then(PointerAction::MoveBy {
duration: None,
x: (rect.2 as i64) - 2,
y: 0,
})
// Release left mouse button.
.then(PointerAction::Up {
button: MOUSE_BUTTON_LEFT,
});

// Press the delete key after the mouse actions. Note that we need to pause
// once for each mouse action, so that the Key down action occurs afterwards.
let key_actions = KeyActions::new("key".to_string())
.pause(Duration::default())
.pause(Duration::default())
.pause(Duration::default())
.pause(Duration::default())
.then(KeyAction::Down {
value: Key::Delete.into(),
});

let actions = Actions::from(mouse_actions).and(key_actions);
c.perform_actions(actions).await?;
assert_eq!(elem.prop("value").await?.unwrap(), "");
Ok(())
}

async fn actions_release(mut c: Client, port: u16) -> Result<(), error::CmdError> {
let sample_url = sample_page_url(port);
c.goto(&sample_url).await?;

// Focus the input element.
let elem = c.find(Locator::Id("text-input")).await?;
elem.click().await?;

// Add initial text.
let mut elem = c.find(Locator::Id("text-input")).await?;
assert_eq!(elem.prop("value").await?.unwrap(), "");

// Press CONTROL key down and hold it.
let key_actions = KeyActions::new("key".to_string()).then(KeyAction::Down {
value: Key::Control.into(),
});
c.perform_actions(vec![key_actions].into()).await?;

// Now release all actions. This should release the control key.
c.release_actions().await?;

// Now press the 'a' key again.
//
// If the Control key was not released, this would do `Ctrl+a` (i.e. select all)
// but there is no text so it would do nothing.
//
// However if the Control key was released (as expected)
// then this will type 'a' into the text element.
let key_actions = KeyActions::new("key".to_string()).then(KeyAction::Down { value: 'a' });
c.perform_actions(vec![key_actions].into()).await?;
assert_eq!(elem.prop("value").await?.unwrap(), "a");
Ok(())
}

mod firefox {
use super::*;

#[test]
#[serial]
fn actions_null_test() {
local_tester!(actions_null, "firefox");
}

#[test]
#[serial]
fn actions_key_test() {
local_tester!(actions_key, "firefox");
}

#[test]
#[serial]
fn actions_mouse_test() {
local_tester!(actions_mouse, "firefox");
}

#[test]
#[serial]
fn actions_mouse_move_test() {
local_tester!(actions_mouse_move, "firefox");
}

#[test]
#[serial]
fn actions_release_test() {
local_tester!(actions_release, "firefox");
}
}

mod chrome {
use super::*;

#[test]
fn actions_null_test() {
local_tester!(actions_null, "chrome");
}

#[test]
fn actions_key_test() {
local_tester!(actions_key, "chrome");
}

#[test]
fn actions_mouse_test() {
local_tester!(actions_mouse, "chrome");
}

#[test]
fn actions_mouse_move_test() {
local_tester!(actions_mouse_move, "chrome");
}

#[test]
fn actions_release_test() {
local_tester!(actions_release, "chrome");
}
}
Loading

0 comments on commit 8fd8ce0

Please sign in to comment.