mirror of
https://github.com/appium/appium.git
synced 2026-10-26 02:29:38 +08:00
893 lines
26 KiB
JavaScript
893 lines
26 KiB
JavaScript
import ES6Error from 'es6-error';
|
||
import _ from 'lodash';
|
||
import { util, logger } from 'appium-support';
|
||
import HTTPStatusCodes from 'http-status-codes';
|
||
|
||
const mjsonwpLog = logger.getLogger('MJSONWP');
|
||
const w3cLog = logger.getLogger('W3C');
|
||
|
||
const W3C_UNKNOWN_ERROR = 'unknown error';
|
||
|
||
// base error class for all of our errors
|
||
class ProtocolError extends ES6Error {
|
||
constructor (msg, jsonwpCode, w3cStatus, error) {
|
||
super(msg);
|
||
this.jsonwpCode = jsonwpCode;
|
||
this.error = error || W3C_UNKNOWN_ERROR;
|
||
if (this.jsonwpCode === null) {
|
||
this.jsonwpCode = 13;
|
||
}
|
||
this.w3cStatus = w3cStatus || HTTPStatusCodes.BAD_REQUEST;
|
||
this._stacktrace = null;
|
||
}
|
||
|
||
get stacktrace () {
|
||
return this._stacktrace || this.stack;
|
||
}
|
||
|
||
set stacktrace (value) {
|
||
this._stacktrace = value;
|
||
}
|
||
}
|
||
|
||
// https://github.com/SeleniumHQ/selenium/blob/176b4a9e3082ac1926f2a436eb346760c37a5998/java/client/src/org/openqa/selenium/remote/ErrorCodes.java#L215
|
||
// https://github.com/SeleniumHQ/selenium/issues/5562#issuecomment-370379470
|
||
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-error-code
|
||
|
||
class NoSuchDriverError extends ProtocolError {
|
||
static code () {
|
||
return 6;
|
||
}
|
||
// W3C Error is called InvalidSessionID
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'invalid session id';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A session is either terminated or not started', NoSuchDriverError.code(),
|
||
NoSuchDriverError.w3cStatus(), NoSuchDriverError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchElementError extends ProtocolError {
|
||
static code () {
|
||
return 7;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'no such element';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An element could not be located on the page using the given ' +
|
||
'search parameters.', NoSuchElementError.code(), NoSuchElementError.w3cStatus(),
|
||
NoSuchElementError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchFrameError extends ProtocolError {
|
||
static code () {
|
||
return 8;
|
||
}
|
||
static error () {
|
||
return 'no such frame';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A request to switch to a frame could not be satisfied because ' +
|
||
'the frame could not be found.', NoSuchFrameError.code(),
|
||
NoSuchFrameError.w3cStatus(), NoSuchFrameError.error());
|
||
}
|
||
}
|
||
|
||
class UnknownCommandError extends ProtocolError {
|
||
static code () {
|
||
return 9;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'unknown command';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'The requested resource could not be found, or a request was ' +
|
||
'received using an HTTP method that is not supported by the mapped ' +
|
||
'resource.', UnknownCommandError.code(), UnknownCommandError.w3cStatus(), UnknownCommandError.error());
|
||
}
|
||
}
|
||
|
||
class StaleElementReferenceError extends ProtocolError {
|
||
static code () {
|
||
return 10;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'stale element reference';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An element command failed because the referenced element is no ' +
|
||
'longer attached to the DOM.', StaleElementReferenceError.code(),
|
||
StaleElementReferenceError.w3cStatus(), StaleElementReferenceError.error());
|
||
}
|
||
}
|
||
|
||
class ElementNotVisibleError extends ProtocolError {
|
||
static code () {
|
||
return 11;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
static error () {
|
||
return 'element not visible';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An element command could not be completed because the element is ' +
|
||
'not visible on the page.', ElementNotVisibleError.code(),
|
||
ElementNotVisibleError.w3cStatus(), ElementNotVisibleError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidElementStateError extends ProtocolError {
|
||
static code () {
|
||
return 12;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
static error () {
|
||
return 'invalid element state';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An element command could not be completed because the element is ' +
|
||
'in an invalid state (e.g. attempting to click a disabled element).',
|
||
InvalidElementStateError.code(), InvalidElementStateError.w3cStatus(),
|
||
InvalidElementStateError.error());
|
||
}
|
||
}
|
||
|
||
class UnknownError extends ProtocolError {
|
||
static code () {
|
||
return 13;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return W3C_UNKNOWN_ERROR;
|
||
}
|
||
constructor (originalError) {
|
||
let origMessage = originalError;
|
||
if (originalError instanceof Error) {
|
||
origMessage = originalError.message;
|
||
}
|
||
let message = 'An unknown server-side error occurred while processing ' +
|
||
'the command.';
|
||
if (originalError) {
|
||
message = `${message} Original error: ${origMessage}`;
|
||
}
|
||
|
||
super(message, UnknownError.code(), UnknownError.w3cStatus(), UnknownError.error());
|
||
}
|
||
}
|
||
|
||
class UnknownMethodError extends ProtocolError {
|
||
static code () {
|
||
return 405;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.METHOD_NOT_ALLOWED;
|
||
}
|
||
static error () {
|
||
return 'unknown method';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'The requested command matched a known URL but did not match an method for that URL',
|
||
UnknownMethodError.code(), UnknownMethodError.w3cStatus(), UnknownMethodError.error());
|
||
}
|
||
}
|
||
|
||
class UnsupportedOperationError extends ProtocolError {
|
||
static code () {
|
||
return 405;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unsupported operation';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A server-side error occurred. Command cannot be supported.',
|
||
UnsupportedOperationError.code(), UnsupportedOperationError.w3cStatus(),
|
||
UnsupportedOperationError.error());
|
||
}
|
||
}
|
||
|
||
class ElementIsNotSelectableError extends ProtocolError {
|
||
static code () {
|
||
return 15;
|
||
}
|
||
static error () {
|
||
return 'element not selectable';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An attempt was made to select an element that cannot be selected.',
|
||
ElementIsNotSelectableError.code(), ElementIsNotSelectableError.w3cStatus(),
|
||
ElementIsNotSelectableError.error());
|
||
}
|
||
}
|
||
|
||
class ElementClickInterceptedError extends ProtocolError {
|
||
static code () {
|
||
return 64;
|
||
}
|
||
static error () {
|
||
return 'element click intercepted';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'The Element Click command could not be completed because the element receiving ' +
|
||
'the events is obscuring the element that was requested clicked',
|
||
ElementClickInterceptedError.code(), ElementClickInterceptedError.w3cStatus(),
|
||
ElementClickInterceptedError.error());
|
||
}
|
||
}
|
||
|
||
class ElementNotInteractableError extends ProtocolError {
|
||
static code () {
|
||
return 60;
|
||
}
|
||
static error () {
|
||
return 'element not interactable';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A command could not be completed because the element is not pointer- or keyboard interactable',
|
||
ElementNotInteractableError.code(), ElementNotInteractableError.w3cStatus(),
|
||
ElementNotInteractableError.error());
|
||
}
|
||
}
|
||
|
||
class InsecureCertificateError extends ProtocolError {
|
||
static error () {
|
||
return 'insecure certificate';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate',
|
||
ElementIsNotSelectableError.code(), null, InsecureCertificateError.error());
|
||
}
|
||
}
|
||
|
||
class JavaScriptError extends ProtocolError {
|
||
static code () {
|
||
return 17;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'javascript error';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An error occurred while executing user supplied JavaScript.',
|
||
JavaScriptError.code(), JavaScriptError.w3cStatus(), JavaScriptError.error());
|
||
}
|
||
}
|
||
|
||
class XPathLookupError extends ProtocolError {
|
||
static code () {
|
||
return 19;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
static error () {
|
||
return 'invalid selector';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An error occurred while searching for an element by XPath.',
|
||
XPathLookupError.code(), XPathLookupError.w3cStatus(), XPathLookupError.error());
|
||
}
|
||
}
|
||
|
||
class TimeoutError extends ProtocolError {
|
||
static code () {
|
||
return 21;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.REQUEST_TIMEOUT;
|
||
}
|
||
static error () {
|
||
return 'timeout';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An operation did not complete before its timeout expired.',
|
||
TimeoutError.code(), TimeoutError.w3cStatus(), TimeoutError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchWindowError extends ProtocolError {
|
||
static code () {
|
||
return 23;
|
||
}
|
||
static error () {
|
||
return 'no such window';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A request to switch to a different window could not be satisfied ' +
|
||
'because the window could not be found.', NoSuchWindowError.code(),
|
||
NoSuchWindowError.w3cStatus(), NoSuchWindowError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidArgumentError extends ProtocolError {
|
||
static code () {
|
||
return 61;
|
||
}
|
||
static error () {
|
||
return 'invalid argument';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'The arguments passed to the command are either invalid or malformed',
|
||
InvalidArgumentError.code(), InvalidArgumentError.w3cStatus(),
|
||
InvalidArgumentError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidCookieDomainError extends ProtocolError {
|
||
static code () {
|
||
return 24;
|
||
}
|
||
static error () {
|
||
return 'invalid cookie domain';
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An illegal attempt was made to set a cookie under a different ' +
|
||
'domain than the current page.', InvalidCookieDomainError.code(),
|
||
InvalidCookieDomainError.w3cStatus(), InvalidCookieDomainError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchCookieError extends ProtocolError {
|
||
static code () {
|
||
return 62;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'no such cookie';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document',
|
||
NoSuchCookieError.code(), NoSuchCookieError.w3cStatus(), NoSuchCookieError.error());
|
||
}
|
||
}
|
||
|
||
class UnableToSetCookieError extends ProtocolError {
|
||
static code () {
|
||
return 25;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unable to set cookie';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A request to set a cookie\'s value could not be satisfied.',
|
||
UnableToSetCookieError.code(), UnableToSetCookieError.w3cStatus(), UnableToSetCookieError.error());
|
||
}
|
||
}
|
||
|
||
class UnexpectedAlertOpenError extends ProtocolError {
|
||
static code () {
|
||
return 26;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unexpected alert open';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A modal dialog was open, blocking this operation',
|
||
UnexpectedAlertOpenError.code(), UnexpectedAlertOpenError.w3cStatus(), UnexpectedAlertOpenError.error());
|
||
}
|
||
}
|
||
|
||
class NoAlertOpenError extends ProtocolError {
|
||
static code () {
|
||
return 27;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
static error () {
|
||
return 'no such alert';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An attempt was made to operate on a modal dialog when one ' +
|
||
'was not open.', NoAlertOpenError.code(), NoAlertOpenError.w3cStatus(), NoAlertOpenError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchAlertError extends NoAlertOpenError {}
|
||
|
||
class ScriptTimeoutError extends ProtocolError {
|
||
static code () {
|
||
return 28;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.REQUEST_TIMEOUT;
|
||
}
|
||
static error () {
|
||
return 'script timeout';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A script did not complete before its timeout expired.',
|
||
ScriptTimeoutError.code(), ScriptTimeoutError.w3cStatus(), ScriptTimeoutError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidElementCoordinatesError extends ProtocolError {
|
||
static code () {
|
||
return 29;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
static error () {
|
||
return 'invalid coordinates';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'The coordinates provided to an interactions operation are invalid.',
|
||
InvalidElementCoordinatesError.code(), InvalidElementCoordinatesError.w3cStatus(),
|
||
InvalidElementCoordinatesError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidCoordinatesError extends InvalidElementCoordinatesError {}
|
||
|
||
class IMENotAvailableError extends ProtocolError {
|
||
static code () {
|
||
return 30;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unsupported operation';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'IME was not available.', IMENotAvailableError.code(),
|
||
IMENotAvailableError.w3cStatus(), IMENotAvailableError.error());
|
||
}
|
||
}
|
||
|
||
class IMEEngineActivationFailedError extends ProtocolError {
|
||
static code () {
|
||
return 31;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unsupported operation';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'An IME engine could not be started.',
|
||
IMEEngineActivationFailedError.code(), IMEEngineActivationFailedError.w3cStatus(),
|
||
IMEEngineActivationFailedError.error());
|
||
}
|
||
}
|
||
|
||
class InvalidSelectorError extends ProtocolError {
|
||
static code () {
|
||
return 32;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
static error () {
|
||
return 'invalid selector';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'Argument was an invalid selector (e.g. XPath/CSS).',
|
||
InvalidSelectorError.code(), InvalidSelectorError.w3cStatus(),
|
||
InvalidSelectorError.error());
|
||
}
|
||
}
|
||
|
||
class SessionNotCreatedError extends ProtocolError {
|
||
static code () {
|
||
return 33;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'session not created';
|
||
}
|
||
constructor (details) {
|
||
let message = 'A new session could not be created.';
|
||
if (details) {
|
||
message += ` Details: ${details}`;
|
||
}
|
||
|
||
super(message, SessionNotCreatedError.code(), SessionNotCreatedError.w3cStatus(), SessionNotCreatedError.error());
|
||
}
|
||
}
|
||
|
||
class MoveTargetOutOfBoundsError extends ProtocolError {
|
||
static code () {
|
||
return 34;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'move target out of bounds';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'Target provided for a move action is out of bounds.',
|
||
MoveTargetOutOfBoundsError.code(), MoveTargetOutOfBoundsError.w3cStatus(), MoveTargetOutOfBoundsError.error());
|
||
}
|
||
}
|
||
|
||
class NoSuchContextError extends ProtocolError {
|
||
static code () {
|
||
return 35;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'No such context found.', NoSuchContextError.code());
|
||
}
|
||
}
|
||
|
||
class InvalidContextError extends ProtocolError {
|
||
static code () {
|
||
return 36;
|
||
}
|
||
constructor (err) {
|
||
super(err || 'That command could not be executed in the current context.',
|
||
InvalidContextError.code());
|
||
}
|
||
}
|
||
|
||
// This is an alias for UnknownMethodError
|
||
class NotYetImplementedError extends ProtocolError {
|
||
static code () {
|
||
return 13;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.NOT_FOUND; // W3C equivalent is called 'Unknown Command' (A command could not be executed because the remote end is not aware of it)
|
||
}
|
||
static error () {
|
||
return 'unknown method';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'Method has not yet been implemented',
|
||
NotYetImplementedError.code(), NotYetImplementedError.w3cStatus(), NotYetImplementedError.error());
|
||
}
|
||
}
|
||
|
||
class NotImplementedError extends ProtocolError {
|
||
static code () {
|
||
return 13;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.METHOD_NOT_ALLOWED; // W3C equivalent is 'Unknown Method' (The requested command matched a known URL but did not match an method for that URL)
|
||
}
|
||
constructor (err) {
|
||
super(err || 'Method is not implemented', NotImplementedError.code(), NotImplementedError.w3cStatus());
|
||
}
|
||
}
|
||
|
||
class UnableToCaptureScreen extends ProtocolError {
|
||
static code () {
|
||
return 63;
|
||
}
|
||
static w3cStatus () {
|
||
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
}
|
||
static error () {
|
||
return 'unable to capture screen';
|
||
}
|
||
constructor (err) {
|
||
super(err || 'A screen capture was made impossible',
|
||
UnableToCaptureScreen.code(), UnableToCaptureScreen.w3cStatus(), UnableToCaptureScreen.error());
|
||
}
|
||
}
|
||
|
||
|
||
// Equivalent to W3C InvalidArgumentError
|
||
class BadParametersError extends ES6Error {
|
||
static error () {
|
||
return 'invalid argument';
|
||
}
|
||
constructor (requiredParams, actualParams, errMessage) {
|
||
let message;
|
||
if (!errMessage) {
|
||
message = `Parameters were incorrect. We wanted ` +
|
||
`${JSON.stringify(requiredParams)} and you ` +
|
||
`sent ${JSON.stringify(actualParams)}`;
|
||
} else {
|
||
message = `Parameters were incorrect. You sent ${JSON.stringify(actualParams)}, ${errMessage}`;
|
||
}
|
||
super(message);
|
||
this.w3cStatus = HTTPStatusCodes.BAD_REQUEST;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ProxyRequestError is a custom error and will be thrown up on unsuccessful proxy request and
|
||
* will contain information about the proxy failure.
|
||
* In case of ProxyRequestError should fetch the actual error by calling `getActualError()`
|
||
* for proxy failure to generate the client response.
|
||
*/
|
||
class ProxyRequestError extends ES6Error {
|
||
constructor (err, responseError, httpStatus) {
|
||
let responseErrorObj = util.safeJsonParse(responseError);
|
||
if (!_.isPlainObject(responseErrorObj)) {
|
||
responseErrorObj = {};
|
||
}
|
||
let origMessage = _.isString(responseError) ? responseError : '';
|
||
if (!_.isEmpty(responseErrorObj)) {
|
||
if (_.isString(responseErrorObj.value)) {
|
||
origMessage = responseErrorObj.value;
|
||
} else if (_.isPlainObject(responseErrorObj.value) && _.isString(responseErrorObj.value.message)) {
|
||
origMessage = responseErrorObj.value.message;
|
||
}
|
||
}
|
||
super(_.isEmpty(err) ? `Proxy request unsuccessful. ${origMessage}` : err);
|
||
|
||
this.w3cStatus = HTTPStatusCodes.BAD_REQUEST;
|
||
|
||
// If the response error is an object and value is an object, it's a W3C error (for JSONWP value is a string)
|
||
if (_.isPlainObject(responseErrorObj.value) && _.has(responseErrorObj.value, 'error')) {
|
||
this.w3c = responseErrorObj.value;
|
||
this.w3cStatus = httpStatus || HTTPStatusCodes.BAD_REQUEST;
|
||
} else {
|
||
this.jsonwp = responseErrorObj;
|
||
}
|
||
}
|
||
|
||
getActualError () {
|
||
// If it's MJSONWP error, returns actual error cause for request failure based on `jsonwp.status`
|
||
if (util.hasValue(this.jsonwp) && util.hasValue(this.jsonwp.status) && util.hasValue(this.jsonwp.value)) {
|
||
return errorFromMJSONWPStatusCode(this.jsonwp.status, this.jsonwp.value);
|
||
} else if (util.hasValue(this.w3c) && _.isNumber(this.w3cStatus) && this.w3cStatus >= 300) {
|
||
return errorFromW3CJsonCode(this.w3c.error, this.w3c.message || this.message, this.w3c.stacktrace);
|
||
}
|
||
return new UnknownError(this.message);
|
||
}
|
||
}
|
||
// map of error class name to error class
|
||
const errors = {NotYetImplementedError,
|
||
NotImplementedError,
|
||
BadParametersError,
|
||
InvalidArgumentError,
|
||
NoSuchDriverError,
|
||
NoSuchElementError,
|
||
UnknownCommandError,
|
||
StaleElementReferenceError,
|
||
ElementNotVisibleError,
|
||
InvalidElementStateError,
|
||
UnknownError,
|
||
ElementIsNotSelectableError,
|
||
ElementClickInterceptedError,
|
||
ElementNotInteractableError,
|
||
InsecureCertificateError,
|
||
JavaScriptError,
|
||
XPathLookupError,
|
||
TimeoutError,
|
||
NoSuchWindowError,
|
||
NoSuchCookieError,
|
||
InvalidCookieDomainError,
|
||
InvalidCoordinatesError,
|
||
UnableToSetCookieError,
|
||
UnexpectedAlertOpenError,
|
||
NoAlertOpenError,
|
||
ScriptTimeoutError,
|
||
InvalidElementCoordinatesError,
|
||
IMENotAvailableError,
|
||
IMEEngineActivationFailedError,
|
||
InvalidSelectorError,
|
||
SessionNotCreatedError,
|
||
MoveTargetOutOfBoundsError,
|
||
NoSuchAlertError,
|
||
NoSuchContextError,
|
||
InvalidContextError,
|
||
NoSuchFrameError,
|
||
UnableToCaptureScreen,
|
||
UnknownMethodError,
|
||
UnsupportedOperationError,
|
||
ProxyRequestError};
|
||
|
||
// map of error code to error class
|
||
const jsonwpErrorCodeMap = {};
|
||
for (let ErrorClass of _.values(errors)) {
|
||
if (ErrorClass.code) {
|
||
jsonwpErrorCodeMap[ErrorClass.code()] = ErrorClass;
|
||
}
|
||
}
|
||
|
||
const w3cErrorCodeMap = {};
|
||
for (let ErrorClass of _.values(errors)) {
|
||
if (ErrorClass.error) {
|
||
w3cErrorCodeMap[ErrorClass.error()] = ErrorClass;
|
||
}
|
||
}
|
||
|
||
function isUnknownError (err) {
|
||
return !err.constructor.name ||
|
||
!_.values(errors).find(function (error) {
|
||
return error.name === err.constructor.name;
|
||
});
|
||
}
|
||
|
||
function isErrorType (err, type) {
|
||
// `name` property is the constructor name
|
||
if (type.name === ProtocolError.name) {
|
||
// `jsonwpCode` is `0` on success
|
||
return !!err.jsonwpCode;
|
||
} else if (type.name === ProxyRequestError.name) {
|
||
// `status` is `0` on success
|
||
if (err.jsonwp) {
|
||
return !!err.jsonwp.status;
|
||
}
|
||
|
||
if (_.isPlainObject(err.w3c)) {
|
||
return _.isNumber(err.w3cStatus) && err.w3cStatus >= 300;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
return err.constructor.name === type.name;
|
||
}
|
||
|
||
/**
|
||
* Retrieve an error derived from MJSONWP status
|
||
* @param {number} code JSONWP status code
|
||
* @param {string|Object} value The error message, or an object with a `message` property
|
||
* @return {ProtocolError} The error that is associated with provided JSONWP status code
|
||
*/
|
||
function errorFromMJSONWPStatusCode (code, value = '') {
|
||
// if `value` is an object, pull message from it, otherwise use the plain
|
||
// value, or default to an empty string, if null
|
||
const message = (value || {}).message || value || '';
|
||
if (code !== UnknownError.code() && jsonwpErrorCodeMap[code]) {
|
||
mjsonwpLog.debug(`Matched JSONWP error code ${code} to ${jsonwpErrorCodeMap[code].name}`);
|
||
return new jsonwpErrorCodeMap[code](message);
|
||
}
|
||
mjsonwpLog.debug(`Matched JSONWP error code ${code} to UnknownError`);
|
||
return new UnknownError(message);
|
||
}
|
||
|
||
/**
|
||
* Retrieve an error derived from W3C JSON Code
|
||
* @param {string} code W3C error string (see https://www.w3.org/TR/webdriver/#handling-errors `JSON Error Code` column)
|
||
* @param {string} message the error message
|
||
* @param {?string} stacktrace an optional error stacktrace
|
||
* @return {ProtocolError} The error that is associated with the W3C error string
|
||
*/
|
||
function errorFromW3CJsonCode (code, message, stacktrace = null) {
|
||
if (code && w3cErrorCodeMap[code.toLowerCase()]) {
|
||
w3cLog.debug(`Matched W3C error code '${code}' to ${w3cErrorCodeMap[code.toLowerCase()].name}`);
|
||
const resultError = new w3cErrorCodeMap[code.toLowerCase()](message);
|
||
resultError.stacktrace = stacktrace;
|
||
return resultError;
|
||
}
|
||
w3cLog.debug(`Matched W3C error code '${code}' to UnknownError`);
|
||
const resultError = new UnknownError(message);
|
||
resultError.stacktrace = stacktrace;
|
||
return resultError;
|
||
}
|
||
|
||
/**
|
||
* Convert an Appium error to proper W3C HTTP response
|
||
* @param {ProtocolError} err The error that needs to be translated
|
||
*/
|
||
function getResponseForW3CError (err) {
|
||
let httpStatus;
|
||
|
||
// W3C defined error message (https://www.w3.org/TR/webdriver/#dfn-error-code)
|
||
let w3cErrorString;
|
||
|
||
if (!err.w3cStatus) {
|
||
err = util.hasValue(err.status)
|
||
// If it's a JSONWP error, find corresponding error
|
||
? errorFromMJSONWPStatusCode(err.status, err.value)
|
||
: new errors.UnknownError(err.message);
|
||
}
|
||
|
||
if (isErrorType(err, errors.BadParametersError)) {
|
||
// respond with a 400 if we have bad parameters
|
||
w3cLog.debug(`Bad parameters: ${err}`);
|
||
w3cErrorString = BadParametersError.error();
|
||
} else {
|
||
w3cErrorString = err.error;
|
||
}
|
||
|
||
httpStatus = err.w3cStatus;
|
||
|
||
if (!w3cErrorString) {
|
||
w3cErrorString = UnknownError.error();
|
||
}
|
||
|
||
let httpResBody = {
|
||
value: {
|
||
error: w3cErrorString,
|
||
message: err.message,
|
||
stacktrace: err.stacktrace || err.stack,
|
||
}
|
||
};
|
||
return [httpStatus, httpResBody];
|
||
}
|
||
|
||
/**
|
||
* Convert an Appium error to a proper JSONWP response
|
||
* @param {ProtocolError} err The error to be converted
|
||
*/
|
||
function getResponseForJsonwpError (err) {
|
||
if (isUnknownError(err)) {
|
||
err = new errors.UnknownError(err);
|
||
}
|
||
// MJSONWP errors are usually 500 status code so set it to that by default
|
||
let httpStatus = HTTPStatusCodes.INTERNAL_SERVER_ERROR;
|
||
let httpResBody = {
|
||
status: err.jsonwpCode,
|
||
value: {
|
||
message: err.message
|
||
}
|
||
};
|
||
|
||
if (isErrorType(err, errors.BadParametersError)) {
|
||
// respond with a 400 if we have bad parameters
|
||
mjsonwpLog.debug(`Bad parameters: ${err}`);
|
||
httpStatus = HTTPStatusCodes.BAD_REQUEST;
|
||
httpResBody = err.message;
|
||
} else if (isErrorType(err, errors.NotYetImplementedError) ||
|
||
isErrorType(err, errors.NotImplementedError)) {
|
||
// respond with a 501 if the method is not implemented
|
||
httpStatus = HTTPStatusCodes.NOT_IMPLEMENTED;
|
||
} else if (isErrorType(err, errors.NoSuchDriverError)) {
|
||
// respond with a 404 if there is no driver for the session
|
||
httpStatus = HTTPStatusCodes.NOT_FOUND;
|
||
}
|
||
|
||
|
||
return [httpStatus, httpResBody];
|
||
}
|
||
|
||
export {
|
||
ProtocolError, errors, isErrorType, isUnknownError,
|
||
errorFromMJSONWPStatusCode, errorFromW3CJsonCode,
|
||
getResponseForW3CError, getResponseForJsonwpError,
|
||
};
|