1import collections
2import json
3
4from typing import ClassVar, DefaultDict, Type
5
6
7class WebDriverException(Exception):
8    # The status_code class variable is used to map the JSON Error Code (see
9    # https://w3c.github.io/webdriver/#errors) to a WebDriverException subclass.
10    # However, http_status need not match, and both are set as instance
11    # variables, shadowing the class variables. TODO: Match on both http_status
12    # and status_code and let these be class variables only.
13    http_status = None  # type: ClassVar[int]
14    status_code = None  # type: ClassVar[str]
15
16    def __init__(self, http_status=None, status_code=None, message=None, stacktrace=None):
17        super(WebDriverException, self)
18
19        if http_status is not None:
20            self.http_status = http_status
21        if status_code is not None:
22            self.status_code = status_code
23        self.message = message
24        self.stacktrace = stacktrace
25
26    def __repr__(self):
27        return "<%s http_status=%s>" % (self.__class__.__name__, self.http_status)
28
29    def __str__(self):
30        message = "%s (%s)" % (self.status_code, self.http_status)
31
32        if self.message is not None:
33            message += ": %s" % self.message
34        message += "\n"
35
36        if self.stacktrace:
37            message += ("\nRemote-end stacktrace:\n\n%s" % self.stacktrace)
38
39        return message
40
41
42class DetachedShadowRootException(WebDriverException):
43    http_status = 404
44    status_code = "detached shadow root"
45
46
47class ElementClickInterceptedException(WebDriverException):
48    http_status = 400
49    status_code = "element click intercepted"
50
51
52class ElementNotSelectableException(WebDriverException):
53    http_status = 400
54    status_code = "element not selectable"
55
56
57class ElementNotVisibleException(WebDriverException):
58    http_status = 400
59    status_code = "element not visible"
60
61
62class InsecureCertificateException(WebDriverException):
63    http_status = 400
64    status_code = "insecure certificate"
65
66
67class InvalidArgumentException(WebDriverException):
68    http_status = 400
69    status_code = "invalid argument"
70
71
72class InvalidCookieDomainException(WebDriverException):
73    http_status = 400
74    status_code = "invalid cookie domain"
75
76
77class InvalidElementCoordinatesException(WebDriverException):
78    http_status = 400
79    status_code = "invalid element coordinates"
80
81
82class InvalidElementStateException(WebDriverException):
83    http_status = 400
84    status_code = "invalid element state"
85
86
87class InvalidSelectorException(WebDriverException):
88    http_status = 400
89    status_code = "invalid selector"
90
91
92class InvalidSessionIdException(WebDriverException):
93    http_status = 404
94    status_code = "invalid session id"
95
96
97class JavascriptErrorException(WebDriverException):
98    http_status = 500
99    status_code = "javascript error"
100
101
102class MoveTargetOutOfBoundsException(WebDriverException):
103    http_status = 500
104    status_code = "move target out of bounds"
105
106
107class NoSuchAlertException(WebDriverException):
108    http_status = 404
109    status_code = "no such alert"
110
111
112class NoSuchCookieException(WebDriverException):
113    http_status = 404
114    status_code = "no such cookie"
115
116
117class NoSuchElementException(WebDriverException):
118    http_status = 404
119    status_code = "no such element"
120
121
122class NoSuchFrameException(WebDriverException):
123    http_status = 404
124    status_code = "no such frame"
125
126
127class NoSuchShadowRootException(WebDriverException):
128    http_status = 404
129    status_code = "no such shadow root"
130
131
132class NoSuchWindowException(WebDriverException):
133    http_status = 404
134    status_code = "no such window"
135
136
137class ScriptTimeoutException(WebDriverException):
138    http_status = 500
139    status_code = "script timeout"
140
141
142class SessionNotCreatedException(WebDriverException):
143    http_status = 500
144    status_code = "session not created"
145
146
147class StaleElementReferenceException(WebDriverException):
148    http_status = 404
149    status_code = "stale element reference"
150
151
152class TimeoutException(WebDriverException):
153    http_status = 500
154    status_code = "timeout"
155
156
157class UnableToSetCookieException(WebDriverException):
158    http_status = 500
159    status_code = "unable to set cookie"
160
161
162class UnexpectedAlertOpenException(WebDriverException):
163    http_status = 500
164    status_code = "unexpected alert open"
165
166
167class UnknownErrorException(WebDriverException):
168    http_status = 500
169    status_code = "unknown error"
170
171
172class UnknownCommandException(WebDriverException):
173    http_status = 404
174    status_code = "unknown command"
175
176
177class UnknownMethodException(WebDriverException):
178    http_status = 405
179    status_code = "unknown method"
180
181
182class UnsupportedOperationException(WebDriverException):
183    http_status = 500
184    status_code = "unsupported operation"
185
186
187def from_response(response):
188    """
189    Unmarshals an error from a ``Response``'s `body`, failing
190    if not all three required `error`, `message`, and `stacktrace`
191    fields are given.  Defaults to ``WebDriverException`` if `error`
192    is unknown.
193    """
194    if response.status == 200:
195        raise UnknownErrorException(
196            response.status,
197            None,
198            "Response is not an error:\n"
199            "%s" % json.dumps(response.body))
200
201    if "value" in response.body:
202        value = response.body["value"]
203    else:
204        raise UnknownErrorException(
205            response.status,
206            None,
207            "Expected 'value' key in response body:\n"
208            "%s" % json.dumps(response.body))
209
210    # all fields must exist, but stacktrace can be an empty string
211    code = value["error"]
212    message = value["message"]
213    stack = value["stacktrace"] or None
214
215    cls = get(code)
216    return cls(response.status, code, message, stacktrace=stack)
217
218
219def get(error_code):
220    """
221    Gets exception from `error_code`, falling back to
222    ``WebDriverException`` if it is not found.
223    """
224    return _errors.get(error_code, WebDriverException)
225
226
227_errors: DefaultDict[str, Type[WebDriverException]] = collections.defaultdict()
228for item in list(locals().values()):
229    if type(item) == type and issubclass(item, WebDriverException):
230        _errors[item.status_code] = item
231