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.
- appium/__init__.py +17 -0
- appium/common/__init__.py +17 -0
- appium/common/exceptions.py +26 -0
- appium/common/helper.py +42 -0
- appium/common/logger.py +28 -0
- appium/options/__init__.py +0 -0
- appium/options/android/__init__.py +2 -0
- appium/options/android/common/__init__.py +0 -0
- appium/options/android/common/adb/__init__.py +0 -0
- appium/options/android/common/adb/adb_exec_timeout_option.py +41 -0
- appium/options/android/common/adb/adb_port_option.py +38 -0
- appium/options/android/common/adb/allow_delay_adb_option.py +39 -0
- appium/options/android/common/adb/build_tools_version_option.py +42 -0
- appium/options/android/common/adb/clear_device_logs_on_start_option.py +40 -0
- appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py +40 -0
- appium/options/android/common/adb/logcat_filter_specs_option.py +42 -0
- appium/options/android/common/adb/logcat_format_option.py +39 -0
- appium/options/android/common/adb/mock_location_app_option.py +42 -0
- appium/options/android/common/adb/remote_adb_host_option.py +39 -0
- appium/options/android/common/adb/skip_logcat_capture_option.py +40 -0
- appium/options/android/common/adb/suppress_kill_server_option.py +39 -0
- appium/options/android/common/app/__init__.py +0 -0
- appium/options/android/common/app/allow_test_packages_option.py +40 -0
- appium/options/android/common/app/android_install_timeout_option.py +43 -0
- appium/options/android/common/app/app_activity_option.py +39 -0
- appium/options/android/common/app/app_package_option.py +39 -0
- appium/options/android/common/app/app_wait_activity_option.py +40 -0
- appium/options/android/common/app/app_wait_duration_option.py +41 -0
- appium/options/android/common/app/app_wait_for_launch_option.py +41 -0
- appium/options/android/common/app/app_wait_package_option.py +40 -0
- appium/options/android/common/app/auto_grant_premissions_option.py +40 -0
- appium/options/android/common/app/enforce_app_install_option.py +40 -0
- appium/options/android/common/app/intent_action_option.py +40 -0
- appium/options/android/common/app/intent_category_option.py +40 -0
- appium/options/android/common/app/intent_flags_option.py +40 -0
- appium/options/android/common/app/optional_intent_arguments_option.py +40 -0
- appium/options/android/common/app/remote_apps_cache_limit_option.py +42 -0
- appium/options/android/common/app/uninstall_other_packages_option.py +39 -0
- appium/options/android/common/avd/__init__.py +0 -0
- appium/options/android/common/avd/avd_args_option.py +38 -0
- appium/options/android/common/avd/avd_env_option.py +38 -0
- appium/options/android/common/avd/avd_launch_timeout_option.py +41 -0
- appium/options/android/common/avd/avd_option.py +41 -0
- appium/options/android/common/avd/avd_ready_timeout_option.py +41 -0
- appium/options/android/common/avd/gps_enabled_option.py +39 -0
- appium/options/android/common/avd/network_speed_option.py +41 -0
- appium/options/android/common/context/__init__.py +0 -0
- appium/options/android/common/context/auto_webview_timeout_option.py +41 -0
- appium/options/android/common/context/chrome_logging_prefs_option.py +41 -0
- appium/options/android/common/context/chrome_options_option.py +40 -0
- appium/options/android/common/context/chromedriver_args_option.py +41 -0
- appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py +43 -0
- appium/options/android/common/context/chromedriver_disable_build_check_option.py +41 -0
- appium/options/android/common/context/chromedriver_executable_dir_option.py +43 -0
- appium/options/android/common/context/chromedriver_executable_option.py +38 -0
- appium/options/android/common/context/chromedriver_port_option.py +39 -0
- appium/options/android/common/context/chromedriver_ports_option.py +39 -0
- appium/options/android/common/context/chromedriver_use_system_executable_option.py +40 -0
- appium/options/android/common/context/ensure_webviews_have_pages_option.py +40 -0
- appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py +40 -0
- appium/options/android/common/context/native_web_screenshot_option.py +40 -0
- appium/options/android/common/context/recreate_chrome_driver_sessions_option.py +41 -0
- appium/options/android/common/context/show_chromedriver_log_option.py +39 -0
- appium/options/android/common/context/webview_devtools_port_option.py +40 -0
- appium/options/android/common/localization/__init__.py +0 -0
- appium/options/android/common/localization/locale_script_option.py +40 -0
- appium/options/android/common/locking/__init__.py +0 -0
- appium/options/android/common/locking/skip_unlock_option.py +42 -0
- appium/options/android/common/locking/unlock_key_option.py +40 -0
- appium/options/android/common/locking/unlock_strategy_option.py +40 -0
- appium/options/android/common/locking/unlock_success_timeout_option.py +43 -0
- appium/options/android/common/locking/unlock_type_option.py +40 -0
- appium/options/android/common/mjpeg/__init__.py +0 -0
- appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py +40 -0
- appium/options/android/common/other/__init__.py +0 -0
- appium/options/android/common/other/disable_suppress_accessibility_service_option.py +40 -0
- appium/options/android/common/other/user_profile_option.py +42 -0
- appium/options/android/common/signing/__init__.py +0 -0
- appium/options/android/common/signing/key_alias_option.py +40 -0
- appium/options/android/common/signing/key_password_option.py +40 -0
- appium/options/android/common/signing/keystore_password_option.py +40 -0
- appium/options/android/common/signing/keystore_path_option.py +40 -0
- appium/options/android/common/signing/no_sign_option.py +42 -0
- appium/options/android/common/signing/use_keystore_option.py +42 -0
- appium/options/android/espresso/__init__.py +0 -0
- appium/options/android/espresso/activity_options_option.py +41 -0
- appium/options/android/espresso/app_locale_option.py +44 -0
- appium/options/android/espresso/base.py +221 -0
- appium/options/android/espresso/espresso_build_config_option.py +46 -0
- appium/options/android/espresso/espresso_server_launch_timeout_option.py +43 -0
- appium/options/android/espresso/force_espresso_rebuild_option.py +41 -0
- appium/options/android/espresso/intent_options_option.py +41 -0
- appium/options/android/espresso/show_gradle_log_option.py +39 -0
- appium/options/android/uiautomator2/__init__.py +0 -0
- appium/options/android/uiautomator2/base.py +221 -0
- appium/options/android/uiautomator2/disable_window_animation_option.py +40 -0
- appium/options/android/uiautomator2/mjpeg_server_port_option.py +41 -0
- appium/options/android/uiautomator2/skip_device_initialization_option.py +40 -0
- appium/options/android/uiautomator2/skip_server_installation_option.py +44 -0
- appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py +44 -0
- appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py +44 -0
- appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py +46 -0
- appium/options/common/__init__.py +1 -0
- appium/options/common/app_option.py +41 -0
- appium/options/common/auto_web_view_option.py +40 -0
- appium/options/common/automation_name_option.py +38 -0
- appium/options/common/base.py +125 -0
- appium/options/common/browser_name_option.py +38 -0
- appium/options/common/bundle_id_option.py +38 -0
- appium/options/common/clear_system_files_option.py +38 -0
- appium/options/common/device_name_option.py +38 -0
- appium/options/common/enable_performance_logging_option.py +38 -0
- appium/options/common/event_timings_option.py +40 -0
- appium/options/common/full_reset_option.py +38 -0
- appium/options/common/is_headless_option.py +39 -0
- appium/options/common/language_option.py +38 -0
- appium/options/common/locale_option.py +38 -0
- appium/options/common/new_command_timeout_option.py +41 -0
- appium/options/common/no_reset_option.py +38 -0
- appium/options/common/orientation_option.py +40 -0
- appium/options/common/other_apps_option.py +39 -0
- appium/options/common/platform_version_option.py +40 -0
- appium/options/common/postrun_option.py +39 -0
- appium/options/common/prerun_option.py +40 -0
- appium/options/common/print_page_source_on_find_failure_option.py +40 -0
- appium/options/common/skip_log_capture_option.py +38 -0
- appium/options/common/supports_capabilities.py +26 -0
- appium/options/common/system_host_option.py +38 -0
- appium/options/common/system_port_option.py +38 -0
- appium/options/common/udid_option.py +38 -0
- appium/options/flutter_integration/__init__.py +15 -0
- appium/options/flutter_integration/base.py +39 -0
- appium/options/flutter_integration/flutter_element_wait_timeout_option.py +50 -0
- appium/options/flutter_integration/flutter_enable_mock_camera_option.py +44 -0
- appium/options/flutter_integration/flutter_server_launch_timeout_option.py +51 -0
- appium/options/flutter_integration/flutter_system_port_option.py +45 -0
- appium/options/gecko/__init__.py +1 -0
- appium/options/gecko/android_storage_option.py +39 -0
- appium/options/gecko/base.py +51 -0
- appium/options/gecko/firefox_options_option.py +38 -0
- appium/options/gecko/marionette_port_option.py +43 -0
- appium/options/gecko/verbosity_option.py +40 -0
- appium/options/ios/__init__.py +2 -0
- appium/options/ios/safari/__init__.py +0 -0
- appium/options/ios/safari/automatic_inspection_option.py +41 -0
- appium/options/ios/safari/automatic_profiling_option.py +41 -0
- appium/options/ios/safari/base.py +51 -0
- appium/options/ios/safari/device_name_option.py +43 -0
- appium/options/ios/safari/device_type_option.py +41 -0
- appium/options/ios/safari/device_udid_option.py +43 -0
- appium/options/ios/safari/platform_build_version_option.py +41 -0
- appium/options/ios/safari/platform_version_option.py +41 -0
- appium/options/ios/safari/use_simulator_option.py +41 -0
- appium/options/ios/safari/webkit_webrtc_option.py +52 -0
- appium/options/ios/xcuitest/__init__.py +0 -0
- appium/options/ios/xcuitest/app/__init__.py +0 -0
- appium/options/ios/xcuitest/app/app_install_strategy_option.py +46 -0
- appium/options/ios/xcuitest/app/app_push_timeout_option.py +42 -0
- appium/options/ios/xcuitest/app/localizable_strings_dir_option.py +39 -0
- appium/options/ios/xcuitest/base.py +223 -0
- appium/options/ios/xcuitest/general/__init__.py +0 -0
- appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py +41 -0
- appium/options/ios/xcuitest/general/reset_location_service_option.py +39 -0
- appium/options/ios/xcuitest/other/__init__.py +0 -0
- appium/options/ios/xcuitest/other/command_timeouts_option.py +57 -0
- appium/options/ios/xcuitest/other/launch_with_idb_option.py +42 -0
- appium/options/ios/xcuitest/other/show_ios_log_option.py +39 -0
- appium/options/ios/xcuitest/other/use_json_source_option.py +39 -0
- appium/options/ios/xcuitest/simulator/__init__.py +0 -0
- appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py +41 -0
- appium/options/ios/xcuitest/simulator/calendar_format_option.py +38 -0
- appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py +43 -0
- appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py +39 -0
- appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py +39 -0
- appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py +45 -0
- appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py +38 -0
- appium/options/ios/xcuitest/simulator/keep_key_chains_option.py +39 -0
- appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py +44 -0
- appium/options/ios/xcuitest/simulator/permissions_option.py +50 -0
- appium/options/ios/xcuitest/simulator/reduce_motion_option.py +40 -0
- appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py +41 -0
- appium/options/ios/xcuitest/simulator/scale_factor_option.py +44 -0
- appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py +44 -0
- appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py +41 -0
- appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py +42 -0
- appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py +46 -0
- appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py +41 -0
- appium/options/ios/xcuitest/simulator/simulator_window_center_option.py +41 -0
- appium/options/ios/xcuitest/wda/__init__.py +0 -0
- appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py +40 -0
- appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py +39 -0
- appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py +39 -0
- appium/options/ios/xcuitest/wda/derived_data_path_option.py +41 -0
- appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py +40 -0
- appium/options/ios/xcuitest/wda/force_app_launch_option.py +42 -0
- appium/options/ios/xcuitest/wda/keychain_password_option.py +39 -0
- appium/options/ios/xcuitest/wda/keychain_path_option.py +39 -0
- appium/options/ios/xcuitest/wda/max_typing_frequency_option.py +40 -0
- appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py +42 -0
- appium/options/ios/xcuitest/wda/process_arguments_option.py +42 -0
- appium/options/ios/xcuitest/wda/result_bundle_path_option.py +42 -0
- appium/options/ios/xcuitest/wda/screenshot_quality_option.py +42 -0
- appium/options/ios/xcuitest/wda/should_terminate_app_option.py +42 -0
- appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py +39 -0
- appium/options/ios/xcuitest/wda/show_xcode_log_option.py +40 -0
- appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py +42 -0
- appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py +39 -0
- appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py +41 -0
- appium/options/ios/xcuitest/wda/use_new_wda_option.py +51 -0
- appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py +39 -0
- appium/options/ios/xcuitest/wda/use_simple_build_test_option.py +40 -0
- appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py +49 -0
- appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py +45 -0
- appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py +42 -0
- appium/options/ios/xcuitest/wda/wda_base_url_option.py +41 -0
- appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py +43 -0
- appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py +46 -0
- appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py +41 -0
- appium/options/ios/xcuitest/wda/wda_local_port_option.py +41 -0
- appium/options/ios/xcuitest/wda/wda_startup_retries_option.py +39 -0
- appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py +43 -0
- appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py +39 -0
- appium/options/ios/xcuitest/wda/xcode_org_id_option.py +39 -0
- appium/options/ios/xcuitest/wda/xcode_signing_id_option.py +39 -0
- appium/options/ios/xcuitest/webview/__init__.py +0 -0
- appium/options/ios/xcuitest/webview/absolute_web_locations_option.py +42 -0
- appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py +40 -0
- appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py +39 -0
- appium/options/ios/xcuitest/webview/full_context_list_option.py +42 -0
- appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py +41 -0
- appium/options/ios/xcuitest/webview/native_web_tap_option.py +40 -0
- appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py +39 -0
- appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py +39 -0
- appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py +42 -0
- appium/options/ios/xcuitest/webview/safari_initial_url_option.py +38 -0
- appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py +43 -0
- appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py +40 -0
- appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py +39 -0
- appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py +41 -0
- appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py +41 -0
- appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py +43 -0
- appium/options/ios/xcuitest/webview/webview_connect_retries_option.py +40 -0
- appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py +43 -0
- appium/options/mac/__init__.py +1 -0
- appium/options/mac/mac2/__init__.py +0 -0
- appium/options/mac/mac2/app_path_option.py +39 -0
- appium/options/mac/mac2/arguments_option.py +39 -0
- appium/options/mac/mac2/base.py +113 -0
- appium/options/mac/mac2/bootstrap_root_option.py +41 -0
- appium/options/mac/mac2/environment_option.py +41 -0
- appium/options/mac/mac2/server_startup_timeout_option.py +44 -0
- appium/options/mac/mac2/show_server_logs_option.py +39 -0
- appium/options/mac/mac2/skip_app_kill_option.py +40 -0
- appium/options/mac/mac2/web_driver_agent_mac_url_option.py +39 -0
- appium/options/windows/__init__.py +1 -0
- appium/options/windows/windows/__init__.py +0 -0
- appium/options/windows/windows/app_arguments_option.py +40 -0
- appium/options/windows/windows/app_top_level_window_option.py +40 -0
- appium/options/windows/windows/app_working_dir_option.py +40 -0
- appium/options/windows/windows/base.py +97 -0
- appium/options/windows/windows/create_session_timeout_option.py +45 -0
- appium/options/windows/windows/expreimental_web_driver_option.py +39 -0
- appium/options/windows/windows/wait_for_app_launch_option.py +43 -0
- appium/protocols/__init__.py +13 -0
- appium/protocols/webdriver/__init__.py +13 -0
- appium/protocols/webdriver/can_execute_commands.py +23 -0
- appium/protocols/webdriver/can_execute_scripts.py +27 -0
- appium/protocols/webdriver/can_find_elements.py +32 -0
- appium/protocols/webdriver/can_remember_extension_presence.py +23 -0
- appium/py.typed +0 -0
- appium/version.py +22 -0
- appium/webdriver/__init__.py +20 -0
- appium/webdriver/appium_connection.py +65 -0
- appium/webdriver/appium_service.py +330 -0
- appium/webdriver/applicationstate.py +21 -0
- appium/webdriver/client_config.py +38 -0
- appium/webdriver/clipboard_content_type.py +19 -0
- appium/webdriver/command_method.py +27 -0
- appium/webdriver/common/__init__.py +17 -0
- appium/webdriver/common/appiumby.py +54 -0
- appium/webdriver/connectiontype.py +42 -0
- appium/webdriver/errorhandler.py +125 -0
- appium/webdriver/extensions/__init__.py +13 -0
- appium/webdriver/extensions/action_helpers.py +188 -0
- appium/webdriver/extensions/android/__init__.py +0 -0
- appium/webdriver/extensions/android/activities.py +65 -0
- appium/webdriver/extensions/android/common.py +59 -0
- appium/webdriver/extensions/android/display.py +48 -0
- appium/webdriver/extensions/android/gsm.py +147 -0
- appium/webdriver/extensions/android/nativekey.py +1119 -0
- appium/webdriver/extensions/android/network.py +175 -0
- appium/webdriver/extensions/android/performance.py +85 -0
- appium/webdriver/extensions/android/power.py +80 -0
- appium/webdriver/extensions/android/sms.py +50 -0
- appium/webdriver/extensions/android/system_bars.py +58 -0
- appium/webdriver/extensions/applications.py +274 -0
- appium/webdriver/extensions/clipboard.py +107 -0
- appium/webdriver/extensions/context.py +63 -0
- appium/webdriver/extensions/device_time.py +75 -0
- appium/webdriver/extensions/execute_driver.py +60 -0
- appium/webdriver/extensions/execute_mobile_command.py +62 -0
- appium/webdriver/extensions/flutter_integration/__init__.py +13 -0
- appium/webdriver/extensions/flutter_integration/flutter_commands.py +296 -0
- appium/webdriver/extensions/flutter_integration/flutter_finder.py +55 -0
- appium/webdriver/extensions/flutter_integration/scroll_directions.py +6 -0
- appium/webdriver/extensions/hw_actions.py +149 -0
- appium/webdriver/extensions/images_comparison.py +132 -0
- appium/webdriver/extensions/keyboard.py +168 -0
- appium/webdriver/extensions/location.py +98 -0
- appium/webdriver/extensions/log_event.py +68 -0
- appium/webdriver/extensions/logs.py +53 -0
- appium/webdriver/extensions/remote_fs.py +110 -0
- appium/webdriver/extensions/screen_record.py +207 -0
- appium/webdriver/extensions/session.py +41 -0
- appium/webdriver/extensions/settings.py +49 -0
- appium/webdriver/locator_converter.py +29 -0
- appium/webdriver/mobilecommand.py +104 -0
- appium/webdriver/switch_to.py +35 -0
- appium/webdriver/webdriver.py +495 -0
- appium/webdriver/webelement.py +130 -0
- appium_python_client-5.2.0.dist-info/METADATA +573 -0
- appium_python_client-5.2.0.dist-info/RECORD +324 -0
- appium_python_client-5.2.0.dist-info/WHEEL +4 -0
- 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))
|