From 11f2ba1be23e4628fb1bcd46fe24fe65a8d7ae08 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 2 Aug 2025 13:54:34 +0900 Subject: [PATCH 1/2] chore: fix some mypy --- appium/webdriver/extensions/android/activities.py | 4 +++- appium/webdriver/webdriver.py | 15 +++++++++++++-- appium/webdriver/webelement.py | 12 ++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 807b6a1f..76da521b 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -50,7 +50,9 @@ def wait_activity(self, activity: str, timeout: int, interval: int = 1) -> bool: `True` if the target activity is shown """ try: - WebDriverWait(self, timeout, interval).until(lambda d: d.current_activity == activity) + WebDriverWait(self, timeout, interval).until( # type: ignore[type-var] + lambda d: d.current_activity == activity + ) return True except TimeoutException: return False diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index e5459335..ad63cc4c 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Union from selenium import webdriver from selenium.common.exceptions import ( @@ -250,12 +250,15 @@ def __init__( ) super().__init__( command_executor=command_executor, - options=options, + options=options, # type: ignore[arg-type] locator_converter=AppiumLocatorConverter(), web_element_cls=MobileWebElement, client_config=client_config, ) + # to explicitly set type after the initialization + self.command_executor: RemoteConnection + self._add_commands() self.error_handler = MobileErrorHandler() @@ -286,6 +289,14 @@ def __init__( method, url_cmd = instance.add_command() self.command_executor.add_command(method_name, method.upper(), url_cmd) + if TYPE_CHECKING: + + def find_element(self, by: str, value: Union[str, Dict, None] = None) -> 'MobileWebElement': # type: ignore[override] + ... + + def find_elements(self, by: str, value: Union[str, Dict, None] = None) -> List['MobileWebElement']: # type: ignore[override] + ... + def delete_extensions(self) -> None: """Delete extensions added in the class with 'setattr'""" for extension in self._extensions: diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 3a3e8e56..a8b1439d 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Callable, Dict, Optional, Union +from typing import TYPE_CHECKING, Callable, Dict, Optional, Union from selenium.webdriver.common.utils import keys_to_typing from selenium.webdriver.remote.command import Command as RemoteCommand @@ -26,6 +26,14 @@ class WebElement(SeleniumWebElement): _execute: Callable _id: str + if TYPE_CHECKING: + + def find_element(self, by: str, value: Union[str, Dict, None] = None) -> Self: # type: ignore[override] + ... + + def find_elements(self, by: str, value: Union[str, Dict, None] = None) -> Self: # type: ignore[override] + ... + def get_attribute(self, name: str) -> Optional[Union[str, Dict]]: # type: ignore[override] """Gets the given attribute or property of the element. @@ -108,7 +116,7 @@ def location_in_view(self) -> Dict[str, int]: return self._execute(Command.LOCATION_IN_VIEW)['value'] # Override - def send_keys(self, *value: str) -> Self: + def send_keys(self, *value: str) -> Self: # type: ignore[override] """Simulates typing into the element. Args: From 739e52b54779ff7f98695ff4c3e836f63b80c9d3 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 2 Aug 2025 14:32:08 +0900 Subject: [PATCH 2/2] fix type more --- appium/webdriver/common/appiumby.py | 1 + .../flutter_integration/flutter_commands.py | 2 +- .../flutter_integration/flutter_finder.py | 12 ++++++------ appium/webdriver/webdriver.py | 15 ++------------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/appium/webdriver/common/appiumby.py b/appium/webdriver/common/appiumby.py index 14526899..371573ab 100644 --- a/appium/webdriver/common/appiumby.py +++ b/appium/webdriver/common/appiumby.py @@ -49,5 +49,6 @@ class AppiumBy(By): '-flutter semantics label', '-flutter type', '-flutter key', + '-flutter text', '-flutter text containing', ] diff --git a/appium/webdriver/extensions/flutter_integration/flutter_commands.py b/appium/webdriver/extensions/flutter_integration/flutter_commands.py index fd4d4968..5e469cae 100644 --- a/appium/webdriver/extensions/flutter_integration/flutter_commands.py +++ b/appium/webdriver/extensions/flutter_integration/flutter_commands.py @@ -290,7 +290,7 @@ def execute_flutter_command(self, scriptName: str, params: dict) -> Any: """ return self.driver.execute_script(f'flutter: {scriptName}', params) - def __get_locator_options(self, locator: Union[WebElement, 'FlutterFinder']) -> Dict[str, dict]: + def __get_locator_options(self, locator: Union[WebElement, 'FlutterFinder']) -> Dict[str, Union[dict, WebElement]]: if isinstance(locator, WebElement): return {'element': locator} return {'locator': locator.to_dict()} diff --git a/appium/webdriver/extensions/flutter_integration/flutter_finder.py b/appium/webdriver/extensions/flutter_integration/flutter_finder.py index 2e5db4ec..0aed46a9 100644 --- a/appium/webdriver/extensions/flutter_integration/flutter_finder.py +++ b/appium/webdriver/extensions/flutter_integration/flutter_finder.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Tuple, Union +from typing import Tuple, Union, cast from selenium.webdriver.common.by import ByType as SeleniumByType @@ -30,23 +30,23 @@ def __init__(self, using: Union[SeleniumByType, AppiumByType], value: str) -> No @staticmethod def by_key(value: str) -> 'FlutterFinder': - return FlutterFinder(AppiumBy.FLUTTER_INTEGRATION_KEY, value) + return FlutterFinder(cast(AppiumByType, AppiumBy.FLUTTER_INTEGRATION_KEY), value) @staticmethod def by_text(value: str) -> 'FlutterFinder': - return FlutterFinder(AppiumBy.FLUTTER_INTEGRATION_TEXT, value) + return FlutterFinder(cast(AppiumByType, AppiumBy.FLUTTER_INTEGRATION_TEXT), value) @staticmethod def by_semantics_label(value: str) -> 'FlutterFinder': - return FlutterFinder(AppiumBy.FLUTTER_INTEGRATION_SEMANTICS_LABEL, value) + return FlutterFinder(cast(AppiumByType, AppiumBy.FLUTTER_INTEGRATION_SEMANTICS_LABEL), value) @staticmethod def by_type(value: str) -> 'FlutterFinder': - return FlutterFinder(AppiumBy.FLUTTER_INTEGRATION_TYPE, value) + return FlutterFinder(cast(AppiumByType, AppiumBy.FLUTTER_INTEGRATION_TYPE), value) @staticmethod def by_text_containing(value: str) -> 'FlutterFinder': - return FlutterFinder(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, value) + return FlutterFinder(cast(AppiumByType, AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING), value) def to_dict(self) -> dict: return {'using': self.using, 'value': self.value} diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index ad63cc4c..06c61f44 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union from selenium import webdriver from selenium.common.exceptions import ( @@ -21,14 +21,12 @@ UnknownMethodException, WebDriverException, ) -from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand from selenium.webdriver.remote.remote_connection import RemoteConnection from typing_extensions import Self from appium.common.logger import logger from appium.options.common.base import AppiumOptions -from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection from .client_config import AppiumClientConfig @@ -241,7 +239,7 @@ class WebDriver( def __init__( self, command_executor: Union[str, AppiumConnection] = 'http://127.0.0.1:4723', - extensions: Optional[List['WebDriver']] = None, + extensions: Optional[List[Type['ExtensionBase']]] = None, options: Union[AppiumOptions, List[AppiumOptions], None] = None, client_config: Optional[AppiumClientConfig] = None, ): @@ -266,15 +264,6 @@ def __init__( if client_config and client_config.direct_connection: self._update_command_executor(keep_alive=client_config.keep_alive) - # add new method to the `find_by_*` pantheon - By.IOS_PREDICATE = AppiumBy.IOS_PREDICATE - By.IOS_CLASS_CHAIN = AppiumBy.IOS_CLASS_CHAIN - By.ANDROID_UIAUTOMATOR = AppiumBy.ANDROID_UIAUTOMATOR - By.ANDROID_VIEWTAG = AppiumBy.ANDROID_VIEWTAG - By.ACCESSIBILITY_ID = AppiumBy.ACCESSIBILITY_ID - By.IMAGE = AppiumBy.IMAGE - By.CUSTOM = AppiumBy.CUSTOM - self._absent_extensions: Set[str] = set() self._extensions = extensions or []