summaryrefslogtreecommitdiffstats
path: root/testing/webdriver/src/command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'testing/webdriver/src/command.rs')
-rw-r--r--testing/webdriver/src/command.rs1536
1 files changed, 1536 insertions, 0 deletions
diff --git a/testing/webdriver/src/command.rs b/testing/webdriver/src/command.rs
new file mode 100644
index 0000000000..889fb55f79
--- /dev/null
+++ b/testing/webdriver/src/command.rs
@@ -0,0 +1,1536 @@
+/* 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::actions::ActionSequence;
+use crate::capabilities::{
+ BrowserCapabilities, Capabilities, CapabilitiesMatching, LegacyNewSessionParameters,
+ SpecNewSessionParameters,
+};
+use crate::common::{Date, FrameId, LocatorStrategy, WebElement, MAX_SAFE_INTEGER};
+use crate::error::{ErrorStatus, WebDriverError, WebDriverResult};
+use crate::httpapi::{Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute};
+use crate::Parameters;
+use serde::de::{self, Deserialize, Deserializer};
+use serde_json::{self, Value};
+
+#[derive(Debug, PartialEq)]
+pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
+ NewSession(NewSessionParameters),
+ DeleteSession,
+ Get(GetParameters),
+ GetCurrentUrl,
+ GoBack,
+ GoForward,
+ Refresh,
+ GetTitle,
+ GetPageSource,
+ GetWindowHandle,
+ GetWindowHandles,
+ NewWindow(NewWindowParameters),
+ CloseWindow,
+ GetWindowRect,
+ SetWindowRect(WindowRectParameters),
+ MinimizeWindow,
+ MaximizeWindow,
+ FullscreenWindow,
+ SwitchToWindow(SwitchToWindowParameters),
+ SwitchToFrame(SwitchToFrameParameters),
+ SwitchToParentFrame,
+ FindElement(LocatorParameters),
+ FindElements(LocatorParameters),
+ FindElementElement(WebElement, LocatorParameters),
+ FindElementElements(WebElement, LocatorParameters),
+ GetActiveElement,
+ GetShadowRoot(WebElement),
+ IsDisplayed(WebElement),
+ IsSelected(WebElement),
+ GetElementAttribute(WebElement, String),
+ GetElementProperty(WebElement, String),
+ GetCSSValue(WebElement, String),
+ GetElementText(WebElement),
+ GetElementTagName(WebElement),
+ GetElementRect(WebElement),
+ IsEnabled(WebElement),
+ ExecuteScript(JavascriptCommandParameters),
+ ExecuteAsyncScript(JavascriptCommandParameters),
+ GetCookies,
+ GetNamedCookie(String),
+ AddCookie(AddCookieParameters),
+ DeleteCookies,
+ DeleteCookie(String),
+ GetTimeouts,
+ SetTimeouts(TimeoutsParameters),
+ ElementClick(WebElement),
+ ElementClear(WebElement),
+ ElementSendKeys(WebElement, SendKeysParameters),
+ PerformActions(ActionsParameters),
+ ReleaseActions,
+ DismissAlert,
+ AcceptAlert,
+ GetAlertText,
+ SendAlertText(SendKeysParameters),
+ TakeScreenshot,
+ TakeElementScreenshot(WebElement),
+ Print(PrintParameters),
+ Status,
+ Extension(T),
+}
+
+pub trait WebDriverExtensionCommand: Clone + Send {
+ fn parameters_json(&self) -> Option<Value>;
+}
+
+#[derive(Clone, Debug)]
+pub struct VoidWebDriverExtensionCommand;
+
+impl WebDriverExtensionCommand for VoidWebDriverExtensionCommand {
+ fn parameters_json(&self) -> Option<Value> {
+ panic!("No extensions implemented");
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct WebDriverMessage<U: WebDriverExtensionRoute = VoidWebDriverExtensionRoute> {
+ pub session_id: Option<String>,
+ pub command: WebDriverCommand<U::Command>,
+}
+
+impl<U: WebDriverExtensionRoute> WebDriverMessage<U> {
+ pub fn new(
+ session_id: Option<String>,
+ command: WebDriverCommand<U::Command>,
+ ) -> WebDriverMessage<U> {
+ WebDriverMessage {
+ session_id,
+ command,
+ }
+ }
+
+ pub fn from_http(
+ match_type: Route<U>,
+ params: &Parameters,
+ raw_body: &str,
+ requires_body: bool,
+ ) -> WebDriverResult<WebDriverMessage<U>> {
+ let session_id = WebDriverMessage::<U>::get_session_id(params);
+ let body_data = WebDriverMessage::<U>::decode_body(raw_body, requires_body)?;
+ let command = match match_type {
+ Route::NewSession => WebDriverCommand::NewSession(serde_json::from_str(raw_body)?),
+ Route::DeleteSession => WebDriverCommand::DeleteSession,
+ Route::Get => WebDriverCommand::Get(serde_json::from_str(raw_body)?),
+ Route::GetCurrentUrl => WebDriverCommand::GetCurrentUrl,
+ Route::GoBack => WebDriverCommand::GoBack,
+ Route::GoForward => WebDriverCommand::GoForward,
+ Route::Refresh => WebDriverCommand::Refresh,
+ Route::GetTitle => WebDriverCommand::GetTitle,
+ Route::GetPageSource => WebDriverCommand::GetPageSource,
+ Route::GetWindowHandle => WebDriverCommand::GetWindowHandle,
+ Route::GetWindowHandles => WebDriverCommand::GetWindowHandles,
+ Route::NewWindow => WebDriverCommand::NewWindow(serde_json::from_str(raw_body)?),
+ Route::CloseWindow => WebDriverCommand::CloseWindow,
+ Route::GetTimeouts => WebDriverCommand::GetTimeouts,
+ Route::SetTimeouts => WebDriverCommand::SetTimeouts(serde_json::from_str(raw_body)?),
+ Route::GetWindowRect | Route::GetWindowPosition | Route::GetWindowSize => {
+ WebDriverCommand::GetWindowRect
+ }
+ Route::SetWindowRect | Route::SetWindowPosition | Route::SetWindowSize => {
+ WebDriverCommand::SetWindowRect(serde_json::from_str(raw_body)?)
+ }
+ Route::MinimizeWindow => WebDriverCommand::MinimizeWindow,
+ Route::MaximizeWindow => WebDriverCommand::MaximizeWindow,
+ Route::FullscreenWindow => WebDriverCommand::FullscreenWindow,
+ Route::SwitchToWindow => {
+ WebDriverCommand::SwitchToWindow(serde_json::from_str(raw_body)?)
+ }
+ Route::SwitchToFrame => {
+ WebDriverCommand::SwitchToFrame(serde_json::from_str(raw_body)?)
+ }
+ Route::SwitchToParentFrame => WebDriverCommand::SwitchToParentFrame,
+ Route::FindElement => WebDriverCommand::FindElement(serde_json::from_str(raw_body)?),
+ Route::FindElements => WebDriverCommand::FindElements(serde_json::from_str(raw_body)?),
+ Route::FindElementElement => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::FindElementElement(element, serde_json::from_str(raw_body)?)
+ }
+ Route::FindElementElements => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::FindElementElements(element, serde_json::from_str(raw_body)?)
+ }
+ Route::GetActiveElement => WebDriverCommand::GetActiveElement,
+ Route::GetShadowRoot => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::GetShadowRoot(element)
+ }
+ Route::IsDisplayed => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::IsDisplayed(element)
+ }
+ Route::IsSelected => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::IsSelected(element)
+ }
+ Route::GetElementAttribute => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ let attr = try_opt!(
+ params.get("name"),
+ ErrorStatus::InvalidArgument,
+ "Missing name parameter"
+ )
+ .as_str();
+ WebDriverCommand::GetElementAttribute(element, attr.into())
+ }
+ Route::GetElementProperty => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ let property = try_opt!(
+ params.get("name"),
+ ErrorStatus::InvalidArgument,
+ "Missing name parameter"
+ )
+ .as_str();
+ WebDriverCommand::GetElementProperty(element, property.into())
+ }
+ Route::GetCSSValue => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ let property = try_opt!(
+ params.get("propertyName"),
+ ErrorStatus::InvalidArgument,
+ "Missing propertyName parameter"
+ )
+ .as_str();
+ WebDriverCommand::GetCSSValue(element, property.into())
+ }
+ Route::GetElementText => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::GetElementText(element)
+ }
+ Route::GetElementTagName => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::GetElementTagName(element)
+ }
+ Route::GetElementRect => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::GetElementRect(element)
+ }
+ Route::IsEnabled => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::IsEnabled(element)
+ }
+ Route::ElementClick => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::ElementClick(element)
+ }
+ Route::ElementClear => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::ElementClear(element)
+ }
+ Route::ElementSendKeys => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::ElementSendKeys(element, serde_json::from_str(raw_body)?)
+ }
+ Route::ExecuteScript => {
+ WebDriverCommand::ExecuteScript(serde_json::from_str(raw_body)?)
+ }
+ Route::ExecuteAsyncScript => {
+ WebDriverCommand::ExecuteAsyncScript(serde_json::from_str(raw_body)?)
+ }
+ Route::GetCookies => WebDriverCommand::GetCookies,
+ Route::GetNamedCookie => {
+ let name = try_opt!(
+ params.get("name"),
+ ErrorStatus::InvalidArgument,
+ "Missing 'name' parameter"
+ )
+ .as_str()
+ .into();
+ WebDriverCommand::GetNamedCookie(name)
+ }
+ Route::AddCookie => WebDriverCommand::AddCookie(serde_json::from_str(raw_body)?),
+ Route::DeleteCookies => WebDriverCommand::DeleteCookies,
+ Route::DeleteCookie => {
+ let name = try_opt!(
+ params.get("name"),
+ ErrorStatus::InvalidArgument,
+ "Missing name parameter"
+ )
+ .as_str()
+ .into();
+ WebDriverCommand::DeleteCookie(name)
+ }
+ Route::PerformActions => {
+ WebDriverCommand::PerformActions(serde_json::from_str(raw_body)?)
+ }
+ Route::ReleaseActions => WebDriverCommand::ReleaseActions,
+ Route::DismissAlert => WebDriverCommand::DismissAlert,
+ Route::AcceptAlert => WebDriverCommand::AcceptAlert,
+ Route::GetAlertText => WebDriverCommand::GetAlertText,
+ Route::SendAlertText => {
+ WebDriverCommand::SendAlertText(serde_json::from_str(raw_body)?)
+ }
+ Route::TakeScreenshot => WebDriverCommand::TakeScreenshot,
+ Route::TakeElementScreenshot => {
+ let element_id = try_opt!(
+ params.get("elementId"),
+ ErrorStatus::InvalidArgument,
+ "Missing elementId parameter"
+ );
+ let element = WebElement(element_id.as_str().into());
+ WebDriverCommand::TakeElementScreenshot(element)
+ }
+ Route::Print => WebDriverCommand::Print(serde_json::from_str(raw_body)?),
+ Route::Status => WebDriverCommand::Status,
+ Route::Extension(ref extension) => extension.command(params, &body_data)?,
+ };
+ Ok(WebDriverMessage::new(session_id, command))
+ }
+
+ fn get_session_id(params: &Parameters) -> Option<String> {
+ params.get("sessionId").cloned()
+ }
+
+ fn decode_body(body: &str, requires_body: bool) -> WebDriverResult<Value> {
+ if requires_body {
+ match serde_json::from_str(body) {
+ Ok(x @ Value::Object(_)) => Ok(x),
+ Ok(_) => Err(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ "Body was not a JSON Object",
+ )),
+ Err(e) => {
+ if e.is_io() {
+ Err(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ format!("I/O error whilst decoding body: {}", e),
+ ))
+ } else {
+ let msg = format!("Failed to decode request as JSON: {}", body);
+ let stack = format!("Syntax error at :{}:{}", e.line(), e.column());
+ Err(WebDriverError::new_with_stack(
+ ErrorStatus::InvalidArgument,
+ msg,
+ stack,
+ ))
+ }
+ }
+ }
+ } else {
+ Ok(Value::Null)
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct ActionsParameters {
+ pub actions: Vec<ActionSequence>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(remote = "Self")]
+pub struct AddCookieParameters {
+ pub name: String,
+ pub value: String,
+ pub path: Option<String>,
+ pub domain: Option<String>,
+ #[serde(default)]
+ pub secure: bool,
+ #[serde(default)]
+ pub httpOnly: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub expiry: Option<Date>,
+ pub sameSite: Option<String>,
+}
+
+impl<'de> Deserialize<'de> for AddCookieParameters {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ #[derive(Deserialize)]
+ struct Wrapper {
+ #[serde(with = "AddCookieParameters")]
+ cookie: AddCookieParameters,
+ }
+
+ Wrapper::deserialize(deserializer).map(|wrapper| wrapper.cookie)
+ }
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct GetParameters {
+ pub url: String,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct GetNamedCookieParameters {
+ pub name: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct JavascriptCommandParameters {
+ pub script: String,
+ pub args: Option<Vec<Value>>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct LocatorParameters {
+ pub using: LocatorStrategy,
+ pub value: String,
+}
+
+/// Wrapper around the two supported variants of new session paramters.
+///
+/// The Spec variant is used for storing spec-compliant parameters whereas
+/// the legacy variant is used to store `desiredCapabilities`/`requiredCapabilities`
+/// parameters, and is intended to minimise breakage as we transition users to
+/// the spec design.
+#[derive(Debug, PartialEq)]
+pub enum NewSessionParameters {
+ Spec(SpecNewSessionParameters),
+ Legacy(LegacyNewSessionParameters),
+}
+
+impl<'de> Deserialize<'de> for NewSessionParameters {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let value = serde_json::Value::deserialize(deserializer)?;
+ if let Some(caps) = value.get("capabilities") {
+ if !caps.is_object() {
+ return Err(de::Error::custom("capabilities must be objects"));
+ }
+ let caps = SpecNewSessionParameters::deserialize(caps).map_err(de::Error::custom)?;
+ return Ok(NewSessionParameters::Spec(caps));
+ }
+
+ warn!("You are using deprecated legacy session negotiation patterns (desiredCapabilities/requiredCapabilities), see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities#Legacy");
+ let legacy = LegacyNewSessionParameters::deserialize(value).map_err(de::Error::custom)?;
+ Ok(NewSessionParameters::Legacy(legacy))
+ }
+}
+
+impl CapabilitiesMatching for NewSessionParameters {
+ fn match_browser<T: BrowserCapabilities>(
+ &self,
+ browser_capabilities: &mut T,
+ ) -> WebDriverResult<Option<Capabilities>> {
+ match self {
+ NewSessionParameters::Spec(x) => x.match_browser(browser_capabilities),
+ NewSessionParameters::Legacy(x) => x.match_browser(browser_capabilities),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct NewWindowParameters {
+ #[serde(rename = "type")]
+ pub type_hint: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "camelCase")]
+pub struct PrintParameters {
+ pub orientation: PrintOrientation,
+ #[serde(deserialize_with = "deserialize_to_print_scale_f64")]
+ pub scale: f64,
+ pub background: bool,
+ pub page: PrintPage,
+ pub margin: PrintMargins,
+ pub page_ranges: Vec<String>,
+ pub shrink_to_fit: bool,
+}
+
+impl Default for PrintParameters {
+ fn default() -> Self {
+ PrintParameters {
+ orientation: PrintOrientation::default(),
+ scale: 1.0,
+ background: false,
+ page: PrintPage::default(),
+ margin: PrintMargins::default(),
+ page_ranges: Vec::new(),
+ shrink_to_fit: true,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PrintOrientation {
+ Landscape,
+ Portrait,
+}
+
+impl Default for PrintOrientation {
+ fn default() -> Self {
+ PrintOrientation::Portrait
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(default)]
+pub struct PrintPage {
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub width: f64,
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub height: f64,
+}
+
+impl Default for PrintPage {
+ fn default() -> Self {
+ PrintPage {
+ width: 21.59,
+ height: 27.94,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(default)]
+pub struct PrintMargins {
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub top: f64,
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub bottom: f64,
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub left: f64,
+ #[serde(deserialize_with = "deserialize_to_positive_f64")]
+ pub right: f64,
+}
+
+impl Default for PrintMargins {
+ fn default() -> Self {
+ PrintMargins {
+ top: 1.0,
+ bottom: 1.0,
+ left: 1.0,
+ right: 1.0,
+ }
+ }
+}
+
+fn deserialize_to_positive_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let val = f64::deserialize(deserializer)?;
+ if val < 0.0 {
+ return Err(de::Error::custom(format!("{} is negative", val)));
+ };
+ Ok(val)
+}
+
+fn deserialize_to_print_scale_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let val = f64::deserialize(deserializer)?;
+ if !(0.1..=2.0).contains(&val) {
+ return Err(de::Error::custom(format!("{} is outside range 0.1-2", val)));
+ };
+ Ok(val)
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct SendKeysParameters {
+ pub text: String,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct SwitchToFrameParameters {
+ pub id: Option<FrameId>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct SwitchToWindowParameters {
+ pub handle: String,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct TakeScreenshotParameters {
+ pub element: Option<WebElement>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct TimeoutsParameters {
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_u64"
+ )]
+ pub implicit: Option<u64>,
+ #[serde(
+ default,
+ rename = "pageLoad",
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_u64"
+ )]
+ pub page_load: Option<u64>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_nullable_u64"
+ )]
+ #[allow(clippy::option_option)]
+ pub script: Option<Option<u64>>,
+}
+
+#[allow(clippy::option_option)]
+fn deserialize_to_nullable_u64<'de, D>(deserializer: D) -> Result<Option<Option<u64>>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt: Option<f64> = Option::deserialize(deserializer)?;
+ let value = match opt {
+ Some(n) => {
+ if n < 0.0 || n.fract() != 0.0 {
+ return Err(de::Error::custom(format!(
+ "{} is not a positive Integer",
+ n
+ )));
+ }
+ if (n as u64) > MAX_SAFE_INTEGER {
+ return Err(de::Error::custom(format!(
+ "{} is greater than maximum safe integer",
+ n
+ )));
+ }
+ Some(Some(n as u64))
+ }
+ None => Some(None),
+ };
+
+ Ok(value)
+}
+
+fn deserialize_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt: Option<f64> = Option::deserialize(deserializer)?;
+ let value = match opt {
+ Some(n) => {
+ if n < 0.0 || n.fract() != 0.0 {
+ return Err(de::Error::custom(format!(
+ "{} is not a positive Integer",
+ n
+ )));
+ }
+ if (n as u64) > MAX_SAFE_INTEGER {
+ return Err(de::Error::custom(format!(
+ "{} is greater than maximum safe integer",
+ n
+ )));
+ }
+ Some(n as u64)
+ }
+ None => return Err(de::Error::custom("null is not a positive integer")),
+ };
+
+ Ok(value)
+}
+
+/// A top-level browsing context’s window rect is a dictionary of the
+/// [`screenX`], [`screenY`], `width`, and `height` attributes of the
+/// `WindowProxy`.
+///
+/// In some user agents the operating system’s window dimensions, including
+/// decorations, are provided by the proprietary `window.outerWidth` and
+/// `window.outerHeight` DOM properties.
+///
+/// [`screenX`]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-screenx
+/// [`screenY`]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-screeny
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct WindowRectParameters {
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_i32"
+ )]
+ pub x: Option<i32>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_i32"
+ )]
+ pub y: Option<i32>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_positive_i32"
+ )]
+ pub width: Option<i32>,
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ deserialize_with = "deserialize_to_positive_i32"
+ )]
+ pub height: Option<i32>,
+}
+
+fn deserialize_to_i32<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt = Option::deserialize(deserializer)?.map(|value: f64| value as i64);
+ let value = match opt {
+ Some(n) => {
+ if n < i64::from(i32::min_value()) || n > i64::from(i32::max_value()) {
+ return Err(de::Error::custom(format!("'{}' is larger than i32", n)));
+ }
+ Some(n as i32)
+ }
+ None => None,
+ };
+
+ Ok(value)
+}
+
+fn deserialize_to_positive_i32<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let opt = Option::deserialize(deserializer)?.map(|value: f64| value as i64);
+ let value = match opt {
+ Some(n) => {
+ if n < 0 || n > i64::from(i32::max_value()) {
+ return Err(de::Error::custom(format!("'{}' is outside of i32", n)));
+ }
+ Some(n as i32)
+ }
+ None => None,
+ };
+
+ Ok(value)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::capabilities::SpecNewSessionParameters;
+ use crate::common::ELEMENT_KEY;
+ use crate::test::assert_de;
+ use serde_json::{self, json};
+
+ #[test]
+ fn test_json_actions_parameters_missing_actions_field() {
+ assert!(serde_json::from_value::<ActionsParameters>(json!({})).is_err());
+ }
+
+ #[test]
+ fn test_json_actions_parameters_invalid() {
+ assert!(serde_json::from_value::<ActionsParameters>(json!({ "actions": null })).is_err());
+ }
+
+ #[test]
+ fn test_json_action_parameters_empty_list() {
+ assert_de(
+ &ActionsParameters { actions: vec![] },
+ json!({"actions": []}),
+ );
+ }
+
+ #[test]
+ fn test_json_action_parameters_with_unknown_field() {
+ assert_de(
+ &ActionsParameters { actions: vec![] },
+ json!({"actions": [], "foo": "bar"}),
+ );
+ }
+
+ #[test]
+ fn test_json_add_cookie_parameters_with_values() {
+ let json = json!({"cookie": {
+ "name": "foo",
+ "value": "bar",
+ "path": "/",
+ "domain": "foo.bar",
+ "expiry": 123,
+ "secure": true,
+ "httpOnly": false,
+ "sameSite": "Lax",
+ }});
+ let cookie = AddCookieParameters {
+ name: "foo".into(),
+ value: "bar".into(),
+ path: Some("/".into()),
+ domain: Some("foo.bar".into()),
+ expiry: Some(Date(123)),
+ secure: true,
+ httpOnly: false,
+ sameSite: Some("Lax".into()),
+ };
+
+ assert_de(&cookie, json);
+ }
+
+ #[test]
+ fn test_json_add_cookie_parameters_with_optional_null_fields() {
+ let json = json!({"cookie": {
+ "name": "foo",
+ "value": "bar",
+ "path": null,
+ "domain": null,
+ "expiry": null,
+ "secure": true,
+ "httpOnly": false,
+ "sameSite": null,
+ }});
+ let cookie = AddCookieParameters {
+ name: "foo".into(),
+ value: "bar".into(),
+ path: None,
+ domain: None,
+ expiry: None,
+ secure: true,
+ httpOnly: false,
+ sameSite: None,
+ };
+
+ assert_de(&cookie, json);
+ }
+
+ #[test]
+ fn test_json_add_cookie_parameters_without_optional_fields() {
+ let json = json!({"cookie": {
+ "name": "foo",
+ "value": "bar",
+ "secure": true,
+ "httpOnly": false,
+ }});
+ let cookie = AddCookieParameters {
+ name: "foo".into(),
+ value: "bar".into(),
+ path: None,
+ domain: None,
+ expiry: None,
+ secure: true,
+ httpOnly: false,
+ sameSite: None,
+ };
+
+ assert_de(&cookie, json);
+ }
+
+ #[test]
+ fn test_json_add_cookie_parameters_with_invalid_cookie_field() {
+ assert!(serde_json::from_value::<AddCookieParameters>(json!({"name": "foo"})).is_err());
+ }
+
+ #[test]
+ fn test_json_add_cookie_parameters_with_unknown_field() {
+ let json = json!({"cookie": {
+ "name": "foo",
+ "value": "bar",
+ "secure": true,
+ "httpOnly": false,
+ "foo": "bar",
+ }, "baz": "bah"});
+ let cookie = AddCookieParameters {
+ name: "foo".into(),
+ value: "bar".into(),
+ path: None,
+ domain: None,
+ expiry: None,
+ secure: true,
+ httpOnly: false,
+ sameSite: None,
+ };
+
+ assert_de(&cookie, json);
+ }
+
+ #[test]
+ fn test_json_get_parameters_with_url() {
+ assert_de(
+ &GetParameters {
+ url: "foo.bar".into(),
+ },
+ json!({"url": "foo.bar"}),
+ );
+ }
+
+ #[test]
+ fn test_json_get_parameters_with_invalid_url_value() {
+ assert!(serde_json::from_value::<GetParameters>(json!({"url": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_get_parameters_with_invalid_url_field() {
+ assert!(serde_json::from_value::<GetParameters>(json!({"foo": "bar"})).is_err());
+ }
+
+ #[test]
+ fn test_json_get_parameters_with_unknown_field() {
+ assert_de(
+ &GetParameters {
+ url: "foo.bar".into(),
+ },
+ json!({"url": "foo.bar", "foo": "bar"}),
+ );
+ }
+
+ #[test]
+ fn test_json_get_named_cookie_parameters_with_value() {
+ assert_de(
+ &GetNamedCookieParameters {
+ name: Some("foo".into()),
+ },
+ json!({"name": "foo"}),
+ );
+ }
+
+ #[test]
+ fn test_json_get_named_cookie_parameters_with_optional_null_field() {
+ assert_de(
+ &GetNamedCookieParameters { name: None },
+ json!({ "name": null }),
+ );
+ }
+
+ #[test]
+ fn test_json_get_named_cookie_parameters_without_optional_null_field() {
+ assert_de(&GetNamedCookieParameters { name: None }, json!({}));
+ }
+
+ #[test]
+ fn test_json_get_named_cookie_parameters_with_invalid_name_field() {
+ assert!(serde_json::from_value::<GetNamedCookieParameters>(json!({"name": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_get_named_cookie_parameters_with_unknown_field() {
+ assert_de(
+ &GetNamedCookieParameters {
+ name: Some("foo".into()),
+ },
+ json!({"name": "foo", "foo": "bar"}),
+ );
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_with_values() {
+ let json = json!({
+ "script": "foo",
+ "args": ["1", 2],
+ });
+ let execute_script = JavascriptCommandParameters {
+ script: "foo".into(),
+ args: Some(vec!["1".into(), 2.into()]),
+ };
+
+ assert_de(&execute_script, json);
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_with_optional_null_field() {
+ let json = json!({
+ "script": "foo",
+ "args": null,
+ });
+ let execute_script = JavascriptCommandParameters {
+ script: "foo".into(),
+ args: None,
+ };
+
+ assert_de(&execute_script, json);
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_without_optional_null_field() {
+ let execute_script = JavascriptCommandParameters {
+ script: "foo".into(),
+ args: None,
+ };
+ assert_de(&execute_script, json!({"script": "foo"}));
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_invalid_script_field() {
+ let json = json!({ "script": null });
+ assert!(serde_json::from_value::<JavascriptCommandParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_invalid_args_field() {
+ let json = json!({
+ "script": null,
+ "args": "1",
+ });
+ assert!(serde_json::from_value::<JavascriptCommandParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_missing_script_field() {
+ let json = json!({ "args": null });
+ assert!(serde_json::from_value::<JavascriptCommandParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_javascript_command_parameters_with_unknown_field() {
+ let json = json!({
+ "script": "foo",
+ "foo": "bar",
+ });
+ let execute_script = JavascriptCommandParameters {
+ script: "foo".into(),
+ args: None,
+ };
+
+ assert_de(&execute_script, json);
+ }
+
+ #[test]
+ fn test_json_locator_parameters_with_values() {
+ let json = json!({
+ "using": "xpath",
+ "value": "bar",
+ });
+ let locator = LocatorParameters {
+ using: LocatorStrategy::XPath,
+ value: "bar".into(),
+ };
+
+ assert_de(&locator, json);
+ }
+
+ #[test]
+ fn test_json_locator_parameters_invalid_using_field() {
+ let json = json!({
+ "using": "foo",
+ "value": "bar",
+ });
+ assert!(serde_json::from_value::<LocatorParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_locator_parameters_invalid_value_field() {
+ let json = json!({
+ "using": "xpath",
+ "value": 3,
+ });
+ assert!(serde_json::from_value::<LocatorParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_locator_parameters_missing_using_field() {
+ assert!(serde_json::from_value::<LocatorParameters>(json!({"value": "bar"})).is_err());
+ }
+
+ #[test]
+ fn test_json_locator_parameters_missing_value_field() {
+ assert!(serde_json::from_value::<LocatorParameters>(json!({"using": "xpath"})).is_err());
+ }
+
+ #[test]
+ fn test_json_locator_parameters_with_unknown_field() {
+ let json = json!({
+ "using": "xpath",
+ "value": "bar",
+ "foo": "bar",
+ });
+ let locator = LocatorParameters {
+ using: LocatorStrategy::XPath,
+ value: "bar".into(),
+ };
+
+ assert_de(&locator, json);
+ }
+
+ #[test]
+ fn test_json_new_session_parameters_spec() {
+ let json = json!({"capabilities": {
+ "alwaysMatch": {},
+ "firstMatch": [{}],
+ }});
+ let caps = NewSessionParameters::Spec(SpecNewSessionParameters {
+ alwaysMatch: Capabilities::new(),
+ firstMatch: vec![Capabilities::new()],
+ });
+
+ assert_de(&caps, json);
+ }
+
+ #[test]
+ fn test_json_new_session_parameters_capabilities_null() {
+ let json = json!({ "capabilities": null });
+ assert!(serde_json::from_value::<NewSessionParameters>(json).is_err());
+ }
+
+ #[test]
+ fn test_json_new_session_parameters_legacy() {
+ let json = json!({
+ "desiredCapabilities": {},
+ "requiredCapabilities": {},
+ });
+ let caps = NewSessionParameters::Legacy(LegacyNewSessionParameters {
+ desired: Capabilities::new(),
+ required: Capabilities::new(),
+ });
+
+ assert_de(&caps, json);
+ }
+
+ #[test]
+ fn test_json_new_session_parameters_spec_and_legacy() {
+ let json = json!({
+ "capabilities": {
+ "alwaysMatch": {},
+ "firstMatch": [{}],
+ },
+ "desiredCapabilities": {},
+ "requiredCapabilities": {},
+ });
+ let caps = NewSessionParameters::Spec(SpecNewSessionParameters {
+ alwaysMatch: Capabilities::new(),
+ firstMatch: vec![Capabilities::new()],
+ });
+
+ assert_de(&caps, json);
+ }
+
+ #[test]
+ fn test_json_new_session_parameters_with_unknown_field() {
+ let json = json!({
+ "capabilities": {
+ "alwaysMatch": {},
+ "firstMatch": [{}]
+ },
+ "foo": "bar",
+ });
+ let caps = NewSessionParameters::Spec(SpecNewSessionParameters {
+ alwaysMatch: Capabilities::new(),
+ firstMatch: vec![Capabilities::new()],
+ });
+
+ assert_de(&caps, json);
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_without_type() {
+ assert_de(&NewWindowParameters { type_hint: None }, json!({}));
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_with_optional_null_type() {
+ assert_de(
+ &NewWindowParameters { type_hint: None },
+ json!({ "type": null }),
+ );
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_with_supported_type() {
+ assert_de(
+ &NewWindowParameters {
+ type_hint: Some("tab".into()),
+ },
+ json!({"type": "tab"}),
+ );
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_with_unknown_type() {
+ assert_de(
+ &NewWindowParameters {
+ type_hint: Some("foo".into()),
+ },
+ json!({"type": "foo"}),
+ );
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_with_invalid_type() {
+ assert!(serde_json::from_value::<NewWindowParameters>(json!({"type": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_new_window_parameters_with_unknown_field() {
+ let json = json!({
+ "type": "tab",
+ "foo": "bar",
+ });
+ let new_window = NewWindowParameters {
+ type_hint: Some("tab".into()),
+ };
+
+ assert_de(&new_window, json);
+ }
+
+ #[test]
+ fn test_json_print_defaults() {
+ let params = PrintParameters::default();
+ assert_de(&params, json!({}));
+ }
+
+ #[test]
+ fn test_json_print() {
+ let params = PrintParameters {
+ orientation: PrintOrientation::Landscape,
+ page: PrintPage {
+ width: 10.0,
+ ..Default::default()
+ },
+ margin: PrintMargins {
+ top: 10.0,
+ ..Default::default()
+ },
+ scale: 1.5,
+ ..Default::default()
+ };
+ assert_de(
+ &params,
+ json!({"orientation": "landscape", "page": {"width": 10}, "margin": {"top": 10}, "scale": 1.5}),
+ );
+ }
+
+ #[test]
+ fn test_json_scale_invalid() {
+ assert!(serde_json::from_value::<PrintParameters>(json!({"scale": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_send_keys_parameters_with_value() {
+ assert_de(
+ &SendKeysParameters { text: "foo".into() },
+ json!({"text": "foo"}),
+ );
+ }
+
+ #[test]
+ fn test_json_send_keys_parameters_invalid_text_field() {
+ assert!(serde_json::from_value::<SendKeysParameters>(json!({"text": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_send_keys_parameters_missing_text_field() {
+ assert!(serde_json::from_value::<SendKeysParameters>(json!({})).is_err());
+ }
+
+ #[test]
+ fn test_json_send_keys_parameters_with_unknown_field() {
+ let json = json!({
+ "text": "foo",
+ "foo": "bar",
+ });
+ let send_keys = SendKeysParameters { text: "foo".into() };
+
+ assert_de(&send_keys, json);
+ }
+
+ #[test]
+ fn test_json_switch_to_frame_parameters_with_value() {
+ assert_de(
+ &SwitchToFrameParameters {
+ id: Some(FrameId::Short(3)),
+ },
+ json!({"id": 3}),
+ );
+ }
+
+ #[test]
+ fn test_json_switch_to_frame_parameters_with_optional_null_field() {
+ assert_de(&SwitchToFrameParameters { id: None }, json!({ "id": null }));
+ }
+
+ #[test]
+ fn test_json_switch_to_frame_parameters_without_optional_null_field() {
+ assert_de(&SwitchToFrameParameters { id: None }, json!({}));
+ }
+
+ #[test]
+ fn test_json_switch_to_frame_parameters_with_invalid_id_field() {
+ assert!(serde_json::from_value::<SwitchToFrameParameters>(json!({"id": "3"})).is_err());
+ }
+
+ #[test]
+ fn test_json_switch_to_frame_parameters_with_unknown_field() {
+ let json = json!({
+ "id":3,
+ "foo": "bar",
+ });
+ let switch_to_frame = SwitchToFrameParameters {
+ id: Some(FrameId::Short(3)),
+ };
+
+ assert_de(&switch_to_frame, json);
+ }
+
+ #[test]
+ fn test_json_switch_to_window_parameters_with_value() {
+ assert_de(
+ &SwitchToWindowParameters {
+ handle: "foo".into(),
+ },
+ json!({"handle": "foo"}),
+ );
+ }
+
+ #[test]
+ fn test_json_switch_to_window_parameters_invalid_handle_field() {
+ assert!(serde_json::from_value::<SwitchToWindowParameters>(json!({"handle": 3})).is_err());
+ }
+
+ #[test]
+ fn test_json_switch_to_window_parameters_missing_handle_field() {
+ assert!(serde_json::from_value::<SwitchToWindowParameters>(json!({})).is_err());
+ }
+
+ #[test]
+ fn test_json_switch_to_window_parameters_with_unknown_field() {
+ let json = json!({
+ "handle": "foo",
+ "foo": "bar",
+ });
+ let switch_to_window = SwitchToWindowParameters {
+ handle: "foo".into(),
+ };
+
+ assert_de(&switch_to_window, json);
+ }
+
+ #[test]
+ fn test_json_take_screenshot_parameters_with_element() {
+ assert_de(
+ &TakeScreenshotParameters {
+ element: Some(WebElement("elem".into())),
+ },
+ json!({"element": {ELEMENT_KEY: "elem"}}),
+ );
+ }
+
+ #[test]
+ fn test_json_take_screenshot_parameters_with_optional_null_field() {
+ assert_de(
+ &TakeScreenshotParameters { element: None },
+ json!({ "element": null }),
+ );
+ }
+
+ #[test]
+ fn test_json_take_screenshot_parameters_without_optional_null_field() {
+ assert_de(&TakeScreenshotParameters { element: None }, json!({}));
+ }
+
+ #[test]
+ fn test_json_take_screenshot_parameters_with_invalid_element_field() {
+ assert!(
+ serde_json::from_value::<TakeScreenshotParameters>(json!({"element": "foo"})).is_err()
+ );
+ }
+
+ #[test]
+ fn test_json_take_screenshot_parameters_with_unknown_field() {
+ let json = json!({
+ "element": {ELEMENT_KEY: "elem"},
+ "foo": "bar",
+ });
+ let take_screenshot = TakeScreenshotParameters {
+ element: Some(WebElement("elem".into())),
+ };
+
+ assert_de(&take_screenshot, json);
+ }
+
+ #[test]
+ fn test_json_timeout_parameters_with_only_null_script_timeout() {
+ let timeouts = TimeoutsParameters {
+ implicit: None,
+ page_load: None,
+ script: Some(None),
+ };
+ assert_de(&timeouts, json!({ "script": null }));
+ }
+
+ #[test]
+ fn test_json_timeout_parameters_with_only_null_implicit_timeout() {
+ assert!(serde_json::from_value::<TimeoutsParameters>(json!({ "implicit": null })).is_err());
+ }
+
+ #[test]
+ fn test_json_timeout_parameters_with_only_null_pageload_timeout() {
+ assert!(serde_json::from_value::<TimeoutsParameters>(json!({ "pageLoad": null })).is_err());
+ }
+
+ #[test]
+ fn test_json_timeout_parameters_without_optional_null_field() {
+ let timeouts = TimeoutsParameters {
+ implicit: None,
+ page_load: None,
+ script: None,
+ };
+ assert_de(&timeouts, json!({}));
+ }
+
+ #[test]
+ fn test_json_timeout_parameters_with_unknown_field() {
+ let json = json!({
+ "script": 60000,
+ "foo": "bar",
+ });
+ let timeouts = TimeoutsParameters {
+ implicit: None,
+ page_load: None,
+ script: Some(Some(60000)),
+ };
+
+ assert_de(&timeouts, json);
+ }
+
+ #[test]
+ fn test_json_window_rect_parameters_with_values() {
+ let json = json!({
+ "x": 0,
+ "y": 1,
+ "width": 2,
+ "height": 3,
+ });
+ let rect = WindowRectParameters {
+ x: Some(0i32),
+ y: Some(1i32),
+ width: Some(2i32),
+ height: Some(3i32),
+ };
+
+ assert_de(&rect, json);
+ }
+
+ #[test]
+ fn test_json_window_rect_parameters_with_optional_null_fields() {
+ let json = json!({
+ "x": null,
+ "y": null,
+ "width": null,
+ "height": null,
+ });
+ let rect = WindowRectParameters {
+ x: None,
+ y: None,
+ width: None,
+ height: None,
+ };
+
+ assert_de(&rect, json);
+ }
+
+ #[test]
+ fn test_json_window_rect_parameters_without_optional_fields() {
+ let rect = WindowRectParameters {
+ x: None,
+ y: None,
+ width: None,
+ height: None,
+ };
+ assert_de(&rect, json!({}));
+ }
+
+ #[test]
+ fn test_json_window_rect_parameters_invalid_values_float() {
+ let json = json!({
+ "x": 1.1,
+ "y": 2.2,
+ "width": 3.3,
+ "height": 4.4,
+ });
+ let rect = WindowRectParameters {
+ x: Some(1),
+ y: Some(2),
+ width: Some(3),
+ height: Some(4),
+ };
+
+ assert_de(&rect, json);
+ }
+
+ #[test]
+ fn test_json_window_rect_parameters_with_unknown_field() {
+ let json = json!({
+ "x": 1.1,
+ "y": 2.2,
+ "foo": "bar",
+ });
+ let rect = WindowRectParameters {
+ x: Some(1),
+ y: Some(2),
+ width: None,
+ height: None,
+ };
+
+ assert_de(&rect, json);
+ }
+}