/* 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 icu_segmenter::GraphemeClusterSegmenter;
use serde::de::{
self , Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_json::Value;
use std::default::Default;
use std::f64;
#[ 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 GraphemeClusterSegmenter::new().segment_str(&value).count() !=
2 {
return Err(de::Error::custom(format!(
"'{}' should only contain a single Unicode code point" ,
value
)));
}
Ok(value)
})?
}
#[ derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[ serde(rename_all =
"lowercase" )]
pub enum PointerType {
#[ default]
Mouse,
Pen,
Touch,
}
#[ 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, Default, 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" )]
#[ default]
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 !(
0 f64..=
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 !(
0 f64..=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 !(
0 f64..=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());
}
}
}
}
}
Messung V0.5 in Prozent C=92 H=98 G=94
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-06-19)
¤
*© Formatika GbR, Deutschland