summaryrefslogtreecommitdiffstats
path: root/testing/webdriver/src/actions.rs
diff options
context:
space:
mode:
Diffstat (limited to 'testing/webdriver/src/actions.rs')
-rw-r--r--testing/webdriver/src/actions.rs1403
1 files changed, 1403 insertions, 0 deletions
diff --git a/testing/webdriver/src/actions.rs b/testing/webdriver/src/actions.rs
new file mode 100644
index 0000000000..f5bc26ed3f
--- /dev/null
+++ b/testing/webdriver/src/actions.rs
@@ -0,0 +1,1403 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::common::{WebElement, ELEMENT_KEY};
+use serde::de::{self, Deserialize, Deserializer};
+use serde::ser::{Serialize, Serializer};
+use serde_json::Value;
+use std::default::Default;
+use std::f64;
+use unicode_segmentation::UnicodeSegmentation;
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct ActionSequence {
+ pub id: String,
+ #[serde(flatten)]
+ pub actions: ActionsType,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum ActionsType {
+ #[serde(rename = "none")]
+ Null { actions: Vec<NullActionItem> },
+ #[serde(rename = "key")]
+ Key { actions: Vec<KeyActionItem> },
+ #[serde(rename = "pointer")]
+ Pointer {
+ #[serde(default)]
+ parameters: PointerActionParameters,
+ actions: Vec<PointerActionItem>,
+ },
+ #[serde(rename = "wheel")]
+ Wheel { actions: Vec<WheelActionItem> },
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum NullActionItem {
+ General(GeneralAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum GeneralAction {
+ #[serde(rename = "pause")]
+ Pause(PauseAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct PauseAction {
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub duration: Option<u64>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum KeyActionItem {
+ General(GeneralAction),
+ Key(KeyAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum KeyAction {
+ #[serde(rename = "keyDown")]
+ Down(KeyDownAction),
+ #[serde(rename = "keyUp")]
+ Up(KeyUpAction),
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyDownAction {
+ #[serde(deserialize_with = "deserialize_key_action_value")]
+ pub value: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyUpAction {
+ #[serde(deserialize_with = "deserialize_key_action_value")]
+ pub value: String,
+}
+
+fn deserialize_key_action_value<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ String::deserialize(deserializer).map(|value| {
+ // Only a single Unicode grapheme cluster is allowed
+ if value.graphemes(true).count() != 1 {
+ return Err(de::Error::custom(format!(
+ "'{}' should only contain a single Unicode code point",
+ value
+ )));
+ }
+
+ Ok(value)
+ })?
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PointerType {
+ Mouse,
+ Pen,
+ Touch,
+}
+
+impl Default for PointerType {
+ fn default() -> PointerType {
+ PointerType::Mouse
+ }
+}
+
+#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PointerActionParameters {
+ #[serde(rename = "pointerType")]
+ pub pointer_type: PointerType,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum PointerActionItem {
+ General(GeneralAction),
+ Pointer(PointerAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum PointerAction {
+ #[serde(rename = "pointerCancel")]
+ Cancel,
+ #[serde(rename = "pointerDown")]
+ Down(PointerDownAction),
+ #[serde(rename = "pointerMove")]
+ Move(PointerMoveAction),
+ #[serde(rename = "pointerUp")]
+ Up(PointerUpAction),
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PointerDownAction {
+ pub button: u64,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub width: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub height: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_pressure"
+ )]
+ pub pressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tangential_pressure"
+ )]
+ pub tangentialPressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltX: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltY: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_twist"
+ )]
+ pub twist: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_altitude_angle"
+ )]
+ pub altitudeAngle: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_azimuth_angle"
+ )]
+ pub azimuthAngle: Option<f64>,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PointerMoveAction {
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub duration: Option<u64>,
+ #[serde(default)]
+ pub origin: PointerOrigin,
+ pub x: i64,
+ pub y: i64,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub width: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub height: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_pressure"
+ )]
+ pub pressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tangential_pressure"
+ )]
+ pub tangentialPressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltX: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltY: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_twist"
+ )]
+ pub twist: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_altitude_angle"
+ )]
+ pub altitudeAngle: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_azimuth_angle"
+ )]
+ pub azimuthAngle: Option<f64>,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PointerUpAction {
+ pub button: u64,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub width: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub height: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_pressure"
+ )]
+ pub pressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tangential_pressure"
+ )]
+ pub tangentialPressure: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltX: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_tilt"
+ )]
+ pub tiltY: Option<i64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_twist"
+ )]
+ pub twist: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_altitude_angle"
+ )]
+ pub altitudeAngle: Option<f64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_azimuth_angle"
+ )]
+ pub azimuthAngle: Option<f64>,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize)]
+pub enum PointerOrigin {
+ #[serde(
+ rename = "element-6066-11e4-a52e-4f735466cecf",
+ serialize_with = "serialize_webelement_id"
+ )]
+ Element(WebElement),
+ #[serde(rename = "pointer")]
+ Pointer,
+ #[serde(rename = "viewport")]
+ Viewport,
+}
+
+impl Default for PointerOrigin {
+ fn default() -> PointerOrigin {
+ PointerOrigin::Viewport
+ }
+}
+
+// TODO: The custom deserializer can be removed once the support of the legacy
+// ELEMENT key has been removed from Selenium bindings
+// See: https://github.com/SeleniumHQ/selenium/issues/6393
+impl<'de> Deserialize<'de> for PointerOrigin {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let value = Value::deserialize(deserializer)?;
+ if let Some(web_element) = value.get(ELEMENT_KEY) {
+ String::deserialize(web_element)
+ .map(|id| PointerOrigin::Element(WebElement(id)))
+ .map_err(de::Error::custom)
+ } else if value == "pointer" {
+ Ok(PointerOrigin::Pointer)
+ } else if value == "viewport" {
+ Ok(PointerOrigin::Viewport)
+ } else {
+ Err(de::Error::custom(format!(
+ "unknown value `{}`, expected `pointer`, `viewport`, or `element-6066-11e4-a52e-4f735466cecf`",
+ value
+ )))
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum WheelActionItem {
+ General(GeneralAction),
+ Wheel(WheelAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum WheelAction {
+ #[serde(rename = "scroll")]
+ Scroll(WheelScrollAction),
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct WheelScrollAction {
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_option_u64"
+ )]
+ pub duration: Option<u64>,
+ #[serde(default)]
+ pub origin: PointerOrigin,
+ pub x: Option<i64>,
+ pub y: Option<i64>,
+ pub deltaX: Option<i64>,
+ pub deltaY: Option<i64>,
+}
+
+fn serialize_webelement_id<S>(element: &WebElement, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ element.to_string().serialize(serializer)
+}
+
+fn deserialize_to_option_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ Option::deserialize(deserializer)?
+ .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
+}
+
+fn deserialize_to_option_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ Option::deserialize(deserializer)?
+ .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
+}
+
+fn deserialize_to_option_f64<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ Option::deserialize(deserializer)?
+ .ok_or_else(|| de::Error::custom("invalid type: null, expected f64"))
+}
+
+fn deserialize_to_pressure<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_f64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(0f64..=1.0).contains(&value) {
+ return Err(de::Error::custom(format!("{} is outside range 0-1", value)));
+ }
+ };
+ Ok(opt_value)
+}
+
+fn deserialize_to_tangential_pressure<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_f64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(-1.0..=1.0).contains(&value) {
+ return Err(de::Error::custom(format!(
+ "{} is outside range -1-1",
+ value
+ )));
+ }
+ };
+ Ok(opt_value)
+}
+
+fn deserialize_to_tilt<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_i64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(-90..=90).contains(&value) {
+ return Err(de::Error::custom(format!(
+ "{} is outside range -90-90",
+ value
+ )));
+ }
+ };
+ Ok(opt_value)
+}
+
+fn deserialize_to_twist<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_u64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(0..=359).contains(&value) {
+ return Err(de::Error::custom(format!(
+ "{} is outside range 0-359",
+ value
+ )));
+ }
+ };
+ Ok(opt_value)
+}
+
+fn deserialize_to_altitude_angle<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_f64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(0f64..=f64::consts::FRAC_PI_2).contains(&value) {
+ return Err(de::Error::custom(format!(
+ "{} is outside range 0-PI/2",
+ value
+ )));
+ }
+ };
+ Ok(opt_value)
+}
+
+fn deserialize_to_azimuth_angle<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt_value = deserialize_to_option_f64(deserializer)?;
+ if let Some(value) = opt_value {
+ if !(0f64..=f64::consts::TAU).contains(&value) {
+ return Err(de::Error::custom(format!(
+ "{} is outside range 0-2*PI",
+ value
+ )));
+ }
+ };
+ Ok(opt_value)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::test::{assert_de, assert_ser_de};
+ use serde_json::{self, json, Value};
+
+ #[test]
+ fn test_json_action_sequence_null() {
+ let json = json!({
+ "id": "some_key",
+ "type": "none",
+ "actions": [{
+ "type": "pause",
+ "duration": 1,
+ }]
+ });
+ let seq = ActionSequence {
+ id: "some_key".into(),
+ actions: ActionsType::Null {
+ actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
+ duration: Some(1),
+ }))],
+ },
+ };
+
+ assert_ser_de(&seq, json);
+ }
+
+ #[test]
+ fn test_json_action_sequence_key() {
+ let json = json!({
+ "id": "some_key",
+ "type": "key",
+ "actions": [
+ {"type": "keyDown", "value": "f"},
+ ],
+ });
+ let seq = ActionSequence {
+ id: "some_key".into(),
+ actions: ActionsType::Key {
+ actions: vec![KeyActionItem::Key(KeyAction::Down(KeyDownAction {
+ value: String::from("f"),
+ }))],
+ },
+ };
+
+ assert_ser_de(&seq, json);
+ }
+
+ #[test]
+ fn test_json_action_sequence_pointer() {
+ let json = json!({
+ "id": "some_pointer",
+ "type": "pointer",
+ "parameters": {
+ "pointerType": "mouse"
+ },
+ "actions": [
+ {"type": "pointerDown", "button": 0},
+ {"type": "pointerMove", "origin": "pointer", "x": 10, "y": 20},
+ {"type": "pointerUp", "button": 0},
+ ]
+ });
+ let seq = ActionSequence {
+ id: "some_pointer".into(),
+ actions: ActionsType::Pointer {
+ parameters: PointerActionParameters {
+ pointer_type: PointerType::Mouse,
+ },
+ actions: vec![
+ PointerActionItem::Pointer(PointerAction::Down(PointerDownAction {
+ button: 0,
+ ..Default::default()
+ })),
+ PointerActionItem::Pointer(PointerAction::Move(PointerMoveAction {
+ origin: PointerOrigin::Pointer,
+ duration: None,
+ x: 10,
+ y: 20,
+ ..Default::default()
+ })),
+ PointerActionItem::Pointer(PointerAction::Up(PointerUpAction {
+ button: 0,
+ ..Default::default()
+ })),
+ ],
+ },
+ };
+
+ assert_ser_de(&seq, json);
+ }
+
+ #[test]
+ fn test_json_action_sequence_id_missing() {
+ let json = json!({
+ "type": "key",
+ "actions": [],
+ });
+ assert!(serde_json::from_value::<ActionSequence>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_action_sequence_id_null() {
+ let json = json!({
+ "id": null,
+ "type": "key",
+ "actions": [],
+ });
+ assert!(serde_json::from_value::<ActionSequence>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_action_sequence_actions_missing() {
+ assert!(serde_json::from_value::<ActionSequence>(json!({"id": "3"})).is_err());
+ }
+
+ #[test]
+ fn test_json_action_sequence_actions_null() {
+ let json = json!({
+ "id": "3",
+ "actions": null,
+ });
+ assert!(serde_json::from_value::<ActionSequence>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_action_sequence_actions_invalid_type() {
+ let json = json!({
+ "id": "3",
+ "actions": "foo",
+ });
+ assert!(serde_json::from_value::<ActionSequence>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_actions_type_null() {
+ let json = json!({
+ "type": "none",
+ "actions": [{
+ "type": "pause",
+ "duration": 1,
+ }],
+ });
+ let null = ActionsType::Null {
+ actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
+ duration: Some(1),
+ }))],
+ };
+
+ assert_ser_de(&null, json);
+ }
+
+ #[test]
+ fn test_json_actions_type_key() {
+ let json = json!({
+ "type": "key",
+ "actions": [{
+ "type": "keyDown",
+ "value": "f",
+ }],
+ });
+ let key = ActionsType::Key {
+ actions: vec![KeyActionItem::Key(KeyAction::Down(KeyDownAction {
+ value: String::from("f"),
+ }))],
+ };
+
+ assert_ser_de(&key, json);
+ }
+
+ #[test]
+ fn test_json_actions_type_pointer() {
+ let json = json!({
+ "type": "pointer",
+ "parameters": {"pointerType": "mouse"},
+ "actions": [
+ {"type": "pointerDown", "button": 1},
+ ]});
+ let pointer = ActionsType::Pointer {
+ parameters: PointerActionParameters {
+ pointer_type: PointerType::Mouse,
+ },
+ actions: vec![PointerActionItem::Pointer(PointerAction::Down(
+ PointerDownAction {
+ button: 1,
+ ..Default::default()
+ },
+ ))],
+ };
+
+ assert_ser_de(&pointer, json);
+ }
+
+ #[test]
+ fn test_json_actions_type_pointer_with_parameters_missing() {
+ let json = json!({
+ "type": "pointer",
+ "actions": [
+ {"type": "pointerDown", "button": 1},
+ ]});
+ let pointer = ActionsType::Pointer {
+ parameters: PointerActionParameters {
+ pointer_type: PointerType::Mouse,
+ },
+ actions: vec![PointerActionItem::Pointer(PointerAction::Down(
+ PointerDownAction {
+ button: 1,
+ ..Default::default()
+ },
+ ))],
+ };
+
+ assert_de(&pointer, json);
+ }
+
+ #[test]
+ fn test_json_actions_type_pointer_with_parameters_invalid_type() {
+ let json = json!({
+ "type": "pointer",
+ "parameters": null,
+ "actions": [
+ {"type":"pointerDown", "button": 1},
+ ]});
+ assert!(serde_json::from_value::<ActionsType>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_actions_type_invalid() {
+ let json = json!({"actions": [{"foo": "bar"}]});
+ assert!(serde_json::from_value::<ActionsType>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_null_action_item_general() {
+ let pause =
+ NullActionItem::General(GeneralAction::Pause(PauseAction { duration: Some(1) }));
+ assert_ser_de(&pause, json!({"type": "pause", "duration": 1}));
+ }
+
+ #[test]
+ fn test_json_null_action_item_invalid_type() {
+ assert!(serde_json::from_value::<NullActionItem>(json!({"type": "invalid"})).is_err());
+ }
+
+ #[test]
+ fn test_json_general_action_pause() {
+ let pause = GeneralAction::Pause(PauseAction { duration: Some(1) });
+ assert_ser_de(&pause, json!({"type": "pause", "duration": 1}));
+ }
+
+ #[test]
+ fn test_json_general_action_pause_with_duration_missing() {
+ let pause = GeneralAction::Pause(PauseAction { duration: None });
+ assert_ser_de(&pause, json!({"type": "pause"}));
+ }
+
+ #[test]
+ fn test_json_general_action_pause_with_duration_null() {
+ let json = json!({"type": "pause", "duration": null});
+ assert!(serde_json::from_value::<GeneralAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_general_action_pause_with_duration_invalid_type() {
+ let json = json!({"type": "pause", "duration":" foo"});
+ assert!(serde_json::from_value::<GeneralAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_general_action_pause_with_duration_negative() {
+ let json = json!({"type": "pause", "duration": -30});
+ assert!(serde_json::from_value::<GeneralAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_item_general() {
+ let pause = KeyActionItem::General(GeneralAction::Pause(PauseAction { duration: Some(1) }));
+ assert_ser_de(&pause, json!({"type": "pause", "duration": 1}));
+ }
+
+ #[test]
+ fn test_json_key_action_item_key() {
+ let key_down = KeyActionItem::Key(KeyAction::Down(KeyDownAction {
+ value: String::from("f"),
+ }));
+ assert_ser_de(&key_down, json!({"type": "keyDown", "value": "f"}));
+ }
+
+ #[test]
+ fn test_json_key_action_item_invalid_type() {
+ assert!(serde_json::from_value::<KeyActionItem>(json!({"type": "invalid"})).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_missing_subtype() {
+ assert!(serde_json::from_value::<KeyAction>(json!({"value": "f"})).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_wrong_subtype() {
+ let json = json!({"type": "pause", "value": "f"});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_down() {
+ let key_down = KeyAction::Down(KeyDownAction {
+ value: "f".to_string(),
+ });
+ assert_ser_de(&key_down, json!({"type": "keyDown", "value": "f"}));
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_value_unicode() {
+ let key_down = KeyAction::Down(KeyDownAction {
+ value: "à".to_string(),
+ });
+ assert_ser_de(&key_down, json!({"type": "keyDown", "value": "à"}));
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_value_unicode_encoded() {
+ let key_down = KeyAction::Down(KeyDownAction {
+ value: "à".to_string(),
+ });
+ assert_de(&key_down, json!({"type": "keyDown", "value": "\u{00E0}"}));
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_value_missing() {
+ assert!(serde_json::from_value::<KeyAction>(json!({"type": "keyDown"})).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_value_null() {
+ let json = json!({"type": "keyDown", "value": null});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_value_invalid_type() {
+ let json = json!({"type": "keyDown", "value": ["f", "o", "o"]});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_down_with_multiple_code_points() {
+ let json = json!({"type": "keyDown", "value": "fo"});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_up() {
+ let key_up = KeyAction::Up(KeyUpAction {
+ value: "f".to_string(),
+ });
+ assert_ser_de(&key_up, json!({"type": "keyUp", "value": "f"}));
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_value_unicode() {
+ let key_up = KeyAction::Up(KeyUpAction {
+ value: "à".to_string(),
+ });
+ assert_ser_de(&key_up, json!({"type":"keyUp", "value": "à"}));
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_value_unicode_encoded() {
+ let key_up = KeyAction::Up(KeyUpAction {
+ value: "à".to_string(),
+ });
+ assert_de(&key_up, json!({"type": "keyUp", "value": "\u{00E0}"}));
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_value_missing() {
+ assert!(serde_json::from_value::<KeyAction>(json!({"type": "keyUp"})).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_value_null() {
+ let json = json!({"type": "keyUp", "value": null});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_value_invalid_type() {
+ let json = json!({"type": "keyUp", "value": ["f","o","o"]});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_key_action_up_with_multiple_code_points() {
+ let json = json!({"type": "keyUp", "value": "fo"});
+ assert!(serde_json::from_value::<KeyAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_item_general() {
+ let pause =
+ PointerActionItem::General(GeneralAction::Pause(PauseAction { duration: Some(1) }));
+ assert_ser_de(&pause, json!({"type": "pause", "duration": 1}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_item_pointer() {
+ let cancel = PointerActionItem::Pointer(PointerAction::Cancel);
+ assert_ser_de(&cancel, json!({"type": "pointerCancel"}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_item_invalid() {
+ assert!(serde_json::from_value::<PointerActionItem>(json!({"type": "invalid"})).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_parameters_mouse() {
+ let mouse = PointerActionParameters {
+ pointer_type: PointerType::Mouse,
+ };
+ assert_ser_de(&mouse, json!({"pointerType": "mouse"}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_parameters_pen() {
+ let pen = PointerActionParameters {
+ pointer_type: PointerType::Pen,
+ };
+ assert_ser_de(&pen, json!({"pointerType": "pen"}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_parameters_touch() {
+ let touch = PointerActionParameters {
+ pointer_type: PointerType::Touch,
+ };
+ assert_ser_de(&touch, json!({"pointerType": "touch"}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_item_invalid_type() {
+ let json = json!({"type": "pointerInvalid"});
+ assert!(serde_json::from_value::<PointerActionItem>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_missing_subtype() {
+ assert!(serde_json::from_value::<PointerAction>(json!({"button": 1})).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_invalid_subtype() {
+ let json = json!({"type": "invalid", "button": 1});
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_cancel() {
+ assert_ser_de(&PointerAction::Cancel, json!({"type": "pointerCancel"}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_down() {
+ let pointer_down = PointerAction::Down(PointerDownAction {
+ button: 1,
+ ..Default::default()
+ });
+ assert_ser_de(&pointer_down, json!({"type": "pointerDown", "button": 1}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_down_with_button_missing() {
+ let json = json!({"type": "pointerDown"});
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_down_with_button_null() {
+ let json = json!({
+ "type": "pointerDown",
+ "button": null,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_down_with_button_invalid_type() {
+ let json = json!({
+ "type": "pointerDown",
+ "button": "foo",
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_down_with_button_negative() {
+ let json = json!({
+ "type": "pointerDown",
+ "button": -30,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ let pointer_move = PointerAction::Move(PointerMoveAction {
+ duration: Some(100),
+ origin: PointerOrigin::Viewport,
+ x: 5,
+ y: 10,
+ ..Default::default()
+ });
+
+ assert_ser_de(&pointer_move, json);
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_missing_subtype() {
+ let json = json!({
+ "duration": 100,
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_wrong_subtype() {
+ let json = json!({
+ "type": "pointerUp",
+ "duration": 100,
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_duration_missing() {
+ let json = json!({
+ "type": "pointerMove",
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ let pointer_move = PointerAction::Move(PointerMoveAction {
+ duration: None,
+ origin: PointerOrigin::Viewport,
+ x: 5,
+ y: 10,
+ ..Default::default()
+ });
+
+ assert_ser_de(&pointer_move, json);
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_duration_null() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": null,
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_duration_invalid_type() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": "invalid",
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_duration_negative() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": -30,
+ "origin": "viewport",
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_origin_missing() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "x": 5,
+ "y": 10,
+ });
+ let pointer_move = PointerAction::Move(PointerMoveAction {
+ duration: Some(100),
+ origin: PointerOrigin::Viewport,
+ x: 5,
+ y: 10,
+ ..Default::default()
+ });
+
+ assert_de(&pointer_move, json);
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_origin_webelement() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": {ELEMENT_KEY: "elem"},
+ "x": 5,
+ "y": 10,
+ });
+ let pointer_move = PointerAction::Move(PointerMoveAction {
+ duration: Some(100),
+ origin: PointerOrigin::Element(WebElement("elem".into())),
+ x: 5,
+ y: 10,
+ ..Default::default()
+ });
+
+ assert_ser_de(&pointer_move, json);
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_origin_webelement_and_legacy_element() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": {ELEMENT_KEY: "elem"},
+ "x": 5,
+ "y": 10,
+ });
+ let pointer_move = PointerAction::Move(PointerMoveAction {
+ duration: Some(100),
+ origin: PointerOrigin::Element(WebElement("elem".into())),
+ x: 5,
+ y: 10,
+ ..Default::default()
+ });
+
+ assert_de(&pointer_move, json);
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_origin_only_legacy_element() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": {ELEMENT_KEY: "elem"},
+ "x": 5,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerOrigin>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_x_null() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": "viewport",
+ "x": null,
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_x_invalid_type() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": "viewport",
+ "x": "invalid",
+ "y": 10,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_y_null() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": "viewport",
+ "x": 5,
+ "y": null,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_move_with_y_invalid_type() {
+ let json = json!({
+ "type": "pointerMove",
+ "duration": 100,
+ "origin": "viewport",
+ "x": 5,
+ "y": "invalid",
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_up() {
+ let pointer_up = PointerAction::Up(PointerUpAction {
+ button: 1,
+ ..Default::default()
+ });
+ assert_ser_de(&pointer_up, json!({"type": "pointerUp", "button": 1}));
+ }
+
+ #[test]
+ fn test_json_pointer_action_up_with_button_missing() {
+ assert!(serde_json::from_value::<PointerAction>(json!({"type": "pointerUp"})).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_up_with_button_null() {
+ let json = json!({
+ "type": "pointerUp",
+ "button": null,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_up_with_button_invalid_type() {
+ let json = json!({
+ "type": "pointerUp",
+ "button": "foo",
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_action_up_with_button_negative() {
+ let json = json!({
+ "type": "pointerUp",
+ "button": -30,
+ });
+ assert!(serde_json::from_value::<PointerAction>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_origin_pointer() {
+ assert_ser_de(&PointerOrigin::Pointer, json!("pointer"));
+ }
+
+ #[test]
+ fn test_json_pointer_origin_viewport() {
+ assert_ser_de(&PointerOrigin::Viewport, json!("viewport"));
+ }
+
+ #[test]
+ fn test_json_pointer_origin_web_element() {
+ let element = PointerOrigin::Element(WebElement("elem".into()));
+ assert_ser_de(&element, json!({ELEMENT_KEY: "elem"}));
+ }
+
+ #[test]
+ fn test_json_pointer_origin_invalid_type() {
+ assert!(serde_json::from_value::<PointerOrigin>(json!("invalid")).is_err());
+ }
+
+ #[test]
+ fn test_json_pointer_type_mouse() {
+ assert_ser_de(&PointerType::Mouse, json!("mouse"));
+ }
+
+ #[test]
+ fn test_json_pointer_type_pen() {
+ assert_ser_de(&PointerType::Pen, json!("pen"));
+ }
+
+ #[test]
+ fn test_json_pointer_type_touch() {
+ assert_ser_de(&PointerType::Touch, json!("touch"));
+ }
+
+ #[test]
+ fn test_json_pointer_type_invalid_type() {
+ assert!(serde_json::from_value::<PointerType>(json!("invalid")).is_err());
+ }
+
+ #[test]
+ fn test_pointer_properties() {
+ // Ideally these would be seperate tests, but it was too much boilerplate to write
+ // and adding a macro seemed like overkill.
+ for actionType in ["pointerUp", "pointerDown", "pointerMove"] {
+ for (prop_name, value, is_valid) in [
+ ("pressure", Value::from(0), true),
+ ("pressure", Value::from(0.5), true),
+ ("pressure", Value::from(1), true),
+ ("pressure", Value::from(1.1), false),
+ ("pressure", Value::from(-0.1), false),
+ ("tangentialPressure", Value::from(-1), true),
+ ("tangentialPressure", Value::from(0), true),
+ ("tangentialPressure", Value::from(1.0), true),
+ ("tangentialPressure", Value::from(-1.1), false),
+ ("tangentialPressure", Value::from(1.1), false),
+ ("tiltX", Value::from(-90), true),
+ ("tiltX", Value::from(0), true),
+ ("tiltX", Value::from(45), true),
+ ("tiltX", Value::from(90), true),
+ ("tiltX", Value::from(0.5), false),
+ ("tiltX", Value::from(-91), false),
+ ("tiltX", Value::from(91), false),
+ ("tiltY", Value::from(-90), true),
+ ("tiltY", Value::from(0), true),
+ ("tiltY", Value::from(45), true),
+ ("tiltY", Value::from(90), true),
+ ("tiltY", Value::from(0.5), false),
+ ("tiltY", Value::from(-91), false),
+ ("tiltY", Value::from(91), false),
+ ("twist", Value::from(0), true),
+ ("twist", Value::from(180), true),
+ ("twist", Value::from(359), true),
+ ("twist", Value::from(360), false),
+ ("twist", Value::from(-1), false),
+ ("twist", Value::from(23.5), false),
+ ("altitudeAngle", Value::from(0), true),
+ ("altitudeAngle", Value::from(f64::consts::FRAC_PI_4), true),
+ ("altitudeAngle", Value::from(f64::consts::FRAC_PI_2), true),
+ (
+ "altitudeAngle",
+ Value::from(f64::consts::FRAC_PI_2 + 0.1),
+ false,
+ ),
+ ("altitudeAngle", Value::from(-f64::consts::FRAC_PI_4), false),
+ ("azimuthAngle", Value::from(0), true),
+ ("azimuthAngle", Value::from(f64::consts::PI), true),
+ ("azimuthAngle", Value::from(f64::consts::TAU), true),
+ ("azimuthAngle", Value::from(f64::consts::TAU + 0.01), false),
+ ("azimuthAngle", Value::from(-f64::consts::FRAC_PI_4), false),
+ ] {
+ let mut json = serde_json::Map::new();
+ json.insert("type".into(), actionType.into());
+ if actionType != "pointerMove" {
+ json.insert("button".into(), Value::from(0));
+ } else {
+ json.insert("x".into(), Value::from(0));
+ json.insert("y".into(), Value::from(0));
+ }
+ json.insert(prop_name.into(), value);
+ println!("{:?}", json);
+ let deserialized = serde_json::from_value::<PointerAction>(json.into());
+ if is_valid {
+ assert!(deserialized.is_ok());
+ } else {
+ assert!(deserialized.is_err());
+ }
+ }
+ }
+ }
+}