use serde::{Deserialize, Serialize};
pub const MAX_ACTIONS: usize = 100;
pub const MAX_TOTAL_WAIT_SECS: u64 = 300;
pub const MAX_SINGLE_WAIT_MS: u64 = 300_000;
pub const MAX_SELECTOR_LEN: usize = 4096;
pub const MAX_SCRIPT_LEN: usize = 1_048_576;
pub const MAX_TEXT_LEN: usize = 1_048_576;
pub const MAX_SCROLL_AMOUNT: i64 = 100_000;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
#[derive(Default)]
pub enum PageAction {
Click {
selector: String,
},
#[serde(rename = "type")]
TypeText {
selector: String,
text: String,
},
Press {
key: String,
},
Scroll {
direction: ScrollDirection,
#[serde(default, skip_serializing_if = "Option::is_none")]
selector: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
amount: Option<i64>,
},
Wait {
#[serde(default, skip_serializing_if = "Option::is_none")]
milliseconds: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
selector: Option<String>,
},
Screenshot {
#[serde(
default,
rename = "fullPage",
alias = "full_page",
skip_serializing_if = "Option::is_none"
)]
full_page: Option<bool>,
},
#[serde(rename = "executeJs")]
ExecuteJs {
script: String,
},
#[default]
Scrape,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ScrollDirection {
Up,
#[default]
Down,
}
impl From<ScrollDirection> for String {
fn from(direction: ScrollDirection) -> Self {
match direction {
ScrollDirection::Up => "up".to_owned(),
ScrollDirection::Down => "down".to_owned(),
}
}
}
impl From<String> for ScrollDirection {
fn from(value: String) -> Self {
value.as_str().into()
}
}
impl From<&str> for ScrollDirection {
fn from(value: &str) -> Self {
match value {
"up" | "Up" | "UP" => Self::Up,
_ => Self::Down,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_wait_with_only_required_fields() {
let json = r#"{"type":"wait"}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(
action,
PageAction::Wait {
milliseconds: None,
selector: None
}
);
}
#[test]
fn deserialize_wait_selector_only() {
let json = r##"{"type":"wait","selector":"#content"}"##;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(
action,
PageAction::Wait {
milliseconds: None,
selector: Some("#content".to_owned())
}
);
}
#[test]
fn deserialize_wait_milliseconds_only() {
let json = r#"{"type":"wait","milliseconds":500}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(
action,
PageAction::Wait {
milliseconds: Some(500),
selector: None
}
);
}
#[test]
fn deserialize_scroll_direction_only() {
let json = r#"{"type":"scroll","direction":"down"}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(
action,
PageAction::Scroll {
direction: ScrollDirection::Down,
selector: None,
amount: None
}
);
}
#[test]
fn deserialize_scroll_with_selector_no_amount() {
let json = r##"{"type":"scroll","direction":"up","selector":"#list"}"##;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(
action,
PageAction::Scroll {
direction: ScrollDirection::Up,
selector: Some("#list".to_owned()),
amount: None,
}
);
}
#[test]
fn deserialize_screenshot_no_fields() {
let json = r#"{"type":"screenshot"}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(action, PageAction::Screenshot { full_page: None });
}
#[test]
fn deserialize_screenshot_full_page_true() {
let json = r#"{"type":"screenshot","fullPage":true}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(action, PageAction::Screenshot { full_page: Some(true) });
}
#[test]
fn deserialize_screenshot_full_page_alias() {
let json = r#"{"type":"screenshot","full_page":false}"#;
let action: PageAction = serde_json::from_str(json).unwrap();
assert_eq!(action, PageAction::Screenshot { full_page: Some(false) });
}
}