UNPKG

17.9 kBJavaScriptView Raw
1// Licensed to the Software Freedom Conservancy (SFC) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The SFC licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18'use strict'
19
20/**
21 * The base WebDriver error type. This error type is only used directly when a
22 * more appropriate category is not defined for the offending error.
23 */
24class WebDriverError extends Error {
25 /** @param {string=} opt_error the error message, if any. */
26 constructor(opt_error) {
27 super(opt_error)
28
29 /** @override */
30 this.name = this.constructor.name
31
32 /**
33 * A stacktrace reported by the remote webdriver endpoint that initially
34 * reported this error. This property will be an empty string if the remote
35 * end did not provide a stacktrace.
36 * @type {string}
37 */
38 this.remoteStacktrace = ''
39 }
40}
41
42/**
43 * Indicates a {@linkplain ./webdriver.WebElement#click click command} could not
44 * completed because the click target is obscured by other elements on the
45 * page.
46 */
47class ElementClickInterceptedError extends WebDriverError {
48 /** @param {string=} opt_error the error message, if any. */
49 constructor(opt_error) {
50 super(opt_error)
51 }
52}
53
54/**
55 * An attempt was made to select an element that cannot be selected.
56 */
57class ElementNotSelectableError extends WebDriverError {
58 /** @param {string=} opt_error the error message, if any. */
59 constructor(opt_error) {
60 super(opt_error)
61 }
62}
63
64/**
65 * Indicates a command could not be completed because the target element is
66 * not pointer or keyboard interactable. This will often occur if an element
67 * is present in the DOM, but not rendered (i.e. its CSS style has
68 * "display: none").
69 */
70class ElementNotInteractableError extends WebDriverError {
71 /** @param {string=} opt_error the error message, if any. */
72 constructor(opt_error) {
73 super(opt_error)
74 }
75}
76
77/**
78 * Indicates a navigation event caused the browser to generate a certificate
79 * warning. This is usually caused by an expired or invalid TLS certificate.
80 */
81class InsecureCertificateError extends WebDriverError {
82 /** @param {string=} opt_error the error message, if any. */
83 constructor(opt_error) {
84 super(opt_error)
85 }
86}
87
88/**
89 * The arguments passed to a command are either invalid or malformed.
90 */
91class InvalidArgumentError extends WebDriverError {
92 /** @param {string=} opt_error the error message, if any. */
93 constructor(opt_error) {
94 super(opt_error)
95 }
96}
97
98/**
99 * An illegal attempt was made to set a cookie under a different domain than
100 * the current page.
101 */
102class InvalidCookieDomainError extends WebDriverError {
103 /** @param {string=} opt_error the error message, if any. */
104 constructor(opt_error) {
105 super(opt_error)
106 }
107}
108
109/**
110 * The coordinates provided to an interactions operation are invalid.
111 */
112class InvalidCoordinatesError extends WebDriverError {
113 /** @param {string=} opt_error the error message, if any. */
114 constructor(opt_error) {
115 super(opt_error)
116 }
117}
118
119/**
120 * An element command could not be completed because the element is in an
121 * invalid state, e.g. attempting to click an element that is no longer attached
122 * to the document.
123 */
124class InvalidElementStateError extends WebDriverError {
125 /** @param {string=} opt_error the error message, if any. */
126 constructor(opt_error) {
127 super(opt_error)
128 }
129}
130
131/**
132 * Argument was an invalid selector.
133 */
134class InvalidSelectorError extends WebDriverError {
135 /** @param {string=} opt_error the error message, if any. */
136 constructor(opt_error) {
137 super(opt_error)
138 }
139}
140
141/**
142 * Occurs when a command is directed to a session that does not exist.
143 */
144class NoSuchSessionError extends WebDriverError {
145 /** @param {string=} opt_error the error message, if any. */
146 constructor(opt_error) {
147 super(opt_error)
148 }
149}
150
151/**
152 * An error occurred while executing JavaScript supplied by the user.
153 */
154class JavascriptError extends WebDriverError {
155 /** @param {string=} opt_error the error message, if any. */
156 constructor(opt_error) {
157 super(opt_error)
158 }
159}
160
161/**
162 * The target for mouse interaction is not in the browser’s viewport and cannot
163 * be brought into that viewport.
164 */
165class MoveTargetOutOfBoundsError extends WebDriverError {
166 /** @param {string=} opt_error the error message, if any. */
167 constructor(opt_error) {
168 super(opt_error)
169 }
170}
171
172/**
173 * An attempt was made to operate on a modal dialog when one was not open.
174 */
175class NoSuchAlertError extends WebDriverError {
176 /** @param {string=} opt_error the error message, if any. */
177 constructor(opt_error) {
178 super(opt_error)
179 }
180}
181
182/**
183 * Indicates a named cookie could not be found in the cookie jar for the
184 * currently selected document.
185 */
186class NoSuchCookieError extends WebDriverError {
187 /** @param {string=} opt_error the error message, if any. */
188 constructor(opt_error) {
189 super(opt_error)
190 }
191}
192
193/**
194 * An element could not be located on the page using the given search
195 * parameters.
196 */
197class NoSuchElementError extends WebDriverError {
198 /** @param {string=} opt_error the error message, if any. */
199 constructor(opt_error) {
200 super(opt_error)
201 }
202}
203
204/**
205 * A request to switch to a frame could not be satisfied because the frame
206 * could not be found.
207 */
208class NoSuchFrameError extends WebDriverError {
209 /** @param {string=} opt_error the error message, if any. */
210 constructor(opt_error) {
211 super(opt_error)
212 }
213}
214
215/**
216 * A request to switch to a window could not be satisfied because the window
217 * could not be found.
218 */
219class NoSuchWindowError extends WebDriverError {
220 /** @param {string=} opt_error the error message, if any. */
221 constructor(opt_error) {
222 super(opt_error)
223 }
224}
225
226/**
227 * A script did not complete before its timeout expired.
228 */
229class ScriptTimeoutError extends WebDriverError {
230 /** @param {string=} opt_error the error message, if any. */
231 constructor(opt_error) {
232 super(opt_error)
233 }
234}
235
236/**
237 * A new session could not be created.
238 */
239class SessionNotCreatedError extends WebDriverError {
240 /** @param {string=} opt_error the error message, if any. */
241 constructor(opt_error) {
242 super(opt_error)
243 }
244}
245
246/**
247 * An element command failed because the referenced element is no longer
248 * attached to the DOM.
249 */
250class StaleElementReferenceError extends WebDriverError {
251 /** @param {string=} opt_error the error message, if any. */
252 constructor(opt_error) {
253 super(opt_error)
254 }
255}
256
257/**
258 * An operation did not complete before its timeout expired.
259 */
260class TimeoutError extends WebDriverError {
261 /** @param {string=} opt_error the error message, if any. */
262 constructor(opt_error) {
263 super(opt_error)
264 }
265}
266
267/**
268 * A request to set a cookie’s value could not be satisfied.
269 */
270class UnableToSetCookieError extends WebDriverError {
271 /** @param {string=} opt_error the error message, if any. */
272 constructor(opt_error) {
273 super(opt_error)
274 }
275}
276
277/**
278 * A screen capture operation was not possible.
279 */
280class UnableToCaptureScreenError extends WebDriverError {
281 /** @param {string=} opt_error the error message, if any. */
282 constructor(opt_error) {
283 super(opt_error)
284 }
285}
286
287/**
288 * A modal dialog was open, blocking this operation.
289 */
290class UnexpectedAlertOpenError extends WebDriverError {
291 /**
292 * @param {string=} opt_error the error message, if any.
293 * @param {string=} opt_text the text of the open dialog, if available.
294 */
295 constructor(opt_error, opt_text) {
296 super(opt_error)
297
298 /** @private {(string|undefined)} */
299 this.text_ = opt_text
300 }
301
302 /**
303 * @return {(string|undefined)} The text displayed with the unhandled alert,
304 * if available.
305 */
306 getAlertText() {
307 return this.text_
308 }
309}
310
311/**
312 * A command could not be executed because the remote end is not aware of it.
313 */
314class UnknownCommandError extends WebDriverError {
315 /** @param {string=} opt_error the error message, if any. */
316 constructor(opt_error) {
317 super(opt_error)
318 }
319}
320
321/**
322 * The requested command matched a known URL but did not match an method for
323 * that URL.
324 */
325class UnknownMethodError extends WebDriverError {
326 /** @param {string=} opt_error the error message, if any. */
327 constructor(opt_error) {
328 super(opt_error)
329 }
330}
331
332/**
333 * Reports an unsupported operation.
334 */
335class UnsupportedOperationError extends WebDriverError {
336 /** @param {string=} opt_error the error message, if any. */
337 constructor(opt_error) {
338 super(opt_error)
339 }
340}
341
342// TODO(jleyba): Define UnknownError as an alias of WebDriverError?
343
344/**
345 * Enum of legacy error codes.
346 * TODO: remove this when all code paths have been switched to the new error
347 * types.
348 * @deprecated
349 * @enum {number}
350 */
351const ErrorCode = {
352 SUCCESS: 0,
353 NO_SUCH_SESSION: 6,
354 NO_SUCH_ELEMENT: 7,
355 NO_SUCH_FRAME: 8,
356 UNKNOWN_COMMAND: 9,
357 UNSUPPORTED_OPERATION: 9,
358 STALE_ELEMENT_REFERENCE: 10,
359 ELEMENT_NOT_VISIBLE: 11,
360 INVALID_ELEMENT_STATE: 12,
361 UNKNOWN_ERROR: 13,
362 ELEMENT_NOT_SELECTABLE: 15,
363 JAVASCRIPT_ERROR: 17,
364 XPATH_LOOKUP_ERROR: 19,
365 TIMEOUT: 21,
366 NO_SUCH_WINDOW: 23,
367 INVALID_COOKIE_DOMAIN: 24,
368 UNABLE_TO_SET_COOKIE: 25,
369 UNEXPECTED_ALERT_OPEN: 26,
370 NO_SUCH_ALERT: 27,
371 SCRIPT_TIMEOUT: 28,
372 INVALID_ELEMENT_COORDINATES: 29,
373 IME_NOT_AVAILABLE: 30,
374 IME_ENGINE_ACTIVATION_FAILED: 31,
375 INVALID_SELECTOR_ERROR: 32,
376 SESSION_NOT_CREATED: 33,
377 MOVE_TARGET_OUT_OF_BOUNDS: 34,
378 SQL_DATABASE_ERROR: 35,
379 INVALID_XPATH_SELECTOR: 51,
380 INVALID_XPATH_SELECTOR_RETURN_TYPE: 52,
381 ELEMENT_NOT_INTERACTABLE: 60,
382 INVALID_ARGUMENT: 61,
383 NO_SUCH_COOKIE: 62,
384 UNABLE_TO_CAPTURE_SCREEN: 63,
385 ELEMENT_CLICK_INTERCEPTED: 64,
386 METHOD_NOT_ALLOWED: 405,
387}
388
389const LEGACY_ERROR_CODE_TO_TYPE = new Map([
390 [ErrorCode.NO_SUCH_SESSION, NoSuchSessionError],
391 [ErrorCode.NO_SUCH_ELEMENT, NoSuchElementError],
392 [ErrorCode.NO_SUCH_FRAME, NoSuchFrameError],
393 [ErrorCode.UNSUPPORTED_OPERATION, UnsupportedOperationError],
394 [ErrorCode.STALE_ELEMENT_REFERENCE, StaleElementReferenceError],
395 [ErrorCode.INVALID_ELEMENT_STATE, InvalidElementStateError],
396 [ErrorCode.UNKNOWN_ERROR, WebDriverError],
397 [ErrorCode.ELEMENT_NOT_SELECTABLE, ElementNotSelectableError],
398 [ErrorCode.JAVASCRIPT_ERROR, JavascriptError],
399 [ErrorCode.XPATH_LOOKUP_ERROR, InvalidSelectorError],
400 [ErrorCode.TIMEOUT, TimeoutError],
401 [ErrorCode.NO_SUCH_WINDOW, NoSuchWindowError],
402 [ErrorCode.INVALID_COOKIE_DOMAIN, InvalidCookieDomainError],
403 [ErrorCode.UNABLE_TO_SET_COOKIE, UnableToSetCookieError],
404 [ErrorCode.UNEXPECTED_ALERT_OPEN, UnexpectedAlertOpenError],
405 [ErrorCode.NO_SUCH_ALERT, NoSuchAlertError],
406 [ErrorCode.SCRIPT_TIMEOUT, ScriptTimeoutError],
407 [ErrorCode.INVALID_ELEMENT_COORDINATES, InvalidCoordinatesError],
408 [ErrorCode.INVALID_SELECTOR_ERROR, InvalidSelectorError],
409 [ErrorCode.SESSION_NOT_CREATED, SessionNotCreatedError],
410 [ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS, MoveTargetOutOfBoundsError],
411 [ErrorCode.INVALID_XPATH_SELECTOR, InvalidSelectorError],
412 [ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPE, InvalidSelectorError],
413 [ErrorCode.ELEMENT_NOT_INTERACTABLE, ElementNotInteractableError],
414 [ErrorCode.INVALID_ARGUMENT, InvalidArgumentError],
415 [ErrorCode.NO_SUCH_COOKIE, NoSuchCookieError],
416 [ErrorCode.UNABLE_TO_CAPTURE_SCREEN, UnableToCaptureScreenError],
417 [ErrorCode.ELEMENT_CLICK_INTERCEPTED, ElementClickInterceptedError],
418 [ErrorCode.METHOD_NOT_ALLOWED, UnsupportedOperationError],
419])
420
421const ERROR_CODE_TO_TYPE = new Map([
422 ['unknown error', WebDriverError],
423 ['element click intercepted', ElementClickInterceptedError],
424 ['element not interactable', ElementNotInteractableError],
425 ['element not selectable', ElementNotSelectableError],
426 ['insecure certificate', InsecureCertificateError],
427 ['invalid argument', InvalidArgumentError],
428 ['invalid cookie domain', InvalidCookieDomainError],
429 ['invalid coordinates', InvalidCoordinatesError],
430 ['invalid element state', InvalidElementStateError],
431 ['invalid selector', InvalidSelectorError],
432 ['invalid session id', NoSuchSessionError],
433 ['javascript error', JavascriptError],
434 ['move target out of bounds', MoveTargetOutOfBoundsError],
435 ['no such alert', NoSuchAlertError],
436 ['no such cookie', NoSuchCookieError],
437 ['no such element', NoSuchElementError],
438 ['no such frame', NoSuchFrameError],
439 ['no such window', NoSuchWindowError],
440 ['script timeout', ScriptTimeoutError],
441 ['session not created', SessionNotCreatedError],
442 ['stale element reference', StaleElementReferenceError],
443 ['timeout', TimeoutError],
444 ['unable to set cookie', UnableToSetCookieError],
445 ['unable to capture screen', UnableToCaptureScreenError],
446 ['unexpected alert open', UnexpectedAlertOpenError],
447 ['unknown command', UnknownCommandError],
448 ['unknown method', UnknownMethodError],
449 ['unsupported operation', UnsupportedOperationError],
450])
451
452const TYPE_TO_ERROR_CODE = new Map()
453ERROR_CODE_TO_TYPE.forEach((value, key) => {
454 TYPE_TO_ERROR_CODE.set(value, key)
455})
456
457/**
458 * @param {*} err The error to encode.
459 * @return {{error: string, message: string}} the encoded error.
460 */
461function encodeError(err) {
462 let type = WebDriverError
463 if (
464 err instanceof WebDriverError &&
465 TYPE_TO_ERROR_CODE.has(err.constructor)
466 ) {
467 type = err.constructor
468 }
469
470 let message = err instanceof Error ? err.message : err + ''
471
472 let code = /** @type {string} */ (TYPE_TO_ERROR_CODE.get(type))
473 return { error: code, message: message }
474}
475
476/**
477 * Checks a response object from a server that adheres to the W3C WebDriver
478 * protocol.
479 * @param {*} data The response data to check.
480 * @return {*} The response data if it was not an encoded error.
481 * @throws {WebDriverError} the decoded error, if present in the data object.
482 * @deprecated Use {@link #throwDecodedError(data)} instead.
483 * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
484 */
485function checkResponse(data) {
486 if (data && typeof data.error === 'string') {
487 let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError
488 throw new ctor(data.message)
489 }
490 return data
491}
492
493/**
494 * Tests if the given value is a valid error response object according to the
495 * W3C WebDriver spec.
496 *
497 * @param {?} data The value to test.
498 * @return {boolean} Whether the given value data object is a valid error
499 * response.
500 * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
501 */
502function isErrorResponse(data) {
503 return data && typeof data === 'object' && typeof data.error === 'string'
504}
505
506/**
507 * Throws an error coded from the W3C protocol. A generic error will be thrown
508 * if the provided `data` is not a valid encoded error.
509 *
510 * @param {{error: string, message: string}} data The error data to decode.
511 * @throws {WebDriverError} the decoded error.
512 * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
513 */
514function throwDecodedError(data) {
515 if (isErrorResponse(data)) {
516 let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError
517 let err = new ctor(data.message)
518 // TODO(jleyba): remove whichever case is excluded from the final W3C spec.
519 if (typeof data.stacktrace === 'string') {
520 err.remoteStacktrace = data.stacktrace
521 } else if (typeof data.stackTrace === 'string') {
522 err.remoteStacktrace = data.stackTrace
523 }
524 throw err
525 }
526 throw new WebDriverError('Unknown error: ' + JSON.stringify(data))
527}
528
529/**
530 * Checks a legacy response from the Selenium 2.0 wire protocol for an error.
531 * @param {*} responseObj the response object to check.
532 * @return {*} responseObj the original response if it does not define an error.
533 * @throws {WebDriverError} if the response object defines an error.
534 */
535function checkLegacyResponse(responseObj) {
536 // Handle the legacy Selenium error response format.
537 if (
538 responseObj &&
539 typeof responseObj === 'object' &&
540 typeof responseObj['status'] === 'number' &&
541 responseObj['status'] !== 0
542 ) {
543 let status = responseObj['status']
544 let ctor = LEGACY_ERROR_CODE_TO_TYPE.get(status) || WebDriverError
545
546 let value = responseObj['value']
547
548 if (!value || typeof value !== 'object') {
549 throw new ctor(value + '')
550 } else {
551 let message = value['message'] + ''
552 if (ctor !== UnexpectedAlertOpenError) {
553 throw new ctor(message)
554 }
555
556 let text = ''
557 if (value['alert'] && typeof value['alert']['text'] === 'string') {
558 text = value['alert']['text']
559 }
560 throw new UnexpectedAlertOpenError(message, text)
561 }
562 }
563 return responseObj
564}
565
566// PUBLIC API
567
568module.exports = {
569 ErrorCode,
570
571 WebDriverError,
572 ElementClickInterceptedError,
573 ElementNotInteractableError,
574 ElementNotSelectableError,
575 InsecureCertificateError,
576 InvalidArgumentError,
577 InvalidCookieDomainError,
578 InvalidCoordinatesError,
579 InvalidElementStateError,
580 InvalidSelectorError,
581 JavascriptError,
582 MoveTargetOutOfBoundsError,
583 NoSuchAlertError,
584 NoSuchCookieError,
585 NoSuchElementError,
586 NoSuchFrameError,
587 NoSuchSessionError,
588 NoSuchWindowError,
589 ScriptTimeoutError,
590 SessionNotCreatedError,
591 StaleElementReferenceError,
592 TimeoutError,
593 UnableToSetCookieError,
594 UnableToCaptureScreenError,
595 UnexpectedAlertOpenError,
596 UnknownCommandError,
597 UnknownMethodError,
598 UnsupportedOperationError,
599
600 checkResponse,
601 checkLegacyResponse,
602 encodeError,
603 isErrorResponse,
604 throwDecodedError,
605}