| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| 'use strict' |
| |
| const { isObject } = require('./util') |
| |
| /** |
| * The base WebDriver error type. This error type is only used directly when a |
| * more appropriate category is not defined for the offending error. |
| */ |
| class WebDriverError extends Error { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| |
| /** @override */ |
| this.name = this.constructor.name |
| |
| /** |
| * A stacktrace reported by the remote webdriver endpoint that initially |
| * reported this error. This property will be an empty string if the remote |
| * end did not provide a stacktrace. |
| * @type {string} |
| */ |
| this.remoteStacktrace = '' |
| } |
| } |
| |
| /** |
| * Indicates the shadow root is no longer attached to the DOM |
| */ |
| class DetachedShadowRootError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Indicates a {@linkplain ./webdriver.WebElement#click click command} could not |
| * completed because the click target is obscured by other elements on the |
| * page. |
| */ |
| class ElementClickInterceptedError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An attempt was made to select an element that cannot be selected. |
| */ |
| class ElementNotSelectableError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Indicates a command could not be completed because the target element is |
| * not pointer or keyboard interactable. This will often occur if an element |
| * is present in the DOM, but not rendered (i.e. its CSS style has |
| * "display: none"). |
| */ |
| class ElementNotInteractableError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Indicates a navigation event caused the browser to generate a certificate |
| * warning. This is usually caused by an expired or invalid TLS certificate. |
| */ |
| class InsecureCertificateError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * The arguments passed to a command are either invalid or malformed. |
| */ |
| class InvalidArgumentError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An illegal attempt was made to set a cookie under a different domain than |
| * the current page. |
| */ |
| class InvalidCookieDomainError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * The coordinates provided to an interactions operation are invalid. |
| */ |
| class InvalidCoordinatesError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An element command could not be completed because the element is in an |
| * invalid state, e.g. attempting to click an element that is no longer attached |
| * to the document. |
| */ |
| class InvalidElementStateError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Argument was an invalid selector. |
| */ |
| class InvalidSelectorError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Occurs when a command is directed to a session that does not exist. |
| */ |
| class NoSuchSessionError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An error occurred while executing JavaScript supplied by the user. |
| */ |
| class JavascriptError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * The target for mouse interaction is not in the browser’s viewport and cannot |
| * be brought into that viewport. |
| */ |
| class MoveTargetOutOfBoundsError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An attempt was made to operate on a modal dialog when one was not open. |
| */ |
| class NoSuchAlertError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Indicates a named cookie could not be found in the cookie jar for the |
| * currently selected document. |
| */ |
| class NoSuchCookieError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An element could not be located on the page using the given search |
| * parameters. |
| */ |
| class NoSuchElementError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A ShadowRoot could not be located on the element |
| */ |
| class NoSuchShadowRootError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A request to switch to a frame could not be satisfied because the frame |
| * could not be found. |
| */ |
| class NoSuchFrameError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A request to switch to a window could not be satisfied because the window |
| * could not be found. |
| */ |
| class NoSuchWindowError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A script did not complete before its timeout expired. |
| */ |
| class ScriptTimeoutError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A new session could not be created. |
| */ |
| class SessionNotCreatedError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An element command failed because the referenced element is no longer |
| * attached to the DOM. |
| */ |
| class StaleElementReferenceError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * An operation did not complete before its timeout expired. |
| */ |
| class TimeoutError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A request to set a cookie’s value could not be satisfied. |
| */ |
| class UnableToSetCookieError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A screen capture operation was not possible. |
| */ |
| class UnableToCaptureScreenError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * A modal dialog was open, blocking this operation. |
| */ |
| class UnexpectedAlertOpenError extends WebDriverError { |
| /** |
| * @param {string=} opt_error the error message, if any. |
| * @param {string=} opt_text the text of the open dialog, if available. |
| */ |
| constructor(opt_error, opt_text) { |
| super(opt_error) |
| |
| /** @private {(string|undefined)} */ |
| this.text_ = opt_text |
| } |
| |
| /** |
| * @return {(string|undefined)} The text displayed with the unhandled alert, |
| * if available. |
| */ |
| getAlertText() { |
| return this.text_ |
| } |
| } |
| |
| /** |
| * A command could not be executed because the remote end is not aware of it. |
| */ |
| class UnknownCommandError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * The requested command matched a known URL but did not match an method for |
| * that URL. |
| */ |
| class UnknownMethodError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| /** |
| * Reports an unsupported operation. |
| */ |
| class UnsupportedOperationError extends WebDriverError { |
| /** @param {string=} opt_error the error message, if any. */ |
| constructor(opt_error) { |
| super(opt_error) |
| } |
| } |
| |
| // TODO(jleyba): Define UnknownError as an alias of WebDriverError? |
| |
| /** |
| * Enum of legacy error codes. |
| * TODO: remove this when all code paths have been switched to the new error |
| * types. |
| * @deprecated |
| * @enum {number} |
| */ |
| const ErrorCode = { |
| SUCCESS: 0, |
| NO_SUCH_SESSION: 6, |
| NO_SUCH_ELEMENT: 7, |
| NO_SUCH_FRAME: 8, |
| UNKNOWN_COMMAND: 9, |
| UNSUPPORTED_OPERATION: 9, |
| STALE_ELEMENT_REFERENCE: 10, |
| ELEMENT_NOT_VISIBLE: 11, |
| INVALID_ELEMENT_STATE: 12, |
| UNKNOWN_ERROR: 13, |
| ELEMENT_NOT_SELECTABLE: 15, |
| JAVASCRIPT_ERROR: 17, |
| XPATH_LOOKUP_ERROR: 19, |
| TIMEOUT: 21, |
| NO_SUCH_WINDOW: 23, |
| INVALID_COOKIE_DOMAIN: 24, |
| UNABLE_TO_SET_COOKIE: 25, |
| UNEXPECTED_ALERT_OPEN: 26, |
| NO_SUCH_ALERT: 27, |
| SCRIPT_TIMEOUT: 28, |
| INVALID_ELEMENT_COORDINATES: 29, |
| IME_NOT_AVAILABLE: 30, |
| IME_ENGINE_ACTIVATION_FAILED: 31, |
| INVALID_SELECTOR_ERROR: 32, |
| SESSION_NOT_CREATED: 33, |
| MOVE_TARGET_OUT_OF_BOUNDS: 34, |
| SQL_DATABASE_ERROR: 35, |
| INVALID_XPATH_SELECTOR: 51, |
| INVALID_XPATH_SELECTOR_RETURN_TYPE: 52, |
| ELEMENT_NOT_INTERACTABLE: 60, |
| INVALID_ARGUMENT: 61, |
| NO_SUCH_COOKIE: 62, |
| UNABLE_TO_CAPTURE_SCREEN: 63, |
| ELEMENT_CLICK_INTERCEPTED: 64, |
| DETACHED_SHADOW_ROOT: 65, |
| METHOD_NOT_ALLOWED: 405, |
| } |
| |
| const LEGACY_ERROR_CODE_TO_TYPE = new Map([ |
| [ErrorCode.NO_SUCH_SESSION, NoSuchSessionError], |
| [ErrorCode.NO_SUCH_ELEMENT, NoSuchElementError], |
| [ErrorCode.NO_SUCH_FRAME, NoSuchFrameError], |
| [ErrorCode.UNSUPPORTED_OPERATION, UnsupportedOperationError], |
| [ErrorCode.STALE_ELEMENT_REFERENCE, StaleElementReferenceError], |
| [ErrorCode.INVALID_ELEMENT_STATE, InvalidElementStateError], |
| [ErrorCode.UNKNOWN_ERROR, WebDriverError], |
| [ErrorCode.ELEMENT_NOT_SELECTABLE, ElementNotSelectableError], |
| [ErrorCode.JAVASCRIPT_ERROR, JavascriptError], |
| [ErrorCode.XPATH_LOOKUP_ERROR, InvalidSelectorError], |
| [ErrorCode.TIMEOUT, TimeoutError], |
| [ErrorCode.NO_SUCH_WINDOW, NoSuchWindowError], |
| [ErrorCode.INVALID_COOKIE_DOMAIN, InvalidCookieDomainError], |
| [ErrorCode.UNABLE_TO_SET_COOKIE, UnableToSetCookieError], |
| [ErrorCode.UNEXPECTED_ALERT_OPEN, UnexpectedAlertOpenError], |
| [ErrorCode.NO_SUCH_ALERT, NoSuchAlertError], |
| [ErrorCode.SCRIPT_TIMEOUT, ScriptTimeoutError], |
| [ErrorCode.INVALID_ELEMENT_COORDINATES, InvalidCoordinatesError], |
| [ErrorCode.INVALID_SELECTOR_ERROR, InvalidSelectorError], |
| [ErrorCode.SESSION_NOT_CREATED, SessionNotCreatedError], |
| [ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS, MoveTargetOutOfBoundsError], |
| [ErrorCode.INVALID_XPATH_SELECTOR, InvalidSelectorError], |
| [ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPE, InvalidSelectorError], |
| [ErrorCode.ELEMENT_NOT_INTERACTABLE, ElementNotInteractableError], |
| [ErrorCode.INVALID_ARGUMENT, InvalidArgumentError], |
| [ErrorCode.NO_SUCH_COOKIE, NoSuchCookieError], |
| [ErrorCode.UNABLE_TO_CAPTURE_SCREEN, UnableToCaptureScreenError], |
| [ErrorCode.ELEMENT_CLICK_INTERCEPTED, ElementClickInterceptedError], |
| [ErrorCode.DETACHED_SHADOW_ROOT, DetachedShadowRootError], |
| [ErrorCode.METHOD_NOT_ALLOWED, UnsupportedOperationError], |
| ]) |
| |
| const ERROR_CODE_TO_TYPE = new Map([ |
| ['unknown error', WebDriverError], |
| ['detached shadow root', DetachedShadowRootError], |
| ['element click intercepted', ElementClickInterceptedError], |
| ['element not interactable', ElementNotInteractableError], |
| ['element not selectable', ElementNotSelectableError], |
| ['insecure certificate', InsecureCertificateError], |
| ['invalid argument', InvalidArgumentError], |
| ['invalid cookie domain', InvalidCookieDomainError], |
| ['invalid coordinates', InvalidCoordinatesError], |
| ['invalid element state', InvalidElementStateError], |
| ['invalid selector', InvalidSelectorError], |
| ['invalid session id', NoSuchSessionError], |
| ['javascript error', JavascriptError], |
| ['move target out of bounds', MoveTargetOutOfBoundsError], |
| ['no such alert', NoSuchAlertError], |
| ['no such cookie', NoSuchCookieError], |
| ['no such element', NoSuchElementError], |
| ['no such frame', NoSuchFrameError], |
| ['no such shadow root', NoSuchShadowRootError], |
| ['no such window', NoSuchWindowError], |
| ['script timeout', ScriptTimeoutError], |
| ['session not created', SessionNotCreatedError], |
| ['stale element reference', StaleElementReferenceError], |
| ['timeout', TimeoutError], |
| ['unable to set cookie', UnableToSetCookieError], |
| ['unable to capture screen', UnableToCaptureScreenError], |
| ['unexpected alert open', UnexpectedAlertOpenError], |
| ['unknown command', UnknownCommandError], |
| ['unknown method', UnknownMethodError], |
| ['unsupported operation', UnsupportedOperationError], |
| ]) |
| |
| const TYPE_TO_ERROR_CODE = new Map() |
| ERROR_CODE_TO_TYPE.forEach((value, key) => { |
| TYPE_TO_ERROR_CODE.set(value, key) |
| }) |
| |
| /** |
| * @param {*} err The error to encode. |
| * @return {{error: string, message: string}} the encoded error. |
| */ |
| function encodeError(err) { |
| let type = WebDriverError |
| if (err instanceof WebDriverError && TYPE_TO_ERROR_CODE.has(err.constructor)) { |
| type = err.constructor |
| } |
| |
| let message = err instanceof Error ? err.message : err + '' |
| |
| let code = /** @type {string} */ (TYPE_TO_ERROR_CODE.get(type)) |
| return { error: code, message: message } |
| } |
| |
| /** |
| * Tests if the given value is a valid error response object according to the |
| * W3C WebDriver spec. |
| * |
| * @param {?} data The value to test. |
| * @return {boolean} Whether the given value data object is a valid error |
| * response. |
| * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol |
| */ |
| function isErrorResponse(data) { |
| return isObject(data) && typeof data.error === 'string' |
| } |
| |
| /** |
| * Throws an error coded from the W3C protocol. A generic error will be thrown |
| * if the provided `data` is not a valid encoded error. |
| * |
| * @param {{error: string, message: string}} data The error data to decode. |
| * @throws {WebDriverError} the decoded error. |
| * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol |
| */ |
| function throwDecodedError(data) { |
| if (isErrorResponse(data)) { |
| let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError |
| let err = new ctor(data.message) |
| // TODO(jleyba): remove whichever case is excluded from the final W3C spec. |
| if (typeof data.stacktrace === 'string') { |
| err.remoteStacktrace = data.stacktrace |
| } else if (typeof data.stackTrace === 'string') { |
| err.remoteStacktrace = data.stackTrace |
| } |
| throw err |
| } |
| throw new WebDriverError('Unknown error: ' + JSON.stringify(data)) |
| } |
| |
| /** |
| * Checks a legacy response from the Selenium 2.0 wire protocol for an error. |
| * @param {*} responseObj the response object to check. |
| * @return {*} responseObj the original response if it does not define an error. |
| * @throws {WebDriverError} if the response object defines an error. |
| */ |
| function checkLegacyResponse(responseObj) { |
| // Handle the legacy Selenium error response format. |
| if (isObject(responseObj) && typeof responseObj.status === 'number' && responseObj.status !== 0) { |
| const { status, value } = responseObj |
| |
| let ctor = LEGACY_ERROR_CODE_TO_TYPE.get(status) || WebDriverError |
| |
| if (!value || typeof value !== 'object') { |
| throw new ctor(value + '') |
| } else { |
| let message = value['message'] + '' |
| if (ctor !== UnexpectedAlertOpenError) { |
| throw new ctor(message) |
| } |
| |
| let text = '' |
| if (value['alert'] && typeof value['alert']['text'] === 'string') { |
| text = value['alert']['text'] |
| } |
| throw new UnexpectedAlertOpenError(message, text) |
| } |
| } |
| return responseObj |
| } |
| |
| // PUBLIC API |
| |
| module.exports = { |
| ErrorCode, |
| |
| WebDriverError, |
| DetachedShadowRootError, |
| ElementClickInterceptedError, |
| ElementNotInteractableError, |
| ElementNotSelectableError, |
| InsecureCertificateError, |
| InvalidArgumentError, |
| InvalidCookieDomainError, |
| InvalidCoordinatesError, |
| InvalidElementStateError, |
| InvalidSelectorError, |
| JavascriptError, |
| MoveTargetOutOfBoundsError, |
| NoSuchAlertError, |
| NoSuchCookieError, |
| NoSuchElementError, |
| NoSuchFrameError, |
| NoSuchShadowRootError, |
| NoSuchSessionError, |
| NoSuchWindowError, |
| ScriptTimeoutError, |
| SessionNotCreatedError, |
| StaleElementReferenceError, |
| TimeoutError, |
| UnableToSetCookieError, |
| UnableToCaptureScreenError, |
| UnexpectedAlertOpenError, |
| UnknownCommandError, |
| UnknownMethodError, |
| UnsupportedOperationError, |
| checkLegacyResponse, |
| encodeError, |
| isErrorResponse, |
| throwDecodedError, |
| } |