From 4ab9f7bfce64dc6aa91e1dde52e4e78e9a2728d6 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 26 Jun 2022 08:49:01 +0200 Subject: [PATCH 1/4] chore: Improve autocompletion for methods returning self instance --- appium/webdriver/extensions/action_helpers.py | 26 +++++++------- .../extensions/android/activities.py | 9 +++-- appium/webdriver/extensions/android/common.py | 9 +++-- appium/webdriver/extensions/android/gsm.py | 17 +++++----- .../webdriver/extensions/android/network.py | 13 ++++--- appium/webdriver/extensions/android/power.py | 13 ++++--- appium/webdriver/extensions/android/sms.py | 9 +++-- appium/webdriver/extensions/applications.py | 34 +++++++++---------- appium/webdriver/extensions/clipboard.py | 28 +++++++++------ .../extensions/execute_mobile_command.py | 9 +++-- appium/webdriver/extensions/hw_actions.py | 25 +++++++------- appium/webdriver/extensions/ime.py | 14 ++++---- appium/webdriver/extensions/keyboard.py | 25 ++++++-------- appium/webdriver/extensions/location.py | 16 ++++----- appium/webdriver/extensions/log_event.py | 9 +++-- appium/webdriver/extensions/remote_fs.py | 11 +++--- appium/webdriver/extensions/settings.py | 7 ++-- appium/webdriver/switch_to.py | 17 +++++++--- appium/webdriver/webdriver.py | 7 ++-- 19 files changed, 148 insertions(+), 150 deletions(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 64faa2055..84d730aef 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 TYPE_CHECKING, List, Optional, Tuple +from typing import List, Optional, Tuple, TypeVar from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.actions import interaction @@ -20,14 +20,14 @@ from selenium.webdriver.common.actions.mouse_button import MouseButton from selenium.webdriver.common.actions.pointer_input import PointerInput +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.webelement import WebElement -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class ActionHelpers: - def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> 'WebDriver': + def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> T: """Scrolls from one element to another Args: @@ -68,9 +68,9 @@ def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Op actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': + def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: """Drag the origin element to the destination element Args: @@ -86,9 +86,9 @@ def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'W actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> 'WebDriver': + def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> T: """Taps on an particular place with up to five fingers, holding for a certain time @@ -136,9 +136,9 @@ def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) new_input.create_pause(0.1) new_input.create_pointer_up(MouseButton.LEFT) actions.perform() - return self # type: ignore + return self - def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> 'WebDriver': + 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: @@ -162,9 +162,9 @@ def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: in actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDriver': + 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: @@ -186,4 +186,4 @@ def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDrive actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index ae25178bf..5a9f2cc95 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait @@ -20,12 +20,11 @@ from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Activities(CanExecuteCommands): - def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'WebDriver': + def start_activity(self: T, 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. @@ -58,7 +57,7 @@ def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'W if key in opts: data[value] = opts[key] self.execute(Command.START_ACTIVITY, data) - return self # type: ignore + return self @property def current_activity(self) -> str: diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index a6eeabf5c..b121d339f 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any +from typing import Any, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Common(CanExecuteCommands): @@ -41,14 +40,14 @@ def end_test_coverage(self, intent: str, path: str) -> Any: # TODO Check return } return self.execute(Command.END_TEST_COVERAGE, data)['value'] - def open_notifications(self) -> 'WebDriver': + def open_notifications(self: T) -> T: """Open notification shade in Android (API Level 18 and above) Returns: Union['WebDriver', 'Common']: Self instance """ self.execute(Command.OPEN_NOTIFICATIONS, {}) - return self # type: ignore + return self @property def current_package(self) -> str: diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index 1df33ac7a..0dae0b4ba 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class GsmCallActions: @@ -49,7 +48,7 @@ class GsmVoiceState: class Gsm(CanExecuteCommands): - def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': + def make_gsm_call(self: T, phone_number: str, action: str) -> T: """Make GSM call (Emulator only) Android only. @@ -72,9 +71,9 @@ def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': f'(e.g. {GsmCallActions.__name__}.CALL)' ) self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) - return self # type: ignore + return self - def set_gsm_signal(self, strength: int) -> 'WebDriver': + def set_gsm_signal(self: T, strength: int) -> T: """Set GSM signal strength (Emulator only) Android only. @@ -96,9 +95,9 @@ def set_gsm_signal(self, strength: int) -> 'WebDriver': f'(e.g. {GsmSignalStrength.__name__}.GOOD)' ) self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) - return self # type: ignore + return self - def set_gsm_voice(self, state: str) -> 'WebDriver': + def set_gsm_voice(self: T, state: str) -> T: """Set GSM voice state (Emulator only) Android only. @@ -120,7 +119,7 @@ def set_gsm_voice(self, state: str) -> 'WebDriver': f'(e.g. {GsmVoiceState.__name__}.HOME)' ) self.execute(Command.SET_GSM_VOICE, {'state': state}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 70a32802d..46bfce8b0 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class NetSpeed: @@ -75,16 +74,16 @@ def set_network_connection(self, connection_type: int) -> int: data = {'parameters': {'type': connection_type}} return self.execute(Command.SET_NETWORK_CONNECTION, data)['value'] - def toggle_wifi(self) -> 'WebDriver': + def toggle_wifi(self: T) -> T: """Toggle the wifi on the device, Android only. Returns: Union['WebDriver', 'Network']: Self instance """ self.execute(Command.TOGGLE_WIFI, {}) - return self # type: ignore + return self - def set_network_speed(self, speed_type: str) -> 'WebDriver': + def set_network_speed(self: T, speed_type: str) -> T: """Set the network speed emulation. Android Emulator only. @@ -107,7 +106,7 @@ def set_network_speed(self, speed_type: str) -> 'WebDriver': ) self.execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index a74d5e82f..c3d762f8d 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -12,20 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Power(CanExecuteCommands): AC_OFF, AC_ON = 'off', 'on' - def set_power_capacity(self, percent: int) -> 'WebDriver': + def set_power_capacity(self: T, percent: int) -> T: """Emulate power capacity change on the connected emulator. Android only. @@ -40,9 +39,9 @@ def set_power_capacity(self, percent: int) -> 'WebDriver': Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_CAPACITY, {'percent': percent}) - return self # type: ignore + return self - def set_power_ac(self, ac_state: str) -> 'WebDriver': + def set_power_ac(self: T, ac_state: str) -> T: """Emulate power state change on the connected emulator. Android only. @@ -58,7 +57,7 @@ def set_power_ac(self, ac_state: str) -> 'WebDriver': Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_AC, {'state': ac_state}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/sms.py b/appium/webdriver/extensions/android/sms.py index 0fc10f398..013494dd1 100644 --- a/appium/webdriver/extensions/android/sms.py +++ b/appium/webdriver/extensions/android/sms.py @@ -12,17 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Sms(CanExecuteCommands): - def send_sms(self, phone_number: str, message: str) -> 'WebDriver': + def send_sms(self: T, phone_number: str, message: str) -> T: """Emulate send SMS event on the connected emulator. Android only. @@ -38,7 +37,7 @@ def send_sms(self, phone_number: str, message: str) -> 'WebDriver': Union['WebDriver', 'Sms']: Self instance """ self.execute(Command.SEND_SMS, {'phoneNumber': phone_number, 'message': message}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index 537de6b62..cbd5055b1 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import warnings -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Applications(CanExecuteCommands): - def background_app(self, seconds: int) -> 'WebDriver': + def background_app(self: T, seconds: int) -> T: """Puts the application in the background on the device for a certain duration. Args: @@ -37,7 +35,7 @@ def background_app(self, seconds: int) -> 'WebDriver': 'seconds': seconds, } self.execute(Command.BACKGROUND, data) - return self # type: ignore + return self def is_app_installed(self, bundle_id: str) -> bool: """Checks whether the application specified by `bundle_id` is installed on the device. @@ -53,7 +51,7 @@ def is_app_installed(self, bundle_id: str) -> bool: } return self.execute(Command.IS_APP_INSTALLED, data)['value'] - def install_app(self, app_path: str, **options: Any) -> 'WebDriver': + def install_app(self: T, app_path: str, **options: Any) -> T: """Install the application found at `app_path` on the device. Args: @@ -79,9 +77,9 @@ def install_app(self, app_path: str, **options: Any) -> 'WebDriver': if options: data.update({'options': options}) self.execute(Command.INSTALL_APP, data) - return self # type: ignore + return self - def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': + def remove_app(self: T, app_id: str, **options: Any) -> T: """Remove the specified application from the device. Args: @@ -102,9 +100,9 @@ def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': if options: data.update({'options': options}) self.execute(Command.REMOVE_APP, data) - return self # type: ignore + return self - def launch_app(self) -> 'WebDriver': + def launch_app(self: T) -> T: """Start on the device the application specified in the desired capabilities. deprecated:: 2.0.0 @@ -118,9 +116,9 @@ def launch_app(self) -> 'WebDriver': ) self.execute(Command.LAUNCH_APP) - return self # type: ignore + return self - def close_app(self) -> 'WebDriver': + def close_app(self: T) -> T: """Stop the running application, specified in the desired capabilities, on the device. deprecated:: 2.0.0 @@ -135,7 +133,7 @@ def close_app(self) -> 'WebDriver': ) self.execute(Command.CLOSE_APP) - return self # type: ignore + return self def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. @@ -157,7 +155,7 @@ def terminate_app(self, app_id: str, **options: Any) -> bool: data.update({'options': options}) return self.execute(Command.TERMINATE_APP, data)['value'] - def activate_app(self, app_id: str) -> 'WebDriver': + def activate_app(self: T, app_id: str) -> T: """Activates the application if it is not running or is running in the background. @@ -171,7 +169,7 @@ def activate_app(self, app_id: str) -> 'WebDriver': 'appId': app_id, } self.execute(Command.ACTIVATE_APP, data) - return self # type: ignore + return self def query_app_state(self, app_id: str) -> int: """Queries the state of the application. @@ -206,7 +204,7 @@ def app_strings(self, language: str = None, string_file: str = None) -> Dict[str data['stringFile'] = string_file return self.execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self) -> 'WebDriver': + def reset(self: T) -> T: """Resets the current application on the device. deprecated:: 2.0.0 @@ -220,7 +218,7 @@ def reset(self) -> 'WebDriver': ) self.execute(Command.RESET) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 587f5316d..30d76f8b3 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,22 +13,31 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar +from appium.protocols.protocol import Protocol from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) + + +class CanSetClipboard(Protocol): + def set_clipboard( + self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None + ) -> T: + ... + + +TS = TypeVar('TS', bound=CanSetClipboard) class Clipboard(CanExecuteCommands): def set_clipboard( - self, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None - ) -> 'WebDriver': + self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None + ) -> T: """Set the content of the system clipboard Args: @@ -47,9 +56,9 @@ def set_clipboard( if label: options['label'] = label self.execute(Command.SET_CLIPBOARD, options) - return self # type: ignore + return self - def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriver': + def set_clipboard_text(self: TS, text: str, label: Optional[str] = None) -> TS: """Copies the given text to the system clipboard Args: @@ -60,8 +69,7 @@ def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriv Union['WebDriver', 'Clipboard']: Self instance """ - self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) - return self # type: ignore + return self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) def get_clipboard(self, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: """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 4152e471b..2518db4c7 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,16 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteScripts) class ExecuteMobileCommand(CanExecuteScripts): - def press_button(self, button_name: str) -> 'WebDriver': + def press_button(self: T, button_name: str) -> T: """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -37,7 +36,7 @@ def press_button(self, button_name: str) -> 'WebDriver': """ data = {'name': button_name} self.execute_script('mobile: pressButton', data) - return self # type: ignore + return self @property def battery_info(self) -> Dict[str, Any]: diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index 6cfb68888..75ca14821 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,18 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Optional +from typing import Any, Optional, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class HardwareActions(CanExecuteCommands): - def lock(self, seconds: Optional[int] = None) -> 'WebDriver': + def lock(self: T, seconds: Optional[int] = None) -> T: """Lock the device. No changes are made if the device is already unlocked. Args: @@ -39,16 +38,16 @@ def lock(self, seconds: Optional[int] = None) -> 'WebDriver': self.execute(Command.LOCK) else: self.execute(Command.LOCK, {'seconds': seconds}) - return self # type: ignore + return self - def unlock(self) -> 'WebDriver': + def unlock(self: T) -> T: """Unlock the device. No changes are made if the device is already locked. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.UNLOCK) - return self # type: ignore + return self def is_locked(self) -> bool: """Checks whether the device is locked. @@ -58,16 +57,16 @@ def is_locked(self) -> bool: """ return self.execute(Command.IS_LOCKED)['value'] - def shake(self) -> 'WebDriver': + def shake(self: T) -> T: """Shake the device. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.SHAKE) - return self # type: ignore + return self - def touch_id(self, match: bool) -> 'WebDriver': + def touch_id(self: T, match: bool) -> T: """Simulate touchId on iOS Simulator Args: @@ -78,16 +77,16 @@ def touch_id(self, match: bool) -> 'WebDriver': """ data = {'match': match} self.execute(Command.TOUCH_ID, data) - return self # type: ignore + return self - def toggle_touch_id_enrollment(self) -> 'WebDriver': + def toggle_touch_id_enrollment(self: T) -> T: """Toggle enroll touchId on iOS Simulator Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.TOGGLE_TOUCH_ID_ENROLLMENT) - return self # type: ignore + return self def finger_print(self, finger_id: int) -> Any: """Authenticate users by using their finger print scans on supported Android emulators. diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index c18376e71..24f1c6767 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -12,15 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List +from typing import List, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class IME(CanExecuteCommands): @@ -45,7 +43,7 @@ def is_ime_active(self) -> bool: """ return self.execute(Command.IS_IME_ACTIVE, {})['value'] # pylint: disable=unsubscriptable-object - def activate_ime_engine(self, engine: str) -> 'WebDriver': + def activate_ime_engine(self: T, engine: str) -> T: """Activates the given IME engine on the device. Android only. @@ -59,9 +57,9 @@ def activate_ime_engine(self, engine: str) -> 'WebDriver': """ data = {'engine': engine} self.execute(Command.ACTIVATE_IME_ENGINE, data) - return self # type: ignore + return self - def deactivate_ime_engine(self) -> 'WebDriver': + def deactivate_ime_engine(self: T) -> T: """Deactivates the currently active IME engine on the device. Android only. @@ -70,7 +68,7 @@ def deactivate_ime_engine(self) -> 'WebDriver': Union['WebDriver', 'IME']: Self instance """ self.execute(Command.DEACTIVATE_IME_ENGINE, {}) - return self # type: ignore + return self @property def active_ime_engine(self) -> str: diff --git a/appium/webdriver/extensions/keyboard.py b/appium/webdriver/extensions/keyboard.py index 07a323ba2..9e7491a74 100644 --- a/appium/webdriver/extensions/keyboard.py +++ b/appium/webdriver/extensions/keyboard.py @@ -12,20 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Optional +from typing import Dict, Optional, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Keyboard(CanExecuteCommands): def hide_keyboard( - self, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None - ) -> 'WebDriver': + self: T, 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 @@ -48,7 +47,7 @@ def hide_keyboard( strategy = 'tapOutside' data['strategy'] = strategy self.execute(Command.HIDE_KEYBOARD, data) - return self # type: ignore + return self def is_keyboard_shown(self) -> bool: """Attempts to detect whether a software keyboard is present @@ -58,7 +57,7 @@ def is_keyboard_shown(self) -> bool: """ return self.execute(Command.IS_KEYBOARD_SHOWN)['value'] - def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver': + def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. @@ -77,9 +76,9 @@ def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver' if metastate is not None: data['metastate'] = metastate self.execute(Command.KEY_EVENT, data) - return self # type: ignore + return self - def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> 'WebDriver': + def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. Possible keycodes can be found @@ -101,11 +100,9 @@ def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Op if flags is not None: data['flags'] = flags self.execute(Command.PRESS_KEYCODE, data) - return self # type: ignore + return self - def long_press_keycode( - self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None - ) -> 'WebDriver': + def long_press_keycode(self: T, 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 @@ -125,7 +122,7 @@ def long_press_keycode( if flags is not None: data['flags'] = flags self.execute(Command.LONG_PRESS_KEYCODE, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 0db48300c..aff5390cf 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Union +from typing import Dict, TypeVar, Union from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Location(CanExecuteCommands): - def toggle_location_services(self) -> 'WebDriver': + def toggle_location_services(self: T) -> T: """Toggle the location services on the device. Android only. @@ -33,16 +31,16 @@ def toggle_location_services(self) -> 'WebDriver': Union['WebDriver', 'Location']: Self instance """ self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) - return self # type: ignore + return self def set_location( - self, + self: T, latitude: Union[float, str], longitude: Union[float, str], altitude: Union[float, str, None] = None, speed: Union[float, str, None] = None, satellites: Union[float, str, None] = None, - ) -> 'WebDriver': + ) -> T: """Set the location of the device Args: @@ -68,7 +66,7 @@ def set_location( if satellites is not None: data['location']['satellites'] = satellites self.execute(Command.SET_LOCATION, data) - return self # type: ignore + return self @property def location(self) -> Dict[str, float]: diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index 50cdcb32d..028e4e51f 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, List, Union +from typing import Dict, List, TypeVar, Union from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class LogEvent(CanExecuteCommands): @@ -46,7 +45,7 @@ def get_events(self, type: List[str] = None) -> Dict[str, Union[str, int]]: data['type'] = type return self.execute(Command.GET_EVENTS, data)['value'] - def log_event(self, vendor: str, event: str) -> 'WebDriver': + def log_event(self: T, vendor: str, event: str) -> T: """Log a custom event on the Appium server. (Since Appium 1.16.0) @@ -62,7 +61,7 @@ def log_event(self, vendor: str, event: str) -> 'WebDriver': """ data = {'vendor': vendor, 'event': event} self.execute(Command.LOG_EVENT, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index 4d273c67f..9f65576df 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,7 +13,7 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar from selenium.common.exceptions import InvalidArgumentException @@ -21,8 +21,7 @@ from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class RemoteFS(CanExecuteCommands): @@ -55,8 +54,8 @@ def pull_folder(self, path: str) -> str: return self.execute(Command.PULL_FOLDER, data)['value'] def push_file( - self, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None - ) -> 'WebDriver': + self: T, 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` @@ -87,7 +86,7 @@ def push_file( 'data': base64data, } self.execute(Command.PUSH_FILE, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index 5815ab7a8..3c3026f70 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Settings(CanExecuteCommands): @@ -34,7 +33,7 @@ def get_settings(self) -> Dict[str, Any]: """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self, settings: Dict[str, Any]) -> 'WebDriver': + def update_settings(self: T, 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/switch_to.py b/appium/webdriver/switch_to.py index 64b3b40c7..3dc48391f 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,22 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar from selenium.webdriver.remote.switch_to import SwitchTo +from appium.protocols.protocol import Protocol from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from .mobilecommand import MobileCommand -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver + +class HasDriver(Protocol): + # noinspection PyPropertyDefinition + @property + def _driver(self) -> CanExecuteCommands: + ... + + +T = TypeVar('T', bound=HasDriver) class MobileSwitchTo(SwitchTo): _driver: CanExecuteCommands - def context(self, context_name: Optional[str]) -> 'WebDriver': + def context(self: T, context_name: Optional[str]) -> T: """Sets the context for the current session. Passing `None` is equal to switching to native context. @@ -38,5 +46,4 @@ def context(self, context_name: Optional[str]) -> 'WebDriver': driver.switch_to.context('WEBVIEW_1') """ self._driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {'name': context_name}) - # noinspection PyTypeChecker return self diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 5815069bd..c64465072 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -14,7 +14,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union from selenium import webdriver from selenium.common.exceptions import InvalidArgumentException, SessionNotCreatedException, WebDriverException @@ -24,6 +24,7 @@ from appium.common.logger import logger from appium.options.common.base import AppiumOptions +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection @@ -58,6 +59,8 @@ from .switch_to import MobileSwitchTo from .webelement import WebElement as MobileWebElement +T = TypeVar('T', bound=CanExecuteCommands) + class ExtensionBase: """ @@ -417,7 +420,7 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """ return MobileWebElement(self, element_id) - def set_value(self, element: MobileWebElement, value: str) -> 'WebDriver': + def set_value(self: T, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. Args: From adca29032954b7b2cbf0b5e2eaf69a794f5ed91a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 26 Jun 2022 10:06:23 +0200 Subject: [PATCH 2/4] simplify definitions --- appium/webdriver/extensions/clipboard.py | 1 - appium/webdriver/switch_to.py | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 30d76f8b3..d3f1fd826 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -68,7 +68,6 @@ def set_clipboard_text(self: TS, text: str, label: Optional[str] = None) -> TS: Returns: Union['WebDriver', 'Clipboard']: Self instance """ - return self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) def get_clipboard(self, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index 3dc48391f..e6d46ec82 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -23,18 +23,13 @@ class HasDriver(Protocol): - # noinspection PyPropertyDefinition - @property - def _driver(self) -> CanExecuteCommands: - ... + _driver: CanExecuteCommands T = TypeVar('T', bound=HasDriver) -class MobileSwitchTo(SwitchTo): - _driver: CanExecuteCommands - +class MobileSwitchTo(SwitchTo, HasDriver): def context(self: T, context_name: Optional[str]) -> T: """Sets the context for the current session. Passing `None` is equal to switching to native context. From 756bd98c9b677dbb0d1d752308e6cafef10585a5 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 26 Jun 2022 14:20:21 +0200 Subject: [PATCH 3/4] Options --- appium/options/common/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appium/options/common/base.py b/appium/options/common/base.py index 41d36d06d..cf8b61c7f 100644 --- a/appium/options/common/base.py +++ b/appium/options/common/base.py @@ -67,7 +67,7 @@ def __init__(self) -> None: # FIXME: https://github.com/SeleniumHQ/selenium/issues/10755 self._ignore_local_proxy = False - def set_capability(self, name: str, value: Any) -> T: + def set_capability(self: T, name: str, value: Any) -> T: w3c_name = name if name in self.W3C_CAPABILITY_NAMES or ':' in name else f'{APPIUM_PREFIX}{name}' if value is None: if w3c_name in self._caps: @@ -80,7 +80,7 @@ def get_capability(self, name: str) -> Any: """Fetches capability value or None if the capability is not set""" return self._caps[name] if name in self._caps else self._caps.get(f'{APPIUM_PREFIX}{name}') - def load_capabilities(self, caps: Dict[str, Any]) -> T: + def load_capabilities(self: T, caps: Dict[str, Any]) -> T: """Sets multiple capabilities""" for name, value in caps.items(): self.set_capability(name, value) From 2c4d8aeab45e74bde9ebd7f503df0a454bb8dd1f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 26 Jun 2022 14:25:30 +0200 Subject: [PATCH 4/4] simplify --- appium/webdriver/extensions/clipboard.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index d3f1fd826..4009a93c0 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -15,23 +15,12 @@ import base64 from typing import Optional, TypeVar -from appium.protocols.protocol import Protocol from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) - - -class CanSetClipboard(Protocol): - def set_clipboard( - self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None - ) -> T: - ... - - -TS = TypeVar('TS', bound=CanSetClipboard) +T = TypeVar('T', bound='Clipboard') class Clipboard(CanExecuteCommands): @@ -58,7 +47,7 @@ def set_clipboard( self.execute(Command.SET_CLIPBOARD, options) return self - def set_clipboard_text(self: TS, text: str, label: Optional[str] = None) -> TS: + def set_clipboard_text(self: T, text: str, label: Optional[str] = None) -> T: """Copies the given text to the system clipboard Args: