Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 101 additions & 9 deletions appium/webdriver/errorhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,111 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Dict
import json
from typing import Any, Dict, List, Sequence, Type, Union

from selenium.common.exceptions import WebDriverException
import selenium.common.exceptions as sel_exceptions
from selenium.webdriver.remote import errorhandler

from appium.common.exceptions import NoSuchContextException
import appium.common.exceptions as appium_exceptions

ERROR_TO_EXC_MAPPING: Dict[str, Type[sel_exceptions.WebDriverException]] = {
'element click intercepted': sel_exceptions.ElementClickInterceptedException,
'element not interactable': sel_exceptions.ElementNotInteractableException,
'insecure certificate': sel_exceptions.InsecureCertificateException,
'invalid argument': sel_exceptions.InvalidArgumentException,
'invalid cookie domain': sel_exceptions.InvalidCookieDomainException,
'invalid element state': sel_exceptions.InvalidElementStateException,
'invalid selector': sel_exceptions.InvalidSelectorException,
'invalid session id': sel_exceptions.InvalidSessionIdException,
'javascript error': sel_exceptions.JavascriptException,
'move target out of bounds': sel_exceptions.MoveTargetOutOfBoundsException,
'no such alert': sel_exceptions.NoAlertPresentException,
'no such cookie': sel_exceptions.NoSuchCookieException,
'no such element': sel_exceptions.NoSuchElementException,
'no such frame': sel_exceptions.NoSuchFrameException,
'no such window': sel_exceptions.NoSuchWindowException,
'no such shadow root': sel_exceptions.NoSuchShadowRootException,
'script timeout': sel_exceptions.TimeoutException,
'session not created': sel_exceptions.SessionNotCreatedException,
'stale element reference': sel_exceptions.StaleElementReferenceException,
'detached shadow root': sel_exceptions.NoSuchShadowRootException,
'timeout': sel_exceptions.TimeoutException,
'unable to set cookie': sel_exceptions.UnableToSetCookieException,
'unable to capture screen': sel_exceptions.ScreenshotException,
'unexpected alert open': sel_exceptions.UnexpectedAlertPresentException,
'unknown command': sel_exceptions.UnknownMethodException,
'unknown error': sel_exceptions.WebDriverException,
'unknown method': sel_exceptions.UnknownMethodException,
'unsupported operation': sel_exceptions.UnknownMethodException,
'element not visible': sel_exceptions.ElementNotVisibleException,
'element not selectable': sel_exceptions.ElementNotSelectableException,
'invalid coordinates': sel_exceptions.InvalidCoordinatesException,
}


def format_stacktrace(original: Union[None, str, Sequence]) -> List[str]:
if not original:
return []
if isinstance(original, str):
return original.split('\n')

result: List[str] = []
try:
for frame in original:
if not isinstance(frame, dict):
continue

line = frame.get('lineNumber', '')
file = frame.get('fileName', '<anonymous>')
if line:
file = f'{file}:{line}'
meth = frame.get('methodName', '<anonymous>')
if 'className' in frame:
meth = f'{frame["className"]}.{meth}'
result.append(f' at {meth} ({file})')
except TypeError:
pass
return result


class MobileErrorHandler(errorhandler.ErrorHandler):
def check_response(self, response: Dict) -> None:
def check_response(self, response: Dict[str, Any]) -> None:
"""
https://www.w3.org/TR/webdriver/#errors
"""
payload = response.get('value', '')
try:
super().check_response(response)
except WebDriverException as wde:
if wde.msg == 'No such context found.':
raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) from wde
raise wde
payload_dict = json.loads(payload)
except (json.JSONDecodeError, TypeError):
return
if not isinstance(payload_dict, dict):
return
value = payload_dict.get('value')
if not isinstance(value, dict):
return
error = value.get('error')
if not error:
return

message = value.get('message', error)
stacktrace = value.get('stacktrace', '')
# In theory, we should also be checking HTTP status codes.
# Java client, for example, prints a warning if the actual `error`
# value does not match to the response's HTTP status code.
exception_class: Type[sel_exceptions.WebDriverException] = ERROR_TO_EXC_MAPPING.get(
error, sel_exceptions.WebDriverException
)
if exception_class is sel_exceptions.WebDriverException and message:
if message == 'No such context found.':
exception_class = appium_exceptions.NoSuchContextException
elif message == 'That command could not be executed in the current context.':
exception_class = appium_exceptions.InvalidSwitchToTargetException

if exception_class is sel_exceptions.UnexpectedAlertPresentException:
raise sel_exceptions.UnexpectedAlertPresentException(
msg=message,
stacktrace=format_stacktrace(stacktrace),
alert_text=value.get('data'),
)
raise exception_class(msg=message, stacktrace=format_stacktrace(stacktrace))