Skip to main content

webdriver_client/
messages.rs

1//! Messages sent and received in the WebDriver protocol.
2
3#![allow(non_snake_case)]
4
5use ::util::merge_json_mut;
6use serde::{Serialize, Deserialize, Serializer, Deserializer};
7use serde::de::{Visitor, MapAccess};
8use serde::de::Error as DeError;
9use serde::ser::SerializeStruct;
10use serde_json::Value as JsonValue;
11use std::fmt;
12use std::collections::BTreeMap;
13
14#[derive(Debug)]
15pub enum LocationStrategy {
16    Css,
17    LinkText,
18    PartialLinkText,
19    XPath,
20}
21
22impl Serialize for LocationStrategy {
23    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
24        match self {
25            &LocationStrategy::Css => s.serialize_str("css selector"),
26            &LocationStrategy::LinkText => s.serialize_str("link text"),
27            &LocationStrategy::PartialLinkText => s.serialize_str("partial link text"),
28            &LocationStrategy::XPath => s.serialize_str("xpath"),
29        }
30    }
31}
32
33#[derive(Debug, Deserialize)]
34pub struct WebDriverError {
35    pub error: String,
36    pub message: String,
37    pub stacktrace: Option<String>,
38}
39
40#[derive(Serialize, Default)]
41struct Capabilities {
42    alwaysMatch: JsonValue,
43}
44
45/// The arguments to create a new session, including the capabilities
46/// as defined by the [WebDriver specification][cap-spec].
47///
48/// [cap-spec]: https://www.w3.org/TR/webdriver/#capabilities
49#[derive(Serialize)]
50pub struct NewSessionCmd {
51    capabilities: Capabilities,
52}
53
54impl NewSessionCmd {
55    /// Merges a new `alwaysMatch` capability with the given `key` and
56    /// `value` into the new session's capabilities.
57    ///
58    /// For the merging rules, see the documentation of
59    /// `webdriver_client::util::merge_json`.
60    pub fn always_match(&mut self, key: &str, value: JsonValue) -> &mut Self {
61        merge_json_mut(&mut self.capabilities.alwaysMatch,
62                       &json!({ key: value }));
63        self
64    }
65
66    /// Resets the `alwaysMatch` capabilities to an empty JSON object.
67    pub fn reset_always_match(&mut self) -> &mut Self {
68        self.capabilities.alwaysMatch = json!({});
69        self
70    }
71}
72
73impl Default for NewSessionCmd {
74    fn default() -> Self {
75        NewSessionCmd {
76            capabilities: Capabilities {
77                alwaysMatch: json!({
78
79                    // By default chromedriver is NOT compliant with the w3c
80                    // spec. But one can request w3c compliance with a capability
81                    // extension included in the new session command payload.
82                    "goog:chromeOptions": {
83                        "w3c": true
84                    }
85                })
86            },
87        }
88    }
89}
90
91#[derive(Debug, Deserialize)]
92pub struct Session {
93    pub sessionId: String,
94    pub capabilities: BTreeMap<String, JsonValue>,
95}
96
97#[derive(Serialize)]
98pub struct GoCmd {
99    pub url: String,
100}
101
102#[derive(Debug, Deserialize)]
103pub struct Value<T> {
104    pub value: T,
105}
106
107#[derive(Debug, Deserialize)]
108pub struct CurrentTitle {
109    pub title: String,
110}
111
112#[derive(Serialize)]
113pub struct SwitchFrameCmd {
114    pub id: JsonValue,
115}
116
117impl SwitchFrameCmd {
118    pub fn from(id: JsonValue) -> Self {
119        SwitchFrameCmd { id: id }
120    }
121}
122
123#[derive(Serialize)]
124pub struct SwitchWindowCmd {
125    handle: String,
126}
127
128impl SwitchWindowCmd {
129    pub fn from(handle: &str) -> Self {
130        SwitchWindowCmd { handle: handle.to_string() }
131    }
132}
133
134#[derive(Debug, Deserialize, Serialize)]
135pub struct Empty {}
136
137#[derive(Serialize)]
138pub struct FindElementCmd<'a> {
139    pub using: LocationStrategy,
140    pub value: &'a str,
141}
142
143#[derive(PartialEq, Debug)]
144pub struct ElementReference {
145    pub reference: String,
146}
147
148impl ElementReference {
149    pub fn from_str(handle: &str) -> ElementReference {
150        ElementReference { reference: handle.to_string() }
151    }
152}
153
154impl Serialize for ElementReference {
155    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
156        let mut ss = s.serialize_struct("ElementReference", 1)?;
157        ss.serialize_field("element-6066-11e4-a52e-4f735466cecf", &self.reference)?;
158        // even in w3c compliance mode chromedriver only accepts a reference name ELEMENT
159        ss.serialize_field("ELEMENT", &self.reference)?;
160        ss.end()
161    }
162}
163
164impl<'de> Deserialize<'de> for ElementReference {
165    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
166        enum Field { Reference };
167
168        impl<'de> Deserialize<'de> for Field {
169            fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
170                struct FieldVisitor;
171                impl<'de> Visitor<'de> for FieldVisitor {
172                    type Value = Field;
173                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
174                        formatter.write_str("element-6066-11e4-a52e-4f735466cecf")
175                    }
176
177                    fn visit_str<E: DeError>(self, value: &str) -> Result<Field, E>
178                    {
179                        match value {
180                            "element-6066-11e4-a52e-4f735466cecf" => Ok(Field::Reference),
181                            _ => Err(DeError::unknown_field(value, FIELDS)),
182                        }
183                    }
184                }
185
186                d.deserialize_identifier(FieldVisitor)
187            }
188        }
189
190        struct ElementReferenceVisitor;
191        impl<'de> Visitor<'de> for ElementReferenceVisitor {
192            type Value = ElementReference;
193
194            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
195                formatter.write_str("struct ElementReference")
196            }
197
198            fn visit_map<V>(self, mut visitor: V) -> Result<ElementReference, V::Error>
199                where V: MapAccess<'de>
200            {
201                let mut reference = None;
202                while let Some(key) = visitor.next_key()? {
203                    match key {
204                        Field::Reference => {
205                            if reference.is_some() {
206                                return Err(DeError::duplicate_field("element-6066-11e4-a52e-4f735466cecf"));
207                            }
208                            reference = Some(visitor.next_value()?);
209                        }
210                    }
211                }
212                match reference {
213                    Some(r) => Ok(ElementReference { reference: r }),
214                    None => return Err(DeError::missing_field("element-6066-11e4-a52e-4f735466cecf")),
215                }
216            }
217        }
218
219        const FIELDS: &'static [&'static str] = &["element-6066-11e4-a52e-4f735466cecf"];
220        d.deserialize_struct("ElementReference", FIELDS, ElementReferenceVisitor)
221    }
222}
223
224#[derive(Debug, Deserialize)]
225pub struct Cookie {
226    pub name: String,
227    pub value: String,
228    pub path: String,
229    pub domain: String,
230    pub secure: bool,
231    pub httpOnly: bool,
232    // TODO: expiry:
233}
234
235#[derive(Serialize)]
236pub struct ExecuteCmd {
237    pub script: String,
238    pub args: Vec<JsonValue>,
239}
240
241#[derive(Serialize)]
242pub struct SendAlertTextCmd {
243    pub text: String,
244}
245
246#[cfg(test)]
247mod tests {
248    use super::NewSessionCmd;
249    #[test]
250    fn capability_extend() {
251        let mut session = NewSessionCmd::default();
252        session.always_match("cap", json!({"a": true}));
253        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!({"a": true}));
254
255        session.always_match("cap", json!({"b": false}));
256        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!({"a": true, "b": false}));
257
258        session.always_match("cap", json!({"a": false}));
259        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!({"a": false, "b": false}));
260    }
261    #[test]
262    fn capability_extend_replaces_non_obj() {
263        let mut session = NewSessionCmd::default();
264        session.always_match("cap", json!("value"));
265        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!("value"));
266
267        session.always_match("cap", json!({"a": false}));
268        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!({"a": false}));
269    }
270    #[test]
271    fn capability_extend_replaces_obj_with_non_obj() {
272        let mut session = NewSessionCmd::default();
273        session.always_match("cap", json!({"value": true}))
274               .always_match("cap", json!("new"));
275        assert_eq!(session.capabilities.alwaysMatch.get("cap").unwrap(), &json!("new"));
276    }
277}