Appium-Python-Client 5.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. appium/__init__.py +17 -0
  2. appium/common/__init__.py +17 -0
  3. appium/common/exceptions.py +26 -0
  4. appium/common/helper.py +42 -0
  5. appium/common/logger.py +28 -0
  6. appium/options/__init__.py +0 -0
  7. appium/options/android/__init__.py +2 -0
  8. appium/options/android/common/__init__.py +0 -0
  9. appium/options/android/common/adb/__init__.py +0 -0
  10. appium/options/android/common/adb/adb_exec_timeout_option.py +41 -0
  11. appium/options/android/common/adb/adb_port_option.py +38 -0
  12. appium/options/android/common/adb/allow_delay_adb_option.py +39 -0
  13. appium/options/android/common/adb/build_tools_version_option.py +42 -0
  14. appium/options/android/common/adb/clear_device_logs_on_start_option.py +40 -0
  15. appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py +40 -0
  16. appium/options/android/common/adb/logcat_filter_specs_option.py +42 -0
  17. appium/options/android/common/adb/logcat_format_option.py +39 -0
  18. appium/options/android/common/adb/mock_location_app_option.py +42 -0
  19. appium/options/android/common/adb/remote_adb_host_option.py +39 -0
  20. appium/options/android/common/adb/skip_logcat_capture_option.py +40 -0
  21. appium/options/android/common/adb/suppress_kill_server_option.py +39 -0
  22. appium/options/android/common/app/__init__.py +0 -0
  23. appium/options/android/common/app/allow_test_packages_option.py +40 -0
  24. appium/options/android/common/app/android_install_timeout_option.py +43 -0
  25. appium/options/android/common/app/app_activity_option.py +39 -0
  26. appium/options/android/common/app/app_package_option.py +39 -0
  27. appium/options/android/common/app/app_wait_activity_option.py +40 -0
  28. appium/options/android/common/app/app_wait_duration_option.py +41 -0
  29. appium/options/android/common/app/app_wait_for_launch_option.py +41 -0
  30. appium/options/android/common/app/app_wait_package_option.py +40 -0
  31. appium/options/android/common/app/auto_grant_premissions_option.py +40 -0
  32. appium/options/android/common/app/enforce_app_install_option.py +40 -0
  33. appium/options/android/common/app/intent_action_option.py +40 -0
  34. appium/options/android/common/app/intent_category_option.py +40 -0
  35. appium/options/android/common/app/intent_flags_option.py +40 -0
  36. appium/options/android/common/app/optional_intent_arguments_option.py +40 -0
  37. appium/options/android/common/app/remote_apps_cache_limit_option.py +42 -0
  38. appium/options/android/common/app/uninstall_other_packages_option.py +39 -0
  39. appium/options/android/common/avd/__init__.py +0 -0
  40. appium/options/android/common/avd/avd_args_option.py +38 -0
  41. appium/options/android/common/avd/avd_env_option.py +38 -0
  42. appium/options/android/common/avd/avd_launch_timeout_option.py +41 -0
  43. appium/options/android/common/avd/avd_option.py +41 -0
  44. appium/options/android/common/avd/avd_ready_timeout_option.py +41 -0
  45. appium/options/android/common/avd/gps_enabled_option.py +39 -0
  46. appium/options/android/common/avd/network_speed_option.py +41 -0
  47. appium/options/android/common/context/__init__.py +0 -0
  48. appium/options/android/common/context/auto_webview_timeout_option.py +41 -0
  49. appium/options/android/common/context/chrome_logging_prefs_option.py +41 -0
  50. appium/options/android/common/context/chrome_options_option.py +40 -0
  51. appium/options/android/common/context/chromedriver_args_option.py +41 -0
  52. appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py +43 -0
  53. appium/options/android/common/context/chromedriver_disable_build_check_option.py +41 -0
  54. appium/options/android/common/context/chromedriver_executable_dir_option.py +43 -0
  55. appium/options/android/common/context/chromedriver_executable_option.py +38 -0
  56. appium/options/android/common/context/chromedriver_port_option.py +39 -0
  57. appium/options/android/common/context/chromedriver_ports_option.py +39 -0
  58. appium/options/android/common/context/chromedriver_use_system_executable_option.py +40 -0
  59. appium/options/android/common/context/ensure_webviews_have_pages_option.py +40 -0
  60. appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py +40 -0
  61. appium/options/android/common/context/native_web_screenshot_option.py +40 -0
  62. appium/options/android/common/context/recreate_chrome_driver_sessions_option.py +41 -0
  63. appium/options/android/common/context/show_chromedriver_log_option.py +39 -0
  64. appium/options/android/common/context/webview_devtools_port_option.py +40 -0
  65. appium/options/android/common/localization/__init__.py +0 -0
  66. appium/options/android/common/localization/locale_script_option.py +40 -0
  67. appium/options/android/common/locking/__init__.py +0 -0
  68. appium/options/android/common/locking/skip_unlock_option.py +42 -0
  69. appium/options/android/common/locking/unlock_key_option.py +40 -0
  70. appium/options/android/common/locking/unlock_strategy_option.py +40 -0
  71. appium/options/android/common/locking/unlock_success_timeout_option.py +43 -0
  72. appium/options/android/common/locking/unlock_type_option.py +40 -0
  73. appium/options/android/common/mjpeg/__init__.py +0 -0
  74. appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py +40 -0
  75. appium/options/android/common/other/__init__.py +0 -0
  76. appium/options/android/common/other/disable_suppress_accessibility_service_option.py +40 -0
  77. appium/options/android/common/other/user_profile_option.py +42 -0
  78. appium/options/android/common/signing/__init__.py +0 -0
  79. appium/options/android/common/signing/key_alias_option.py +40 -0
  80. appium/options/android/common/signing/key_password_option.py +40 -0
  81. appium/options/android/common/signing/keystore_password_option.py +40 -0
  82. appium/options/android/common/signing/keystore_path_option.py +40 -0
  83. appium/options/android/common/signing/no_sign_option.py +42 -0
  84. appium/options/android/common/signing/use_keystore_option.py +42 -0
  85. appium/options/android/espresso/__init__.py +0 -0
  86. appium/options/android/espresso/activity_options_option.py +41 -0
  87. appium/options/android/espresso/app_locale_option.py +44 -0
  88. appium/options/android/espresso/base.py +221 -0
  89. appium/options/android/espresso/espresso_build_config_option.py +46 -0
  90. appium/options/android/espresso/espresso_server_launch_timeout_option.py +43 -0
  91. appium/options/android/espresso/force_espresso_rebuild_option.py +41 -0
  92. appium/options/android/espresso/intent_options_option.py +41 -0
  93. appium/options/android/espresso/show_gradle_log_option.py +39 -0
  94. appium/options/android/uiautomator2/__init__.py +0 -0
  95. appium/options/android/uiautomator2/base.py +221 -0
  96. appium/options/android/uiautomator2/disable_window_animation_option.py +40 -0
  97. appium/options/android/uiautomator2/mjpeg_server_port_option.py +41 -0
  98. appium/options/android/uiautomator2/skip_device_initialization_option.py +40 -0
  99. appium/options/android/uiautomator2/skip_server_installation_option.py +44 -0
  100. appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py +44 -0
  101. appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py +44 -0
  102. appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py +46 -0
  103. appium/options/common/__init__.py +1 -0
  104. appium/options/common/app_option.py +41 -0
  105. appium/options/common/auto_web_view_option.py +40 -0
  106. appium/options/common/automation_name_option.py +38 -0
  107. appium/options/common/base.py +125 -0
  108. appium/options/common/browser_name_option.py +38 -0
  109. appium/options/common/bundle_id_option.py +38 -0
  110. appium/options/common/clear_system_files_option.py +38 -0
  111. appium/options/common/device_name_option.py +38 -0
  112. appium/options/common/enable_performance_logging_option.py +38 -0
  113. appium/options/common/event_timings_option.py +40 -0
  114. appium/options/common/full_reset_option.py +38 -0
  115. appium/options/common/is_headless_option.py +39 -0
  116. appium/options/common/language_option.py +38 -0
  117. appium/options/common/locale_option.py +38 -0
  118. appium/options/common/new_command_timeout_option.py +41 -0
  119. appium/options/common/no_reset_option.py +38 -0
  120. appium/options/common/orientation_option.py +40 -0
  121. appium/options/common/other_apps_option.py +39 -0
  122. appium/options/common/platform_version_option.py +40 -0
  123. appium/options/common/postrun_option.py +39 -0
  124. appium/options/common/prerun_option.py +40 -0
  125. appium/options/common/print_page_source_on_find_failure_option.py +40 -0
  126. appium/options/common/skip_log_capture_option.py +38 -0
  127. appium/options/common/supports_capabilities.py +26 -0
  128. appium/options/common/system_host_option.py +38 -0
  129. appium/options/common/system_port_option.py +38 -0
  130. appium/options/common/udid_option.py +38 -0
  131. appium/options/flutter_integration/__init__.py +15 -0
  132. appium/options/flutter_integration/base.py +39 -0
  133. appium/options/flutter_integration/flutter_element_wait_timeout_option.py +50 -0
  134. appium/options/flutter_integration/flutter_enable_mock_camera_option.py +44 -0
  135. appium/options/flutter_integration/flutter_server_launch_timeout_option.py +51 -0
  136. appium/options/flutter_integration/flutter_system_port_option.py +45 -0
  137. appium/options/gecko/__init__.py +1 -0
  138. appium/options/gecko/android_storage_option.py +39 -0
  139. appium/options/gecko/base.py +51 -0
  140. appium/options/gecko/firefox_options_option.py +38 -0
  141. appium/options/gecko/marionette_port_option.py +43 -0
  142. appium/options/gecko/verbosity_option.py +40 -0
  143. appium/options/ios/__init__.py +2 -0
  144. appium/options/ios/safari/__init__.py +0 -0
  145. appium/options/ios/safari/automatic_inspection_option.py +41 -0
  146. appium/options/ios/safari/automatic_profiling_option.py +41 -0
  147. appium/options/ios/safari/base.py +51 -0
  148. appium/options/ios/safari/device_name_option.py +43 -0
  149. appium/options/ios/safari/device_type_option.py +41 -0
  150. appium/options/ios/safari/device_udid_option.py +43 -0
  151. appium/options/ios/safari/platform_build_version_option.py +41 -0
  152. appium/options/ios/safari/platform_version_option.py +41 -0
  153. appium/options/ios/safari/use_simulator_option.py +41 -0
  154. appium/options/ios/safari/webkit_webrtc_option.py +52 -0
  155. appium/options/ios/xcuitest/__init__.py +0 -0
  156. appium/options/ios/xcuitest/app/__init__.py +0 -0
  157. appium/options/ios/xcuitest/app/app_install_strategy_option.py +46 -0
  158. appium/options/ios/xcuitest/app/app_push_timeout_option.py +42 -0
  159. appium/options/ios/xcuitest/app/localizable_strings_dir_option.py +39 -0
  160. appium/options/ios/xcuitest/base.py +223 -0
  161. appium/options/ios/xcuitest/general/__init__.py +0 -0
  162. appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py +41 -0
  163. appium/options/ios/xcuitest/general/reset_location_service_option.py +39 -0
  164. appium/options/ios/xcuitest/other/__init__.py +0 -0
  165. appium/options/ios/xcuitest/other/command_timeouts_option.py +57 -0
  166. appium/options/ios/xcuitest/other/launch_with_idb_option.py +42 -0
  167. appium/options/ios/xcuitest/other/show_ios_log_option.py +39 -0
  168. appium/options/ios/xcuitest/other/use_json_source_option.py +39 -0
  169. appium/options/ios/xcuitest/simulator/__init__.py +0 -0
  170. appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py +41 -0
  171. appium/options/ios/xcuitest/simulator/calendar_format_option.py +38 -0
  172. appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py +43 -0
  173. appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py +39 -0
  174. appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py +39 -0
  175. appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py +45 -0
  176. appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py +38 -0
  177. appium/options/ios/xcuitest/simulator/keep_key_chains_option.py +39 -0
  178. appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py +44 -0
  179. appium/options/ios/xcuitest/simulator/permissions_option.py +50 -0
  180. appium/options/ios/xcuitest/simulator/reduce_motion_option.py +40 -0
  181. appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py +41 -0
  182. appium/options/ios/xcuitest/simulator/scale_factor_option.py +44 -0
  183. appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py +44 -0
  184. appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py +41 -0
  185. appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py +42 -0
  186. appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py +46 -0
  187. appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py +41 -0
  188. appium/options/ios/xcuitest/simulator/simulator_window_center_option.py +41 -0
  189. appium/options/ios/xcuitest/wda/__init__.py +0 -0
  190. appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py +40 -0
  191. appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py +39 -0
  192. appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py +39 -0
  193. appium/options/ios/xcuitest/wda/derived_data_path_option.py +41 -0
  194. appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py +40 -0
  195. appium/options/ios/xcuitest/wda/force_app_launch_option.py +42 -0
  196. appium/options/ios/xcuitest/wda/keychain_password_option.py +39 -0
  197. appium/options/ios/xcuitest/wda/keychain_path_option.py +39 -0
  198. appium/options/ios/xcuitest/wda/max_typing_frequency_option.py +40 -0
  199. appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py +42 -0
  200. appium/options/ios/xcuitest/wda/process_arguments_option.py +42 -0
  201. appium/options/ios/xcuitest/wda/result_bundle_path_option.py +42 -0
  202. appium/options/ios/xcuitest/wda/screenshot_quality_option.py +42 -0
  203. appium/options/ios/xcuitest/wda/should_terminate_app_option.py +42 -0
  204. appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py +39 -0
  205. appium/options/ios/xcuitest/wda/show_xcode_log_option.py +40 -0
  206. appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py +42 -0
  207. appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py +39 -0
  208. appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py +41 -0
  209. appium/options/ios/xcuitest/wda/use_new_wda_option.py +51 -0
  210. appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py +39 -0
  211. appium/options/ios/xcuitest/wda/use_simple_build_test_option.py +40 -0
  212. appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py +49 -0
  213. appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py +45 -0
  214. appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py +42 -0
  215. appium/options/ios/xcuitest/wda/wda_base_url_option.py +41 -0
  216. appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py +43 -0
  217. appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py +46 -0
  218. appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py +41 -0
  219. appium/options/ios/xcuitest/wda/wda_local_port_option.py +41 -0
  220. appium/options/ios/xcuitest/wda/wda_startup_retries_option.py +39 -0
  221. appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py +43 -0
  222. appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py +39 -0
  223. appium/options/ios/xcuitest/wda/xcode_org_id_option.py +39 -0
  224. appium/options/ios/xcuitest/wda/xcode_signing_id_option.py +39 -0
  225. appium/options/ios/xcuitest/webview/__init__.py +0 -0
  226. appium/options/ios/xcuitest/webview/absolute_web_locations_option.py +42 -0
  227. appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py +40 -0
  228. appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py +39 -0
  229. appium/options/ios/xcuitest/webview/full_context_list_option.py +42 -0
  230. appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py +41 -0
  231. appium/options/ios/xcuitest/webview/native_web_tap_option.py +40 -0
  232. appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py +39 -0
  233. appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py +39 -0
  234. appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py +42 -0
  235. appium/options/ios/xcuitest/webview/safari_initial_url_option.py +38 -0
  236. appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py +43 -0
  237. appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py +40 -0
  238. appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py +39 -0
  239. appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py +41 -0
  240. appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py +41 -0
  241. appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py +43 -0
  242. appium/options/ios/xcuitest/webview/webview_connect_retries_option.py +40 -0
  243. appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py +43 -0
  244. appium/options/mac/__init__.py +1 -0
  245. appium/options/mac/mac2/__init__.py +0 -0
  246. appium/options/mac/mac2/app_path_option.py +39 -0
  247. appium/options/mac/mac2/arguments_option.py +39 -0
  248. appium/options/mac/mac2/base.py +113 -0
  249. appium/options/mac/mac2/bootstrap_root_option.py +41 -0
  250. appium/options/mac/mac2/environment_option.py +41 -0
  251. appium/options/mac/mac2/server_startup_timeout_option.py +44 -0
  252. appium/options/mac/mac2/show_server_logs_option.py +39 -0
  253. appium/options/mac/mac2/skip_app_kill_option.py +40 -0
  254. appium/options/mac/mac2/web_driver_agent_mac_url_option.py +39 -0
  255. appium/options/windows/__init__.py +1 -0
  256. appium/options/windows/windows/__init__.py +0 -0
  257. appium/options/windows/windows/app_arguments_option.py +40 -0
  258. appium/options/windows/windows/app_top_level_window_option.py +40 -0
  259. appium/options/windows/windows/app_working_dir_option.py +40 -0
  260. appium/options/windows/windows/base.py +97 -0
  261. appium/options/windows/windows/create_session_timeout_option.py +45 -0
  262. appium/options/windows/windows/expreimental_web_driver_option.py +39 -0
  263. appium/options/windows/windows/wait_for_app_launch_option.py +43 -0
  264. appium/protocols/__init__.py +13 -0
  265. appium/protocols/webdriver/__init__.py +13 -0
  266. appium/protocols/webdriver/can_execute_commands.py +23 -0
  267. appium/protocols/webdriver/can_execute_scripts.py +27 -0
  268. appium/protocols/webdriver/can_find_elements.py +32 -0
  269. appium/protocols/webdriver/can_remember_extension_presence.py +23 -0
  270. appium/py.typed +0 -0
  271. appium/version.py +22 -0
  272. appium/webdriver/__init__.py +20 -0
  273. appium/webdriver/appium_connection.py +65 -0
  274. appium/webdriver/appium_service.py +330 -0
  275. appium/webdriver/applicationstate.py +21 -0
  276. appium/webdriver/client_config.py +38 -0
  277. appium/webdriver/clipboard_content_type.py +19 -0
  278. appium/webdriver/command_method.py +27 -0
  279. appium/webdriver/common/__init__.py +17 -0
  280. appium/webdriver/common/appiumby.py +54 -0
  281. appium/webdriver/connectiontype.py +42 -0
  282. appium/webdriver/errorhandler.py +125 -0
  283. appium/webdriver/extensions/__init__.py +13 -0
  284. appium/webdriver/extensions/action_helpers.py +188 -0
  285. appium/webdriver/extensions/android/__init__.py +0 -0
  286. appium/webdriver/extensions/android/activities.py +65 -0
  287. appium/webdriver/extensions/android/common.py +59 -0
  288. appium/webdriver/extensions/android/display.py +48 -0
  289. appium/webdriver/extensions/android/gsm.py +147 -0
  290. appium/webdriver/extensions/android/nativekey.py +1119 -0
  291. appium/webdriver/extensions/android/network.py +175 -0
  292. appium/webdriver/extensions/android/performance.py +85 -0
  293. appium/webdriver/extensions/android/power.py +80 -0
  294. appium/webdriver/extensions/android/sms.py +50 -0
  295. appium/webdriver/extensions/android/system_bars.py +58 -0
  296. appium/webdriver/extensions/applications.py +274 -0
  297. appium/webdriver/extensions/clipboard.py +107 -0
  298. appium/webdriver/extensions/context.py +63 -0
  299. appium/webdriver/extensions/device_time.py +75 -0
  300. appium/webdriver/extensions/execute_driver.py +60 -0
  301. appium/webdriver/extensions/execute_mobile_command.py +62 -0
  302. appium/webdriver/extensions/flutter_integration/__init__.py +13 -0
  303. appium/webdriver/extensions/flutter_integration/flutter_commands.py +296 -0
  304. appium/webdriver/extensions/flutter_integration/flutter_finder.py +55 -0
  305. appium/webdriver/extensions/flutter_integration/scroll_directions.py +6 -0
  306. appium/webdriver/extensions/hw_actions.py +149 -0
  307. appium/webdriver/extensions/images_comparison.py +132 -0
  308. appium/webdriver/extensions/keyboard.py +168 -0
  309. appium/webdriver/extensions/location.py +98 -0
  310. appium/webdriver/extensions/log_event.py +68 -0
  311. appium/webdriver/extensions/logs.py +53 -0
  312. appium/webdriver/extensions/remote_fs.py +110 -0
  313. appium/webdriver/extensions/screen_record.py +207 -0
  314. appium/webdriver/extensions/session.py +41 -0
  315. appium/webdriver/extensions/settings.py +49 -0
  316. appium/webdriver/locator_converter.py +29 -0
  317. appium/webdriver/mobilecommand.py +104 -0
  318. appium/webdriver/switch_to.py +35 -0
  319. appium/webdriver/webdriver.py +495 -0
  320. appium/webdriver/webelement.py +130 -0
  321. appium_python_client-5.2.0.dist-info/METADATA +573 -0
  322. appium_python_client-5.2.0.dist-info/RECORD +324 -0
  323. appium_python_client-5.2.0.dist-info/WHEEL +4 -0
  324. appium_python_client-5.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ import re
17
+ import subprocess as sp
18
+ import sys
19
+ import time
20
+ from typing import Any, Callable, List, Optional, Set
21
+
22
+ from selenium.webdriver.remote.remote_connection import urllib3
23
+
24
+ DEFAULT_HOST = '127.0.0.1'
25
+ DEFAULT_PORT = 4723
26
+ STARTUP_TIMEOUT_MS = 60000
27
+ STATE_CHECK_INTERVAL_MS = 500
28
+ MAIN_SCRIPT_PATH = 'appium/build/lib/main.js'
29
+ STATUS_URL = '/status'
30
+ DEFAULT_BASE_PATH = '/'
31
+ HTTP_STATUS_ERROR = 400
32
+
33
+
34
+ class AppiumServiceError(RuntimeError):
35
+ pass
36
+
37
+
38
+ class AppiumStartupError(RuntimeError):
39
+ pass
40
+
41
+
42
+ class AppiumService:
43
+ def __init__(self) -> None:
44
+ self._process: Optional[sp.Popen] = None
45
+ self._cmd: Optional[List[str]] = None
46
+
47
+ def start(self, **kwargs: Any) -> sp.Popen:
48
+ """Starts Appium service with given arguments.
49
+
50
+ If you use the service to start Appium 1.x
51
+ then consider providing ['--base-path', '/wd/hub'] arguments. By default,
52
+ the service assumes Appium server listens on '/' path, which is the default path
53
+ for Appium 2.
54
+
55
+ The service will be forcefully restarted if it is already running.
56
+
57
+ Args:
58
+ env (dict): Environment variables mapping. The default system environment,
59
+ which is inherited from the parent process, is assigned by default.
60
+ node (str): The full path to the main NodeJS executable. The service will try
61
+ to retrieve it automatically if not provided.
62
+ npm (str): The full path to the Node Package Manager (npm) script. The service will try
63
+ to retrieve it automatically if not provided.
64
+ stdout (int): Check the documentation for subprocess.Popen for more details.
65
+ The default value is subprocess.DEVNULL on Windows and subprocess.PIPE on other platforms.
66
+ stderr (int): Check the documentation for subprocess.Popen for more details.
67
+ The default value is subprocess.DEVNULL on Windows and subprocess.PIPE on other platforms.
68
+ timeout_ms (int): The maximum time to wait until Appium process starts listening
69
+ for HTTP connections. If set to zero or a negative number then no wait will be applied.
70
+ 60000 ms by default.
71
+ main_script (str): The full path to the main Appium executable
72
+ (usually located at build/lib/main.js). If not set
73
+ then the service tries to detect the path automatically.
74
+ args (str): List of Appium arguments (all must be strings). Check
75
+ https://appium.io/docs/en/writing-running-appium/server-args/ for more details
76
+ about possible arguments and their values.
77
+
78
+ Returns: You can use Popen.communicate interface or stderr/stdout properties
79
+ of the instance (stdout/stderr must not be set to None in such case) in order to retrieve the actual process
80
+ output.
81
+
82
+ """
83
+ self.stop()
84
+
85
+ env = kwargs['env'] if 'env' in kwargs else None
86
+ node: str = kwargs.get('node') or get_node()
87
+ npm: str = kwargs.get('npm') or get_npm()
88
+ main_script: str = kwargs.get('main_script') or get_main_script(node, npm)
89
+ # A workaround for https://github.com/appium/python-client/issues/534
90
+ default_std = sp.DEVNULL if sys.platform == 'win32' else sp.PIPE
91
+ stdout = kwargs['stdout'] if 'stdout' in kwargs else default_std
92
+ stderr = kwargs['stderr'] if 'stderr' in kwargs else default_std
93
+ timeout_ms = int(kwargs['timeout_ms']) if 'timeout_ms' in kwargs else STARTUP_TIMEOUT_MS
94
+ args: List[str] = [node, main_script]
95
+ if 'args' in kwargs:
96
+ args.extend(kwargs['args'])
97
+ self._cmd = args
98
+ self._process = sp.Popen(args=args, stdout=stdout, stderr=stderr, env=env)
99
+ error_msg: Optional[str] = None
100
+ startup_failure_msg = (
101
+ 'Appium server process is unable to start. Make sure proper values have been '
102
+ f"provided to 'node' ({node}), 'npm' ({npm}) and 'main_script' ({main_script}) "
103
+ f'method arguments.'
104
+ )
105
+ if timeout_ms > 0:
106
+ server_url = _make_server_url(args)
107
+ try:
108
+ if not is_service_listening(
109
+ server_url,
110
+ timeout=timeout_ms / 1000,
111
+ custom_validator=self._assert_is_running,
112
+ ):
113
+ error_msg = (
114
+ f'Appium server has started but is not listening on {server_url} '
115
+ f'within {timeout_ms}ms timeout. Make sure proper values have been provided '
116
+ f'to --base-path, --address and --port process arguments.'
117
+ )
118
+ except AppiumStartupError:
119
+ error_msg = startup_failure_msg
120
+ elif not self.is_running:
121
+ error_msg = startup_failure_msg
122
+ if error_msg is not None:
123
+ if stderr == sp.PIPE and self._process.stderr is not None:
124
+ # noinspection PyUnresolvedReferences
125
+ err_output = self._process.stderr.read()
126
+ if err_output:
127
+ error_msg += f'\nOriginal error: {str(err_output)}'
128
+ self.stop()
129
+ raise AppiumServiceError(error_msg)
130
+ return self._process
131
+
132
+ def stop(self, timeout: float = 5.5) -> bool:
133
+ """Stops Appium service if it is running.
134
+
135
+ The call will be ignored if the service is not running
136
+ or has been already stopped.
137
+
138
+ Args:
139
+ timeout: The maximum time in float seconds to wait for the server process to terminate
140
+
141
+ Returns:
142
+ `True` if the service was running before being stopped
143
+ """
144
+ was_running = False
145
+ if self.is_running:
146
+ assert self._process
147
+ was_running = True
148
+ self._process.terminate()
149
+ try:
150
+ self._process.communicate(timeout=timeout)
151
+ except sp.SubprocessError:
152
+ if sys.platform == 'win32':
153
+ sp.call(['taskkill', '/f', '/pid', str(self._process.pid)])
154
+ else:
155
+ self._process.kill()
156
+ self._process = None
157
+ self._cmd = None
158
+ return was_running
159
+
160
+ @property
161
+ def is_running(self) -> bool:
162
+ """Check if the service is running.
163
+
164
+ :return: `True` if the service is running
165
+ """
166
+ return self._process is not None and self._cmd is not None and self._process.poll() is None
167
+
168
+ @property
169
+ def is_listening(self) -> bool:
170
+ """Check if the service is listening on the given/default host/port.
171
+
172
+ The fact, that the service is running, does not always mean it is listening.
173
+ The default host/port/base path values can be customized by providing
174
+ --address/--port/--base-path command line arguments while starting the service.
175
+
176
+ Returns:
177
+ `True` if the service is running and listening on the given/default host/port
178
+ """
179
+ if not self.is_running:
180
+ return False
181
+
182
+ assert self._cmd
183
+ try:
184
+ return is_service_listening(
185
+ _make_server_url(self._cmd),
186
+ timeout=STATE_CHECK_INTERVAL_MS,
187
+ custom_validator=self._assert_is_running,
188
+ )
189
+ except AppiumStartupError:
190
+ return False
191
+
192
+ def _assert_is_running(self) -> None:
193
+ if not self.is_running:
194
+ raise AppiumStartupError()
195
+
196
+
197
+ def is_service_listening(url: str, timeout: float = 5, custom_validator: Optional[Callable[[], None]] = None) -> bool:
198
+ """
199
+ Check if the service is running
200
+
201
+ Args:
202
+ url: Full server url
203
+ timeout: Timeout in float seconds
204
+ custom_validator: Custom callable method to be executed upon each validation loop before the timeout happens
205
+
206
+ Returns:
207
+ True if Appium server is running before the timeout
208
+ """
209
+ time_started_sec = time.perf_counter()
210
+ conn = urllib3.PoolManager(timeout=1.0)
211
+ while time.perf_counter() < time_started_sec + timeout:
212
+ if custom_validator is not None:
213
+ custom_validator()
214
+ # noinspection PyUnresolvedReferences
215
+ try:
216
+ resp = conn.request('HEAD', url)
217
+ if resp.status < HTTP_STATUS_ERROR:
218
+ return True
219
+ except urllib3.exceptions.HTTPError:
220
+ pass
221
+ time.sleep(STATE_CHECK_INTERVAL_MS / 1000.0)
222
+ return False
223
+
224
+
225
+ def find_executable(executable: str) -> Optional[str]:
226
+ path = os.environ['PATH']
227
+ paths = path.split(os.pathsep)
228
+ _, ext = os.path.splitext(executable)
229
+ if sys.platform == 'win32' and not ext:
230
+ executable = executable + '.exe'
231
+
232
+ if os.path.isfile(executable):
233
+ return executable
234
+
235
+ for p in paths:
236
+ full_path = os.path.join(p, executable)
237
+ if os.path.isfile(full_path):
238
+ return full_path
239
+
240
+ return None
241
+
242
+
243
+ def get_node() -> str:
244
+ result = find_executable('node')
245
+ if result is None:
246
+ raise AppiumServiceError('NodeJS main executable cannot be found. Make sure it is installed and present in PATH')
247
+ return result
248
+
249
+
250
+ def get_npm() -> str:
251
+ result = find_executable('npm.cmd' if sys.platform == 'win32' else 'npm')
252
+ if result is None:
253
+ raise AppiumServiceError(
254
+ 'Node Package Manager executable cannot be found. Make sure it is installed and present in PATH'
255
+ )
256
+ return result
257
+
258
+
259
+ def get_main_script(node: Optional[str], npm: Optional[str]) -> str:
260
+ result: Optional[str] = None
261
+ npm_path = npm or get_npm()
262
+ for args in [['root', '-g'], ['root']]:
263
+ try:
264
+ modules_root = sp.check_output([npm_path] + args).strip().decode('utf-8')
265
+ full_path = os.path.join(modules_root, *MAIN_SCRIPT_PATH.split('/'))
266
+ if os.path.exists(full_path):
267
+ result = full_path
268
+ break
269
+ except sp.CalledProcessError:
270
+ continue
271
+ if result is None:
272
+ node_path = node or get_node()
273
+ try:
274
+ result = (
275
+ sp.check_output([node_path, '-e', f'console.log(require.resolve("{MAIN_SCRIPT_PATH}"))'])
276
+ .decode('utf-8')
277
+ .strip()
278
+ )
279
+ except sp.CalledProcessError as e:
280
+ raise AppiumServiceError(e.output) from e
281
+ return result
282
+
283
+
284
+ def _parse_arg_value(args: List[str], arg_names: Set[str], default: str) -> str:
285
+ for idx, arg in enumerate(args):
286
+ if arg in arg_names and idx < len(args) - 1:
287
+ return args[idx + 1]
288
+ return default
289
+
290
+
291
+ def _parse_port(args: List[str]) -> int:
292
+ return int(_parse_arg_value(args, {'--port', '-p'}, str(DEFAULT_PORT)))
293
+
294
+
295
+ def _parse_base_path(args: List[str]) -> str:
296
+ return _parse_arg_value(args, {'--base-path', '-pa'}, DEFAULT_BASE_PATH)
297
+
298
+
299
+ def _parse_host(args: List[str]) -> str:
300
+ return _parse_arg_value(args, {'--address', '-a'}, DEFAULT_HOST)
301
+
302
+
303
+ def _parse_protocol(args: List[str]) -> str:
304
+ return (
305
+ 'https'
306
+ if _parse_arg_value(args, {'--ssl-cert-path'}, '') and _parse_arg_value(args, {'--ssl-key-path'}, '')
307
+ else 'http'
308
+ )
309
+
310
+
311
+ def _make_status_path(args: List[str]) -> str:
312
+ base_path = _parse_base_path(args)
313
+ return STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"/+$", "", base_path)}{STATUS_URL}'
314
+
315
+
316
+ def _make_server_url(args: List[str]) -> str:
317
+ return f'{_parse_protocol(args)}://{_parse_host(args)}:{_parse_port(args)}{_make_status_path(args)}'
318
+
319
+
320
+ if __name__ == '__main__':
321
+ assert find_executable('node') is not None
322
+ assert find_executable('npm') is not None
323
+ service = AppiumService()
324
+ service.start(args=['--address', '127.0.0.1', '-p', str(DEFAULT_PORT)])
325
+ # service.start(args=['--address', '127.0.0.1', '-p', '80'], timeout_ms=2000)
326
+ assert service.is_running
327
+ assert service.is_listening
328
+ service.stop()
329
+ assert not service.is_running
330
+ assert not service.is_listening
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ class ApplicationState:
17
+ NOT_INSTALLED = 0
18
+ NOT_RUNNING = 1
19
+ RUNNING_IN_BACKGROUND_SUSPENDED = 2
20
+ RUNNING_IN_BACKGROUND = 3
21
+ RUNNING_IN_FOREGROUND = 4
@@ -0,0 +1,38 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ from selenium.webdriver.remote.client_config import ClientConfig
14
+
15
+
16
+ class AppiumClientConfig(ClientConfig):
17
+ """ClientConfig class for Appium Python client.
18
+ This class inherits selenium.webdriver.remote.client_config.ClientConfig.
19
+ """
20
+
21
+ def __init__(self, remote_server_addr: str, *args, **kwargs):
22
+ """
23
+ Please refer to selenium.webdriver.remote.client_config.ClientConfig documentation
24
+ about available arguments. Only 'direct_connection' below is AppiumClientConfig
25
+ specific argument.
26
+
27
+ Args:
28
+ direct_connection: If enables [directConnect](https://github.com/appium/python-client?tab=readme-ov-file#direct-connect-urls)
29
+ feature.
30
+ """
31
+ self._direct_connection = kwargs.pop('direct_connection', False)
32
+ super().__init__(remote_server_addr, *args, **kwargs)
33
+
34
+ @property
35
+ def direct_connection(self) -> bool:
36
+ """Return if [directConnect](https://github.com/appium/python-client?tab=readme-ov-file#direct-connect-urls)
37
+ is enabled."""
38
+ return self._direct_connection
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ class ClipboardContentType:
17
+ PLAINTEXT = 'plaintext'
18
+ IMAGE = 'image'
19
+ URL = 'url'
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import enum
16
+
17
+
18
+ class CommandMethod(enum.Enum):
19
+ GET = 'GET'
20
+ HEAD = 'HEAD'
21
+ POST = 'POST'
22
+ PUT = 'PUT'
23
+ DELETE = 'DELETE'
24
+ CONNECT = 'CONNECT'
25
+ OPTIONS = 'OPTIONS'
26
+ TRACE = 'TRACE'
27
+ PATCH = 'PATCH'
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Appium Python Client: WebDriver common classes
17
+ """
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Literal
16
+
17
+ from selenium.webdriver.common.by import By
18
+
19
+
20
+ class AppiumBy(By):
21
+ IOS_PREDICATE = '-ios predicate string'
22
+ IOS_CLASS_CHAIN = '-ios class chain'
23
+ ANDROID_UIAUTOMATOR = '-android uiautomator'
24
+ ANDROID_VIEWTAG = '-android viewtag'
25
+ ANDROID_DATA_MATCHER = '-android datamatcher'
26
+ ANDROID_VIEW_MATCHER = '-android viewmatcher'
27
+ ACCESSIBILITY_ID = 'accessibility id'
28
+ IMAGE = '-image'
29
+ CUSTOM = '-custom'
30
+
31
+ # For Flutter integration usage https://github.com/AppiumTestDistribution/appium-flutter-integration-driver/tree/main
32
+ FLUTTER_INTEGRATION_SEMANTICS_LABEL = '-flutter semantics label'
33
+ FLUTTER_INTEGRATION_TYPE = '-flutter type'
34
+ FLUTTER_INTEGRATION_KEY = '-flutter key'
35
+ FLUTTER_INTEGRATION_TEXT = '-flutter text'
36
+ FLUTTER_INTEGRATION_TEXT_CONTAINING = '-flutter text containing'
37
+
38
+
39
+ ByType = Literal[
40
+ '-ios predicate string',
41
+ '-ios class chain',
42
+ '-android uiautomator',
43
+ '-android viewtag',
44
+ '-android datamatcher',
45
+ '-android viewmatcher',
46
+ 'accessibility id',
47
+ '-image',
48
+ '-custom',
49
+ '-flutter semantics label',
50
+ '-flutter type',
51
+ '-flutter key',
52
+ '-flutter text',
53
+ '-flutter text containing',
54
+ ]
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """
17
+ Connection types are specified here:
18
+ https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile#120
19
+
20
+ +--------------------+------+------+---------------+
21
+ | Value (Alias) | Data | Wifi | Airplane Mode |
22
+ +====================+======+======+===============+
23
+ | 0 (None) | 0 | 0 | 0 |
24
+ +--------------------+------+------+---------------+
25
+ | 1 (Airplane Mode) | 0 | 0 | 1 |
26
+ +--------------------+------+------+---------------+
27
+ | 2 (Wifi only) | 0 | 1 | 0 |
28
+ +--------------------+------+------+---------------+
29
+ | 4 (Data only) | 1 | 0 | 0 |
30
+ +--------------------+------+------+---------------+
31
+ | 6 (All network on) | 1 | 1 | 0 |
32
+ +--------------------+------+------+---------------+
33
+
34
+ """
35
+
36
+
37
+ class ConnectionType:
38
+ NO_CONNECTION = 0
39
+ AIRPLANE_MODE = 1
40
+ WIFI_ONLY = 2
41
+ DATA_ONLY = 4
42
+ ALL_NETWORK_ON = 6
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ from typing import Any, Dict, List, Sequence, Type, Union
17
+
18
+ import selenium.common.exceptions as sel_exceptions
19
+ from selenium.webdriver.remote import errorhandler
20
+
21
+ import appium.common.exceptions as appium_exceptions
22
+
23
+ ERROR_TO_EXC_MAPPING: Dict[str, Type[sel_exceptions.WebDriverException]] = {
24
+ 'element click intercepted': sel_exceptions.ElementClickInterceptedException,
25
+ 'element not interactable': sel_exceptions.ElementNotInteractableException,
26
+ 'insecure certificate': sel_exceptions.InsecureCertificateException,
27
+ 'invalid argument': sel_exceptions.InvalidArgumentException,
28
+ 'invalid cookie domain': sel_exceptions.InvalidCookieDomainException,
29
+ 'invalid element state': sel_exceptions.InvalidElementStateException,
30
+ 'invalid selector': sel_exceptions.InvalidSelectorException,
31
+ 'invalid session id': sel_exceptions.InvalidSessionIdException,
32
+ 'javascript error': sel_exceptions.JavascriptException,
33
+ 'move target out of bounds': sel_exceptions.MoveTargetOutOfBoundsException,
34
+ 'no such alert': sel_exceptions.NoAlertPresentException,
35
+ 'no such cookie': sel_exceptions.NoSuchCookieException,
36
+ 'no such element': sel_exceptions.NoSuchElementException,
37
+ 'no such frame': sel_exceptions.NoSuchFrameException,
38
+ 'no such window': sel_exceptions.NoSuchWindowException,
39
+ 'no such shadow root': sel_exceptions.NoSuchShadowRootException,
40
+ 'script timeout': sel_exceptions.TimeoutException,
41
+ 'session not created': sel_exceptions.SessionNotCreatedException,
42
+ 'stale element reference': sel_exceptions.StaleElementReferenceException,
43
+ 'detached shadow root': sel_exceptions.NoSuchShadowRootException,
44
+ 'timeout': sel_exceptions.TimeoutException,
45
+ 'unable to set cookie': sel_exceptions.UnableToSetCookieException,
46
+ 'unable to capture screen': sel_exceptions.ScreenshotException,
47
+ 'unexpected alert open': sel_exceptions.UnexpectedAlertPresentException,
48
+ 'unknown command': sel_exceptions.UnknownMethodException,
49
+ 'unknown error': sel_exceptions.WebDriverException,
50
+ 'unknown method': sel_exceptions.UnknownMethodException,
51
+ 'unsupported operation': sel_exceptions.UnknownMethodException,
52
+ 'element not visible': sel_exceptions.ElementNotVisibleException,
53
+ 'element not selectable': sel_exceptions.ElementNotSelectableException,
54
+ 'invalid coordinates': sel_exceptions.InvalidCoordinatesException,
55
+ }
56
+
57
+
58
+ def format_stacktrace(original: Union[None, str, Sequence]) -> List[str]:
59
+ if not original:
60
+ return []
61
+ if isinstance(original, str):
62
+ return original.split('\n')
63
+
64
+ result: List[str] = []
65
+ try:
66
+ for frame in original:
67
+ if not isinstance(frame, dict):
68
+ continue
69
+
70
+ line = frame.get('lineNumber', '')
71
+ file = frame.get('fileName', '<anonymous>')
72
+ if line:
73
+ file = f'{file}:{line}'
74
+ meth = frame.get('methodName', '<anonymous>')
75
+ if 'className' in frame:
76
+ meth = f'{frame["className"]}.{meth}'
77
+ result.append(f' at {meth} ({file})')
78
+ except TypeError:
79
+ pass
80
+ return result
81
+
82
+
83
+ class MobileErrorHandler(errorhandler.ErrorHandler):
84
+ def check_response(self, response: Dict[str, Any]) -> None:
85
+ """
86
+ https://www.w3.org/TR/webdriver/#errors
87
+ """
88
+ payload = response.get('value', '')
89
+ if isinstance(payload, dict):
90
+ payload_dict = payload
91
+ else:
92
+ try:
93
+ payload_dict = json.loads(payload)
94
+ except (json.JSONDecodeError, TypeError):
95
+ return
96
+ if not isinstance(payload_dict, dict):
97
+ return
98
+ value = payload_dict.get('value')
99
+ if not isinstance(value, dict):
100
+ return
101
+ error = value.get('error')
102
+ if not error:
103
+ return
104
+
105
+ message = value.get('message', error)
106
+ stacktrace = value.get('stacktrace', '')
107
+ # In theory, we should also be checking HTTP status codes.
108
+ # Java client, for example, prints a warning if the actual `error`
109
+ # value does not match to the response's HTTP status code.
110
+ exception_class: Type[sel_exceptions.WebDriverException] = ERROR_TO_EXC_MAPPING.get(
111
+ error, sel_exceptions.WebDriverException
112
+ )
113
+ if exception_class is sel_exceptions.WebDriverException and message:
114
+ if message == 'No such context found.':
115
+ exception_class = appium_exceptions.NoSuchContextException
116
+ elif message == 'That command could not be executed in the current context.':
117
+ exception_class = appium_exceptions.InvalidSwitchToTargetException
118
+
119
+ if exception_class is sel_exceptions.UnexpectedAlertPresentException:
120
+ raise sel_exceptions.UnexpectedAlertPresentException(
121
+ msg=message,
122
+ stacktrace=format_stacktrace(stacktrace),
123
+ alert_text=value.get('data'),
124
+ )
125
+ raise exception_class(msg=message, stacktrace=format_stacktrace(stacktrace))