Files
2026-07-01 08:54:13 +02:00

1126 lines
30 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {logger, util} from '@appium/support';
import type {Class, ErrorBiDiCommandResponse} from '@appium/types';
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
const mjsonwpLog = logger.getLogger('MJSONWP');
const w3cLog = logger.getLogger('W3C');
class BaseError extends Error {
public cause: Error | undefined;
public message: string;
public name: string;
public stack: string | undefined;
constructor(message: string = '', cause?: Error) {
super(message);
this.cause = cause;
this.message = message;
this.name = this.constructor.name;
this._formatStack();
}
private _formatStack(): void {
// eslint-disable-next-line no-prototype-builtins
if (Error.hasOwnProperty('captureStackTrace') && util.isEmpty(this.stack)) {
Error.captureStackTrace(this, this.constructor);
}
if (typeof this.cause?.stack !== 'string') {
return;
}
if (util.isEmpty(this.stack)) {
this.stack = this.cause.stack;
return;
}
const stackLines = this.stack?.split('\n') ?? [];
stackLines.push('The above error is caused by');
stackLines.push(...this.cause.stack.split('\n'));
this.stack = stackLines.join('\n');
}
}
// base error class for all of our errors
export class ProtocolError extends BaseError {
public jsonwpCode: number;
public error: string;
public w3cStatus: number;
protected _stacktrace: string | undefined;
constructor(msg: string, jsonwpCode: number, w3cStatus: number, w3cErrorSignature: string, cause?: Error) {
super(msg, cause);
this.jsonwpCode = jsonwpCode ?? UnknownError.code();
this.error = w3cErrorSignature ?? UnknownError.error();
this.w3cStatus = w3cStatus ?? UnknownError.w3cStatus();
this._stacktrace = undefined;
}
get stacktrace() {
return this._stacktrace || this.stack;
}
set stacktrace(value) {
this._stacktrace = value;
}
/**
* Get the Bidi protocol version of an error
* @param id - the id used in the request for which this error forms the response
* @see https://w3c.github.io/webdriver-bidi/#protocol-definition
* @returns The object conforming to the shape of a BiDi error response
*/
bidiErrObject(id: string | number): ErrorBiDiCommandResponse {
// if we don't have an id, the client didn't send one, so we have nothing to send back.
// send back zero rather than making something up
const intId = (Number.isInteger(id) ? id : parseInt(`${id}`, 10) || 0) as number;
return {
id: intId,
type: 'error',
error: this.error,
stacktrace: this.stacktrace,
message: this.message,
};
}
}
// 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
export class NoSuchDriverError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A session is either terminated or not started',
NoSuchDriverError.code(),
NoSuchDriverError.w3cStatus(),
NoSuchDriverError.error(),
cause,
);
}
static code() {
return 6;
}
// W3C Error is called InvalidSessionID
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'invalid session id';
}
}
export class NoSuchElementError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An element could not be located on the page using the given ' + 'search parameters.',
NoSuchElementError.code(),
NoSuchElementError.w3cStatus(),
NoSuchElementError.error(),
cause,
);
}
static code() {
return 7;
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'no such element';
}
}
/**
* W3C WebDriver "no such shadow root" error. Issued when a command
* targets an element's shadow root that does not exist (or has been
* detached). Previously this was mis-classified as UnknownError — see
* https://github.com/appium/appium/issues/22209.
*/
export class NoSuchShadowRootError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'The element does not have a shadow root attached.',
NoSuchShadowRootError.code(),
NoSuchShadowRootError.w3cStatus(),
NoSuchShadowRootError.error(),
cause,
);
}
// W3C-only error. No historical JSONWP code is assigned to
// "no such shadow root"; keep a placeholder so the super call
// typechecks (other errors in this file follow the same shape).
static code() {
return 65;
}
static error() {
return 'no such shadow root';
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
}
export class NoSuchFrameError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A request to switch to a frame could not be satisfied because ' + 'the frame could not be found.',
NoSuchFrameError.code(),
NoSuchFrameError.w3cStatus(),
NoSuchFrameError.error(),
cause,
);
}
static code() {
return 8;
}
static error() {
return 'no such frame';
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
}
export class UnknownCommandError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'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(),
cause,
);
}
static code() {
return 9;
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'unknown command';
}
}
export class StaleElementReferenceError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An element command failed because the referenced element is no ' + 'longer attached to the DOM.',
StaleElementReferenceError.code(),
StaleElementReferenceError.w3cStatus(),
StaleElementReferenceError.error(),
cause,
);
}
static code() {
return 10;
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'stale element reference';
}
}
export class ElementNotVisibleError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An element command could not be completed because the element is ' + 'not visible on the page.',
ElementNotVisibleError.code(),
ElementNotVisibleError.w3cStatus(),
ElementNotVisibleError.error(),
cause,
);
}
static code() {
return 11;
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
static error() {
return 'element not visible';
}
}
export class InvalidElementStateError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'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(),
cause,
);
}
static code() {
return 12;
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
static error() {
return 'invalid element state';
}
}
export class UnknownError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An unknown server-side error occurred while processing the command.',
UnknownError.code(),
UnknownError.w3cStatus(),
UnknownError.error(),
cause,
);
}
static code() {
return 13;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unknown error';
}
}
export class UnknownMethodError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'The requested command matched a known URL but did not match an method for that URL',
UnknownMethodError.code(),
UnknownMethodError.w3cStatus(),
UnknownMethodError.error(),
cause,
);
}
static code() {
return 405;
}
static w3cStatus() {
return HTTPStatusCodes.METHOD_NOT_ALLOWED;
}
static error() {
return 'unknown method';
}
}
export class UnsupportedOperationError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A server-side error occurred. Command cannot be supported.',
UnsupportedOperationError.code(),
UnsupportedOperationError.w3cStatus(),
UnsupportedOperationError.error(),
cause,
);
}
static code() {
return 405;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unsupported operation';
}
}
export class ElementIsNotSelectableError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An attempt was made to select an element that cannot be selected.',
ElementIsNotSelectableError.code(),
ElementIsNotSelectableError.w3cStatus(),
ElementIsNotSelectableError.error(),
cause,
);
}
static code() {
return 15;
}
static error() {
return 'element not selectable';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class ElementClickInterceptedError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'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(),
cause,
);
}
static code() {
return 64;
}
static error() {
return 'element click intercepted';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class ElementNotInteractableError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A command could not be completed because the element is not pointer- or keyboard interactable',
ElementNotInteractableError.code(),
ElementNotInteractableError.w3cStatus(),
ElementNotInteractableError.error(),
cause,
);
}
static code() {
return 60;
}
static error() {
return 'element not interactable';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class InsecureCertificateError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate',
UnknownError.code(),
InsecureCertificateError.w3cStatus(),
InsecureCertificateError.error(),
cause,
);
}
static error() {
return 'insecure certificate';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class JavaScriptError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An error occurred while executing user supplied JavaScript.',
JavaScriptError.code(),
JavaScriptError.w3cStatus(),
JavaScriptError.error(),
cause,
);
}
static code() {
return 17;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'javascript error';
}
}
export class XPathLookupError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An error occurred while searching for an element by XPath.',
XPathLookupError.code(),
XPathLookupError.w3cStatus(),
XPathLookupError.error(),
cause,
);
}
static code() {
return 19;
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
static error() {
return 'invalid selector';
}
}
export class TimeoutError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An operation did not complete before its timeout expired.',
TimeoutError.code(),
TimeoutError.w3cStatus(),
TimeoutError.error(),
cause,
);
}
static code() {
return 21;
}
static w3cStatus() {
return HTTPStatusCodes.REQUEST_TIMEOUT;
}
static error() {
return 'timeout';
}
}
export class NoSuchWindowError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'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(),
cause,
);
}
static code() {
return 23;
}
static error() {
return 'no such window';
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
}
export class InvalidArgumentError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'The arguments passed to the command are either invalid or malformed',
InvalidArgumentError.code(),
InvalidArgumentError.w3cStatus(),
InvalidArgumentError.error(),
cause,
);
}
static code() {
return 61;
}
static error() {
return 'invalid argument';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class InvalidCookieDomainError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An illegal attempt was made to set a cookie under a different ' + 'domain than the current page.',
InvalidCookieDomainError.code(),
InvalidCookieDomainError.w3cStatus(),
InvalidCookieDomainError.error(),
cause,
);
}
static code() {
return 24;
}
static error() {
return 'invalid cookie domain';
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
}
export class NoSuchCookieError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message ||
'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(),
cause,
);
}
static code() {
return 62;
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'no such cookie';
}
}
export class UnableToSetCookieError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || "A request to set a cookie's value could not be satisfied.",
UnableToSetCookieError.code(),
UnableToSetCookieError.w3cStatus(),
UnableToSetCookieError.error(),
cause,
);
}
static code() {
return 25;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unable to set cookie';
}
}
export class UnexpectedAlertOpenError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A modal dialog was open, blocking this operation',
UnexpectedAlertOpenError.code(),
UnexpectedAlertOpenError.w3cStatus(),
UnexpectedAlertOpenError.error(),
cause,
);
}
static code() {
return 26;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unexpected alert open';
}
}
export class NoAlertOpenError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An attempt was made to operate on a modal dialog when one was not open.',
NoAlertOpenError.code(),
NoAlertOpenError.w3cStatus(),
NoAlertOpenError.error(),
cause,
);
}
static code() {
return 27;
}
static w3cStatus() {
return HTTPStatusCodes.NOT_FOUND;
}
static error() {
return 'no such alert';
}
}
export class NoSuchAlertError extends NoAlertOpenError {}
export class ScriptTimeoutError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A script did not complete before its timeout expired.',
ScriptTimeoutError.code(),
ScriptTimeoutError.w3cStatus(),
ScriptTimeoutError.error(),
cause,
);
}
static code() {
return 28;
}
static w3cStatus() {
return HTTPStatusCodes.REQUEST_TIMEOUT;
}
static error() {
return 'script timeout';
}
}
export class InvalidElementCoordinatesError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'The coordinates provided to an interactions operation are invalid.',
InvalidElementCoordinatesError.code(),
InvalidElementCoordinatesError.w3cStatus(),
InvalidElementCoordinatesError.error(),
cause,
);
}
static code() {
return 29;
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
static error() {
return 'invalid coordinates';
}
}
export class InvalidCoordinatesError extends InvalidElementCoordinatesError {}
export class IMENotAvailableError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'IME was not available.',
IMENotAvailableError.code(),
IMENotAvailableError.w3cStatus(),
IMENotAvailableError.error(),
cause,
);
}
static code() {
return 30;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unsupported operation';
}
}
export class IMEEngineActivationFailedError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'An IME engine could not be started.',
IMEEngineActivationFailedError.code(),
IMEEngineActivationFailedError.w3cStatus(),
IMEEngineActivationFailedError.error(),
cause,
);
}
static code() {
return 31;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unsupported operation';
}
}
export class InvalidSelectorError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'Argument was an invalid selector (e.g. XPath/CSS).',
InvalidSelectorError.code(),
InvalidSelectorError.w3cStatus(),
InvalidSelectorError.error(),
cause,
);
}
static code() {
return 32;
}
static w3cStatus() {
return HTTPStatusCodes.BAD_REQUEST;
}
static error() {
return 'invalid selector';
}
}
export class SessionNotCreatedError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
`A new session could not be created.${message ? ' Details: ' + message : ''}`,
SessionNotCreatedError.code(),
SessionNotCreatedError.w3cStatus(),
SessionNotCreatedError.error(),
cause,
);
}
static code() {
return 33;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'session not created';
}
}
export class MoveTargetOutOfBoundsError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'Target provided for a move action is out of bounds.',
MoveTargetOutOfBoundsError.code(),
MoveTargetOutOfBoundsError.w3cStatus(),
MoveTargetOutOfBoundsError.error(),
cause,
);
}
static code() {
return 34;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'move target out of bounds';
}
}
export class NoSuchContextError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'No such context found.',
NoSuchContextError.code(),
UnknownError.w3cStatus(),
UnknownError.error(),
cause,
);
}
static code() {
return 35;
}
}
export class InvalidContextError extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'That command could not be executed in the current context.',
InvalidContextError.code(),
UnknownError.w3cStatus(),
UnknownError.error(),
cause,
);
}
static code() {
return 36;
}
}
// Aliases to UnknownMethodError
export class NotYetImplementedError extends UnknownMethodError {
constructor(message: string = '', cause?: Error) {
super(message || 'Method has not yet been implemented', cause);
}
}
export class NotImplementedError extends UnknownMethodError {
constructor(message: string = '', cause?: Error) {
super(message || 'Method is not implemented', cause);
}
}
export class UnableToCaptureScreen extends ProtocolError {
constructor(message: string = '', cause?: Error) {
super(
message || 'A screen capture was made impossible',
UnableToCaptureScreen.code(),
UnableToCaptureScreen.w3cStatus(),
UnableToCaptureScreen.error(),
cause,
);
}
static code() {
return 63;
}
static w3cStatus() {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error() {
return 'unable to capture screen';
}
}
// Equivalent to W3C InvalidArgumentError
export class BadParametersError extends InvalidArgumentError {
constructor(paramReqs: ParameterRequirements, paramNames: string[]) {
super(generateBadParametersMessage(paramReqs, paramNames));
}
}
/**
* 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.
*/
export class ProxyRequestError extends BaseError {
private readonly _w3cError?: W3CError;
private readonly _w3cErrorStatus?: number;
private readonly _jwpError?: MJSONWPError;
constructor(message: string, httpResponseData: any, httpStatus?: number, cause?: Error) {
const [responseErrorObj, originalMessage] = ProxyRequestError._parseHttpResponse(httpResponseData);
super(
util.isEmpty(message) ? `Proxy request unsuccessful.${originalMessage ? ' ' + originalMessage : ''}` : message,
cause,
);
// If the response error is an object and value is an object, it's a W3C error (for JSONWP value is a string)
if (util.isPlainObject(responseErrorObj.value) && Object.hasOwn(responseErrorObj.value, 'error')) {
this._w3cError = responseErrorObj.value as unknown as typeof this._w3cError;
this._w3cErrorStatus = httpStatus;
} else if (Object.hasOwn(responseErrorObj, 'status')) {
this._jwpError = responseErrorObj as typeof this._jwpError;
}
}
private static _parseHttpResponse(data: any): [Record<string, any>, string] {
let responseErrorObj: Record<string, any> = util.safeJsonParse(data);
if (!util.isPlainObject(responseErrorObj)) {
responseErrorObj = {};
}
let errorMessage: string = typeof data === 'string' ? data : '';
if (typeof responseErrorObj.value === 'string') {
errorMessage = responseErrorObj.value;
} else if (typeof responseErrorObj.value?.message === 'string') {
errorMessage = responseErrorObj.value.message;
}
return [responseErrorObj, errorMessage];
}
getActualError(): ProtocolError {
if (util.hasValue(this._jwpError?.status) && util.hasValue(this._jwpError?.value)) {
// If it's MJSONWP error, returns actual error cause for request failure based on `jsonwp.status`
return errorFromMJSONWPStatusCode(this._jwpError.status, this._jwpError.value);
}
if (util.hasValue(this._w3cError) && typeof this._w3cErrorStatus === 'number' && this._w3cErrorStatus >= 300) {
return errorFromW3CJsonCode(
this._w3cError.error,
this._w3cError.message || this.message,
this._w3cError.stacktrace || this.stack,
);
}
return new UnknownError(this.message, this.cause);
}
}
function generateBadParametersMessage(paramRequirements: ParameterRequirements, paramNames: string[]): string {
const toArray = function <T>(x: T | T[]): T[] {
if (x === undefined) {
return [];
}
if (Array.isArray(x)) {
return x;
}
return [x];
};
const requiredParamNames = toArray(paramRequirements.required);
const actualParamNames = toArray(paramNames);
const missingRequiredParamNames = requiredParamNames.filter((name) => !actualParamNames.includes(name));
const resultLines: string[] = [];
resultLines.push(
util.isEmpty(missingRequiredParamNames)
? // This should not happen
'Some of the provided parameters are not known'
: `The following required parameter${missingRequiredParamNames.length === 1 ? ' is' : 's are'} missing: ${JSON.stringify(
missingRequiredParamNames,
)}`,
);
if (!util.isEmpty(requiredParamNames)) {
resultLines.push(`Known required parameters are: ${JSON.stringify(requiredParamNames)}`);
}
const optionalParamNames = toArray(paramRequirements.optional).filter(
(name): name is string => typeof name === 'string' && !['sessionId', 'id'].includes(name),
);
if (!util.isEmpty(optionalParamNames)) {
resultLines.push(`Known optional parameters are: ${JSON.stringify(optionalParamNames)}`);
}
resultLines.push(`You have provided${util.isEmpty(actualParamNames) ? ' none' : ': ' + JSON.stringify(paramNames)}`);
return resultLines.join('\n');
}
// map of error class name to error class
export const errors = {
NotYetImplementedError,
NotImplementedError,
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,
NoSuchShadowRootError,
UnableToCaptureScreen,
UnknownMethodError,
UnsupportedOperationError,
ProxyRequestError,
} as const;
const jsonwpErrorCodeMap: Record<string, Class<ProtocolError>> = Object.values(errors).reduce(
(acc: Record<string, Class<ProtocolError>>, ErrorClass: any) => {
if ('code' in ErrorClass) {
acc[ErrorClass.code()] = ErrorClass;
}
return acc;
},
{},
);
const w3cErrorCodeMap: Record<string, Class<ProtocolError>> = Object.values(errors).reduce(
(acc: Record<string, Class<ProtocolError>>, ErrorClass: any) => {
if ('error' in ErrorClass) {
acc[ErrorClass.error()] = ErrorClass;
}
return acc;
},
{},
);
interface MJSONWPError {
status: number;
value?: any;
message?: string;
}
interface W3CError {
error: string;
message?: string;
stacktrace?: string;
}
interface ParameterRequirements {
required: string[] | string;
optional?: string[] | string;
}
/**
* Type guard to check if an Error is of a specific type
*/
export function isErrorType<T>(err: any, type: Class<T>): err is T {
return err.constructor?.name === type.name;
}
/**
* Retrieve an error derived from MJSONWP status
* @param code JSONWP status code
* @param value The error message, or an object with a `message` property
* @return The error that is associated with provided JSONWP status code
*/
export function errorFromMJSONWPStatusCode(code: number, value: string | {message: string} = ''): ProtocolError {
const ErrorClass = jsonwpErrorCodeMap[code] ?? UnknownError;
mjsonwpLog.debug(`Matched JSONWP error code ${code} to ${ErrorClass.name}`);
// 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 || {}) as any).message || value || '';
return new ErrorClass(message);
}
/**
* Retrieve an error derived from W3C JSON Code
* @param signature W3C error string (see https://www.w3.org/TR/webdriver/#handling-errors `JSON Error Code` column)
* @param message the error message
* @param stacktrace an optional error stacktrace
* @return The error that is associated with the W3C error string
*/
export function errorFromW3CJsonCode(signature: string, message: string, stacktrace?: string): ProtocolError {
const ErrorClass = w3cErrorCodeMap[signature.toLowerCase()] ?? UnknownError;
w3cLog.debug(`Matched W3C error code '${signature}' to ${ErrorClass.name}`);
const resultError = new ErrorClass(message);
resultError.stacktrace = stacktrace;
return resultError;
}
/**
* Convert an Appium error to proper W3C HTTP response
*
* @param err The error that needs to be translated
*/
export function getResponseForW3CError(err: any): [number, {value: W3CError}] {
const protocolErrorToResponse: (e: ProtocolError) => [number, {value: W3CError}] = (e: ProtocolError) => [
e.w3cStatus,
{
value: {
error: e.error,
message: e.message,
stacktrace: e.stacktrace || e.stack,
},
},
];
// err is ProtocolError
if (['error', 'w3cStatus'].every((prop) => Object.hasOwn(err, prop))) {
return protocolErrorToResponse(err);
}
// err is ProxyRequestError
if (Object.hasOwn(err, 'getActualError') && typeof err.getActualError === 'function') {
return protocolErrorToResponse(err.getActualError());
}
return protocolErrorToResponse(new UnknownError(err.message, err));
}