From 32cd6bf90d7c96a72f9bc3c6eb46727f992f4620 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sat, 11 Jan 2020 20:48:16 +0900 Subject: [PATCH 01/26] Fixed mypy warning: touch_action.py --- appium/webdriver/common/touch_action.py | 30 ++++++++++++++----------- mypy.ini | 9 ++++++++ 2 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 mypy.ini diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 84668e17..41428f16 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,16 +24,19 @@ # pylint: disable=no-self-use import copy +from typing import Dict, List from appium.webdriver.mobilecommand import MobileCommand as Command +from appium.webdriver.webdriver import WebDriver +from appium.webdriver.webelement import WebElement class TouchAction(object): - def __init__(self, driver=None): - self._driver = driver - self._actions = [] + def __init__(self, driver: WebDriver = None): + self._driver: WebDriver = driver + self._actions: List = [] - def tap(self, element=None, x=None, y=None, count=1): + def tap(self, element: WebElement = None, x: int = None, y: int = None, count: int = 1) -> WebDriver: """Perform a tap action on the element Args: @@ -50,7 +53,7 @@ def tap(self, element=None, x=None, y=None, count=1): return self - def press(self, el=None, x=None, y=None, pressure=None): + def press(self, el: WebElement = None, x: int = None, y: int = None, pressure: float = None) -> WebDriver: """Begin a chain with a press down action at a particular element or point Args: @@ -67,7 +70,7 @@ def press(self, el=None, x=None, y=None, pressure=None): return self - def long_press(self, el=None, x=None, y=None, duration=1000): + def long_press(self, el: WebElement = None, x: int = None, y: int = None, duration: int = 1000) -> WebDriver: """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -83,7 +86,7 @@ def long_press(self, el=None, x=None, y=None, duration=1000): return self - def wait(self, ms=0): + def wait(self, ms: int = 0) -> WebDriver: """Pause for `ms` milliseconds. Args: @@ -101,7 +104,7 @@ def wait(self, ms=0): return self - def move_to(self, el=None, x=None, y=None): + def move_to(self, el: WebElement = None, x: int = None, y: int = None) -> WebDriver: """Move the pointer from the previous point to the element or point specified Args: @@ -116,7 +119,7 @@ def move_to(self, el=None, x=None, y=None): return self - def release(self): + def release(self) -> WebDriver: """End the action by lifting the pointer off the screen Returns: @@ -126,7 +129,7 @@ def release(self): return self - def perform(self): + def perform(self) -> WebDriver: """Perform the action by sending the commands to the server to be operated upon Returns: @@ -141,20 +144,21 @@ def perform(self): return self @property - def json_wire_gestures(self): + def json_wire_gestures(self) -> List[Dict]: gestures = [] for action in self._actions: gestures.append(copy.deepcopy(action)) return gestures - def _add_action(self, action, options): + def _add_action(self, action: str, options: Dict) -> None: gesture = { 'action': action, 'options': options, } self._actions.append(gesture) - def _get_opts(self, element, x, y, duration=None, pressure=None): + def _get_opts(self, element: WebElement, x: int = None, y: int = None, + duration: int = None, pressure: float = None) -> Dict: opts = {} if element is not None: opts['element'] = element.id diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..42606f97 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +check_untyped_defs = True +disallow_untyped_calls = True +disallow_untyped_defs = True +follow_imports = skip +ignore_missing_imports = True +strict_optional = True +warn_redundant_casts = True +warn_unused_ignores = True From a5b94ed7c0570a1d5c42e7264daf3bc4e3b5e391 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sat, 11 Jan 2020 21:31:59 +0900 Subject: [PATCH 02/26] Fixed mypy warning: multi_action.py --- appium/webdriver/common/multi_action.py | 20 +++++++++++++------- appium/webdriver/common/touch_action.py | 18 ++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index f80a111f..ac1e084c 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,17 +19,23 @@ # chaining as the spec requires. import copy +from typing import Dict, List, TypeVar +from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.mobilecommand import MobileCommand as Command +from appium.webdriver.webdriver import WebDriver +from appium.webdriver.webelement import WebElement + +T = TypeVar('T', bound='MultiAction') class MultiAction(object): - def __init__(self, driver, element=None): - self._driver = driver - self._element = element - self._touch_actions = [] + def __init__(self, driver: WebDriver, element: WebElement = None) -> None: + self._driver: WebDriver = driver + self._element: WebElement = element + self._touch_actions: List[TouchAction] = [] - def add(self, *touch_actions): + def add(self: T, *touch_actions: TouchAction) -> None: """Add TouchAction objects to the MultiAction, to be performed later. Args: @@ -49,7 +55,7 @@ def add(self, *touch_actions): self._touch_actions.append(copy.copy(touch_action)) - def perform(self): + def perform(self: T) -> T: """Perform the actions stored in the object. Usage: @@ -68,7 +74,7 @@ def perform(self): return self @property - def json_wire_gestures(self): + def json_wire_gestures(self) -> Dict: actions = [] for action in self._touch_actions: actions.append(action.json_wire_gestures) diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 41428f16..ec3e4fc4 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,19 +24,21 @@ # pylint: disable=no-self-use import copy -from typing import Dict, List +from typing import Dict, List, TypeVar from appium.webdriver.mobilecommand import MobileCommand as Command from appium.webdriver.webdriver import WebDriver from appium.webdriver.webelement import WebElement +T = TypeVar('T', bound='TouchAction') + class TouchAction(object): def __init__(self, driver: WebDriver = None): self._driver: WebDriver = driver self._actions: List = [] - def tap(self, element: WebElement = None, x: int = None, y: int = None, count: int = 1) -> WebDriver: + def tap(self: T, element: WebElement = None, x: int = None, y: int = None, count: int = 1) -> T: """Perform a tap action on the element Args: @@ -53,7 +55,7 @@ def tap(self, element: WebElement = None, x: int = None, y: int = None, count: i return self - def press(self, el: WebElement = None, x: int = None, y: int = None, pressure: float = None) -> WebDriver: + def press(self: T, el: WebElement = None, x: int = None, y: int = None, pressure: float = None) -> T: """Begin a chain with a press down action at a particular element or point Args: @@ -70,7 +72,7 @@ def press(self, el: WebElement = None, x: int = None, y: int = None, pressure: f return self - def long_press(self, el: WebElement = None, x: int = None, y: int = None, duration: int = 1000) -> WebDriver: + def long_press(self: T, el: WebElement = None, x: int = None, y: int = None, duration: int = 1000) -> T: """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -86,7 +88,7 @@ def long_press(self, el: WebElement = None, x: int = None, y: int = None, durati return self - def wait(self, ms: int = 0) -> WebDriver: + def wait(self: T, ms: int = 0) -> T: """Pause for `ms` milliseconds. Args: @@ -104,7 +106,7 @@ def wait(self, ms: int = 0) -> WebDriver: return self - def move_to(self, el: WebElement = None, x: int = None, y: int = None) -> WebDriver: + def move_to(self: T, el: WebElement = None, x: int = None, y: int = None) -> T: """Move the pointer from the previous point to the element or point specified Args: @@ -119,7 +121,7 @@ def move_to(self, el: WebElement = None, x: int = None, y: int = None) -> WebDri return self - def release(self) -> WebDriver: + def release(self: T) -> T: """End the action by lifting the pointer off the screen Returns: @@ -129,7 +131,7 @@ def release(self) -> WebDriver: return self - def perform(self) -> WebDriver: + def perform(self: T) -> T: """Perform the action by sending the commands to the server to be operated upon Returns: From a29821cc7f83469b8852d1d70f353ea3c811e92f Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sat, 11 Jan 2020 23:33:10 +0900 Subject: [PATCH 03/26] Fixed mypy warning: extensions/android --- appium/common/helper.py | 2 +- appium/webdriver/common/multi_action.py | 2 +- .../extensions/android/activities.py | 15 ++++++++--- appium/webdriver/extensions/android/common.py | 12 ++++++--- .../webdriver/extensions/android/display.py | 6 +++-- appium/webdriver/extensions/android/gsm.py | 13 +++++++--- .../webdriver/extensions/android/nativekey.py | 25 +++++++++++++++---- .../webdriver/extensions/android/network.py | 18 +++++++------ .../extensions/android/performance.py | 10 +++++--- appium/webdriver/extensions/android/power.py | 10 +++++--- .../extensions/execute_mobile_command.py | 6 +++-- 11 files changed, 82 insertions(+), 37 deletions(-) diff --git a/appium/common/helper.py b/appium/common/helper.py index b694b9fa..0e65f4fc 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -17,7 +17,7 @@ from appium import version as appium_version -def appium_bytes(value, encoding): +def appium_bytes(value: str, encoding: str) -> str: """Return a bytes-like object Has _appium_ prefix to avoid overriding built-in bytes. diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index ac1e084c..27975e31 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -35,7 +35,7 @@ def __init__(self, driver: WebDriver, element: WebElement = None) -> None: self._element: WebElement = element self._touch_actions: List[TouchAction] = [] - def add(self: T, *touch_actions: TouchAction) -> None: + def add(self, *touch_actions: TouchAction) -> None: """Add TouchAction objects to the MultiAction, to be performed later. Args: diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 236252c3..2574c649 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,15 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class Activities(webdriver.Remote): - def start_activity(self, app_package, app_activity, **opts): + def start_activity(self, app_package: str, app_activity: str, **opts: str) -> T: """Opens an arbitrary activity during a test. If the activity belongs to another application, that application is started and the activity is opened. @@ -59,7 +63,7 @@ def start_activity(self, app_package, app_activity, **opts): return self @property - def current_activity(self): + def current_activity(self) -> str: """Retrieves the current activity running on the device. Returns: @@ -67,7 +71,7 @@ def current_activity(self): """ return self.execute(Command.GET_CURRENT_ACTIVITY)['value'] - def wait_activity(self, activity, timeout, interval=1): + def wait_activity(self, activity: str, timeout: int, interval: int = 1) -> bool: """Wait for an activity: block until target activity presents or time out. This is an Android-only method. @@ -76,6 +80,9 @@ def wait_activity(self, activity, timeout, interval=1): activity (str): target activity timeout (int): max wait time, in seconds interval (int): sleep interval between retries, in seconds + + Returns: + bool: `True` if target activity shows """ try: WebDriverWait(self, timeout, interval).until( @@ -86,7 +93,7 @@ def wait_activity(self, activity, timeout, interval=1): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_CURRENT_ACTIVITY] = \ ('GET', '/session/$sessionId/appium/device/current_activity') self.command_executor._commands[Command.START_ACTIVITY] = \ diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index 4fa3a00f..58b74ca5 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -12,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, TypeVar + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class Common(webdriver.Remote): - def end_test_coverage(self, intent, path): + def end_test_coverage(self, intent: str, path: str) -> Any: # TODO Check return type """Ends the coverage collection and pull the coverage.ec file from the device. Android only. @@ -38,7 +42,7 @@ def end_test_coverage(self, intent, path): } return self.execute(Command.END_TEST_COVERAGE, data)['value'] - def open_notifications(self): + def open_notifications(self) -> T: """Open notification shade in Android (API Level 18 and above) Returns: @@ -48,12 +52,12 @@ def open_notifications(self): return self @property - def current_package(self): + def current_package(self) -> Any: # TODO Check return type """Retrieves the current package running on the device. """ return self.execute(Command.GET_CURRENT_PACKAGE)['value'] - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_CURRENT_PACKAGE] = \ ('GET', '/session/$sessionId/appium/device/current_package') self.command_executor._commands[Command.END_TEST_COVERAGE] = \ diff --git a/appium/webdriver/extensions/android/display.py b/appium/webdriver/extensions/android/display.py index c05e1a5d..73ac1ccb 100644 --- a/appium/webdriver/extensions/android/display.py +++ b/appium/webdriver/extensions/android/display.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class Display(webdriver.Remote): - def get_display_density(self): + def get_display_density(self) -> int: """Get the display density, Android only Returns: @@ -32,6 +34,6 @@ def get_display_density(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_DISPLAY_DENSITY] = \ ('GET', '/session/$sessionId/appium/device/display_density') diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index 4aaf4fc8..b94a0602 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium import webdriver from appium.common.helper import extract_const_attributes @@ -44,9 +46,12 @@ class GsmVoiceState(object): ON = 'on' +T = TypeVar('T', bound=webdriver.Remote) + + class Gsm(webdriver.Remote): - def make_gsm_call(self, phone_number, action): + def make_gsm_call(self, phone_number: str, action: str) -> T: """Make GSM call (Emulator only) Android only. @@ -66,7 +71,7 @@ def make_gsm_call(self, phone_number, action): self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) return self - def set_gsm_signal(self, strength): + def set_gsm_signal(self, strength: int) -> T: """Set GSM signal strength (Emulator only) Android only. @@ -85,7 +90,7 @@ def set_gsm_signal(self, strength): self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) return self - def set_gsm_voice(self, state): + def set_gsm_voice(self, state: str) -> T: """Set GSM voice state (Emulator only) Android only. @@ -106,7 +111,7 @@ def set_gsm_voice(self, state): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.MAKE_GSM_CALL] = \ ('POST', '/session/$sessionId/appium/device/gsm_call') self.command_executor._commands[Command.SET_GSM_SIGNAL] = \ diff --git a/appium/webdriver/extensions/android/nativekey.py b/appium/webdriver/extensions/android/nativekey.py index aca374eb..79973dba 100644 --- a/appium/webdriver/extensions/android/nativekey.py +++ b/appium/webdriver/extensions/android/nativekey.py @@ -1,3 +1,18 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + class AndroidKey: # Key code constant: Unknown key code. UNKNOWN = 0 @@ -1005,14 +1020,14 @@ class AndroidKey: BUTTON_15, BUTTON_16] @staticmethod - def is_gamepad_button(code): + def is_gamepad_button(code: int) -> bool: """Returns true if the specified nativekey is a gamepad button.""" return code in AndroidKey.gamepad_buttons confirm_buttons = [DPAD_CENTER, ENTER, SPACE, NUMPAD_ENTER] @staticmethod - def is_confirm_key(code): + def is_confirm_key(code: int) -> bool: """Returns true if the key will, by default, trigger a click on the focused view.""" return code in AndroidKey.confirm_buttons @@ -1021,7 +1036,7 @@ def is_confirm_key(code): MEDIA_REWIND, MEDIA_RECORD, MEDIA_FAST_FORWARD] @staticmethod - def is_media_key(code): + def is_media_key(code: int) -> bool: """Returns true if this key is a media key, which can be send to apps that are interested in media key events.""" return code in AndroidKey.media_buttons @@ -1035,13 +1050,13 @@ def is_media_key(code): BRIGHTNESS_DOWN, BRIGHTNESS_UP, MEDIA_AUDIO_TRACK] @staticmethod - def is_system_key(code): + def is_system_key(code: int) -> bool: """Returns true if the key is a system key, System keys can not be used for menu shortcuts.""" return code in AndroidKey.system_buttons wake_buttons = [BACK, MENU, WAKEUP, PAIRING, STEM_1, STEM_2, STEM_3] @staticmethod - def is_wake_key(code): + def is_wake_key(code: int) -> bool: """Returns true if the key is a wake key.""" return code in AndroidKey.wake_buttons diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 3a648154..80275cf7 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -12,12 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, TypeVar + from selenium import webdriver from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class NetSpeed(object): GSM = 'gsm' # GSM/CSD (up: 14.4(kbps), down: 14.4(kbps)) @@ -34,7 +38,7 @@ class NetSpeed(object): class Network(webdriver.Remote): @property - def network_connection(self): + def network_connection(self) -> Any: # TODO Check return type """Returns an integer bitmask specifying the network connection type. Android only. @@ -42,7 +46,7 @@ def network_connection(self): """ return self.execute(Command.GET_NETWORK_CONNECTION, {})['value'] - def set_network_connection(self, connection_type): + def set_network_connection(self, connection_type: int) -> Any: # TODO Check return type """Sets the network connection type. Android only. Possible values: @@ -58,8 +62,8 @@ def set_network_connection(self, connection_type): Args: connection_type (int): a member of the enum appium.webdriver.ConnectionType - Returns: - `appium.webdriver.webdriver.WebDriver` + Return: + TODO """ data = { 'parameters': { @@ -68,7 +72,7 @@ def set_network_connection(self, connection_type): } return self.execute(Command.SET_NETWORK_CONNECTION, data)['value'] - def toggle_wifi(self): + def toggle_wifi(self) -> T: """Toggle the wifi on the device, Android only. Returns: @@ -77,7 +81,7 @@ def toggle_wifi(self): self.execute(Command.TOGGLE_WIFI, {}) return self - def set_network_speed(self, speed_type): + def set_network_speed(self, speed_type: str) -> T: """Set the network speed emulation. Android Emulator only. @@ -102,7 +106,7 @@ def set_network_speed(self, speed_type): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.TOGGLE_WIFI] = \ ('POST', '/session/$sessionId/appium/device/toggle_wifi') self.command_executor._commands[Command.GET_NETWORK_CONNECTION] = \ diff --git a/appium/webdriver/extensions/android/performance.py b/appium/webdriver/extensions/android/performance.py index 7b77b74e..b7ea267b 100644 --- a/appium/webdriver/extensions/android/performance.py +++ b/appium/webdriver/extensions/android/performance.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, List, Union + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class Performance(webdriver.Remote): - def get_performance_data(self, package_name, data_type, data_read_timeout=None): + def get_performance_data(self, package_name: str, data_type: str, data_read_timeout: int = None) -> Dict: """Returns the information of the system state which is supported to read as like cpu, memory, network traffic, and battery. @@ -38,12 +40,12 @@ def get_performance_data(self, package_name, data_type, data_read_timeout=None): Returns: dict: The data along to `data_type` """ - data = {'packageName': package_name, 'dataType': data_type} + data: Dict[str, Union[str, int]] = {'packageName': package_name, 'dataType': data_type} if data_read_timeout is not None: data['dataReadTimeout'] = data_read_timeout return self.execute(Command.GET_PERFORMANCE_DATA, data)['value'] - def get_performance_data_types(self): + def get_performance_data_types(self) -> List: """Returns the information types of the system state which is supported to read as like cpu, memory, network traffic, and battery. Android only. @@ -58,7 +60,7 @@ def get_performance_data_types(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_PERFORMANCE_DATA] = \ ('POST', '/session/$sessionId/appium/getPerformanceData') self.command_executor._commands[Command.GET_PERFORMANCE_DATA_TYPES] = \ diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index bfa6a824..0b4e6fa1 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -12,16 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class Power(webdriver.Remote): AC_OFF, AC_ON = 'off', 'on' - def set_power_capacity(self, percent): + def set_power_capacity(self, percent: int) -> T: """Emulate power capacity change on the connected emulator. Android only. @@ -38,7 +42,7 @@ def set_power_capacity(self, percent): self.execute(Command.SET_POWER_CAPACITY, {'percent': percent}) return self - def set_power_ac(self, ac_state): + def set_power_ac(self, ac_state: str) -> T: """Emulate power state change on the connected emulator. Android only. @@ -58,7 +62,7 @@ def set_power_ac(self, ac_state): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.SET_POWER_CAPACITY] = \ ('POST', '/session/$sessionId/appium/device/power_capacity') self.command_executor._commands[Command.SET_POWER_AC] = \ diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 789dcfef..59eab847 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -14,10 +14,12 @@ from selenium import webdriver +from appium.webdriver.webdriver import WebDriver + class ExecuteMobileCommand(webdriver.Remote): - def press_button(self, button_name): + def press_button(self, button_name: str) -> WebDriver: """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -38,7 +40,7 @@ def press_button(self, button_name): return self @property - def battery_info(self): + def battery_info(self) -> dict: """Retrieves battery information for the device under test. Returns: From 160f334db8de48106b9a8f48a8a3ef565abb246f Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 00:20:49 +0900 Subject: [PATCH 04/26] Fixed mypy warning: extensions/search_context --- .../extensions/search_context/android.py | 18 +++++++++++------- .../search_context/base_search_context.py | 8 ++++++-- .../extensions/search_context/custom.py | 7 +++++-- .../webdriver/extensions/search_context/ios.py | 15 +++++++++------ .../extensions/search_context/mobile.py | 10 ++++++---- .../extensions/search_context/windows.py | 7 +++++-- 6 files changed, 42 insertions(+), 23 deletions(-) diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 7c6f127a..64e9cd54 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -15,8 +15,10 @@ # pylint: disable=abstract-method import json +from typing import List from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext @@ -24,7 +26,8 @@ class AndroidSearchContext(BaseSearchContext): """Define search context for Android""" - def find_element_by_android_data_matcher(self, name=None, args=None, className=None): + def find_element_by_android_data_matcher( + self, name: str = None, args: str = None, className: str = None) -> WebElement: """Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -56,7 +59,8 @@ def find_element_by_android_data_matcher(self, name=None, args=None, className=N value=self._build_data_matcher(name=name, args=args, className=className) ) - def find_elements_by_android_data_matcher(self, name=None, args=None, className=None): + def find_elements_by_android_data_matcher( + self, name: str = None, args: str = None, className: str = None) -> List[WebElement]: """Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -83,7 +87,7 @@ def find_elements_by_android_data_matcher(self, name=None, args=None, className= value=self._build_data_matcher(name=name, args=args, className=className) ) - def _build_data_matcher(self, name=None, args=None, className=None): + def _build_data_matcher(self, name: str = None, args: str = None, className: str = None) -> str: result = {} for key, value in {'name': name, 'args': args, 'class': className}.items(): @@ -92,7 +96,7 @@ def _build_data_matcher(self, name=None, args=None, className=None): return json.dumps(result) - def find_element_by_android_uiautomator(self, uia_string): + def find_element_by_android_uiautomator(self, uia_string: str) -> WebElement: """Finds element by uiautomator in Android. Args: @@ -108,7 +112,7 @@ def find_element_by_android_uiautomator(self, uia_string): """ return self.find_element(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_elements_by_android_uiautomator(self, uia_string): + def find_elements_by_android_uiautomator(self, uia_string: str) -> List[WebElement]: """Finds elements by uiautomator in Android. Args: @@ -124,7 +128,7 @@ def find_elements_by_android_uiautomator(self, uia_string): """ return self.find_elements(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_element_by_android_viewtag(self, tag): + def find_element_by_android_viewtag(self, tag: str) -> WebElement: """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -142,7 +146,7 @@ def find_element_by_android_viewtag(self, tag): """ return self.find_element(by=MobileBy.ANDROID_VIEWTAG, value=tag) - def find_elements_by_android_viewtag(self, tag): + def find_elements_by_android_viewtag(self, tag: str) -> List[WebElement]: """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 500a6e3a..6407ffd1 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -14,12 +14,16 @@ # pylint: disable=abstract-method +from typing import Dict, List, Union + +from appium.webdriver.webelement import WebElement + class BaseSearchContext(object): """Used by each search context. Dummy find_element/s are for preventing pylint error""" - def find_element(self, by=None, value=None): + def find_element(self, by: str = None, value: Union[str, Dict] = None) -> WebElement: raise NotImplementedError - def find_elements(self, by=None, value=None): + def find_elements(self, by: str = None, value: Union[str, Dict] = None) -> List[WebElement]: raise NotImplementedError diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index dabcc6fd..d80900b2 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -14,7 +14,10 @@ # pylint: disable=abstract-method +from typing import List + from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext @@ -22,7 +25,7 @@ class CustomSearchContext(BaseSearchContext): """Define search context for custom plugin""" - def find_element_by_custom(self, selector): + def find_element_by_custom(self, selector: str) -> WebElement: """Finds an element in conjunction with a custom element finding plugin Args: @@ -42,7 +45,7 @@ def find_element_by_custom(self, selector): """ return self.find_element(by=MobileBy.CUSTOM, value=selector) - def find_elements_by_custom(self, selector): + def find_elements_by_custom(self, selector: str) -> List[WebElement]: """Finds elements in conjunction with a custom element finding plugin Args: diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index ad42ce91..5480468d 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -14,7 +14,10 @@ # pylint: disable=abstract-method +from typing import List + from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext @@ -22,7 +25,7 @@ class iOSSearchContext(BaseSearchContext): """Define search context for iOS""" - def find_element_by_ios_uiautomation(self, uia_string): + def find_element_by_ios_uiautomation(self, uia_string: str) -> WebElement: """Finds an element by uiautomation in iOS. Args: @@ -39,7 +42,7 @@ def find_element_by_ios_uiautomation(self, uia_string): """ return self.find_element(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_elements_by_ios_uiautomation(self, uia_string): + def find_elements_by_ios_uiautomation(self, uia_string: str) -> List[WebElement]: """Finds elements by uiautomation in iOS. Args: @@ -55,7 +58,7 @@ def find_elements_by_ios_uiautomation(self, uia_string): """ return self.find_elements(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_element_by_ios_predicate(self, predicate_string): + def find_element_by_ios_predicate(self, predicate_string: str) -> WebElement: """Find an element by ios predicate string. Args: @@ -71,7 +74,7 @@ def find_element_by_ios_predicate(self, predicate_string): """ return self.find_element(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_elements_by_ios_predicate(self, predicate_string): + def find_elements_by_ios_predicate(self, predicate_string: str) -> List[WebElement]: """Finds elements by ios predicate string. Args: @@ -87,7 +90,7 @@ def find_elements_by_ios_predicate(self, predicate_string): """ return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_element_by_ios_class_chain(self, class_chain_string): + def find_element_by_ios_class_chain(self, class_chain_string: str) -> WebElement: """Find an element by ios class chain string. Args: @@ -103,7 +106,7 @@ def find_element_by_ios_class_chain(self, class_chain_string): """ return self.find_element(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) - def find_elements_by_ios_class_chain(self, class_chain_string): + def find_elements_by_ios_class_chain(self, class_chain_string: str) -> List[WebElement]: """Finds elements by ios class chain string. Args: diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index c9d68632..4dabcd95 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -15,8 +15,10 @@ # pylint: disable=abstract-method import base64 +from typing import List from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext @@ -24,7 +26,7 @@ class MobileSearchContext(BaseSearchContext): """Define search context for Mobile(Android, iOS)""" - def find_element_by_accessibility_id(self, accessibility_id): + def find_element_by_accessibility_id(self, accessibility_id: str) -> WebElement: """Finds an element by accessibility id. Args: @@ -42,7 +44,7 @@ def find_element_by_accessibility_id(self, accessibility_id): """ return self.find_element(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_elements_by_accessibility_id(self, accessibility_id): + def find_elements_by_accessibility_id(self, accessibility_id: str) -> List[WebElement]: """Finds elements by accessibility id. Args: @@ -59,7 +61,7 @@ def find_elements_by_accessibility_id(self, accessibility_id): """ return self.find_elements(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_element_by_image(self, img_path): + def find_element_by_image(self, img_path: str) -> WebElement: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. @@ -77,7 +79,7 @@ def find_element_by_image(self, img_path): return self.find_element(by=MobileBy.IMAGE, value=b64_data) - def find_elements_by_image(self, img_path): + def find_elements_by_image(self, img_path: str) -> List[WebElement]: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. Note that this will diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index 66ca23a2..93d4610e 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -14,7 +14,10 @@ # pylint: disable=abstract-method +from typing import List + from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext @@ -22,7 +25,7 @@ class WindowsSearchContext(BaseSearchContext): """Define search context for Windows""" - def find_element_by_windows_uiautomation(self, win_uiautomation): + def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> WebElement: """Finds an element by windows uiautomation Args: @@ -39,7 +42,7 @@ def find_element_by_windows_uiautomation(self, win_uiautomation): """ return self.find_element(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) - def find_elements_by_windows_uiautomation(self, win_uiautomation): + def find_elements_by_windows_uiautomation(self, win_uiautomation: str) -> List[WebElement]: """Finds elements by windows uiautomation Args: From 01506dae74a3bef0271d61a450eed908c46c2bf2 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 01:59:03 +0900 Subject: [PATCH 05/26] Updated --- appium/common/helper.py | 2 +- appium/saucetestcase.py | 9 ++++--- appium/webdriver/appium_connection.py | 4 ++- appium/webdriver/appium_service.py | 26 +++++++++--------- appium/webdriver/common/multi_action.py | 4 +-- appium/webdriver/common/touch_action.py | 4 +-- appium/webdriver/extensions/action_helpers.py | 15 +++++++---- appium/webdriver/webdriver.py | 27 +++++++++++-------- 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/appium/common/helper.py b/appium/common/helper.py index 0e65f4fc..a26398b4 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -36,7 +36,7 @@ def appium_bytes(value: str, encoding: str) -> str: return value # Python 2 -def extract_const_attributes(cls): +def extract_const_attributes(cls) -> OrderedDict: """Return dict with constants attributes and values in the class(e.g. {'VAL1': 1, 'VAL2': 2}) Args: diff --git a/appium/saucetestcase.py b/appium/saucetestcase.py index 8192f504..f7faf2f7 100644 --- a/appium/saucetestcase.py +++ b/appium/saucetestcase.py @@ -19,6 +19,7 @@ import os import sys import unittest +from typing import Any, List, TypeVar from sauceclient import SauceClient @@ -29,8 +30,8 @@ sauce = SauceClient(SAUCE_USERNAME, SAUCE_ACCESS_KEY) -def on_platforms(platforms): - def decorator(base_class): +def on_platforms(platforms: List) -> Any: + def decorator(base_class: Any) -> None: module = sys.modules[base_class.__module__].__dict__ for i, platform in enumerate(platforms): name = "%s_%s" % (base_class.__name__, i + 1) @@ -40,7 +41,7 @@ def decorator(base_class): class SauceTestCase(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.desired_capabilities['name'] = self.id() sauce_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" self.driver = webdriver.Remote( @@ -49,7 +50,7 @@ def setUp(self): ) self.driver.implicitly_wait(30) - def tearDown(self): + def tearDown(self) -> None: print("Link to your job: https://saucelabs.com/jobs/%s" % self.driver.session_id) try: if sys.exc_info() == (None, None, None): diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 719923fd..75ae161d 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.helper import library_version @@ -20,7 +22,7 @@ class AppiumConnection(RemoteConnection): @classmethod - def get_remote_connection_headers(cls, parsed_url, keep_alive=True): + def get_remote_connection_headers(cls, parsed_url: str, keep_alive: bool = True) -> Dict: """Override get_remote_connection_headers in RemoteConnection""" headers = RemoteConnection.get_remote_connection_headers(parsed_url, keep_alive=keep_alive) headers['User-Agent'] = 'appium/python {} ({})'.format(library_version(), headers['User-Agent']) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 42527c24..1e569ffa 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. - import os -import subprocess +import subprocess as sp import sys import time +from typing import Dict, List, Union import urllib3 @@ -27,7 +27,7 @@ STATUS_URL = '/wd/hub/status' -def find_executable(executable): +def find_executable(executable: str) -> Union[str, None]: path = os.environ['PATH'] paths = path.split(os.pathsep) base, ext = os.path.splitext(executable) @@ -45,7 +45,7 @@ def find_executable(executable): return None -def poll_url(host, port, path, timeout_ms): +def poll_url(host: str, port: str, path: str, timeout_ms: int) -> bool: time_started_sec = time.time() while time.time() < time_started_sec + timeout_ms / 1000.0: try: @@ -89,19 +89,19 @@ def _get_main_script(self): if not hasattr(self, '_main_script'): for args in [['root', '-g'], ['root']]: try: - modules_root = subprocess.check_output([self._get_npm()] + args).strip().decode('utf-8') + modules_root = sp.check_output([self._get_npm()] + args).strip().decode('utf-8') if os.path.exists(os.path.join(modules_root, MAIN_SCRIPT_PATH)): self._main_script = os.path.join(modules_root, MAIN_SCRIPT_PATH) break - except subprocess.CalledProcessError: + except sp.CalledProcessError: continue if not hasattr(self, '_main_script'): try: - self._main_script = subprocess.check_output( + self._main_script = sp.check_output( [self._get_node(), '-e', 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip() - except subprocess.CalledProcessError as e: + except sp.CalledProcessError as e: raise AppiumServiceError(e.output) return self._main_script @@ -119,7 +119,7 @@ def _parse_host(args): return args[idx + 1] return DEFAULT_HOST - def start(self, **kwargs): + def start(self, **kwargs: Union[Dict, str, int]) -> sp.Popen: """Starts Appium service with given arguments. The service will be forcefully restarted if it is already running. @@ -153,15 +153,15 @@ def start(self, **kwargs): env = kwargs['env'] if 'env' in kwargs else None node = kwargs['node'] if 'node' in kwargs else self._get_node() - stdout = kwargs['stdout'] if 'stdout' in kwargs else subprocess.PIPE - stderr = kwargs['stderr'] if 'stderr' in kwargs else subprocess.PIPE + stdout = kwargs['stdout'] if 'stdout' in kwargs else sp.PIPE + stderr = kwargs['stderr'] if 'stderr' in kwargs else sp.PIPE timeout_ms = int(kwargs['timeout_ms']) if 'timeout_ms' in kwargs else STARTUP_TIMEOUT_MS main_script = kwargs['main_script'] if 'main_script' in kwargs else self._get_main_script() args = [node, main_script] if 'args' in kwargs: args.extend(kwargs['args']) self._cmd = args - self._process = subprocess.Popen(args=args, stdout=stdout, stderr=stderr, env=env) + self._process = sp.Popen(args=args, stdout=stdout, stderr=stderr, env=env) host = self._parse_host(args) port = self._parse_port(args) error_msg = None @@ -169,7 +169,7 @@ def start(self, **kwargs): error_msg = 'Appium has failed to start on {}:{} within {}ms timeout'\ .format(host, port, timeout_ms) if error_msg is not None: - if stderr == subprocess.PIPE: + if stderr == sp.PIPE: err_output = self._process.stderr.read() if err_output: error_msg += '\nOriginal error: {}'.format(err_output) diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 27975e31..54b25862 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,7 +19,7 @@ # chaining as the spec requires. import copy -from typing import Dict, List, TypeVar +from typing import Dict, List, Optional, TypeVar from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.mobilecommand import MobileCommand as Command @@ -32,7 +32,7 @@ class MultiAction(object): def __init__(self, driver: WebDriver, element: WebElement = None) -> None: self._driver: WebDriver = driver - self._element: WebElement = element + self._element: Optional[WebElement] = element self._touch_actions: List[TouchAction] = [] def add(self, *touch_actions: TouchAction) -> None: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index ec3e4fc4..00798883 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -35,7 +35,7 @@ class TouchAction(object): def __init__(self, driver: WebDriver = None): - self._driver: WebDriver = driver + self._driver = driver self._actions: List = [] def tap(self: T, element: WebElement = None, x: int = None, y: int = None, count: int = 1) -> T: @@ -159,7 +159,7 @@ def _add_action(self, action: str, options: Dict) -> None: } self._actions.append(gesture) - def _get_opts(self, element: WebElement, x: int = None, y: int = None, + def _get_opts(self, element: WebElement = None, x: int = None, y: int = None, duration: int = None, pressure: float = None) -> Dict: opts = {} if element is not None: diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 108fcfd5..9796210c 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -12,15 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List, Tuple, TypeVar + from selenium import webdriver from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction +from appium.webdriver.webelement import WebElement + +T = TypeVar('T', bound=webdriver.Remote) class ActionHelpers(webdriver.Remote): - def scroll(self, origin_el, destination_el, duration=None): + def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: int = None) -> T: """Scrolls from one element to another Args: @@ -47,7 +52,7 @@ def scroll(self, origin_el, destination_el, duration=None): action.press(origin_el).wait(duration).move_to(destination_el).release().perform() return self - def drag_and_drop(self, origin_el, destination_el): + def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: """Drag the origin element to the destination element Args: @@ -61,7 +66,7 @@ def drag_and_drop(self, origin_el, destination_el): action.long_press(origin_el).move_to(destination_el).release().perform() return self - def tap(self, positions, duration=None): + def tap(self: T, positions: List[Tuple], duration: int = None) -> T: """Taps on an particular place with up to five fingers, holding for a certain time @@ -100,7 +105,7 @@ def tap(self, positions, duration=None): ma.perform() return self - def swipe(self, start_x, start_y, end_x, end_y, duration=None): + def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: """Swipe from one point to another point, for an optional duration. Args: @@ -127,7 +132,7 @@ def swipe(self, start_x, start_y, end_x, end_y, duration=None): action.perform() return self - def flick(self, start_x, start_y, end_x, end_y): + def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: """Flick from one point to another point. Args: diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 03e7e3fd..f5f82762 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -15,6 +15,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use import copy +from typing import Dict, List, TypeVar, Union from selenium.common.exceptions import InvalidArgumentException from selenium.webdriver.common.by import By @@ -84,7 +85,7 @@ # Add appium prefix for the non-W3C capabilities -def _make_w3c_caps(caps): +def _make_w3c_caps(caps: Dict) -> Dict: appium_prefix = 'appium:' caps = copy.deepcopy(caps) @@ -111,6 +112,9 @@ def _make_w3c_caps(caps): return {'firstMatch': [first_match]} +T = TypeVar('T', bound='WebDriver') + + class WebDriver( AppiumSearchContext, ActionHelpers, @@ -141,8 +145,8 @@ class WebDriver( SystemBars ): - def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=True, direct_connection=False): + def __init__(self, command_executor: str = 'http://127.0.0.1:4444/wd/hub', + desired_capabilities: Dict = None, browser_profile: str = None, proxy: str = None, keep_alive: bool = True, direct_connection: bool = False): super(WebDriver, self).__init__( AppiumConnection(command_executor, keep_alive=keep_alive), @@ -171,7 +175,7 @@ def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', By.IMAGE = MobileBy.IMAGE By.CUSTOM = MobileBy.CUSTOM - def _update_command_executor(self, keep_alive): + def _update_command_executor(self, keep_alive: bool) -> None: """Update command executor following directConnect feature""" direct_protocol = 'directConnectProtocol' direct_host = 'directConnectHost' @@ -201,7 +205,7 @@ def _update_command_executor(self, keep_alive): self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) self._addCommands() - def start_session(self, capabilities, browser_profile=None): + def start_session(self, capabilities: Dict, browser_profile: str = None) -> None: """Creates a new session with the desired capabilities. Override for Appium @@ -239,7 +243,7 @@ def start_session(self, capabilities, browser_profile=None): self.w3c = response.get('status') is None self.command_executor.w3c = self.w3c - def _merge_capabilities(self, capabilities): + def _merge_capabilities(self, capabilities: Dict) -> Dict: """Manage capabilities whether W3C format or MJSONWP format """ if _FORCE_MJSONWP in capabilities: @@ -252,7 +256,7 @@ def _merge_capabilities(self, capabilities): w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} - def find_element(self, by=By.ID, value=None): + def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: # type: ignore """'Private' method used by the find_element_by_* methods. Override for Appium @@ -283,7 +287,8 @@ def find_element(self, by=By.ID, value=None): 'using': by, 'value': value})['value'] - def find_elements(self, by=By.ID, value=None): + # type: ignore + def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> Union[List[MobileWebElement], List]: """'Private' method used by the find_elements_by_* methods. Override for Appium @@ -317,7 +322,7 @@ def find_elements(self, by=By.ID, value=None): 'using': by, 'value': value})['value'] or [] - def create_web_element(self, element_id, w3c=False): + def create_web_element(self, element_id: int, w3c: bool = False) -> MobileWebElement: """Creates a web element with the specified element_id. Overrides method in Selenium WebDriver in order to always give them @@ -332,7 +337,7 @@ def create_web_element(self, element_id, w3c=False): """ return MobileWebElement(self, element_id, w3c) - def set_value(self, element, value): + def set_value(self: T, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. Args: @@ -351,7 +356,7 @@ def set_value(self, element, value): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: # call the overridden command binders from all mixin classes except for # appium.webdriver.webdriver.WebDriver and its sub-classes # https://github.com/appium/python-client/issues/342 From bb215000e19709e96370341e7b0902e14b9d2568 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 02:38:15 +0900 Subject: [PATCH 06/26] Revert some changes to run unit test --- appium/webdriver/common/multi_action.py | 18 ++++++++------- appium/webdriver/common/touch_action.py | 22 ++++++++++--------- .../extensions/execute_mobile_command.py | 4 +--- .../extensions/search_context/android.py | 16 ++++++++------ .../search_context/base_search_context.py | 7 +++--- .../extensions/search_context/custom.py | 7 +++--- .../extensions/search_context/ios.py | 15 +++++++------ .../extensions/search_context/mobile.py | 11 +++++----- .../extensions/search_context/windows.py | 7 +++--- 9 files changed, 58 insertions(+), 49 deletions(-) diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 54b25862..5f63ef34 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -21,21 +21,23 @@ import copy from typing import Dict, List, Optional, TypeVar -from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.mobilecommand import MobileCommand as Command -from appium.webdriver.webdriver import WebDriver -from appium.webdriver.webelement import WebElement + +if False: # For mypy + from appium.webdriver.common.touch_action import TouchAction + from appium.webdriver.webdriver import WebDriver + from appium.webdriver.webelement import WebElement T = TypeVar('T', bound='MultiAction') class MultiAction(object): - def __init__(self, driver: WebDriver, element: WebElement = None) -> None: - self._driver: WebDriver = driver - self._element: Optional[WebElement] = element - self._touch_actions: List[TouchAction] = [] + def __init__(self, driver, element=None) -> None: + self._driver = driver + self._element = element + self._touch_actions = [] - def add(self, *touch_actions: TouchAction) -> None: + def add(self, *touch_actions) -> None: """Add TouchAction objects to the MultiAction, to be performed later. Args: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 00798883..70167be5 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -27,18 +27,20 @@ from typing import Dict, List, TypeVar from appium.webdriver.mobilecommand import MobileCommand as Command -from appium.webdriver.webdriver import WebDriver -from appium.webdriver.webelement import WebElement + +# from appium.webdriver.webelement import WebElement as WebElement +# from appium.webdriver.webdriver import WebDriver as WebDriver T = TypeVar('T', bound='TouchAction') class TouchAction(object): - def __init__(self, driver: WebDriver = None): + + def __init__(self, driver=None): self._driver = driver self._actions: List = [] - def tap(self: T, element: WebElement = None, x: int = None, y: int = None, count: int = 1) -> T: + def tap(self: T, element=None, x: int = None, y: int = None, count: int = 1) -> T: """Perform a tap action on the element Args: @@ -55,7 +57,7 @@ def tap(self: T, element: WebElement = None, x: int = None, y: int = None, count return self - def press(self: T, el: WebElement = None, x: int = None, y: int = None, pressure: float = None) -> T: + def press(self: T, el=None, x: int = None, y: int = None, pressure: float = None) -> T: """Begin a chain with a press down action at a particular element or point Args: @@ -72,7 +74,7 @@ def press(self: T, el: WebElement = None, x: int = None, y: int = None, pressure return self - def long_press(self: T, el: WebElement = None, x: int = None, y: int = None, duration: int = 1000) -> T: + def long_press(self: T, el=None, x: int = None, y: int = None, duration: int = 1000) -> T: """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -106,7 +108,7 @@ def wait(self: T, ms: int = 0) -> T: return self - def move_to(self: T, el: WebElement = None, x: int = None, y: int = None) -> T: + def move_to(self: T, el=None, x: int = None, y: int = None) -> T: """Move the pointer from the previous point to the element or point specified Args: @@ -159,11 +161,11 @@ def _add_action(self, action: str, options: Dict) -> None: } self._actions.append(gesture) - def _get_opts(self, element: WebElement = None, x: int = None, y: int = None, + def _get_opts(self, el=None, x: int = None, y: int = None, duration: int = None, pressure: float = None) -> Dict: opts = {} - if element is not None: - opts['element'] = element.id + if el is not None: + opts['element'] = el.id # it makes no sense to have x but no y, or vice versa. if x is not None and y is not None: diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 59eab847..83568e04 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -14,12 +14,10 @@ from selenium import webdriver -from appium.webdriver.webdriver import WebDriver - class ExecuteMobileCommand(webdriver.Remote): - def press_button(self, button_name: str) -> WebDriver: + def press_button(self, button_name: str): """Sends a physical button name to the device to simulate the user pressing. iOS only. diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 64e9cd54..2a77e7ad 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -18,16 +18,18 @@ from typing import List from appium.webdriver.common.mobileby import MobileBy -from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext +if False: + from appium.webdriver.webelement import WebElement # For mypy + class AndroidSearchContext(BaseSearchContext): """Define search context for Android""" def find_element_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None) -> WebElement: + self, name: str = None, args: str = None, className: str = None): # -> WebElement: """Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -60,7 +62,7 @@ def find_element_by_android_data_matcher( ) def find_elements_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None) -> List[WebElement]: + self, name: str = None, args: str = None, className: str = None): # -> List[WebElement]: """Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -96,7 +98,7 @@ def _build_data_matcher(self, name: str = None, args: str = None, className: str return json.dumps(result) - def find_element_by_android_uiautomator(self, uia_string: str) -> WebElement: + def find_element_by_android_uiautomator(self, uia_string: str): # -> WebElement: """Finds element by uiautomator in Android. Args: @@ -112,7 +114,7 @@ def find_element_by_android_uiautomator(self, uia_string: str) -> WebElement: """ return self.find_element(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_elements_by_android_uiautomator(self, uia_string: str) -> List[WebElement]: + def find_elements_by_android_uiautomator(self, uia_string: str): # -> List[WebElement]: """Finds elements by uiautomator in Android. Args: @@ -128,7 +130,7 @@ def find_elements_by_android_uiautomator(self, uia_string: str) -> List[WebEleme """ return self.find_elements(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_element_by_android_viewtag(self, tag: str) -> WebElement: + def find_element_by_android_viewtag(self, tag: str): # -> WebElement: """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -146,7 +148,7 @@ def find_element_by_android_viewtag(self, tag: str) -> WebElement: """ return self.find_element(by=MobileBy.ANDROID_VIEWTAG, value=tag) - def find_elements_by_android_viewtag(self, tag: str) -> List[WebElement]: + def find_elements_by_android_viewtag(self, tag: str): # -> List[WebElement]: """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 6407ffd1..96cf07a0 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -16,14 +16,15 @@ from typing import Dict, List, Union -from appium.webdriver.webelement import WebElement +if False: + from appium.webdriver.webelement import WebElement class BaseSearchContext(object): """Used by each search context. Dummy find_element/s are for preventing pylint error""" - def find_element(self, by: str = None, value: Union[str, Dict] = None) -> WebElement: + def find_element(self, by: str = None, value: Union[str, Dict] = None): raise NotImplementedError - def find_elements(self, by: str = None, value: Union[str, Dict] = None) -> List[WebElement]: + def find_elements(self, by: str = None, value: Union[str, Dict] = None): raise NotImplementedError diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index d80900b2..941fa597 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -17,15 +17,16 @@ from typing import List from appium.webdriver.common.mobileby import MobileBy -from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext +# from appium.webdriver.webelement import WebElement + class CustomSearchContext(BaseSearchContext): """Define search context for custom plugin""" - def find_element_by_custom(self, selector: str) -> WebElement: + def find_element_by_custom(self, selector: str): # -> WebElement: """Finds an element in conjunction with a custom element finding plugin Args: @@ -45,7 +46,7 @@ def find_element_by_custom(self, selector: str) -> WebElement: """ return self.find_element(by=MobileBy.CUSTOM, value=selector) - def find_elements_by_custom(self, selector: str) -> List[WebElement]: + def find_elements_by_custom(self, selector: str): # -> List[WebElement]: """Finds elements in conjunction with a custom element finding plugin Args: diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index 5480468d..625317b3 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -17,15 +17,16 @@ from typing import List from appium.webdriver.common.mobileby import MobileBy -from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext +# from appium.webdriver.webelement import WebElement + class iOSSearchContext(BaseSearchContext): """Define search context for iOS""" - def find_element_by_ios_uiautomation(self, uia_string: str) -> WebElement: + def find_element_by_ios_uiautomation(self, uia_string: str): # -> WebElement: """Finds an element by uiautomation in iOS. Args: @@ -42,7 +43,7 @@ def find_element_by_ios_uiautomation(self, uia_string: str) -> WebElement: """ return self.find_element(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_elements_by_ios_uiautomation(self, uia_string: str) -> List[WebElement]: + def find_elements_by_ios_uiautomation(self, uia_string: str): # -> List[WebElement]: """Finds elements by uiautomation in iOS. Args: @@ -58,7 +59,7 @@ def find_elements_by_ios_uiautomation(self, uia_string: str) -> List[WebElement] """ return self.find_elements(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_element_by_ios_predicate(self, predicate_string: str) -> WebElement: + def find_element_by_ios_predicate(self, predicate_string: str): # -> WebElement: """Find an element by ios predicate string. Args: @@ -74,7 +75,7 @@ def find_element_by_ios_predicate(self, predicate_string: str) -> WebElement: """ return self.find_element(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_elements_by_ios_predicate(self, predicate_string: str) -> List[WebElement]: + def find_elements_by_ios_predicate(self, predicate_string: str): # -> List[WebElement]: """Finds elements by ios predicate string. Args: @@ -90,7 +91,7 @@ def find_elements_by_ios_predicate(self, predicate_string: str) -> List[WebEleme """ return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_element_by_ios_class_chain(self, class_chain_string: str) -> WebElement: + def find_element_by_ios_class_chain(self, class_chain_string: str): # -> WebElement: """Find an element by ios class chain string. Args: @@ -106,7 +107,7 @@ def find_element_by_ios_class_chain(self, class_chain_string: str) -> WebElement """ return self.find_element(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) - def find_elements_by_ios_class_chain(self, class_chain_string: str) -> List[WebElement]: + def find_elements_by_ios_class_chain(self, class_chain_string: str): # -> List[WebElement]: """Finds elements by ios class chain string. Args: diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index 4dabcd95..6a045ace 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -18,15 +18,16 @@ from typing import List from appium.webdriver.common.mobileby import MobileBy -from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext +# from appium.webdriver.webelement import WebElement + class MobileSearchContext(BaseSearchContext): """Define search context for Mobile(Android, iOS)""" - def find_element_by_accessibility_id(self, accessibility_id: str) -> WebElement: + def find_element_by_accessibility_id(self, accessibility_id: str): # -> WebElement: """Finds an element by accessibility id. Args: @@ -44,7 +45,7 @@ def find_element_by_accessibility_id(self, accessibility_id: str) -> WebElement: """ return self.find_element(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_elements_by_accessibility_id(self, accessibility_id: str) -> List[WebElement]: + def find_elements_by_accessibility_id(self, accessibility_id: str): # -> List[WebElement]: """Finds elements by accessibility id. Args: @@ -61,7 +62,7 @@ def find_elements_by_accessibility_id(self, accessibility_id: str) -> List[WebEl """ return self.find_elements(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_element_by_image(self, img_path: str) -> WebElement: + def find_element_by_image(self, img_path: str): # -> WebElement: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. @@ -79,7 +80,7 @@ def find_element_by_image(self, img_path: str) -> WebElement: return self.find_element(by=MobileBy.IMAGE, value=b64_data) - def find_elements_by_image(self, img_path: str) -> List[WebElement]: + def find_elements_by_image(self, img_path: str): # -> List[WebElement]: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. Note that this will diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index 93d4610e..6c0c02e6 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -17,15 +17,16 @@ from typing import List from appium.webdriver.common.mobileby import MobileBy -from appium.webdriver.webelement import WebElement from .base_search_context import BaseSearchContext +# from appium.webdriver.webelement import WebElement + class WindowsSearchContext(BaseSearchContext): """Define search context for Windows""" - def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> WebElement: + def find_element_by_windows_uiautomation(self, win_uiautomation: str): # -> WebElement: """Finds an element by windows uiautomation Args: @@ -42,7 +43,7 @@ def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> WebElem """ return self.find_element(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) - def find_elements_by_windows_uiautomation(self, win_uiautomation: str) -> List[WebElement]: + def find_elements_by_windows_uiautomation(self, win_uiautomation: str): # -> List[WebElement]: """Finds elements by windows uiautomation Args: From f8bd38269262f8ebd2dd2bdbe6c373c064bec101 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 11:51:30 +0900 Subject: [PATCH 07/26] Review comments --- appium/common/helper.py | 19 ------------------- appium/saucetestcase.py | 6 +++--- appium/webdriver/appium_service.py | 14 +++++++------- appium/webdriver/common/multi_action.py | 4 ++-- .../extensions/android/activities.py | 2 +- appium/webdriver/extensions/android/common.py | 2 +- .../webdriver/extensions/android/network.py | 2 +- appium/webdriver/extensions/clipboard.py | 3 +-- .../extensions/execute_mobile_command.py | 4 +++- test/functional/android/remote_fs_tests.py | 6 ++---- test/functional/ios/applications_tests.py | 1 + test/unit/webdriver/device/clipboard_test.py | 3 +-- test/unit/webdriver/device/remote_fs_test.py | 5 ++--- 13 files changed, 25 insertions(+), 46 deletions(-) diff --git a/appium/common/helper.py b/appium/common/helper.py index a26398b4..9123814e 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -17,25 +17,6 @@ from appium import version as appium_version -def appium_bytes(value: str, encoding: str) -> str: - """Return a bytes-like object - - Has _appium_ prefix to avoid overriding built-in bytes. - - Args: - value (str): A value to convert - encoding (str): A encoding which will convert to - - Returns: - str: A bytes-like object - """ - - try: - return bytes(value, encoding) # Python 3 - except TypeError: - return value # Python 2 - - def extract_const_attributes(cls) -> OrderedDict: """Return dict with constants attributes and values in the class(e.g. {'VAL1': 1, 'VAL2': 2}) diff --git a/appium/saucetestcase.py b/appium/saucetestcase.py index f7faf2f7..48efcc1e 100644 --- a/appium/saucetestcase.py +++ b/appium/saucetestcase.py @@ -19,7 +19,7 @@ import os import sys import unittest -from typing import Any, List, TypeVar +from typing import Any, Callable, List from sauceclient import SauceClient @@ -30,8 +30,8 @@ sauce = SauceClient(SAUCE_USERNAME, SAUCE_ACCESS_KEY) -def on_platforms(platforms: List) -> Any: - def decorator(base_class: Any) -> None: +def on_platforms(platforms: List[str]) -> Callable[[type], None]: + def decorator(base_class: type) -> None: module = sys.modules[base_class.__module__].__dict__ for i, platform in enumerate(platforms): name = "%s_%s" % (base_class.__name__, i + 1) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 1e569ffa..ab09adb7 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -16,7 +16,7 @@ import subprocess as sp import sys import time -from typing import Dict, List, Union +from typing import Any, Optional, Union import urllib3 @@ -27,7 +27,7 @@ STATUS_URL = '/wd/hub/status' -def find_executable(executable: str) -> Union[str, None]: +def find_executable(executable: str) -> Optional[str]: path = os.environ['PATH'] paths = path.split(os.pathsep) base, ext = os.path.splitext(executable) @@ -65,11 +65,11 @@ class AppiumServiceError(RuntimeError): class AppiumService(object): - def __init__(self): + def __init__(self) -> None: self._process = None self._cmd = None - def _get_node(self): + def _get_node(self) -> Optional[str]: if not hasattr(self, '_node_executable'): self._node_executable = find_executable('node') if self._node_executable is None: @@ -77,7 +77,7 @@ def _get_node(self): 'Make sure it is installed and present in PATH') return self._node_executable - def _get_npm(self): + def _get_npm(self) -> Optional[str]: if not hasattr(self, '_npm_executable'): self._npm_executable = find_executable('npm.cmd' if sys.platform == 'win32' else 'npm') if self._npm_executable is None: @@ -85,7 +85,7 @@ def _get_npm(self): 'Make sure it is installed and present in PATH') return self._npm_executable - def _get_main_script(self): + def _get_main_script(self) -> str: if not hasattr(self, '_main_script'): for args in [['root', '-g'], ['root']]: try: @@ -119,7 +119,7 @@ def _parse_host(args): return args[idx + 1] return DEFAULT_HOST - def start(self, **kwargs: Union[Dict, str, int]) -> sp.Popen: + def start(self, **kwargs: Any) -> sp.Popen: """Starts Appium service with given arguments. The service will be forcefully restarted if it is already running. diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 5f63ef34..c15d0955 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,7 +19,7 @@ # chaining as the spec requires. import copy -from typing import Dict, List, Optional, TypeVar +from typing import Dict, List, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command @@ -76,7 +76,7 @@ def perform(self: T) -> T: return self @property - def json_wire_gestures(self) -> Dict: + def json_wire_gestures(self) -> Dict[str, Union[List, str]]: actions = [] for action in self._touch_actions: actions.append(action.json_wire_gestures) diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 2574c649..5baad89b 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -82,7 +82,7 @@ def wait_activity(self, activity: str, timeout: int, interval: int = 1) -> bool: interval (int): sleep interval between retries, in seconds Returns: - bool: `True` if target activity shows + bool: `True` if the target activity is shown """ try: WebDriverWait(self, timeout, interval).until( diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index 58b74ca5..0b451d34 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -52,7 +52,7 @@ def open_notifications(self) -> T: return self @property - def current_package(self) -> Any: # TODO Check return type + def current_package(self) -> str: """Retrieves the current package running on the device. """ return self.execute(Command.GET_CURRENT_PACKAGE)['value'] diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 80275cf7..b8e0a5b1 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -38,7 +38,7 @@ class NetSpeed(object): class Network(webdriver.Remote): @property - def network_connection(self) -> Any: # TODO Check return type + def network_connection(self) -> int: """Returns an integer bitmask specifying the network connection type. Android only. diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index e05e6b86..c8bee751 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -16,7 +16,6 @@ from selenium import webdriver -from appium.common.helper import appium_bytes from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command @@ -49,7 +48,7 @@ def set_clipboard_text(self, text, label=None): label (:obj:`int`, optional):label argument, which only works for Android """ - self.set_clipboard(appium_bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) + self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) def get_clipboard(self, content_type=ClipboardContentType.PLAINTEXT): """Receives the content of the system clipboard diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 83568e04..16f52b0b 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, Dict + from selenium import webdriver @@ -38,7 +40,7 @@ def press_button(self, button_name: str): return self @property - def battery_info(self) -> dict: + def battery_info(self) -> Dict[str, Any]: """Retrieves battery information for the device under test. Returns: diff --git a/test/functional/android/remote_fs_tests.py b/test/functional/android/remote_fs_tests.py index fefe0839..1a7a0092 100644 --- a/test/functional/android/remote_fs_tests.py +++ b/test/functional/android/remote_fs_tests.py @@ -19,15 +19,13 @@ from io import BytesIO from zipfile import ZipFile -from appium.common.helper import appium_bytes - from .helper.test_helper import BaseTestCase class RemoteFsTests(BaseTestCase): def test_push_pull_file(self): dest_path = '/data/local/tmp/test_push_file.txt' - data = appium_bytes('This is the contents of the file to push to the device.', 'utf-8') + data = bytes('This is the contents of the file to push to the device.', 'utf-8') self.driver.push_file(dest_path, base64.b64encode(data).decode('utf-8')) data_ret = base64.b64decode(self.driver.pull_file(dest_path)) @@ -35,7 +33,7 @@ def test_push_pull_file(self): self.assertEqual(data, data_ret) def test_pull_folder(self): - data = appium_bytes('random string data {}'.format(random.randint(0, 1000)), 'utf-8') + data = bytes('random string data {}'.format(random.randint(0, 1000)), 'utf-8') dest_dir = '/data/local/tmp/' for filename in ['1.txt', '2.txt']: diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index 067aec18..00b60325 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -30,6 +30,7 @@ def test_app_management(self): self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), ApplicationState.RUNNING_IN_FOREGROUND) self.driver.background_app(-1) + self.driver.find_element_by_accessibility_id().id self.assertTrue(self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND) self.driver.activate_app(desired_capabilities.BUNDLE_ID) diff --git a/test/unit/webdriver/device/clipboard_test.py b/test/unit/webdriver/device/clipboard_test.py index 22c80dbe..01176029 100644 --- a/test/unit/webdriver/device/clipboard_test.py +++ b/test/unit/webdriver/device/clipboard_test.py @@ -14,7 +14,6 @@ import httpretty -from appium.common.helper import appium_bytes from appium.webdriver.clipboard_content_type import ClipboardContentType from test.unit.helper.test_helper import ( android_w3c_driver, @@ -34,7 +33,7 @@ def test_set_clipboard_with_url(self): appium_command('/session/1234567890/appium/device/set_clipboard'), body='{"value": ""}' ) - driver.set_clipboard(appium_bytes(str('http://appium.io/'), 'UTF-8'), + driver.set_clipboard(bytes(str('http://appium.io/'), 'UTF-8'), ClipboardContentType.URL, 'label for android') d = get_httpretty_request_body(httpretty.last_request()) diff --git a/test/unit/webdriver/device/remote_fs_test.py b/test/unit/webdriver/device/remote_fs_test.py index 0855b936..dace0ffc 100644 --- a/test/unit/webdriver/device/remote_fs_test.py +++ b/test/unit/webdriver/device/remote_fs_test.py @@ -18,7 +18,6 @@ import pytest from selenium.common.exceptions import InvalidArgumentException -from appium.common.helper import appium_bytes from appium.webdriver.webdriver import WebDriver from test.unit.helper.test_helper import ( android_w3c_driver, @@ -37,7 +36,7 @@ def test_push_file(self): appium_command('/session/1234567890/appium/device/push_file'), ) dest_path = '/path/to/file.txt' - data = base64.b64encode(appium_bytes('HelloWorld', 'utf-8')).decode('utf-8') + data = base64.b64encode(bytes('HelloWorld', 'utf-8')).decode('utf-8') assert isinstance(driver.push_file(dest_path, data), WebDriver) @@ -80,7 +79,7 @@ def test_pull_file(self): ) dest_path = '/path/to/file.txt' - assert driver.pull_file(dest_path) == str(base64.b64encode(appium_bytes('HelloWorld', 'utf-8')).decode('utf-8')) + assert driver.pull_file(dest_path) == str(base64.b64encode(bytes('HelloWorld', 'utf-8')).decode('utf-8')) d = get_httpretty_request_body(httpretty.last_request()) assert d['path'] == dest_path From 88776fa1546e8aa38af1ec82d15bee422821caa2 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 13:39:32 +0900 Subject: [PATCH 08/26] Updates --- appium/webdriver/common/multi_action.py | 12 +++---- appium/webdriver/common/touch_action.py | 19 +++++------ appium/webdriver/extensions/applications.py | 32 +++++++++++-------- appium/webdriver/extensions/clipboard.py | 26 +++++++++++---- .../extensions/search_context/android.py | 18 +++++------ .../search_context/base_search_context.py | 8 ++--- .../extensions/search_context/custom.py | 9 +++--- .../extensions/search_context/ios.py | 17 +++++----- .../extensions/search_context/mobile.py | 13 ++++---- .../extensions/search_context/windows.py | 9 +++--- appium/webdriver/extensions/session.py | 12 ++++--- appium/webdriver/webdriver.py | 8 ++--- appium/webdriver/webelement.py | 24 +++++++------- 13 files changed, 114 insertions(+), 93 deletions(-) diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index c15d0955..0e4d5bb0 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,25 +19,25 @@ # chaining as the spec requires. import copy -from typing import Dict, List, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command -if False: # For mypy - from appium.webdriver.common.touch_action import TouchAction +if TYPE_CHECKING: from appium.webdriver.webdriver import WebDriver from appium.webdriver.webelement import WebElement + from appium.webdriver.common.touch_action import TouchAction T = TypeVar('T', bound='MultiAction') class MultiAction(object): - def __init__(self, driver, element=None) -> None: + def __init__(self, driver: 'WebDriver', element: 'WebElement' = None) -> None: self._driver = driver self._element = element - self._touch_actions = [] + self._touch_actions: List['TouchAction'] = [] - def add(self, *touch_actions) -> None: + def add(self, *touch_actions: 'TouchAction') -> None: """Add TouchAction objects to the MultiAction, to be performed later. Args: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 70167be5..9d9623ad 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,23 +24,24 @@ # pylint: disable=no-self-use import copy -from typing import Dict, List, TypeVar +from typing import TYPE_CHECKING, Dict, List, TypeVar from appium.webdriver.mobilecommand import MobileCommand as Command -# from appium.webdriver.webelement import WebElement as WebElement -# from appium.webdriver.webdriver import WebDriver as WebDriver +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement as WebElement + from appium.webdriver.webdriver import WebDriver as WebDriver T = TypeVar('T', bound='TouchAction') class TouchAction(object): - def __init__(self, driver=None): + def __init__(self, driver: 'WebDriver' = None): self._driver = driver self._actions: List = [] - def tap(self: T, element=None, x: int = None, y: int = None, count: int = 1) -> T: + def tap(self: T, element: 'WebElement' = None, x: int = None, y: int = None, count: int = 1) -> T: """Perform a tap action on the element Args: @@ -57,7 +58,7 @@ def tap(self: T, element=None, x: int = None, y: int = None, count: int = 1) -> return self - def press(self: T, el=None, x: int = None, y: int = None, pressure: float = None) -> T: + def press(self: T, el: 'WebElement' = None, x: int = None, y: int = None, pressure: float = None) -> T: """Begin a chain with a press down action at a particular element or point Args: @@ -74,7 +75,7 @@ def press(self: T, el=None, x: int = None, y: int = None, pressure: float = None return self - def long_press(self: T, el=None, x: int = None, y: int = None, duration: int = 1000) -> T: + def long_press(self: T, el: 'WebElement' = None, x: int = None, y: int = None, duration: int = 1000) -> T: """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -108,7 +109,7 @@ def wait(self: T, ms: int = 0) -> T: return self - def move_to(self: T, el=None, x: int = None, y: int = None) -> T: + def move_to(self: T, el: 'WebElement' = None, x: int = None, y: int = None) -> T: """Move the pointer from the previous point to the element or point specified Args: @@ -161,7 +162,7 @@ def _add_action(self, action: str, options: Dict) -> None: } self._actions.append(gesture) - def _get_opts(self, el=None, x: int = None, y: int = None, + def _get_opts(self, el: 'WebElement' = None, x: int = None, y: int = None, duration: int = None, pressure: float = None) -> Dict: opts = {} if el is not None: diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index e89b097d..828cd32e 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, Dict, TypeVar + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class Applications(webdriver.Remote): - def background_app(self, seconds): + def background_app(self, seconds: int) -> T: """Puts the application in the background on the device for a certain duration. Args: @@ -33,7 +37,7 @@ def background_app(self, seconds): self.execute(Command.BACKGROUND, data) return self - def is_app_installed(self, bundle_id): + def is_app_installed(self, bundle_id: str) -> bool: """Checks whether the application specified by `bundle_id` is installed on the device. Args: @@ -47,7 +51,7 @@ def is_app_installed(self, bundle_id): } return self.execute(Command.IS_APP_INSTALLED, data)['value'] - def install_app(self, app_path, **options): + def install_app(self, app_path: str, **options: Any) -> T: """Install the application found at `app_path` on the device. Args: @@ -67,7 +71,7 @@ def install_app(self, app_path, **options): Returns: `appium.webdriver.webdriver.WebDriver` """ - data = { + data: Dict[str, Any] = { 'appPath': app_path, } if options: @@ -75,7 +79,7 @@ def install_app(self, app_path, **options): self.execute(Command.INSTALL_APP, data) return self - def remove_app(self, app_id, **options): + def remove_app(self, app_id: str, **options: Any) -> T: """Remove the specified application from the device. Args: @@ -90,7 +94,7 @@ def remove_app(self, app_id, **options): Returns: `appium.webdriver.webdriver.WebDriver` """ - data = { + data: Dict[str, Any] = { 'appId': app_id, } if options: @@ -98,7 +102,7 @@ def remove_app(self, app_id, **options): self.execute(Command.REMOVE_APP, data) return self - def launch_app(self): + def launch_app(self) -> T: """Start on the device the application specified in the desired capabilities. Returns: @@ -107,7 +111,7 @@ def launch_app(self): self.execute(Command.LAUNCH_APP) return self - def close_app(self): + def close_app(self) -> T: """Stop the running application, specified in the desired capabilities, on the device. @@ -117,7 +121,7 @@ def close_app(self): self.execute(Command.CLOSE_APP) return self - def terminate_app(self, app_id, **options): + def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. Args: @@ -137,7 +141,7 @@ def terminate_app(self, app_id, **options): data.update({'options': options}) return self.execute(Command.TERMINATE_APP, data)['value'] - def activate_app(self, app_id): + def activate_app(self, app_id: str) -> T: """Activates the application if it is not running or is running in the background. @@ -153,7 +157,7 @@ def activate_app(self, app_id): self.execute(Command.ACTIVATE_APP, data) return self - def query_app_state(self, app_id): + def query_app_state(self, app_id: str) -> int: """Queries the state of the application. Args: @@ -168,7 +172,7 @@ class for more details. } return self.execute(Command.QUERY_APP_STATE, data)['value'] - def app_strings(self, language=None, string_file=None): + def app_strings(self, language: str = None, string_file: str = None) -> str: """Returns the application strings from the device for the specified language. @@ -183,7 +187,7 @@ def app_strings(self, language=None, string_file=None): data['stringFile'] = string_file return self.execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self): + def reset(self) -> T: """Resets the current application on the device. """ self.execute(Command.RESET) @@ -191,7 +195,7 @@ def reset(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.BACKGROUND] = \ ('POST', '/session/$sessionId/appium/app/background') self.command_executor._commands[Command.IS_APP_INSTALLED] = \ diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index c8bee751..9cf16988 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +from typing import Any, Dict, Optional, TypeVar from selenium import webdriver @@ -20,17 +21,23 @@ from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound=webdriver.Remote) + class Clipboard(webdriver.Remote): - def set_clipboard(self, content, content_type=ClipboardContentType.PLAINTEXT, label=None): + def set_clipboard(self, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, + label: Optional[str] = None) -> T: """Set the content of the system clipboard Args: - content (str): The content to be set as bytearray string + content (bytes): The content to be set as bytearray string content_type (str): One of ClipboardContentType items. Only ClipboardContentType.PLAINTEXT is supported on Android label (:obj:`str`, optional): label argument, which only works for Android + + Returns: + `appium.webdriver.webdriver.WebDriver` """ options = { 'content': base64.b64encode(content).decode('UTF-8'), @@ -39,18 +46,23 @@ def set_clipboard(self, content, content_type=ClipboardContentType.PLAINTEXT, la if label: options['label'] = label self.execute(Command.SET_CLIPBOARD, options) + return self - def set_clipboard_text(self, text, label=None): + def set_clipboard_text(self, text: str, label: Optional[str] = None) -> T: """Copies the given text to the system clipboard Args: text (str): The text to be set - label (:obj:`int`, optional):label argument, which only works for Android + label (:obj:`str`, optional):label argument, which only works for Android + + Returns: + `appium.webdriver.webdriver.WebDriver` """ self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) + return self - def get_clipboard(self, content_type=ClipboardContentType.PLAINTEXT): + def get_clipboard(self, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: """Receives the content of the system clipboard Args: @@ -65,7 +77,7 @@ def get_clipboard(self, content_type=ClipboardContentType.PLAINTEXT): })['value'] return base64.b64decode(base64_str) - def get_clipboard_text(self): + def get_clipboard_text(self) -> str: """Receives the text of the system clipboard Return: @@ -75,7 +87,7 @@ def get_clipboard_text(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.SET_CLIPBOARD] = \ ('POST', '/session/$sessionId/appium/device/set_clipboard') self.command_executor._commands[Command.GET_CLIPBOARD] = \ diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 2a77e7ad..32696c97 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -15,21 +15,21 @@ # pylint: disable=abstract-method import json -from typing import List +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext -if False: - from appium.webdriver.webelement import WebElement # For mypy +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement class AndroidSearchContext(BaseSearchContext): """Define search context for Android""" def find_element_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None): # -> WebElement: + self, name: str = None, args: str = None, className: str = None) -> 'WebElement': """Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -62,7 +62,7 @@ def find_element_by_android_data_matcher( ) def find_elements_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None): # -> List[WebElement]: + self, name: str = None, args: str = None, className: str = None) -> List['WebElement']: """Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -98,7 +98,7 @@ def _build_data_matcher(self, name: str = None, args: str = None, className: str return json.dumps(result) - def find_element_by_android_uiautomator(self, uia_string: str): # -> WebElement: + def find_element_by_android_uiautomator(self, uia_string: str) -> 'WebElement': """Finds element by uiautomator in Android. Args: @@ -114,7 +114,7 @@ def find_element_by_android_uiautomator(self, uia_string: str): # -> WebElement """ return self.find_element(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_elements_by_android_uiautomator(self, uia_string: str): # -> List[WebElement]: + def find_elements_by_android_uiautomator(self, uia_string: str) -> List['WebElement']: """Finds elements by uiautomator in Android. Args: @@ -130,7 +130,7 @@ def find_elements_by_android_uiautomator(self, uia_string: str): # -> List[WebE """ return self.find_elements(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_element_by_android_viewtag(self, tag: str): # -> WebElement: + def find_element_by_android_viewtag(self, tag: str) -> 'WebElement': """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -148,7 +148,7 @@ def find_element_by_android_viewtag(self, tag: str): # -> WebElement: """ return self.find_element(by=MobileBy.ANDROID_VIEWTAG, value=tag) - def find_elements_by_android_viewtag(self, tag: str): # -> List[WebElement]: + def find_elements_by_android_viewtag(self, tag: str) -> List['WebElement']: """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 96cf07a0..493a55ea 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -14,17 +14,17 @@ # pylint: disable=abstract-method -from typing import Dict, List, Union +from typing import TYPE_CHECKING, Dict, List, Union -if False: +if TYPE_CHECKING: from appium.webdriver.webelement import WebElement class BaseSearchContext(object): """Used by each search context. Dummy find_element/s are for preventing pylint error""" - def find_element(self, by: str = None, value: Union[str, Dict] = None): + def find_element(self, by: str = None, value: Union[str, Dict] = None) -> 'WebElement': raise NotImplementedError - def find_elements(self, by: str = None, value: Union[str, Dict] = None): + def find_elements(self, by: str = None, value: Union[str, Dict] = None) -> List['WebElement']: raise NotImplementedError diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index 941fa597..a790016b 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -14,19 +14,20 @@ # pylint: disable=abstract-method -from typing import List +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext -# from appium.webdriver.webelement import WebElement +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement class CustomSearchContext(BaseSearchContext): """Define search context for custom plugin""" - def find_element_by_custom(self, selector: str): # -> WebElement: + def find_element_by_custom(self, selector: str) -> 'WebElement': """Finds an element in conjunction with a custom element finding plugin Args: @@ -46,7 +47,7 @@ def find_element_by_custom(self, selector: str): # -> WebElement: """ return self.find_element(by=MobileBy.CUSTOM, value=selector) - def find_elements_by_custom(self, selector: str): # -> List[WebElement]: + def find_elements_by_custom(self, selector: str) -> List['WebElement']: """Finds elements in conjunction with a custom element finding plugin Args: diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index 625317b3..3066ad9e 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -14,19 +14,20 @@ # pylint: disable=abstract-method -from typing import List +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext -# from appium.webdriver.webelement import WebElement +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement class iOSSearchContext(BaseSearchContext): """Define search context for iOS""" - def find_element_by_ios_uiautomation(self, uia_string: str): # -> WebElement: + def find_element_by_ios_uiautomation(self, uia_string: str) -> 'WebElement': """Finds an element by uiautomation in iOS. Args: @@ -43,7 +44,7 @@ def find_element_by_ios_uiautomation(self, uia_string: str): # -> WebElement: """ return self.find_element(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_elements_by_ios_uiautomation(self, uia_string: str): # -> List[WebElement]: + def find_elements_by_ios_uiautomation(self, uia_string: str) -> List['WebElement']: """Finds elements by uiautomation in iOS. Args: @@ -59,7 +60,7 @@ def find_elements_by_ios_uiautomation(self, uia_string: str): # -> List[WebElem """ return self.find_elements(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) - def find_element_by_ios_predicate(self, predicate_string: str): # -> WebElement: + def find_element_by_ios_predicate(self, predicate_string: str) -> 'WebElement': """Find an element by ios predicate string. Args: @@ -75,7 +76,7 @@ def find_element_by_ios_predicate(self, predicate_string: str): # -> WebElement """ return self.find_element(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_elements_by_ios_predicate(self, predicate_string: str): # -> List[WebElement]: + def find_elements_by_ios_predicate(self, predicate_string: str) -> List['WebElement']: """Finds elements by ios predicate string. Args: @@ -91,7 +92,7 @@ def find_elements_by_ios_predicate(self, predicate_string: str): # -> List[WebE """ return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string) - def find_element_by_ios_class_chain(self, class_chain_string: str): # -> WebElement: + def find_element_by_ios_class_chain(self, class_chain_string: str) -> 'WebElement': """Find an element by ios class chain string. Args: @@ -107,7 +108,7 @@ def find_element_by_ios_class_chain(self, class_chain_string: str): # -> WebEle """ return self.find_element(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) - def find_elements_by_ios_class_chain(self, class_chain_string: str): # -> List[WebElement]: + def find_elements_by_ios_class_chain(self, class_chain_string: str) -> List['WebElement']: """Finds elements by ios class chain string. Args: diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index 6a045ace..a58f4d23 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -15,19 +15,20 @@ # pylint: disable=abstract-method import base64 -from typing import List +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext -# from appium.webdriver.webelement import WebElement +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement class MobileSearchContext(BaseSearchContext): """Define search context for Mobile(Android, iOS)""" - def find_element_by_accessibility_id(self, accessibility_id: str): # -> WebElement: + def find_element_by_accessibility_id(self, accessibility_id: str) -> 'WebElement': """Finds an element by accessibility id. Args: @@ -45,7 +46,7 @@ def find_element_by_accessibility_id(self, accessibility_id: str): # -> WebElem """ return self.find_element(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_elements_by_accessibility_id(self, accessibility_id: str): # -> List[WebElement]: + def find_elements_by_accessibility_id(self, accessibility_id: str) -> List['WebElement']: """Finds elements by accessibility id. Args: @@ -62,7 +63,7 @@ def find_elements_by_accessibility_id(self, accessibility_id: str): # -> List[W """ return self.find_elements(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_element_by_image(self, img_path: str): # -> WebElement: + def find_element_by_image(self, img_path: str) -> 'WebElement': """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. @@ -80,7 +81,7 @@ def find_element_by_image(self, img_path: str): # -> WebElement: return self.find_element(by=MobileBy.IMAGE, value=b64_data) - def find_elements_by_image(self, img_path: str): # -> List[WebElement]: + def find_elements_by_image(self, img_path: str) -> List['WebElement']: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. Note that this will diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index 6c0c02e6..692aebce 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -14,19 +14,20 @@ # pylint: disable=abstract-method -from typing import List +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext -# from appium.webdriver.webelement import WebElement +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement class WindowsSearchContext(BaseSearchContext): """Define search context for Windows""" - def find_element_by_windows_uiautomation(self, win_uiautomation: str): # -> WebElement: + def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> 'WebElement': """Finds an element by windows uiautomation Args: @@ -43,7 +44,7 @@ def find_element_by_windows_uiautomation(self, win_uiautomation: str): # -> Web """ return self.find_element(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) - def find_elements_by_windows_uiautomation(self, win_uiautomation: str): # -> List[WebElement]: + def find_elements_by_windows_uiautomation(self, win_uiautomation: str) -> List['WebElement']: """Finds elements by windows uiautomation Args: diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index 64d49370..f188de8d 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, List + from selenium import webdriver from appium.common.logger import logger @@ -21,7 +23,7 @@ class Session(webdriver.Remote): @property - def session(self): + def session(self) -> Dict: """ Retrieves session information from the current session Usage: @@ -33,19 +35,19 @@ def session(self): return self.execute(Command.GET_SESSION)['value'] @property - def all_sessions(self): + def all_sessions(self) -> List[Dict]: """ Retrieves all sessions that are open Usage: sessions = driver.all_sessions Returns: - `dict`: containing all open sessions + :obj:`list` of :obj:`dict`: containing all open sessions """ return self.execute(Command.GET_ALL_SESSIONS)['value'] @property - def events(self): + def events(self) -> Dict: """ Retrieves events information from the current session Usage: @@ -63,7 +65,7 @@ def events(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_SESSION] = \ ('GET', '/session/$sessionId') self.command_executor._commands[Command.GET_ALL_SESSIONS] = \ diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index f5f82762..37a2ed08 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -15,7 +15,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use import copy -from typing import Dict, List, TypeVar, Union +from typing import Dict, List, Optional, TypeVar, Union from selenium.common.exceptions import InvalidArgumentException from selenium.webdriver.common.by import By @@ -256,7 +256,7 @@ def _merge_capabilities(self, capabilities: Dict) -> Dict: w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} - def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: # type: ignore + def find_element(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: """'Private' method used by the find_element_by_* methods. Override for Appium @@ -287,8 +287,8 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> Mobil 'using': by, 'value': value})['value'] - # type: ignore - def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> Union[List[MobileWebElement], List]: + def find_elements(self, by: Optional[str] = By.ID, value: Union[str, Dict] + = None) -> Union[List[MobileWebElement], List]: """'Private' method used by the find_elements_by_* methods. Override for Appium diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index bf8efc5b..c433316a 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -12,21 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, List, Optional, TypeVar, Union + from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand from .extensions.search_context import AppiumWebElementSearchContext from .mobilecommand import MobileCommand as Command -# Python 3 imports -try: - str = basestring -except NameError: - pass +T = TypeVar('T', bound='WebElement') class WebElement(AppiumWebElementSearchContext): - def get_attribute(self, name): + def get_attribute(self, name: str) -> Optional[str]: """Gets the given attribute or property of the element. Override for Appium @@ -66,14 +64,14 @@ def get_attribute(self, name): return attributeValue - def is_displayed(self): + def is_displayed(self) -> bool: """Whether the element is visible to a user. Override for Appium """ return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by=By.ID, value=None): + def find_element(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> T: """Find an element given a By strategy and locator Override for Appium @@ -107,7 +105,7 @@ def find_element(self, by=By.ID, value=None): return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - def find_elements(self, by=By.ID, value=None): + def find_elements(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> List[T]: """Find elements given a By strategy and locator Override for Appium @@ -141,7 +139,7 @@ def find_elements(self, by=By.ID, value=None): return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] - def clear(self): + def clear(self) -> T: """Clears text. Override for Appium @@ -153,7 +151,7 @@ def clear(self): self._execute(Command.CLEAR, data) return self - def set_text(self, keys=''): + def set_text(self, keys: str = '') -> T: """Sends text to the element. Previous text is removed. @@ -176,7 +174,7 @@ def set_text(self, keys=''): return self @property - def location_in_view(self): + def location_in_view(self) -> Dict: """Gets the location of an element relative to the view. Usage: @@ -189,7 +187,7 @@ def location_in_view(self): """ return self._execute(Command.LOCATION_IN_VIEW)['value'] - def set_value(self, value): + def set_value(self, value: str) -> T: """Set the value on this element in the application Args: From dde7ac4f1bf4e4de5880fd8c14aadae04b4439e3 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 15:20:03 +0900 Subject: [PATCH 09/26] Updates --- appium/webdriver/extensions/action_helpers.py | 4 ++-- .../extensions/android/activities.py | 2 +- appium/webdriver/extensions/android/common.py | 2 +- appium/webdriver/extensions/android/gsm.py | 2 +- .../webdriver/extensions/android/network.py | 2 +- appium/webdriver/extensions/android/power.py | 2 +- appium/webdriver/extensions/android/sms.py | 8 +++++-- .../extensions/android/system_bars.py | 6 +++-- appium/webdriver/extensions/applications.py | 2 +- appium/webdriver/extensions/clipboard.py | 2 +- appium/webdriver/extensions/context.py | 10 +++++---- appium/webdriver/extensions/device_time.py | 10 +++++---- appium/webdriver/extensions/execute_driver.py | 13 ++++++----- appium/webdriver/extensions/hw_actions.py | 22 +++++++++++-------- .../webdriver/extensions/images_comparison.py | 11 ++++++---- appium/webdriver/extensions/ime.py | 16 +++++++++----- appium/webdriver/extensions/keyboard.py | 19 ++++++++++------ appium/webdriver/extensions/location.py | 13 +++++++---- appium/webdriver/extensions/log_event.py | 12 ++++++---- appium/webdriver/extensions/remote_fs.py | 17 ++++++++------ 20 files changed, 108 insertions(+), 67 deletions(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 9796210c..32bbb9eb 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -20,12 +20,12 @@ from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='ActionHelpers') class ActionHelpers(webdriver.Remote): - def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: int = None) -> T: + def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: int = None) -> T: """Scrolls from one element to another Args: diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 5baad89b..f80805a0 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -20,7 +20,7 @@ from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Activities') class Activities(webdriver.Remote): diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index 0b451d34..65da6733 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -18,7 +18,7 @@ from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Common') class Common(webdriver.Remote): diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index b94a0602..df4ea95e 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -46,7 +46,7 @@ class GsmVoiceState(object): ON = 'on' -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Gsm') class Gsm(webdriver.Remote): diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index b8e0a5b1..2ad924fa 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -20,7 +20,7 @@ from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Network') class NetSpeed(object): diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index 0b4e6fa1..827324e8 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -18,7 +18,7 @@ from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Power') class Power(webdriver.Remote): diff --git a/appium/webdriver/extensions/android/sms.py b/appium/webdriver/extensions/android/sms.py index 4c0675a0..8f7773cf 100644 --- a/appium/webdriver/extensions/android/sms.py +++ b/appium/webdriver/extensions/android/sms.py @@ -12,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='Sms') + class Sms(webdriver.Remote): - def send_sms(self, phone_number, message): + def send_sms(self, phone_number: str, message: str) -> T: """Emulate send SMS event on the connected emulator. Android only. @@ -39,6 +43,6 @@ def send_sms(self, phone_number, message): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.SEND_SMS] = \ ('POST', '/session/$sessionId/appium/device/send_sms') diff --git a/appium/webdriver/extensions/android/system_bars.py b/appium/webdriver/extensions/android/system_bars.py index edc98a5e..085296d9 100644 --- a/appium/webdriver/extensions/android/system_bars.py +++ b/appium/webdriver/extensions/android/system_bars.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Union + from selenium import webdriver from appium.webdriver.mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class SystemBars(webdriver.Remote): - def get_system_bars(self): + def get_system_bars(self) -> Dict[str, Dict[str, Union[int, bool]]]: """Retrieve visibility and bounds information of the status and navigation bars. Android only. @@ -43,6 +45,6 @@ def get_system_bars(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_SYSTEM_BARS] = \ ('GET', '/session/$sessionId/appium/device/system_bars') diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index 828cd32e..55c68503 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -18,7 +18,7 @@ from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Applications') class Applications(webdriver.Remote): diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 9cf16988..e4f7bc71 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -21,7 +21,7 @@ from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=webdriver.Remote) +T = TypeVar('T', bound='Clipboard') class Clipboard(webdriver.Remote): diff --git a/appium/webdriver/extensions/context.py b/appium/webdriver/extensions/context.py index 8581cc3f..9ff5ec62 100644 --- a/appium/webdriver/extensions/context.py +++ b/appium/webdriver/extensions/context.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List + from selenium import webdriver from ..mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class Context(webdriver.Remote): @property - def contexts(self): + def contexts(self) -> List[str]: """Returns the contexts within the current session. Usage: @@ -32,7 +34,7 @@ def contexts(self): return self.execute(Command.CONTEXTS)['value'] @property - def current_context(self): + def current_context(self) -> str: """Returns the current context of the current session. Usage: @@ -44,7 +46,7 @@ def current_context(self): return self.execute(Command.GET_CURRENT_CONTEXT)['value'] @property - def context(self): + def context(self) -> str: """Returns the current context of the current session. Usage: @@ -57,7 +59,7 @@ def context(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.CONTEXTS] = \ ('GET', '/session/$sessionId/contexts') self.command_executor._commands[Command.GET_CURRENT_CONTEXT] = \ diff --git a/appium/webdriver/extensions/device_time.py b/appium/webdriver/extensions/device_time.py index e40b36d2..d16f56b3 100644 --- a/appium/webdriver/extensions/device_time.py +++ b/appium/webdriver/extensions/device_time.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Optional + from selenium import webdriver from ..mobilecommand import MobileCommand as Command @@ -20,7 +22,7 @@ class DeviceTime(webdriver.Remote): @property - def device_time(self): + def device_time(self) -> str: """Returns the date and time from the device. Return: @@ -28,11 +30,11 @@ def device_time(self): """ return self.execute(Command.GET_DEVICE_TIME_GET, {})['value'] - def get_device_time(self, format=None): + def get_device_time(self, format: Optional[str] = None) -> str: """Returns the date and time from the device. Args: - format (optional): The set of format specifiers. Read https://momentjs.com/docs/ + format (:obj:`str`, optional): The set of format specifiers. Read https://momentjs.com/docs/ to get the full list of supported datetime format specifiers. If unset, return :func:`.device_time` as default format is `YYYY-MM-DDTHH:mm:ssZ`, which complies to ISO-8601 @@ -50,7 +52,7 @@ def get_device_time(self, format=None): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_DEVICE_TIME_GET] = \ ('GET', '/session/$sessionId/appium/device/system_time') self.command_executor._commands[Command.GET_DEVICE_TIME_POST] = \ diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index 5c866a39..e7d38829 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, Optional + from selenium import webdriver from ..mobilecommand import MobileCommand as Command @@ -19,15 +21,16 @@ class ExecuteDriver(webdriver.Remote): - def execute_driver(self, script, script_type='webdriverio', timeout_ms=None): + # TODO Inner class case + def execute_driver(self, script: str, script_type: str = 'webdriverio', timeout_ms: Optional[int] = None) -> Any: """Run a set of script against the current session, allowing execution of many commands in one Appium request. Please read http://appium.io/docs/en/commands/session/execute-driver for more details about the acceptable scripts and the output format. Args: - script (string): The string consisting of the script itself - script_type (string): The name of the script type. Defaults to 'webdriverio'. - timeout_ms (optional): The number of `ms` Appium should wait for the script to finish before killing it due to timeout_ms. + script (str): The string consisting of the script itself + script_type (str): The name of the script type. Defaults to 'webdriverio'. + timeout_ms (:obj:`int`, optional): The number of `ms` Appium should wait for the script to finish before killing it due to timeout_ms. Usage: self.driver.execute_driver(script='return [];') @@ -56,6 +59,6 @@ def __init__(self, response): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.EXECUTE_DRIVER] = \ ('POST', '/session/$sessionId/appium/execute_driver') diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index a3d6babb..24ba8105 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,18 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, TypeVar + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='HardwareActions') + class HardwareActions(webdriver.Remote): - def lock(self, seconds=None): + def lock(self, seconds: int = None) -> T: """Lock the device. No changes are made if the device is already unlocked. Args: - seconds (optional): The duration to lock the device, in seconds. + seconds (:obj:`int`, optional): The duration to lock the device, in seconds. The device is going to be locked forever until `unlock` is called if it equals or is less than zero, otherwise this call blocks until the timeout expires and unlocks the screen automatically. @@ -38,7 +42,7 @@ def lock(self, seconds=None): return self - def unlock(self): + def unlock(self) -> T: """Unlock the device. No changes are made if the device is already locked. Returns: @@ -47,7 +51,7 @@ def unlock(self): self.execute(Command.UNLOCK) return self - def is_locked(self): + def is_locked(self) -> bool: """Checks whether the device is locked. Returns: @@ -55,7 +59,7 @@ def is_locked(self): """ return self.execute(Command.IS_LOCKED)['value'] - def shake(self): + def shake(self) -> T: """Shake the device. Returns: @@ -64,7 +68,7 @@ def shake(self): self.execute(Command.SHAKE) return self - def touch_id(self, match): + def touch_id(self, match: bool) -> T: """Simulate touchId on iOS Simulator Args: @@ -79,7 +83,7 @@ def touch_id(self, match): self.execute(Command.TOUCH_ID, data) return self - def toggle_touch_id_enrollment(self): + def toggle_touch_id_enrollment(self) -> T: """Toggle enroll touchId on iOS Simulator Returns: @@ -88,7 +92,7 @@ def toggle_touch_id_enrollment(self): self.execute(Command.TOGGLE_TOUCH_ID_ENROLLMENT) return self - def finger_print(self, finger_id): + def finger_print(self, finger_id: int) -> Any: """Authenticate users by using their finger print scans on supported Android emulators. Args: @@ -101,7 +105,7 @@ def finger_print(self, finger_id): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.LOCK] = \ ('POST', '/session/$sessionId/appium/device/lock') self.command_executor._commands[Command.UNLOCK] = \ diff --git a/appium/webdriver/extensions/images_comparison.py b/appium/webdriver/extensions/images_comparison.py index c0f16a90..1b7b0f0f 100644 --- a/appium/webdriver/extensions/images_comparison.py +++ b/appium/webdriver/extensions/images_comparison.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, Dict, Tuple + from selenium import webdriver from ..mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class ImagesComparison(webdriver.Remote): - def match_images_features(self, base64_image1, base64_image2, **opts): + def match_images_features(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Dict: """Performs images matching by features. Read @@ -73,7 +75,8 @@ def match_images_features(self, base64_image1, base64_image2, **opts): } return self.execute(Command.COMPARE_IMAGES, options)['value'] - def find_image_occurrence(self, base64_full_image, base64_partial_image, **opts): + def find_image_occurrence(self, base64_full_image: bytes, base64_partial_image: bytes, + **opts: Any) -> Tuple[bytes, Dict[str, int]]: """Performs images matching by template to find possible occurrence of the partial image in the full image. @@ -104,7 +107,7 @@ def find_image_occurrence(self, base64_full_image, base64_partial_image, **opts) } return self.execute(Command.COMPARE_IMAGES, options)['value'] - def get_images_similarity(self, base64_image1, base64_image2, **opts): + def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> bool: """Performs images matching to calculate the similarity score between them. The flow there is similar to the one used in @@ -135,6 +138,6 @@ def get_images_similarity(self, base64_image1, base64_image2, **opts): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.COMPARE_IMAGES] = \ ('POST', '/session/$sessionId/appium/compare_images') diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index 9c3230e3..701c4f5c 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -12,15 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List, TypeVar + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='IME') + class IME(webdriver.Remote): @property - def available_ime_engines(self): + def available_ime_engines(self) -> List[str]: """Get the available input methods for an Android device. Package and activity are returned (e.g., ['com.android.inputmethod.latin/.LatinIME']) @@ -31,7 +35,7 @@ def available_ime_engines(self): """ return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] - def is_ime_active(self): + def is_ime_active(self) -> bool: """Checks whether the device has IME service active. Android only. @@ -40,7 +44,7 @@ def is_ime_active(self): """ return self.execute(Command.IS_IME_ACTIVE, {})['value'] - def activate_ime_engine(self, engine): + def activate_ime_engine(self, engine: str) -> T: """Activates the given IME engine on the device. Android only. @@ -58,7 +62,7 @@ def activate_ime_engine(self, engine): self.execute(Command.ACTIVATE_IME_ENGINE, data) return self - def deactivate_ime_engine(self): + def deactivate_ime_engine(self) -> T: """Deactivates the currently active IME engine on the device. Android only. @@ -70,7 +74,7 @@ def deactivate_ime_engine(self): return self @property - def active_ime_engine(self): + def active_ime_engine(self) -> str: """Returns the activity and package of the currently active IME engine(e.g., 'com.android.inputmethod.latin/.LatinIME'). Android only. @@ -82,7 +86,7 @@ def active_ime_engine(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_AVAILABLE_IME_ENGINES] = \ ('GET', '/session/$sessionId/ime/available_engines') self.command_executor._commands[Command.IS_IME_ACTIVE] = \ diff --git a/appium/webdriver/extensions/keyboard.py b/appium/webdriver/extensions/keyboard.py index 612a99b6..029996fc 100644 --- a/appium/webdriver/extensions/keyboard.py +++ b/appium/webdriver/extensions/keyboard.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Optional, TypeVar + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='Keyboard') + class Keyboard(webdriver.Remote): - def hide_keyboard(self, key_name=None, key=None, strategy=None): + def hide_keyboard(self, key_name: Optional[str] = None, key: Optional[str] + = None, strategy: Optional[str] = None) -> T: """Hides the software keyboard on the device. In iOS, use `key_name` to press @@ -30,7 +35,7 @@ def hide_keyboard(self, key_name=None, key=None, strategy=None): key (:obj:`str`, optional): strategy (:obj:`str`, optional): strategy for closing the keyboard (e.g., `tapOutside`) """ - data = {} + data: Dict[str, Optional[str]] = {} if key_name is not None: data['keyName'] = key_name elif key is not None: @@ -41,7 +46,7 @@ def hide_keyboard(self, key_name=None, key=None, strategy=None): self.execute(Command.HIDE_KEYBOARD, data) return self - def is_keyboard_shown(self): + def is_keyboard_shown(self) -> bool: """Attempts to detect whether a software keyboard is present Returns: @@ -49,7 +54,7 @@ def is_keyboard_shown(self): """ return self.execute(Command.IS_KEYBOARD_SHOWN)['value'] - def keyevent(self, keycode, metastate=None): + def keyevent(self, keycode: int, metastate: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. @@ -70,7 +75,7 @@ def keyevent(self, keycode, metastate=None): self.execute(Command.KEY_EVENT, data) return self - def press_keycode(self, keycode, metastate=None, flags=None): + def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. Possible keycodes can be found in http://developer.android.com/reference/android/view/KeyEvent.html. @@ -93,7 +98,7 @@ def press_keycode(self, keycode, metastate=None, flags=None): self.execute(Command.PRESS_KEYCODE, data) return self - def long_press_keycode(self, keycode, metastate=None, flags=None): + def long_press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: """Sends a long press of keycode to the device. Android only. Possible keycodes can be found in http://developer.android.com/reference/android/view/KeyEvent.html. @@ -118,7 +123,7 @@ def long_press_keycode(self, keycode, metastate=None, flags=None): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.HIDE_KEYBOARD] = \ ('POST', '/session/$sessionId/appium/device/hide_keyboard') self.command_executor._commands[Command.IS_KEYBOARD_SHOWN] = \ diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 7dffcb0e..9f30f133 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Optional, TypeVar, Union + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='Location') + class Location(webdriver.Remote): - def toggle_location_services(self): + def toggle_location_services(self) -> T: """Toggle the location services on the device. Android only. @@ -29,7 +33,8 @@ def toggle_location_services(self): self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) return self - def set_location(self, latitude, longitude, altitude=None): + def set_location(self, latitude: Union[float, str], longitude: Union[float, + str], altitude: Union[float, str] = None) -> T: """Set the location of the device Args: @@ -52,7 +57,7 @@ def set_location(self, latitude, longitude, altitude=None): return self @property - def location(self): + def location(self) -> Dict[str, float]: """Retrieves the current location Returns: @@ -65,7 +70,7 @@ def location(self): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.TOGGLE_LOCATION_SERVICES] = \ ('POST', '/session/$sessionId/appium/device/toggle_location_services') self.command_executor._commands[Command.GET_LOCATION] = \ diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index 383664d6..b22c2416 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -12,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, List, TypeVar, Union + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='LogEvent') + class LogEvent(webdriver.Remote): - def get_events(self, type=None): + def get_events(self, type: List[str] = None) -> Dict[str, Union[str, int]]: """ Retrieves events information from the current session (Since Appium 1.16.0) @@ -35,14 +39,14 @@ def get_events(self, type=None): commands: (`list` of `dict`) List of dictionaries containing the following entries cmd: (str) The command name that has been sent to the appium server startTime: (int) Received time - endTime: (init) Response time + endTime: (int) Response time """ data = {} if type is not None: data['type'] = type return self.execute(Command.GET_EVENTS, data)['value'] - def log_event(self, vendor, event): + def log_event(self, vendor: str, event: str) -> T: """Log a custom event on the Appium server. (Since Appium 1.16.0) @@ -65,7 +69,7 @@ def log_event(self, vendor, event): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_EVENTS] = \ ('POST', '/session/$sessionId/appium/events') self.command_executor._commands[Command.LOG_EVENT] = \ diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index 10fbff23..d8844c85 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,15 +13,18 @@ # limitations under the License. import base64 +from typing import Optional, TypeVar from selenium import webdriver from selenium.common.exceptions import InvalidArgumentException from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='RemoteFS') + class RemoteFS(webdriver.Remote): - def pull_file(self, path): + def pull_file(self, path: str) -> bytes: """Retrieves the file at `path`. Args: @@ -35,7 +38,7 @@ def pull_file(self, path): } return self.execute(Command.PULL_FILE, data)['value'] - def pull_folder(self, path): + def pull_folder(self, path: str) -> bytes: """Retrieves a folder at `path`. Args: @@ -49,14 +52,14 @@ def pull_folder(self, path): } return self.execute(Command.PULL_FOLDER, data)['value'] - def push_file(self, destination_path, base64data=None, source_path=None): + def push_file(self, destination_path: str, base64data: Optional[str] = None, source_path: str = None) -> T: """Puts the data from the file at `source_path`, encoded as Base64, in the file specified as `path`. Specify either `base64data` or `source_path`, if both specified default to `source_path` Args: destination_path (str): the location on the device/simulator where the local file contents should be saved - base64data (:obj:`bytes`, optional): file contents, encoded as Base64, to be written to the file on the device/simulator + base64data (:obj:`str`, optional): file contents, encoded as Base64, to be written to the file on the device/simulator source_path (:obj:`str`, optional): local file path for the file to be loaded on device Returns: @@ -68,11 +71,11 @@ def push_file(self, destination_path, base64data=None, source_path=None): if source_path is not None: try: with open(source_path, 'rb') as f: - data = f.read() + file_data = f.read() except IOError: message = 'source_path {} could not be found. Are you sure the file exists?'.format(source_path) raise InvalidArgumentException(message) - base64data = base64.b64encode(data).decode('utf-8') + base64data = base64.b64encode(file_data).decode('utf-8') data = { 'path': destination_path, @@ -83,7 +86,7 @@ def push_file(self, destination_path, base64data=None, source_path=None): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.PULL_FILE] = \ ('POST', '/session/$sessionId/appium/device/pull_file') self.command_executor._commands[Command.PULL_FOLDER] = \ From a3ae246ba904b2668f2760bdaa9f8f4223e9a369 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 16:12:38 +0900 Subject: [PATCH 10/26] Add mypy check to ci.sh --- appium/webdriver/appium_service.py | 27 ++++++++++--------- appium/webdriver/extensions/execute_driver.py | 6 ++--- appium/webdriver/extensions/screen_record.py | 8 +++--- appium/webdriver/extensions/settings.py | 10 ++++--- ci.sh | 7 +++++ 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index ab09adb7..74499671 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -16,7 +16,7 @@ import subprocess as sp import sys import time -from typing import Any, Optional, Union +from typing import Any, List, Optional, TypeVar import urllib3 @@ -45,7 +45,7 @@ def find_executable(executable: str) -> Optional[str]: return None -def poll_url(host: str, port: str, path: str, timeout_ms: int) -> bool: +def poll_url(host: str, port: int, path: str, timeout_ms: int) -> bool: time_started_sec = time.time() while time.time() < time_started_sec + timeout_ms / 1000.0: try: @@ -64,12 +64,15 @@ class AppiumServiceError(RuntimeError): pass +T = TypeVar('T', bound='AppiumService') + + class AppiumService(object): def __init__(self) -> None: - self._process = None - self._cmd = None + self._process: Optional[sp.Popen] = None + self._cmd: Optional[List] = None - def _get_node(self) -> Optional[str]: + def _get_node(self) -> str: if not hasattr(self, '_node_executable'): self._node_executable = find_executable('node') if self._node_executable is None: @@ -77,7 +80,7 @@ def _get_node(self) -> Optional[str]: 'Make sure it is installed and present in PATH') return self._node_executable - def _get_npm(self) -> Optional[str]: + def _get_npm(self) -> str: if not hasattr(self, '_npm_executable'): self._npm_executable = find_executable('npm.cmd' if sys.platform == 'win32' else 'npm') if self._npm_executable is None: @@ -106,14 +109,14 @@ def _get_main_script(self) -> str: return self._main_script @staticmethod - def _parse_port(args): + def _parse_port(args: List[str]) -> int: for idx, arg in enumerate(args or []): if arg in ('--port', '-p') and idx < len(args) - 1: return int(args[idx + 1]) return DEFAULT_PORT @staticmethod - def _parse_host(args): + def _parse_host(args: List[str]) -> str: for idx, arg in enumerate(args or []): if arg in ('--address', '-a') and idx < len(args) - 1: return args[idx + 1] @@ -164,7 +167,7 @@ def start(self, **kwargs: Any) -> sp.Popen: self._process = sp.Popen(args=args, stdout=stdout, stderr=stderr, env=env) host = self._parse_host(args) port = self._parse_port(args) - error_msg = None + error_msg: Optional[str] = None if not self.is_running or (timeout_ms > 0 and not poll_url(host, port, STATUS_URL, timeout_ms)): error_msg = 'Appium has failed to start on {}:{} within {}ms timeout'\ .format(host, port, timeout_ms) @@ -177,7 +180,7 @@ def start(self, **kwargs: Any) -> sp.Popen: raise AppiumServiceError(error_msg) return self._process - def stop(self): + def stop(self) -> bool: """Stops Appium service if it is running. The call will be ignored if the service is not running @@ -195,7 +198,7 @@ def stop(self): return is_terminated @property - def is_running(self): + def is_running(self) -> bool: """Check if the service is running. Returns: @@ -204,7 +207,7 @@ def is_running(self): return self._process is not None and self._process.poll() is None @property - def is_listening(self): + def is_listening(self) -> bool: """Check if the service is listening on the given/default host/port. The fact, that the service is running, does not always mean it is listening. diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index e7d38829..f6c00fb0 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional +from typing import Any, Dict, Optional, Union from selenium import webdriver @@ -46,11 +46,11 @@ def execute_driver(self, script: str, script_type: str = 'webdriverio', timeout_ class Result(object): - def __init__(self, response): + def __init__(self, response: Dict): self.result = response['result'] self.logs = response['logs'] - option = {'script': script, 'type': script_type} + option: Dict[str, Union[str, int]] = {'script': script, 'type': script_type} if timeout_ms is not None: option['timeout'] = timeout_ms diff --git a/appium/webdriver/extensions/screen_record.py b/appium/webdriver/extensions/screen_record.py index 0b7f1bc7..8d2abbf0 100644 --- a/appium/webdriver/extensions/screen_record.py +++ b/appium/webdriver/extensions/screen_record.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any, Union + from selenium import webdriver from ..mobilecommand import MobileCommand as Command @@ -19,7 +21,7 @@ class ScreenRecord(webdriver.Remote): - def start_recording_screen(self, **options): + def start_recording_screen(self, **options: Any) -> Union[bytes, str]: """Start asynchronous screen recording process. Keyword Args: @@ -82,7 +84,7 @@ def start_recording_screen(self, **options): del options['password'] return self.execute(Command.START_RECORDING_SCREEN, {'options': options})['value'] - def stop_recording_screen(self, **options): + def stop_recording_screen(self, **options: Any) -> bytes: """Gather the output from the previously started screen recording to a media file. Keyword Args: @@ -112,7 +114,7 @@ def stop_recording_screen(self, **options): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.START_RECORDING_SCREEN] = \ ('POST', '/session/$sessionId/appium/start_recording_screen') self.command_executor._commands[Command.STOP_RECORDING_SCREEN] = \ diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index 2a0f4235..ad594450 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, TypeVar + from selenium import webdriver from ..mobilecommand import MobileCommand as Command +T = TypeVar('T', bound='Settings') + class Settings(webdriver.Remote): - def get_settings(self): + def get_settings(self) -> Dict: """Returns the appium server Settings for the current session. Do not get Settings confused with Desired Capabilities, they are @@ -29,7 +33,7 @@ def get_settings(self): """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self, settings): + def update_settings(self, settings: Dict) -> T: """Set settings for the current session. For more on settings, see: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md @@ -44,7 +48,7 @@ def update_settings(self, settings): # pylint: disable=protected-access - def _addCommands(self): + def _addCommands(self) -> None: self.command_executor._commands[Command.GET_SETTINGS] = \ ('GET', '/session/$sessionId/appium/settings') self.command_executor._commands[Command.UPDATE_SETTINGS] = \ diff --git a/ci.sh b/ci.sh index 0fb53862..a0b1d8fc 100755 --- a/ci.sh +++ b/ci.sh @@ -34,4 +34,11 @@ if [[ $? -ne 0 ]] ; then EXIT_STATUS=1 fi +( + python -m mypy appium +) +if [[ $? -ne 0 ]] ; then + EXIT_STATUS=1 +fi + exit $EXIT_STATUS From f39e41ca58353ea7bdd970477e16a596218384f2 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 16:26:11 +0900 Subject: [PATCH 11/26] Add mypy to Pipfile --- Pipfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Pipfile b/Pipfile index aca7dc7f..d9795ba9 100644 --- a/Pipfile +++ b/Pipfile @@ -24,3 +24,5 @@ mock = "~=3.0" pylint = "~=2.4" astroid = "~=2.3" isort = "~=4.3" + +mypy = "==0.761" From 0f0f1f9ebad89f31c55793755f44542363c12d7b Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 19:57:29 +0900 Subject: [PATCH 12/26] Updates --- appium/common/helper.py | 4 ++-- appium/common/logger.py | 2 +- appium/saucetestcase.py | 4 ++-- appium/webdriver/appium_service.py | 8 ++++---- appium/webdriver/common/touch_action.py | 2 ++ appium/webdriver/errorhandler.py | 4 +++- appium/webdriver/extensions/applications.py | 2 +- appium/webdriver/extensions/execute_mobile_command.py | 6 ++++-- appium/webdriver/switch_to.py | 7 ++++++- appium/webdriver/webdriver.py | 2 ++ appium/webdriver/webelement.py | 3 --- 11 files changed, 27 insertions(+), 17 deletions(-) diff --git a/appium/common/helper.py b/appium/common/helper.py index 9123814e..4b0a1f2e 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -17,7 +17,7 @@ from appium import version as appium_version -def extract_const_attributes(cls) -> OrderedDict: +def extract_const_attributes(cls: type) -> OrderedDict: """Return dict with constants attributes and values in the class(e.g. {'VAL1': 1, 'VAL2': 2}) Args: @@ -30,7 +30,7 @@ def extract_const_attributes(cls) -> OrderedDict: [(attr, value) for attr, value in vars(cls).items() if not callable(getattr(cls, attr)) and attr.isupper()]) -def library_version(): +def library_version() -> str: """Return a version of this python library """ diff --git a/appium/common/logger.py b/appium/common/logger.py index 372d7fa8..014b69e4 100644 --- a/appium/common/logger.py +++ b/appium/common/logger.py @@ -16,7 +16,7 @@ import sys -def setup_logger(level=logging.NOTSET): +def setup_logger(level: int = logging.NOTSET) -> None: logger.propagate = False logger.setLevel(level) handler = logging.StreamHandler(stream=sys.stderr) diff --git a/appium/saucetestcase.py b/appium/saucetestcase.py index 48efcc1e..a2e94aba 100644 --- a/appium/saucetestcase.py +++ b/appium/saucetestcase.py @@ -42,10 +42,10 @@ def decorator(base_class: type) -> None: class SauceTestCase(unittest.TestCase): def setUp(self) -> None: - self.desired_capabilities['name'] = self.id() + self.desired_capabilities['name'] = self.id() # type: ignore sauce_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" self.driver = webdriver.Remote( - desired_capabilities=self.desired_capabilities, + desired_capabilities=self.desired_capabilities, # type: ignore command_executor=sauce_url % (SAUCE_USERNAME, SAUCE_ACCESS_KEY) ) self.driver.implicitly_wait(30) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 74499671..cc69d586 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -100,10 +100,10 @@ def _get_main_script(self) -> str: continue if not hasattr(self, '_main_script'): try: - self._main_script = sp.check_output( + self._main_script = str(sp.check_output( [self._get_node(), '-e', - 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip() + 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip()) except sp.CalledProcessError as e: raise AppiumServiceError(e.output) return self._main_script @@ -175,7 +175,7 @@ def start(self, **kwargs: Any) -> sp.Popen: if stderr == sp.PIPE: err_output = self._process.stderr.read() if err_output: - error_msg += '\nOriginal error: {}'.format(err_output) + error_msg += '\nOriginal error: {}'.format(str(err_output)) self.stop() raise AppiumServiceError(error_msg) return self._process @@ -190,7 +190,7 @@ def stop(self) -> bool: bool: `True` if the service was running before being stopped """ is_terminated = False - if self.is_running: + if self.is_running and self._process is not None: self._process.terminate() is_terminated = True self._process = None diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 9d9623ad..a7547c3a 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -140,6 +140,8 @@ def perform(self: T) -> T: Returns: `TouchAction`: self instance """ + if self._driver is None: + raise AttributeError('Set driver to constructor as a argument when to create the instance.') params = {'actions': self._actions} self._driver.execute(Command.TOUCH_ACTION, params) diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index 1fa793ba..38426992 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from selenium.common.exceptions import WebDriverException from selenium.webdriver.remote import errorhandler @@ -19,7 +21,7 @@ class MobileErrorHandler(errorhandler.ErrorHandler): - def check_response(self, response): + def check_response(self, response: Dict) -> None: try: super(MobileErrorHandler, self).check_response(response) except WebDriverException as wde: diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index 55c68503..9ab8a295 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -134,7 +134,7 @@ def terminate_app(self, app_id: str, **options: Any) -> bool: Returns: bool: True if the app has been successfully terminated """ - data = { + data: Dict[str, Any] = { 'appId': app_id, } if options: diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 16f52b0b..9866df7f 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict +from typing import Any, Dict, TypeVar from selenium import webdriver +T = TypeVar('T', bound='ExecuteMobileCommand') + class ExecuteMobileCommand(webdriver.Remote): - def press_button(self, button_name: str): + def press_button(self, button_name: str) -> T: """Sends a physical button name to the device to simulate the user pressing. iOS only. diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index d496b7a3..e0266510 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TypeVar + from selenium.webdriver.remote.switch_to import SwitchTo from .mobilecommand import MobileCommand +T = TypeVar('T', bound='MobileSwitchTo') + class MobileSwitchTo(SwitchTo): - def context(self, context_name): + def context(self, context_name: str) -> T: """Sets the context for the current session. Args: @@ -28,3 +32,4 @@ def context(self, context_name): driver.switch_to.context('WEBVIEW_1') """ self._driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {'name': context_name}) + return self diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 37a2ed08..89471a8a 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -222,8 +222,10 @@ def start_session(self, capabilities: Dict, browser_profile: str = None) -> None raise InvalidArgumentException('Capabilities must be a dictionary') if browser_profile: if 'moz:firefoxOptions' in capabilities: + # type: ignore # encodesd is defined in selenium's original codes capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded else: + # type: ignore # encodesd is defined in selenium's original codes capabilities.update({'firefox_profile': browser_profile.encoded}) parameters = self._merge_capabilities(capabilities) diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index c433316a..9f2982da 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -56,9 +56,6 @@ def get_attribute(self, name: str) -> Optional[str]: if attributeValue is None: return None - if not isinstance(attributeValue, str): - attributeValue = unicode(attributeValue) - if name != 'value' and attributeValue.lower() in ('true', 'false'): return attributeValue.lower() From 3d7f60918168a390452bd9865d06339857ba8aa2 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 20:45:59 +0900 Subject: [PATCH 13/26] Update README --- README.md | 8 ++++++-- appium/webdriver/webdriver.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9cb50efc..50834ce6 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,18 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). - Style Guide: https://www.python.org/dev/peps/pep-0008/ - `autopep8` helps to format code automatically - ``` + ```shell $ python -m autopep8 -r --global-config .config-pep8 -i . ``` - `isort` helps to order imports automatically - ``` + ```shell $ python -m isort -rc . ``` - When you use newly 3rd party modules, add it to [.isort.cfg](.isort.cfg) to keep import order correct + - `mypy` helps to check explicit type declarations + ```shell + $ python -m mypy appium + ``` - Docstring style: Google Style - Refer [link](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) - You can customise `CHANGELOG.rst` with commit messages following [.gitchangelog.rc](.gitchangelog.rc) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 89471a8a..0b888654 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -222,11 +222,11 @@ def start_session(self, capabilities: Dict, browser_profile: str = None) -> None raise InvalidArgumentException('Capabilities must be a dictionary') if browser_profile: if 'moz:firefoxOptions' in capabilities: - # type: ignore # encodesd is defined in selenium's original codes - capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded + # encodesd is defined in selenium's original codes + capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded # type: ignore else: - # type: ignore # encodesd is defined in selenium's original codes - capabilities.update({'firefox_profile': browser_profile.encoded}) + # encodesd is defined in selenium's original codes + capabilities.update({'firefox_profile': browser_profile.encoded}) # type: ignore parameters = self._merge_capabilities(capabilities) From c2fb780743b9b00705984fdbf42514800236ea94 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 20:49:16 +0900 Subject: [PATCH 14/26] Revert unexpected changes --- appium/webdriver/webdriver.py | 4 ++-- test/functional/ios/applications_tests.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 0b888654..0330308f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -222,10 +222,10 @@ def start_session(self, capabilities: Dict, browser_profile: str = None) -> None raise InvalidArgumentException('Capabilities must be a dictionary') if browser_profile: if 'moz:firefoxOptions' in capabilities: - # encodesd is defined in selenium's original codes + # encoded is defined in selenium's original codes capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded # type: ignore else: - # encodesd is defined in selenium's original codes + # encoded is defined in selenium's original codes capabilities.update({'firefox_profile': browser_profile.encoded}) # type: ignore parameters = self._merge_capabilities(capabilities) diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index 00b60325..56ed859d 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -30,7 +30,7 @@ def test_app_management(self): self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), ApplicationState.RUNNING_IN_FOREGROUND) self.driver.background_app(-1) - self.driver.find_element_by_accessibility_id().id + self.driver.find_element_by_accessibility_id() self.assertTrue(self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND) self.driver.activate_app(desired_capabilities.BUNDLE_ID) From 809036ab58d0880c12a3fdb494046302fc477341 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 22:53:36 +0900 Subject: [PATCH 15/26] Updates Dict --- appium/webdriver/appium_connection.py | 4 ++-- appium/webdriver/common/touch_action.py | 4 ++-- appium/webdriver/extensions/android/performance.py | 4 ++-- appium/webdriver/extensions/images_comparison.py | 4 ++-- appium/webdriver/extensions/session.py | 8 ++++---- appium/webdriver/extensions/settings.py | 6 +++--- appium/webdriver/webdriver.py | 6 +++--- appium/webdriver/webelement.py | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 75ae161d..f89998af 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict +from typing import Any, Dict from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -22,7 +22,7 @@ class AppiumConnection(RemoteConnection): @classmethod - def get_remote_connection_headers(cls, parsed_url: str, keep_alive: bool = True) -> Dict: + def get_remote_connection_headers(cls, parsed_url: str, keep_alive: bool = True) -> Dict[str, Any]: """Override get_remote_connection_headers in RemoteConnection""" headers = RemoteConnection.get_remote_connection_headers(parsed_url, keep_alive=keep_alive) headers['User-Agent'] = 'appium/python {} ({})'.format(library_version(), headers['User-Agent']) diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index a7547c3a..cba1d088 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,7 +24,7 @@ # pylint: disable=no-self-use import copy -from typing import TYPE_CHECKING, Dict, List, TypeVar +from typing import TYPE_CHECKING, Dict, List, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command @@ -165,7 +165,7 @@ def _add_action(self, action: str, options: Dict) -> None: self._actions.append(gesture) def _get_opts(self, el: 'WebElement' = None, x: int = None, y: int = None, - duration: int = None, pressure: float = None) -> Dict: + duration: int = None, pressure: float = None) -> Dict[str, Union[int, float]]: opts = {} if el is not None: opts['element'] = el.id diff --git a/appium/webdriver/extensions/android/performance.py b/appium/webdriver/extensions/android/performance.py index b7ea267b..3d661d77 100644 --- a/appium/webdriver/extensions/android/performance.py +++ b/appium/webdriver/extensions/android/performance.py @@ -21,7 +21,7 @@ class Performance(webdriver.Remote): - def get_performance_data(self, package_name: str, data_type: str, data_read_timeout: int = None) -> Dict: + def get_performance_data(self, package_name: str, data_type: str, data_read_timeout: int = None) -> List[List[str]]: """Returns the information of the system state which is supported to read as like cpu, memory, network traffic, and battery. @@ -38,7 +38,7 @@ def get_performance_data(self, package_name: str, data_type: str, data_read_time self.driver.get_performance_data('my.app.package', 'cpuinfo', 5) Returns: - dict: The data along to `data_type` + list: The data along to `data_type` """ data: Dict[str, Union[str, int]] = {'packageName': package_name, 'dataType': data_type} if data_read_timeout is not None: diff --git a/appium/webdriver/extensions/images_comparison.py b/appium/webdriver/extensions/images_comparison.py index 1b7b0f0f..7ed538d8 100644 --- a/appium/webdriver/extensions/images_comparison.py +++ b/appium/webdriver/extensions/images_comparison.py @@ -21,7 +21,7 @@ class ImagesComparison(webdriver.Remote): - def match_images_features(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Dict: + def match_images_features(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Dict[str, Any]: """Performs images matching by features. Read @@ -107,7 +107,7 @@ def find_image_occurrence(self, base64_full_image: bytes, base64_partial_image: } return self.execute(Command.COMPARE_IMAGES, options)['value'] - def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> bool: + def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Tuple[bytes, float]: """Performs images matching to calculate the similarity score between them. The flow there is similar to the one used in diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index f188de8d..463f2f46 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List +from typing import Any, Dict, List from selenium import webdriver @@ -23,7 +23,7 @@ class Session(webdriver.Remote): @property - def session(self) -> Dict: + def session(self) -> Dict[str, Any]: """ Retrieves session information from the current session Usage: @@ -35,7 +35,7 @@ def session(self) -> Dict: return self.execute(Command.GET_SESSION)['value'] @property - def all_sessions(self) -> List[Dict]: + def all_sessions(self) -> List[Dict[str, Any]]: """ Retrieves all sessions that are open Usage: @@ -47,7 +47,7 @@ def all_sessions(self) -> List[Dict]: return self.execute(Command.GET_ALL_SESSIONS)['value'] @property - def events(self) -> Dict: + def events(self) -> Any: """ Retrieves events information from the current session Usage: diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index ad594450..b5514621 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, TypeVar +from typing import Any, Dict, TypeVar from selenium import webdriver @@ -22,7 +22,7 @@ class Settings(webdriver.Remote): - def get_settings(self) -> Dict: + def get_settings(self) -> Dict[str, Any]: """Returns the appium server Settings for the current session. Do not get Settings confused with Desired Capabilities, they are @@ -33,7 +33,7 @@ def get_settings(self) -> Dict: """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self, settings: Dict) -> T: + def update_settings(self, settings: Dict[str, Any]) -> T: """Set settings for the current session. For more on settings, see: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 0330308f..0596ace0 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -15,7 +15,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use import copy -from typing import Dict, List, Optional, TypeVar, Union +from typing import Any, Dict, List, Optional, TypeVar, Union from selenium.common.exceptions import InvalidArgumentException from selenium.webdriver.common.by import By @@ -85,7 +85,7 @@ # Add appium prefix for the non-W3C capabilities -def _make_w3c_caps(caps: Dict) -> Dict: +def _make_w3c_caps(caps: Dict) -> Dict[str, List[Dict[str, Any]]]: appium_prefix = 'appium:' caps = copy.deepcopy(caps) @@ -245,7 +245,7 @@ def start_session(self, capabilities: Dict, browser_profile: str = None) -> None self.w3c = response.get('status') is None self.command_executor.w3c = self.w3c - def _merge_capabilities(self, capabilities: Dict) -> Dict: + def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: """Manage capabilities whether W3C format or MJSONWP format """ if _FORCE_MJSONWP in capabilities: diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 9f2982da..e970e0ce 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -171,7 +171,7 @@ def set_text(self, keys: str = '') -> T: return self @property - def location_in_view(self) -> Dict: + def location_in_view(self) -> Dict[str, int]: """Gets the location of an element relative to the view. Usage: From 52069f9b7de997612f730f8f6d081ae95b518a00 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 22:56:03 +0900 Subject: [PATCH 16/26] Revert unexpected changes --- test/functional/ios/applications_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index 56ed859d..067aec18 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -30,7 +30,6 @@ def test_app_management(self): self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), ApplicationState.RUNNING_IN_FOREGROUND) self.driver.background_app(-1) - self.driver.find_element_by_accessibility_id() self.assertTrue(self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND) self.driver.activate_app(desired_capabilities.BUNDLE_ID) From 4af6cb334a568bbd095287821605569f03ba2984 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 12 Jan 2020 23:02:12 +0900 Subject: [PATCH 17/26] Updates --- appium/webdriver/extensions/action_helpers.py | 8 ++++---- appium/webdriver/webdriver.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 32bbb9eb..57615b8f 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -52,7 +52,7 @@ def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: in action.press(origin_el).wait(duration).move_to(destination_el).release().perform() return self - def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: + def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> T: """Drag the origin element to the destination element Args: @@ -66,7 +66,7 @@ def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> action.long_press(origin_el).move_to(destination_el).release().perform() return self - def tap(self: T, positions: List[Tuple], duration: int = None) -> T: + def tap(self, positions: List[Tuple], duration: int = None) -> T: """Taps on an particular place with up to five fingers, holding for a certain time @@ -105,7 +105,7 @@ def tap(self: T, positions: List[Tuple], duration: int = None) -> T: ma.perform() return self - def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: + def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: """Swipe from one point to another point, for an optional duration. Args: @@ -132,7 +132,7 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: action.perform() return self - def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: + def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> T: """Flick from one point to another point. Args: diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 0596ace0..b4ab7e1f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -339,7 +339,7 @@ def create_web_element(self, element_id: int, w3c: bool = False) -> MobileWebEle """ return MobileWebElement(self, element_id, w3c) - def set_value(self: T, element: MobileWebElement, value: str) -> T: + def set_value(self, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. Args: From 24e9bd99441360e2eb496652bbfe99320dc00536 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Wed, 22 Jan 2020 02:10:31 +0900 Subject: [PATCH 18/26] Review comments --- appium/common/helper.py | 10 ++++----- appium/webdriver/appium_service.py | 14 ++++++------- appium/webdriver/common/multi_action.py | 4 ++-- appium/webdriver/common/touch_action.py | 21 +++++++++++-------- appium/webdriver/extensions/action_helpers.py | 6 +++--- .../webdriver/extensions/android/network.py | 4 ++-- appium/webdriver/extensions/hw_actions.py | 4 ++-- appium/webdriver/extensions/location.py | 6 ++++-- appium/webdriver/extensions/remote_fs.py | 3 ++- .../extensions/search_context/android.py | 9 ++++---- .../search_context/base_search_context.py | 6 +++--- appium/webdriver/webdriver.py | 4 ++-- 12 files changed, 49 insertions(+), 42 deletions(-) diff --git a/appium/common/helper.py b/appium/common/helper.py index 4b0a1f2e..d34417fb 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -12,22 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import OrderedDict +from typing import Dict from appium import version as appium_version -def extract_const_attributes(cls: type) -> OrderedDict: +def extract_const_attributes(cls: type) -> Dict: """Return dict with constants attributes and values in the class(e.g. {'VAL1': 1, 'VAL2': 2}) Args: cls (type): Class to be extracted constants Returns: - OrderedDict: dict with constants attributes and values in the class + dict: dict with constants attributes and values in the class """ - return OrderedDict( - [(attr, value) for attr, value in vars(cls).items() if not callable(getattr(cls, attr)) and attr.isupper()]) + return dict([(attr, value) for attr, value in vars(cls).items() + if not callable(getattr(cls, attr)) and attr.isupper()]) def library_version() -> str: diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index cc69d586..7683782a 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -16,7 +16,7 @@ import subprocess as sp import sys import time -from typing import Any, List, Optional, TypeVar +from typing import Any, List, Optional, TypeVar, Union import urllib3 @@ -88,22 +88,22 @@ def _get_npm(self) -> str: 'Make sure it is installed and present in PATH') return self._npm_executable - def _get_main_script(self) -> str: + def _get_main_script(self) -> Union[str, bytes]: if not hasattr(self, '_main_script'): for args in [['root', '-g'], ['root']]: try: modules_root = sp.check_output([self._get_npm()] + args).strip().decode('utf-8') if os.path.exists(os.path.join(modules_root, MAIN_SCRIPT_PATH)): - self._main_script = os.path.join(modules_root, MAIN_SCRIPT_PATH) + self._main_script: Union[str, bytes] = os.path.join(modules_root, MAIN_SCRIPT_PATH) break except sp.CalledProcessError: continue if not hasattr(self, '_main_script'): try: - self._main_script = str(sp.check_output( + self._main_script = sp.check_output( [self._get_node(), '-e', - 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip()) + 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip() except sp.CalledProcessError as e: raise AppiumServiceError(e.output) return self._main_script @@ -190,8 +190,8 @@ def stop(self) -> bool: bool: `True` if the service was running before being stopped """ is_terminated = False - if self.is_running and self._process is not None: - self._process.terminate() + if self.is_running: + self._process.terminate() # type: ignore is_terminated = True self._process = None self._cmd = None diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 0e4d5bb0..f3326534 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,7 +19,7 @@ # chaining as the spec requires. import copy -from typing import TYPE_CHECKING, Dict, List, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command @@ -32,7 +32,7 @@ class MultiAction(object): - def __init__(self, driver: 'WebDriver', element: 'WebElement' = None) -> None: + def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: self._driver = driver self._element = element self._touch_actions: List['TouchAction'] = [] diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index cba1d088..59b702db 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,7 +24,7 @@ # pylint: disable=no-self-use import copy -from typing import TYPE_CHECKING, Dict, List, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command @@ -37,11 +37,12 @@ class TouchAction(object): - def __init__(self, driver: 'WebDriver' = None): + def __init__(self, driver: Optional['WebDriver'] = None): self._driver = driver self._actions: List = [] - def tap(self: T, element: 'WebElement' = None, x: int = None, y: int = None, count: int = 1) -> T: + def tap(self: T, element: Optional['WebElement'] = None, x: Optional[int] + = None, y: Optional[int] = None, count: int = 1) -> T: """Perform a tap action on the element Args: @@ -58,7 +59,8 @@ def tap(self: T, element: 'WebElement' = None, x: int = None, y: int = None, cou return self - def press(self: T, el: 'WebElement' = None, x: int = None, y: int = None, pressure: float = None) -> T: + def press(self: T, el: Optional['WebElement'] = None, x: Optional[int] = None, + y: Optional[int] = None, pressure: Optional[float] = None) -> T: """Begin a chain with a press down action at a particular element or point Args: @@ -75,7 +77,8 @@ def press(self: T, el: 'WebElement' = None, x: int = None, y: int = None, pressu return self - def long_press(self: T, el: 'WebElement' = None, x: int = None, y: int = None, duration: int = 1000) -> T: + def long_press(self: T, el: Optional['WebElement'] = None, x: Optional[int] + = None, y: Optional[int] = None, duration: int = 1000) -> T: """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -109,7 +112,7 @@ def wait(self: T, ms: int = 0) -> T: return self - def move_to(self: T, el: 'WebElement' = None, x: int = None, y: int = None) -> T: + def move_to(self: T, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None) -> T: """Move the pointer from the previous point to the element or point specified Args: @@ -141,7 +144,7 @@ def perform(self: T) -> T: `TouchAction`: self instance """ if self._driver is None: - raise AttributeError('Set driver to constructor as a argument when to create the instance.') + raise TypeError('Set driver to constructor as a argument when to create the instance.') params = {'actions': self._actions} self._driver.execute(Command.TOUCH_ACTION, params) @@ -164,8 +167,8 @@ def _add_action(self, action: str, options: Dict) -> None: } self._actions.append(gesture) - def _get_opts(self, el: 'WebElement' = None, x: int = None, y: int = None, - duration: int = None, pressure: float = None) -> Dict[str, Union[int, float]]: + def _get_opts(self, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None, + duration: Optional[int] = None, pressure: Optional[float] = None) -> Dict[str, Union[int, float]]: opts = {} if el is not None: opts['element'] = el.id diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 57615b8f..98ddbf67 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Tuple, TypeVar +from typing import List, Optional, Tuple, TypeVar from selenium import webdriver @@ -25,7 +25,7 @@ class ActionHelpers(webdriver.Remote): - def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: int = None) -> T: + def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> T: """Scrolls from one element to another Args: @@ -66,7 +66,7 @@ def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> T: action.long_press(origin_el).move_to(destination_el).release().perform() return self - def tap(self, positions: List[Tuple], duration: int = None) -> T: + def tap(self, positions: List[Tuple], duration: Optional[int] = None) -> T: """Taps on an particular place with up to five fingers, holding for a certain time diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 2ad924fa..99c71763 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -46,7 +46,7 @@ def network_connection(self) -> int: """ return self.execute(Command.GET_NETWORK_CONNECTION, {})['value'] - def set_network_connection(self, connection_type: int) -> Any: # TODO Check return type + def set_network_connection(self, connection_type: int) -> int: """Sets the network connection type. Android only. Possible values: @@ -63,7 +63,7 @@ def set_network_connection(self, connection_type: int) -> Any: # TODO Check ret connection_type (int): a member of the enum appium.webdriver.ConnectionType Return: - TODO + int: Set network connection type """ data = { 'parameters': { diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index 24ba8105..c27c7382 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, TypeVar +from typing import Any, Optional, TypeVar from selenium import webdriver @@ -23,7 +23,7 @@ class HardwareActions(webdriver.Remote): - def lock(self, seconds: int = None) -> T: + def lock(self, seconds: Optional[int] = None) -> T: """Lock the device. No changes are made if the device is already unlocked. Args: diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 9f30f133..054cf44e 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -33,8 +33,10 @@ def toggle_location_services(self) -> T: self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) return self - def set_location(self, latitude: Union[float, str], longitude: Union[float, - str], altitude: Union[float, str] = None) -> T: + def set_location(self, + latitude: Union[float, str], + longitude: Union[float, str], + altitude: Union[float, str] = None) -> T: """Set the location of the device Args: diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index d8844c85..c3251cf9 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -52,7 +52,8 @@ def pull_folder(self, path: str) -> bytes: } return self.execute(Command.PULL_FOLDER, data)['value'] - def push_file(self, destination_path: str, base64data: Optional[str] = None, source_path: str = None) -> T: + def push_file(self, destination_path: str, + base64data: Optional[str] = None, source_path: Optional[str] = None) -> T: """Puts the data from the file at `source_path`, encoded as Base64, in the file specified as `path`. Specify either `base64data` or `source_path`, if both specified default to `source_path` diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 32696c97..4197eb99 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -15,7 +15,7 @@ # pylint: disable=abstract-method import json -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional from appium.webdriver.common.mobileby import MobileBy @@ -29,7 +29,7 @@ class AndroidSearchContext(BaseSearchContext): """Define search context for Android""" def find_element_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None) -> 'WebElement': + self, name: Optional[str] = None, args: Optional[str] = None, className: Optional[str] = None) -> 'WebElement': """Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -62,7 +62,7 @@ def find_element_by_android_data_matcher( ) def find_elements_by_android_data_matcher( - self, name: str = None, args: str = None, className: str = None) -> List['WebElement']: + self, name: Optional[str] = None, args: Optional[str] = None, className: Optional[str] = None) -> List['WebElement']: """Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). @@ -89,7 +89,8 @@ def find_elements_by_android_data_matcher( value=self._build_data_matcher(name=name, args=args, className=className) ) - def _build_data_matcher(self, name: str = None, args: str = None, className: str = None) -> str: + def _build_data_matcher(self, name: Optional[str] = None, args: Optional[str] + = None, className: Optional[str] = None) -> str: result = {} for key, value in {'name': name, 'args': args, 'class': className}.items(): diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 493a55ea..08ccc6ee 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -14,7 +14,7 @@ # pylint: disable=abstract-method -from typing import TYPE_CHECKING, Dict, List, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union if TYPE_CHECKING: from appium.webdriver.webelement import WebElement @@ -23,8 +23,8 @@ class BaseSearchContext(object): """Used by each search context. Dummy find_element/s are for preventing pylint error""" - def find_element(self, by: str = None, value: Union[str, Dict] = None) -> 'WebElement': + def find_element(self, by: Optional[str] = None, value: Union[str, Dict] = None) -> 'WebElement': raise NotImplementedError - def find_elements(self, by: str = None, value: Union[str, Dict] = None) -> List['WebElement']: + def find_elements(self, by: Optional[str] = None, value: Union[str, Dict] = None) -> List['WebElement']: raise NotImplementedError diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index b4ab7e1f..93547402 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -146,7 +146,7 @@ class WebDriver( ): def __init__(self, command_executor: str = 'http://127.0.0.1:4444/wd/hub', - desired_capabilities: Dict = None, browser_profile: str = None, proxy: str = None, keep_alive: bool = True, direct_connection: bool = False): + desired_capabilities: Optional[Dict] = None, browser_profile: str = None, proxy: str = None, keep_alive: bool = True, direct_connection: bool = False): super(WebDriver, self).__init__( AppiumConnection(command_executor, keep_alive=keep_alive), @@ -205,7 +205,7 @@ def _update_command_executor(self, keep_alive: bool) -> None: self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) self._addCommands() - def start_session(self, capabilities: Dict, browser_profile: str = None) -> None: + def start_session(self, capabilities: Dict, browser_profile: Optional[str] = None) -> None: """Creates a new session with the desired capabilities. Override for Appium From 561d9f0baf6cdb658c019902b76b4ea458a4147c Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 00:59:23 +0900 Subject: [PATCH 19/26] Review comments --- .../webdriver/extensions/images_comparison.py | 25 +++++++++++-------- appium/webdriver/extensions/session.py | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/appium/webdriver/extensions/images_comparison.py b/appium/webdriver/extensions/images_comparison.py index 7ed538d8..77289fe8 100644 --- a/appium/webdriver/extensions/images_comparison.py +++ b/appium/webdriver/extensions/images_comparison.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, Tuple +from typing import Any, Dict, Union from selenium import webdriver @@ -76,7 +76,7 @@ def match_images_features(self, base64_image1: bytes, base64_image2: bytes, **op return self.execute(Command.COMPARE_IMAGES, options)['value'] def find_image_occurrence(self, base64_full_image: bytes, base64_partial_image: bytes, - **opts: Any) -> Tuple[bytes, Dict[str, int]]: + **opts: Any) -> Dict[str, Union[bytes, Dict]]: """Performs images matching by template to find possible occurrence of the partial image in the full image. @@ -94,10 +94,11 @@ def find_image_occurrence(self, base64_full_image: bytes, base64_partial_image: False by default Returns: - visualization (bytes): base64-encoded content of PNG visualization of the current comparison - operation. This entry is only present if `visualize` option is enabled - rect (dict): The region of the partial image occurrence on the full image. - The rect is represented by a dictionary with 'x', 'y', 'width' and 'height' keys + The dictionary containing the following entries: + visualization (bytes): base64-encoded content of PNG visualization of the current comparison + operation. This entry is only present if `visualize` option is enabled + rect (dict): The region of the partial image occurrence on the full image. + The rect is represented by a dictionary with 'x', 'y', 'width' and 'height' keys """ options = { 'mode': 'matchTemplate', @@ -107,7 +108,8 @@ def find_image_occurrence(self, base64_full_image: bytes, base64_partial_image: } return self.execute(Command.COMPARE_IMAGES, options)['value'] - def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Tuple[bytes, float]: + def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, + **opts: Any) -> Dict[str, Union[bytes, Dict]]: """Performs images matching to calculate the similarity score between them. The flow there is similar to the one used in @@ -123,10 +125,11 @@ def get_images_similarity(self, base64_image1: bytes, base64_image2: bytes, **op False by default Returns: - visualization (bytes): base64-encoded content of PNG visualization of the current comparison - operation. This entry is only present if `visualize` option is enabled - score (float): The similarity score as a float number in range [0.0, 1.0]. - 1.0 is the highest score (means both images are totally equal). + The dictionary containing the following entries: + visualization (bytes): base64-encoded content of PNG visualization of the current comparison + operation. This entry is only present if `visualize` option is enabled + score (float): The similarity score as a float number in range [0.0, 1.0]. + 1.0 is the highest score (means both images are totally equal). """ options = { 'mode': 'getSimilarity', diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index 463f2f46..a8c097a6 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -47,7 +47,7 @@ def all_sessions(self) -> List[Dict[str, Any]]: return self.execute(Command.GET_ALL_SESSIONS)['value'] @property - def events(self) -> Any: + def events(self) -> Dict: """ Retrieves events information from the current session Usage: From 91a26a425f61929f30d587c6a46307e80125db61 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 01:00:41 +0900 Subject: [PATCH 20/26] tweak --- appium/webdriver/common/touch_action.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 59b702db..a8fb299d 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -29,8 +29,8 @@ from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement as WebElement - from appium.webdriver.webdriver import WebDriver as WebDriver + from appium.webdriver.webelement import WebElement + from appium.webdriver.webdriver import WebDriver T = TypeVar('T', bound='TouchAction') From ad0fe093e8bd71f110bd46ae08e58501f658157d Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 01:06:58 +0900 Subject: [PATCH 21/26] Restore and modify changes --- appium/webdriver/webelement.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index e970e0ce..a746eec0 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -56,6 +56,9 @@ def get_attribute(self, name: str) -> Optional[str]: if attributeValue is None: return None + if not isinstance(attributeValue, str): + attributeValue = str(attributeValue) + if name != 'value' and attributeValue.lower() in ('true', 'false'): return attributeValue.lower() From a09eba8af8f8c18f059fc507042e3d79175ccdee Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 01:19:25 +0900 Subject: [PATCH 22/26] Fix wrong return type --- appium/webdriver/extensions/remote_fs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index c3251cf9..eabc4078 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -24,28 +24,28 @@ class RemoteFS(webdriver.Remote): - def pull_file(self, path: str) -> bytes: + def pull_file(self, path: str) -> str: """Retrieves the file at `path`. Args: path (str): the path to the file on the device Returns: - bytes: The file's contents as base64. + str: The file's contents as encoded as Base64. """ data = { 'path': path, } return self.execute(Command.PULL_FILE, data)['value'] - def pull_folder(self, path: str) -> bytes: + def pull_folder(self, path: str) -> str: """Retrieves a folder at `path`. Args: path (str): the path to the folder on the device Returns: - bytes: The folder's contents zipped and encoded as Base64. + str: The folder's contents zipped and encoded as Base64. """ data = { 'path': path, From e77e67e39cf4f331dbdaca09005ccad2ece123dd Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 01:40:50 +0900 Subject: [PATCH 23/26] Add comments --- appium/webdriver/extensions/remote_fs.py | 2 +- appium/webdriver/webelement.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index eabc4078..dd256a7a 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -31,7 +31,7 @@ def pull_file(self, path: str) -> str: path (str): the path to the file on the device Returns: - str: The file's contents as encoded as Base64. + str: The file's contents encoded as Base64. """ data = { 'path': path, diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index a746eec0..01a35c4a 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -56,6 +56,7 @@ def get_attribute(self, name: str) -> Optional[str]: if attributeValue is None: return None + # Convert to str along to the spec if not isinstance(attributeValue, str): attributeValue = str(attributeValue) From edecdd708c522416dfbe6f250ec0f41369dce368 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 23:38:11 +0900 Subject: [PATCH 24/26] Revert unexpected changes --- appium/webdriver/webdriver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 93547402..991ba227 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -258,7 +258,7 @@ def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} - def find_element(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: + def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: """'Private' method used by the find_element_by_* methods. Override for Appium From a7c2b434f74c164ee20f8cba08c7b25ab9ad9b53 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 23:44:14 +0900 Subject: [PATCH 25/26] Fix mypy error --- .../extensions/search_context/base_search_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 08ccc6ee..404aa62a 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -23,8 +23,8 @@ class BaseSearchContext(object): """Used by each search context. Dummy find_element/s are for preventing pylint error""" - def find_element(self, by: Optional[str] = None, value: Union[str, Dict] = None) -> 'WebElement': + def find_element(self, by: str, value: Union[str, Dict] = None) -> 'WebElement': raise NotImplementedError - def find_elements(self, by: Optional[str] = None, value: Union[str, Dict] = None) -> List['WebElement']: + def find_elements(self, by: str, value: Union[str, Dict] = None) -> List['WebElement']: raise NotImplementedError From 47f51ab7475b72394b1fd2ad99c4647e0a263cc6 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Thu, 23 Jan 2020 23:53:30 +0900 Subject: [PATCH 26/26] updates --- appium/webdriver/webdriver.py | 2 +- appium/webdriver/webelement.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 991ba227..13e9619f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -289,7 +289,7 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> Mobil 'using': by, 'value': value})['value'] - def find_elements(self, by: Optional[str] = By.ID, value: Union[str, Dict] + def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> Union[List[MobileWebElement], List]: """'Private' method used by the find_elements_by_* methods. diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 01a35c4a..ce9e8f80 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -72,7 +72,7 @@ def is_displayed(self) -> bool: """ return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> T: + def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> T: """Find an element given a By strategy and locator Override for Appium @@ -106,7 +106,7 @@ def find_element(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - def find_elements(self, by: Optional[str] = By.ID, value: Union[str, Dict] = None) -> List[T]: + def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> List[T]: """Find elements given a By strategy and locator Override for Appium