webdriver/
error.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use base64::DecodeError;
6use http::StatusCode;
7use serde::de::{Deserialize, Deserializer};
8use serde::ser::{Serialize, Serializer};
9use serde_json::Value;
10use std::borrow::Cow;
11use std::collections::BTreeMap;
12use std::error;
13use std::io;
14use thiserror::Error;
15
16#[derive(Debug, PartialEq)]
17pub enum ErrorStatus {
18    /// The [element]'s [ShadowRoot] is not attached to the active document,
19    /// or the reference is stale
20    /// [element]: ../common/struct.WebElement.html
21    /// [ShadowRoot]: ../common/struct.ShadowRoot.html
22    DetachedShadowRoot,
23
24    /// The [`ElementClick`] command could not be completed because the
25    /// [element] receiving the events is obscuring the element that was
26    /// requested clicked.
27    ///
28    /// [`ElementClick`]:
29    /// ../command/enum.WebDriverCommand.html#variant.ElementClick
30    /// [element]: ../common/struct.WebElement.html
31    ElementClickIntercepted,
32
33    /// A [command] could not be completed because the element is not pointer-
34    /// or keyboard interactable.
35    ///
36    /// [command]: ../command/index.html
37    ElementNotInteractable,
38
39    /// An attempt was made to select an [element] that cannot be selected.
40    ///
41    /// [element]: ../common/struct.WebElement.html
42    ElementNotSelectable,
43
44    /// Navigation caused the user agent to hit a certificate warning, which is
45    /// usually the result of an expired or invalid TLS certificate.
46    InsecureCertificate,
47
48    /// The arguments passed to a [command] are either invalid or malformed.
49    ///
50    /// [command]: ../command/index.html
51    InvalidArgument,
52
53    /// An illegal attempt was made to set a cookie under a different domain
54    /// than the current page.
55    InvalidCookieDomain,
56
57    /// The coordinates provided to an interactions operation are invalid.
58    InvalidCoordinates,
59
60    /// A [command] could not be completed because the element is an invalid
61    /// state, e.g. attempting to click an element that is no longer attached
62    /// to the document.
63    ///
64    /// [command]: ../command/index.html
65    InvalidElementState,
66
67    /// Argument was an invalid selector.
68    InvalidSelector,
69
70    /// Occurs if the given session ID is not in the list of active sessions,
71    /// meaning the session either does not exist or that it’s not active.
72    InvalidSessionId,
73
74    /// An error occurred while executing JavaScript supplied by the user.
75    JavascriptError,
76
77    /// The target for mouse interaction is not in the browser’s viewport and
78    /// cannot be brought into that viewport.
79    MoveTargetOutOfBounds,
80
81    /// An attempt was made to operate on a modal dialogue when one was not
82    /// open.
83    NoSuchAlert,
84
85    /// No cookie matching the given path name was found amongst the associated
86    /// cookies of the current browsing context’s active document.
87    NoSuchCookie,
88
89    /// An [element] could not be located on the page using the given search
90    /// parameters.
91    ///
92    /// [element]: ../common/struct.WebElement.html
93    NoSuchElement,
94
95    /// A [command] to switch to a frame could not be satisfied because the
96    /// frame could not be found.
97    ///
98    /// [command]: ../command/index.html
99    NoSuchFrame,
100
101    /// An [element]'s [ShadowRoot] was not found attached to the element.
102    ///
103    /// [element]: ../common/struct.WebElement.html
104    /// [ShadowRoot]: ../common/struct.ShadowRoot.html
105    NoSuchShadowRoot,
106
107    /// A [command] to switch to a window could not be satisfied because the
108    /// window could not be found.
109    ///
110    /// [command]: ../command/index.html
111    NoSuchWindow,
112
113    /// A script did not complete before its timeout expired.
114    ScriptTimeout,
115
116    /// A new session could not be created.
117    SessionNotCreated,
118
119    /// A [command] failed because the referenced [element] is no longer
120    /// attached to the DOM.
121    ///
122    /// [command]: ../command/index.html
123    /// [element]: ../common/struct.WebElement.html
124    StaleElementReference,
125
126    /// An operation did not complete before its timeout expired.
127    Timeout,
128
129    /// A screen capture was made impossible.
130    UnableToCaptureScreen,
131
132    /// Setting the cookie’s value could not be done.
133    UnableToSetCookie,
134
135    /// A modal dialogue was open, blocking this operation.
136    UnexpectedAlertOpen,
137
138    /// The requested command could not be executed because it does not exist.
139    UnknownCommand,
140
141    /// An unknown error occurred in the remote end whilst processing the
142    /// [command].
143    ///
144    /// [command]: ../command/index.html
145    UnknownError,
146
147    /// The requested [command] matched a known endpoint, but did not match a
148    /// method for that endpoint.
149    ///
150    /// [command]: ../command/index.html
151    UnknownMethod,
152
153    /// Indicates that a [command] that should have executed properly is not
154    /// currently supported.
155    UnsupportedOperation,
156}
157
158impl Serialize for ErrorStatus {
159    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
160    where
161        S: Serializer,
162    {
163        self.error_code().serialize(serializer)
164    }
165}
166
167impl<'de> Deserialize<'de> for ErrorStatus {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        let error_string = String::deserialize(deserializer)?;
173        Ok(ErrorStatus::from(error_string))
174    }
175}
176
177impl ErrorStatus {
178    /// Returns the string serialisation of the error type.
179    pub fn error_code(&self) -> &'static str {
180        use self::ErrorStatus::*;
181        match *self {
182            DetachedShadowRoot => "detached shadow root",
183            ElementClickIntercepted => "element click intercepted",
184            ElementNotInteractable => "element not interactable",
185            ElementNotSelectable => "element not selectable",
186            InsecureCertificate => "insecure certificate",
187            InvalidArgument => "invalid argument",
188            InvalidCookieDomain => "invalid cookie domain",
189            InvalidCoordinates => "invalid coordinates",
190            InvalidElementState => "invalid element state",
191            InvalidSelector => "invalid selector",
192            InvalidSessionId => "invalid session id",
193            JavascriptError => "javascript error",
194            MoveTargetOutOfBounds => "move target out of bounds",
195            NoSuchAlert => "no such alert",
196            NoSuchCookie => "no such cookie",
197            NoSuchElement => "no such element",
198            NoSuchFrame => "no such frame",
199            NoSuchShadowRoot => "no such shadow root",
200            NoSuchWindow => "no such window",
201            ScriptTimeout => "script timeout",
202            SessionNotCreated => "session not created",
203            StaleElementReference => "stale element reference",
204            Timeout => "timeout",
205            UnableToCaptureScreen => "unable to capture screen",
206            UnableToSetCookie => "unable to set cookie",
207            UnexpectedAlertOpen => "unexpected alert open",
208            UnknownError => "unknown error",
209            UnknownMethod => "unknown method",
210            UnknownCommand => "unknown command",
211            UnsupportedOperation => "unsupported operation",
212        }
213    }
214
215    /// Returns the correct HTTP status code associated with the error type.
216    pub fn http_status(&self) -> StatusCode {
217        use self::ErrorStatus::*;
218        match *self {
219            DetachedShadowRoot => StatusCode::NOT_FOUND,
220            ElementClickIntercepted => StatusCode::BAD_REQUEST,
221            ElementNotInteractable => StatusCode::BAD_REQUEST,
222            ElementNotSelectable => StatusCode::BAD_REQUEST,
223            InsecureCertificate => StatusCode::BAD_REQUEST,
224            InvalidArgument => StatusCode::BAD_REQUEST,
225            InvalidCookieDomain => StatusCode::BAD_REQUEST,
226            InvalidCoordinates => StatusCode::BAD_REQUEST,
227            InvalidElementState => StatusCode::BAD_REQUEST,
228            InvalidSelector => StatusCode::BAD_REQUEST,
229            InvalidSessionId => StatusCode::NOT_FOUND,
230            JavascriptError => StatusCode::INTERNAL_SERVER_ERROR,
231            MoveTargetOutOfBounds => StatusCode::INTERNAL_SERVER_ERROR,
232            NoSuchAlert => StatusCode::NOT_FOUND,
233            NoSuchCookie => StatusCode::NOT_FOUND,
234            NoSuchElement => StatusCode::NOT_FOUND,
235            NoSuchFrame => StatusCode::NOT_FOUND,
236            NoSuchShadowRoot => StatusCode::NOT_FOUND,
237            NoSuchWindow => StatusCode::NOT_FOUND,
238            ScriptTimeout => StatusCode::INTERNAL_SERVER_ERROR,
239            SessionNotCreated => StatusCode::INTERNAL_SERVER_ERROR,
240            StaleElementReference => StatusCode::NOT_FOUND,
241            Timeout => StatusCode::INTERNAL_SERVER_ERROR,
242            UnableToCaptureScreen => StatusCode::BAD_REQUEST,
243            UnableToSetCookie => StatusCode::INTERNAL_SERVER_ERROR,
244            UnexpectedAlertOpen => StatusCode::INTERNAL_SERVER_ERROR,
245            UnknownCommand => StatusCode::NOT_FOUND,
246            UnknownError => StatusCode::INTERNAL_SERVER_ERROR,
247            UnknownMethod => StatusCode::METHOD_NOT_ALLOWED,
248            UnsupportedOperation => StatusCode::INTERNAL_SERVER_ERROR,
249        }
250    }
251}
252
253/// Deserialises error type from string.
254impl From<String> for ErrorStatus {
255    fn from(s: String) -> ErrorStatus {
256        use self::ErrorStatus::*;
257        match &*s {
258            "detached shadow root" => DetachedShadowRoot,
259            "element click intercepted" => ElementClickIntercepted,
260            "element not interactable" | "element not visible" => ElementNotInteractable,
261            "element not selectable" => ElementNotSelectable,
262            "insecure certificate" => InsecureCertificate,
263            "invalid argument" => InvalidArgument,
264            "invalid cookie domain" => InvalidCookieDomain,
265            "invalid coordinates" | "invalid element coordinates" => InvalidCoordinates,
266            "invalid element state" => InvalidElementState,
267            "invalid selector" => InvalidSelector,
268            "invalid session id" => InvalidSessionId,
269            "javascript error" => JavascriptError,
270            "move target out of bounds" => MoveTargetOutOfBounds,
271            "no such alert" => NoSuchAlert,
272            "no such element" => NoSuchElement,
273            "no such frame" => NoSuchFrame,
274            "no such shadow root" => NoSuchShadowRoot,
275            "no such window" => NoSuchWindow,
276            "script timeout" => ScriptTimeout,
277            "session not created" => SessionNotCreated,
278            "stale element reference" => StaleElementReference,
279            "timeout" => Timeout,
280            "unable to capture screen" => UnableToCaptureScreen,
281            "unable to set cookie" => UnableToSetCookie,
282            "unexpected alert open" => UnexpectedAlertOpen,
283            "unknown command" => UnknownCommand,
284            "unknown error" => UnknownError,
285            "unsupported operation" => UnsupportedOperation,
286            _ => UnknownError,
287        }
288    }
289}
290
291pub type WebDriverResult<T> = Result<T, WebDriverError>;
292
293#[derive(Debug, PartialEq, Serialize, Error)]
294#[serde(remote = "Self")]
295#[error("{}", .error.error_code())]
296pub struct WebDriverError {
297    pub error: ErrorStatus,
298    pub message: Cow<'static, str>,
299    #[serde(rename = "stacktrace")]
300    pub stack: Cow<'static, str>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub data: Option<BTreeMap<Cow<'static, str>, Value>>,
303    #[serde(skip)]
304    pub delete_session: bool,
305}
306
307impl Serialize for WebDriverError {
308    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
309    where
310        S: Serializer,
311    {
312        #[derive(Serialize)]
313        struct Wrapper<'a> {
314            #[serde(with = "WebDriverError")]
315            value: &'a WebDriverError,
316        }
317
318        Wrapper { value: self }.serialize(serializer)
319    }
320}
321
322impl WebDriverError {
323    pub fn new<S>(error: ErrorStatus, message: S) -> WebDriverError
324    where
325        S: Into<Cow<'static, str>>,
326    {
327        WebDriverError {
328            error,
329            message: message.into(),
330            data: None,
331            stack: "".into(),
332            delete_session: false,
333        }
334    }
335
336    pub fn new_with_data<S>(
337        error: ErrorStatus,
338        message: S,
339        data: Option<BTreeMap<Cow<'static, str>, Value>>,
340        stacktrace: Option<S>,
341    ) -> WebDriverError
342    where
343        S: Into<Cow<'static, str>>,
344    {
345        WebDriverError {
346            error,
347            message: message.into(),
348            data,
349            stack: stacktrace.map_or_else(|| "".into(), Into::into),
350            delete_session: false,
351        }
352    }
353
354    pub fn error_code(&self) -> &'static str {
355        self.error.error_code()
356    }
357
358    pub fn http_status(&self) -> StatusCode {
359        self.error.http_status()
360    }
361}
362
363impl From<serde_json::Error> for WebDriverError {
364    fn from(err: serde_json::Error) -> WebDriverError {
365        WebDriverError::new(ErrorStatus::InvalidArgument, err.to_string())
366    }
367}
368
369impl From<io::Error> for WebDriverError {
370    fn from(err: io::Error) -> WebDriverError {
371        WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
372    }
373}
374
375impl From<DecodeError> for WebDriverError {
376    fn from(err: DecodeError) -> WebDriverError {
377        WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
378    }
379}
380
381impl From<Box<dyn error::Error>> for WebDriverError {
382    fn from(err: Box<dyn error::Error>) -> WebDriverError {
383        WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use serde_json::json;
390
391    use super::*;
392    use crate::test::assert_ser;
393
394    #[test]
395    fn test_error_status_serialization() {
396        assert_ser(&ErrorStatus::UnknownError, json!("unknown error"));
397    }
398
399    #[test]
400    fn test_webdriver_error_json() {
401        let data: Option<BTreeMap<Cow<'static, str>, Value>> = Some(
402            [
403                (Cow::Borrowed("foo"), Value::from(42)),
404                (Cow::Borrowed("bar"), Value::from(vec![true])),
405            ]
406            .into_iter()
407            .collect(),
408        );
409
410        let json = json!({"value": {
411            "error": "unknown error",
412            "message": "serialized error",
413            "stacktrace": "foo\nbar",
414            "data": {
415                "foo": 42,
416                "bar": [
417                    true
418                ]
419            }
420        }});
421
422        let error = WebDriverError {
423            error: ErrorStatus::UnknownError,
424            message: "serialized error".into(),
425            stack: "foo\nbar".into(),
426            data,
427            delete_session: true,
428        };
429
430        assert_ser(&error, json);
431    }
432
433    #[test]
434    fn test_webdriver_error_new() {
435        let json = json!({"value": {
436            "error": "unknown error",
437            "message": "error with default stack",
438            "stacktrace": "",
439        }});
440
441        let error = WebDriverError::new::<Cow<'static, str>>(
442            ErrorStatus::UnknownError,
443            "error with default stack".into(),
444        );
445        assert_ser(&error, json);
446    }
447
448    #[test]
449    fn test_webdriver_error_new_with_data_serialization() {
450        let mut data_map = BTreeMap::new();
451        data_map.insert("foo".into(), json!(42));
452        data_map.insert("bar".into(), json!(vec!(true)));
453
454        let error = WebDriverError::new_with_data(
455            ErrorStatus::UnknownError,
456            "serialization test",
457            Some(data_map.clone()),
458            Some("foo\nbar"),
459        );
460
461        let serialized = serde_json::to_string(&error).unwrap();
462        let expected_json = json!({"value": {
463            "error": "unknown error",
464            "message": "serialization test",
465            "stacktrace": "foo\nbar",
466            "data": {
467                "foo": 42,
468                "bar": [true]
469            }
470        }});
471
472        assert_eq!(
473            serde_json::from_str::<Value>(&serialized).unwrap(),
474            expected_json
475        );
476    }
477
478    #[test]
479    fn test_webdriver_error_new_with_data_no_data_no_stacktrace() {
480        let error = WebDriverError::new_with_data(
481            ErrorStatus::UnknownError,
482            "error with no data and stack",
483            None,
484            None,
485        );
486
487        assert_eq!(error.error, ErrorStatus::UnknownError);
488        assert_eq!(error.message, "error with no data and stack");
489        assert_eq!(error.stack, "");
490        assert_eq!(error.data, None);
491    }
492
493    #[test]
494    fn test_webdriver_error_new_with_data_no_stacktrace() {
495        let mut data_map = BTreeMap::new();
496        data_map.insert("foo".into(), json!(42));
497
498        let error = WebDriverError::new_with_data(
499            ErrorStatus::UnknownError,
500            "error with no stack",
501            Some(data_map.clone()),
502            None,
503        );
504
505        assert_eq!(error.error, ErrorStatus::UnknownError);
506        assert_eq!(error.message, "error with no stack");
507        assert_eq!(error.stack, "");
508        assert_eq!(error.data, Some(data_map));
509    }
510
511    #[test]
512    fn test_webdriver_error_new_with_data_no_data() {
513        let error = WebDriverError::new_with_data(
514            ErrorStatus::UnknownError,
515            "error with no data",
516            None,
517            Some("foo\nbar"),
518        );
519
520        assert_eq!(error.error, ErrorStatus::UnknownError);
521        assert_eq!(error.message, "error with no data");
522        assert_eq!(error.stack, "foo\nbar");
523        assert_eq!(error.data, None);
524    }
525}