From e821a31a1856e3233d1280c43f6860a57d09ea8b Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Sat, 11 Jan 2020 16:24:50 +0900 Subject: [PATCH 01/12] Drop py2 support (#478) * Drop py2 support * Support 3.7+ --- .travis.yml | 6 +----- Pipfile | 5 ++--- setup.py | 7 ++----- tox.ini | 6 +----- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e51e993..84e0a624 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,8 @@ language: python dist: xenial python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - "3.7" -# - "3.8-dev" # TODO Remove comment-out when pylint and astroid upgraded to the latest and py2 support dropped + - "3.8" install: - pip install tox-travis diff --git a/Pipfile b/Pipfile index 793690c1..aca7dc7f 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,6 @@ httpretty = "~=0.9" python-dateutil = "~=2.8" mock = "~=3.0" -# TODO Update to the latest ver when py2 support dropped -pylint = "~=1.9" -astroid = "~=1.6" +pylint = "~=2.4" +astroid = "~=2.3" isort = "~=4.3" diff --git a/setup.py b/setup.py index 9912e7eb..774b06c2 100644 --- a/setup.py +++ b/setup.py @@ -37,13 +37,10 @@ packages=find_packages(include=['appium*']), license='Apache 2.0', classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Environment :: Console', 'Environment :: MacOS X', 'Environment :: Win32 (MS Windows)', diff --git a/tox.ini b/tox.ini index c059a95e..85ce3975 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,8 @@ [tox] skipsdist = True envlist = - py27, - py34, - py35, - py36, py37, -# py38 # TODO Remove comment-out when pylint and astroid upgraded to the latest and py2 support dropped + py38 [testenv] deps = From eebff65a027daf8fa0cb0cea8ce638a087b6da43 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Fri, 24 Jan 2020 00:24:05 +0900 Subject: [PATCH 02/12] Add explicit type declarations (#482) * Fixed mypy warning: touch_action.py * Fixed mypy warning: multi_action.py * Fixed mypy warning: extensions/android * Fixed mypy warning: extensions/search_context * Updated * Revert some changes to run unit test * Review comments * Updates * Updates * Add mypy check to ci.sh * Add mypy to Pipfile * Updates * Update README * Revert unexpected changes * Updates Dict * Revert unexpected changes * Updates * Review comments * Review comments * tweak * Restore and modify changes * Fix wrong return type * Add comments * Revert unexpected changes * Fix mypy error * updates --- Pipfile | 2 + README.md | 8 ++- appium/common/helper.py | 31 ++-------- appium/common/logger.py | 2 +- appium/saucetestcase.py | 13 ++-- appium/webdriver/appium_connection.py | 4 +- appium/webdriver/appium_service.py | 59 ++++++++++--------- appium/webdriver/common/multi_action.py | 18 ++++-- appium/webdriver/common/touch_action.py | 42 ++++++++----- appium/webdriver/errorhandler.py | 4 +- appium/webdriver/extensions/action_helpers.py | 15 +++-- .../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 | 12 ++-- appium/webdriver/extensions/android/power.py | 10 +++- appium/webdriver/extensions/android/sms.py | 8 ++- .../extensions/android/system_bars.py | 6 +- appium/webdriver/extensions/applications.py | 34 ++++++----- appium/webdriver/extensions/clipboard.py | 29 ++++++--- appium/webdriver/extensions/context.py | 10 ++-- appium/webdriver/extensions/device_time.py | 10 ++-- appium/webdriver/extensions/execute_driver.py | 17 +++--- .../extensions/execute_mobile_command.py | 8 ++- appium/webdriver/extensions/hw_actions.py | 22 ++++--- .../webdriver/extensions/images_comparison.py | 30 ++++++---- appium/webdriver/extensions/ime.py | 16 +++-- appium/webdriver/extensions/keyboard.py | 19 +++--- appium/webdriver/extensions/location.py | 15 +++-- appium/webdriver/extensions/log_event.py | 12 ++-- appium/webdriver/extensions/remote_fs.py | 22 ++++--- appium/webdriver/extensions/screen_record.py | 8 ++- .../extensions/search_context/android.py | 21 ++++--- .../search_context/base_search_context.py | 9 ++- .../extensions/search_context/custom.py | 9 ++- .../extensions/search_context/ios.py | 17 ++++-- .../extensions/search_context/mobile.py | 12 ++-- .../extensions/search_context/windows.py | 9 ++- appium/webdriver/extensions/session.py | 12 ++-- appium/webdriver/extensions/settings.py | 10 +++- appium/webdriver/switch_to.py | 7 ++- appium/webdriver/webdriver.py | 33 +++++++---- appium/webdriver/webelement.py | 27 ++++----- ci.sh | 7 +++ mypy.ini | 9 +++ test/functional/android/remote_fs_tests.py | 6 +- test/unit/webdriver/device/clipboard_test.py | 3 +- test/unit/webdriver/device/remote_fs_test.py | 5 +- 51 files changed, 482 insertions(+), 289 deletions(-) create mode 100644 mypy.ini 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" 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/common/helper.py b/appium/common/helper.py index b694b9fa..d34417fb 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -12,44 +12,25 @@ # 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 appium_bytes(value, encoding): - """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): +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(): +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 8192f504..a2e94aba 100644 --- a/appium/saucetestcase.py +++ b/appium/saucetestcase.py @@ -19,6 +19,7 @@ import os import sys import unittest +from typing import Any, Callable, List 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[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) @@ -40,16 +41,16 @@ def decorator(base_class): class SauceTestCase(unittest.TestCase): - def setUp(self): - self.desired_capabilities['name'] = self.id() + def setUp(self) -> None: + 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) - 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..f89998af 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 Any, 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[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/appium_service.py b/appium/webdriver/appium_service.py index 42527c24..7683782a 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 Any, List, Optional, TypeVar, Union import urllib3 @@ -27,7 +27,7 @@ STATUS_URL = '/wd/hub/status' -def find_executable(executable): +def find_executable(executable: str) -> Optional[str]: 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: 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): - self._process = None - self._cmd = None + def __init__(self) -> None: + self._process: Optional[sp.Popen] = None + self._cmd: Optional[List] = None - def _get_node(self): + 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): 'Make sure it is installed and present in PATH') return self._node_executable - def _get_npm(self): + 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: @@ -85,41 +88,41 @@ 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) -> Union[str, bytes]: 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) + self._main_script: Union[str, bytes] = 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 @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] return DEFAULT_HOST - def start(self, **kwargs): + def start(self, **kwargs: Any) -> sp.Popen: """Starts Appium service with given arguments. The service will be forcefully restarted if it is already running. @@ -153,31 +156,31 @@ 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 + 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) 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) + error_msg += '\nOriginal error: {}'.format(str(err_output)) self.stop() 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 @@ -188,14 +191,14 @@ def stop(self): """ is_terminated = False if self.is_running: - self._process.terminate() + self._process.terminate() # type: ignore is_terminated = True self._process = None self._cmd = None 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/common/multi_action.py b/appium/webdriver/common/multi_action.py index f80a111f..f3326534 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,17 +19,25 @@ # chaining as the spec requires. import copy +from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command +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): + def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: self._driver = driver self._element = element - self._touch_actions = [] + self._touch_actions: List['TouchAction'] = [] - def add(self, *touch_actions): + def add(self, *touch_actions: 'TouchAction') -> None: """Add TouchAction objects to the MultiAction, to be performed later. Args: @@ -49,7 +57,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 +76,7 @@ def perform(self): return self @property - def json_wire_gestures(self): + 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/common/touch_action.py b/appium/webdriver/common/touch_action.py index 84668e17..a8fb299d 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,16 +24,25 @@ # pylint: disable=no-self-use import copy +from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union from appium.webdriver.mobilecommand import MobileCommand as Command +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement + from appium.webdriver.webdriver import WebDriver + +T = TypeVar('T', bound='TouchAction') + class TouchAction(object): - def __init__(self, driver=None): + + def __init__(self, driver: Optional['WebDriver'] = None): self._driver = driver - self._actions = [] + self._actions: List = [] - def tap(self, element=None, x=None, y=None, count=1): + 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: @@ -50,7 +59,8 @@ 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: 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: @@ -67,7 +77,8 @@ 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: 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: @@ -83,7 +94,7 @@ def long_press(self, el=None, x=None, y=None, duration=1000): return self - def wait(self, ms=0): + def wait(self: T, ms: int = 0) -> T: """Pause for `ms` milliseconds. Args: @@ -101,7 +112,7 @@ def wait(self, ms=0): return self - def move_to(self, el=None, x=None, y=None): + 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: @@ -116,7 +127,7 @@ def move_to(self, el=None, x=None, y=None): return self - def release(self): + def release(self: T) -> T: """End the action by lifting the pointer off the screen Returns: @@ -126,12 +137,14 @@ def release(self): return self - def perform(self): + def perform(self: T) -> T: """Perform the action by sending the commands to the server to be operated upon Returns: `TouchAction`: self instance """ + if self._driver is None: + 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) @@ -141,23 +154,24 @@ 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, 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 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/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/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 108fcfd5..98ddbf67 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, Optional, 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='ActionHelpers') class ActionHelpers(webdriver.Remote): - def scroll(self, origin_el, destination_el, duration=None): + def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[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, 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, positions: List[Tuple], duration: Optional[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, 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, 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/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 236252c3..f80805a0 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='Activities') + 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 the target activity is shown """ 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..65da6733 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='Common') + 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) -> str: """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..df4ea95e 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='Gsm') + + 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..99c71763 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='Network') + 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) -> int: """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) -> int: """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: + int: Set network connection type """ 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..3d661d77 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) -> List[List[str]]: """Returns the information of the system state which is supported to read as like cpu, memory, network traffic, and battery. @@ -36,14 +38,14 @@ def get_performance_data(self, package_name, data_type, data_read_timeout=None): 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 = {'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..827324e8 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='Power') + 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/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 e89b097d..9ab8a295 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='Applications') + 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: @@ -130,14 +134,14 @@ def terminate_app(self, app_id, **options): Returns: bool: True if the app has been successfully terminated """ - data = { + data: Dict[str, Any] = { 'appId': app_id, } if 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 e05e6b86..e4f7bc71 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,25 +13,31 @@ # limitations under the License. import base64 +from typing import Any, Dict, Optional, TypeVar 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 +T = TypeVar('T', bound='Clipboard') + 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'), @@ -40,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(appium_bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) + 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: @@ -66,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: @@ -76,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/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..f6c00fb0 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, Dict, Optional, Union + 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 [];') @@ -43,11 +46,11 @@ def execute_driver(self, script, script_type='webdriverio', timeout_ms=None): 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 @@ -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/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 789dcfef..9866df7f 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,12 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +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): + def press_button(self, button_name: str) -> T: """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -38,7 +42,7 @@ def press_button(self, button_name): return self @property - def battery_info(self): + def battery_info(self) -> Dict[str, Any]: """Retrieves battery information for the device under test. Returns: diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index a3d6babb..c27c7382 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, Optional, 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: Optional[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..77289fe8 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, Union + 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[str, Any]: """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) -> Dict[str, Union[bytes, Dict]]: """Performs images matching by template to find possible occurrence of the partial image in the full image. @@ -91,10 +94,11 @@ def find_image_occurrence(self, base64_full_image, base64_partial_image, **opts) 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', @@ -104,7 +108,8 @@ 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) -> 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 @@ -120,10 +125,11 @@ def get_images_similarity(self, base64_image1, base64_image2, **opts): 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', @@ -135,6 +141,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..054cf44e 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,10 @@ 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 +59,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 +72,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..dd256a7a 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,50 +13,54 @@ # 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) -> 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 encoded as Base64. """ data = { 'path': path, } return self.execute(Command.PULL_FILE, data)['value'] - def pull_folder(self, path): + 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, } 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: 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` 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 +72,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 +87,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] = \ 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/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 7c6f127a..4197eb99 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -15,16 +15,21 @@ # pylint: disable=abstract-method import json +from typing import TYPE_CHECKING, List, Optional from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext +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=None, args=None, className=None): + def find_element_by_android_data_matcher( + 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). @@ -56,7 +61,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: 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). @@ -83,7 +89,8 @@ 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: Optional[str] = None, args: Optional[str] + = None, className: Optional[str] = None) -> str: result = {} for key, value in {'name': name, 'args': args, 'class': className}.items(): @@ -92,7 +99,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 +115,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 +131,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 +149,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..404aa62a 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -14,12 +14,17 @@ # pylint: disable=abstract-method +from typing import TYPE_CHECKING, Dict, List, Optional, Union + +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=None, value=None): + def find_element(self, by: str, value: Union[str, Dict] = None) -> 'WebElement': raise NotImplementedError - def find_elements(self, by=None, value=None): + def find_elements(self, by: str, 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..a790016b 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -14,15 +14,20 @@ # pylint: disable=abstract-method +from typing import TYPE_CHECKING, List + from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext +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): + def find_element_by_custom(self, selector: str) -> 'WebElement': """Finds an element in conjunction with a custom element finding plugin Args: @@ -42,7 +47,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..3066ad9e 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -14,15 +14,20 @@ # pylint: disable=abstract-method +from typing import TYPE_CHECKING, List + from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext +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): + def find_element_by_ios_uiautomation(self, uia_string: str) -> 'WebElement': """Finds an element by uiautomation in iOS. Args: @@ -39,7 +44,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 +60,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 +76,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 +92,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 +108,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..a58f4d23 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -15,16 +15,20 @@ # pylint: disable=abstract-method import base64 +from typing import TYPE_CHECKING, List from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext +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): + def find_element_by_accessibility_id(self, accessibility_id: str) -> 'WebElement': """Finds an element by accessibility id. Args: @@ -42,7 +46,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 +63,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 +81,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..692aebce 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -14,15 +14,20 @@ # pylint: disable=abstract-method +from typing import TYPE_CHECKING, List + from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext +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): + def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> 'WebElement': """Finds an element by windows uiautomation Args: @@ -39,7 +44,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: diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index 64d49370..a8c097a6 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 Any, 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[str, Any]: """ 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[str, Any]]: """ 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/extensions/settings.py b/appium/webdriver/extensions/settings.py index 2a0f4235..b5514621 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 Any, 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[str, Any]: """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[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 @@ -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/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 03e7e3fd..13e9619f 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 Any, Dict, List, Optional, 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[str, List[Dict[str, Any]]]: 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: 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), @@ -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: Optional[str] = None) -> None: """Creates a new session with the desired capabilities. Override for Appium @@ -218,9 +222,11 @@ def start_session(self, capabilities, browser_profile=None): raise InvalidArgumentException('Capabilities must be a dictionary') if browser_profile: if 'moz:firefoxOptions' in capabilities: - capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded + # encoded is defined in selenium's original codes + capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded # type: ignore else: - capabilities.update({'firefox_profile': browser_profile.encoded}) + # encoded is defined in selenium's original codes + capabilities.update({'firefox_profile': browser_profile.encoded}) # type: ignore parameters = self._merge_capabilities(capabilities) @@ -239,7 +245,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[str, Any]: """Manage capabilities whether W3C format or MJSONWP format """ if _FORCE_MJSONWP in capabilities: @@ -252,7 +258,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: """'Private' method used by the find_element_by_* methods. Override for Appium @@ -283,7 +289,8 @@ def find_element(self, by=By.ID, value=None): 'using': by, 'value': value})['value'] - def find_elements(self, by=By.ID, value=None): + 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 +324,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 +339,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, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. Args: @@ -351,7 +358,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 diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index bf8efc5b..ce9e8f80 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 @@ -58,22 +56,23 @@ def get_attribute(self, name): if attributeValue is None: return None + # Convert to str along to the spec if not isinstance(attributeValue, str): - attributeValue = unicode(attributeValue) + attributeValue = str(attributeValue) if name != 'value' and attributeValue.lower() in ('true', 'false'): return attributeValue.lower() 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: str = By.ID, value: Union[str, Dict] = None) -> T: """Find an element given a By strategy and locator Override for Appium @@ -107,7 +106,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: str = By.ID, value: Union[str, Dict] = None) -> List[T]: """Find elements given a By strategy and locator Override for Appium @@ -141,7 +140,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 +152,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 +175,7 @@ def set_text(self, keys=''): return self @property - def location_in_view(self): + def location_in_view(self) -> Dict[str, int]: """Gets the location of an element relative to the view. Usage: @@ -189,7 +188,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: 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 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 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/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 fbe9e11d8703127e9bd7b441d6d644911e03ebcf Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Sat, 25 Jan 2020 00:53:07 +0900 Subject: [PATCH 03/12] Add mypy to pre-commit (#485) --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c612326a..a8af654b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,3 +8,10 @@ hooks: - id: isort args: ["-rc", "."] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.761' + hooks: + - id: mypy + files: ^appium/ + entry: mypy appium/ + pass_filenames: false From 69396f9b74675283aba796048f03ab5ac1a402f2 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Sun, 26 Jan 2020 00:03:21 +0900 Subject: [PATCH 04/12] chore: Applied some py3 formats (#486) * Removed unused import * Removed unnecessary codes * Applied f'' format instead ''.format() * Fixes * tweak --- appium/saucetestcase.py | 2 +- appium/webdriver/appium_service.py | 10 ++++------ appium/webdriver/applicationstate.py | 2 +- appium/webdriver/clipboard_content_type.py | 2 +- appium/webdriver/common/multi_action.py | 2 +- appium/webdriver/common/touch_action.py | 2 +- appium/webdriver/connectiontype.py | 2 +- appium/webdriver/extensions/android/display.py | 2 -- appium/webdriver/extensions/android/gsm.py | 18 +++++++++--------- appium/webdriver/extensions/android/network.py | 8 ++++---- appium/webdriver/extensions/clipboard.py | 2 +- appium/webdriver/extensions/execute_driver.py | 2 +- appium/webdriver/extensions/location.py | 2 +- appium/webdriver/extensions/remote_fs.py | 2 +- .../search_context/base_search_context.py | 4 ++-- appium/webdriver/mobilecommand.py | 2 +- appium/webdriver/webdriver.py | 7 +------ test/functional/android/common_tests.py | 2 +- test/functional/android/ime_tests.py | 1 - test/functional/android/log_event_tests.py | 2 +- test/functional/android/webelement_tests.py | 2 +- .../ios/helper/desired_capabilities.py | 4 ++-- test/functional/ios/webdriver_tests.py | 2 +- test/functional/test_helper.py | 3 +-- test/unit/helper/test_helper.py | 2 +- 25 files changed, 39 insertions(+), 50 deletions(-) diff --git a/appium/saucetestcase.py b/appium/saucetestcase.py index a2e94aba..754146fb 100644 --- a/appium/saucetestcase.py +++ b/appium/saucetestcase.py @@ -19,7 +19,7 @@ import os import sys import unittest -from typing import Any, Callable, List +from typing import Callable, List from sauceclient import SauceClient diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 7683782a..d2aedadb 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -50,8 +50,7 @@ def poll_url(host: str, port: int, path: str, timeout_ms: int) -> bool: while time.time() < time_started_sec + timeout_ms / 1000.0: try: conn = urllib3.PoolManager(timeout=1.0) - resp = conn.request('HEAD', 'http://{host}:{port}{path}'.format( - host=host, port=port, path=path)) + resp = conn.request('HEAD', f'http://{host}:{port}{path}') if resp.status < 400: return True except Exception: @@ -67,7 +66,7 @@ class AppiumServiceError(RuntimeError): T = TypeVar('T', bound='AppiumService') -class AppiumService(object): +class AppiumService: def __init__(self) -> None: self._process: Optional[sp.Popen] = None self._cmd: Optional[List] = None @@ -169,13 +168,12 @@ def start(self, **kwargs: Any) -> sp.Popen: port = self._parse_port(args) 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) + error_msg = f'Appium has failed to start on {host}:{port} within {timeout_ms}ms timeout' if error_msg is not None: if stderr == sp.PIPE: err_output = self._process.stderr.read() if err_output: - error_msg += '\nOriginal error: {}'.format(str(err_output)) + error_msg += f'\nOriginal error: {str(err_output)}' self.stop() raise AppiumServiceError(error_msg) return self._process diff --git a/appium/webdriver/applicationstate.py b/appium/webdriver/applicationstate.py index e3c255ae..0658b264 100644 --- a/appium/webdriver/applicationstate.py +++ b/appium/webdriver/applicationstate.py @@ -13,7 +13,7 @@ # limitations under the License. -class ApplicationState(object): +class ApplicationState: NOT_INSTALLED = 0 NOT_RUNNING = 1 RUNNING_IN_BACKGROUND_SUSPENDED = 2 diff --git a/appium/webdriver/clipboard_content_type.py b/appium/webdriver/clipboard_content_type.py index 67aac351..7c1211b3 100644 --- a/appium/webdriver/clipboard_content_type.py +++ b/appium/webdriver/clipboard_content_type.py @@ -13,7 +13,7 @@ # limitations under the License. -class ClipboardContentType(object): +class ClipboardContentType: PLAINTEXT = 'plaintext' IMAGE = 'image' URL = 'url' diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index f3326534..e6a84cfe 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -31,7 +31,7 @@ T = TypeVar('T', bound='MultiAction') -class MultiAction(object): +class MultiAction: def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: self._driver = driver self._element = element diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index a8fb299d..a9544063 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -35,7 +35,7 @@ T = TypeVar('T', bound='TouchAction') -class TouchAction(object): +class TouchAction: def __init__(self, driver: Optional['WebDriver'] = None): self._driver = driver diff --git a/appium/webdriver/connectiontype.py b/appium/webdriver/connectiontype.py index 5a408ba5..7d477680 100644 --- a/appium/webdriver/connectiontype.py +++ b/appium/webdriver/connectiontype.py @@ -26,7 +26,7 @@ """ -class ConnectionType(object): +class ConnectionType: NO_CONNECTION = 0 AIRPLANE_MODE = 1 WIFI_ONLY = 2 diff --git a/appium/webdriver/extensions/android/display.py b/appium/webdriver/extensions/android/display.py index 73ac1ccb..f07c0875 100644 --- a/appium/webdriver/extensions/android/display.py +++ b/appium/webdriver/extensions/android/display.py @@ -12,8 +12,6 @@ # 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 diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index df4ea95e..325ff573 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -21,14 +21,14 @@ from appium.webdriver.mobilecommand import MobileCommand as Command -class GsmCallActions(object): +class GsmCallActions: CALL = 'call' ACCEPT = 'accept' CANCEL = 'cancel' HOLD = 'hold' -class GsmSignalStrength(object): +class GsmSignalStrength: NONE_OR_UNKNOWN = 0 POOR = 1 MODERATE = 2 @@ -36,7 +36,7 @@ class GsmSignalStrength(object): GREAT = 4 -class GsmVoiceState(object): +class GsmVoiceState: UNREGISTERED = 'unregistered' HOME = 'home' ROAMING = 'roaming' @@ -66,8 +66,8 @@ def make_gsm_call(self, phone_number: str, action: str) -> T: """ constants = extract_const_attributes(GsmCallActions) if action not in constants.values(): - logger.warning('{} is unknown. Consider using one of {} constants. (e.g. {}.CALL)'.format( - action, list(constants.keys()), GsmCallActions.__name__)) + logger.warning( + f'{action} is unknown. Consider using one of {list(constants.keys())} constants. (e.g. {GsmCallActions.__name__}.CALL)') self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) return self @@ -85,8 +85,8 @@ def set_gsm_signal(self, strength: int) -> T: """ constants = extract_const_attributes(GsmSignalStrength) if strength not in constants.values(): - logger.warning('{} is out of range. Consider using one of {} constants. (e.g. {}.GOOD)'.format( - strength, list(constants.keys()), GsmSignalStrength.__name__)) + logger.warning( + f'{strength} is out of range. Consider using one of {list(constants.keys())} constants. (e.g. {GsmSignalStrength.__name__}.GOOD)') self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) return self @@ -104,8 +104,8 @@ def set_gsm_voice(self, state: str) -> T: """ constants = extract_const_attributes(GsmVoiceState) if state not in constants.values(): - logger.warning('{} is unknown. Consider using one of {} constants. (e.g. {}.HOME)'.format( - state, list(constants.keys()), GsmVoiceState.__name__)) + logger.warning( + f'{state} is unknown. Consider using one of {list(constants.keys())} constants. (e.g. {GsmVoiceState.__name__}.HOME)') self.execute(Command.SET_GSM_VOICE, {'state': state}) return self diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 99c71763..4f874687 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.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 TypeVar from selenium import webdriver @@ -23,7 +23,7 @@ T = TypeVar('T', bound='Network') -class NetSpeed(object): +class NetSpeed: GSM = 'gsm' # GSM/CSD (up: 14.4(kbps), down: 14.4(kbps)) SCSD = 'scsd' # HSCSD (up: 14.4, down: 57.6) GPRS = 'gprs' # GPRS (up: 28.8, down: 57.6) @@ -98,8 +98,8 @@ def set_network_speed(self, speed_type: str) -> T: """ constants = extract_const_attributes(NetSpeed) if speed_type not in constants.values(): - logger.warning('{} is unknown. Consider using one of {} constants. (e.g. {}.LTE)'.format( - speed_type, list(constants.keys()), NetSpeed.__name__)) + logger.warning( + f'{speed_type} is unknown. Consider using one of {list(constants.keys())} constants. (e.g. {NetSpeed.__name__}.LTE)') self.execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) return self diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index e4f7bc71..02e3f522 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,7 +13,7 @@ # limitations under the License. import base64 -from typing import Any, Dict, Optional, TypeVar +from typing import Optional, TypeVar from selenium import webdriver diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index f6c00fb0..3b3778c3 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -44,7 +44,7 @@ def execute_driver(self, script: str, script_type: str = 'webdriverio', timeout_ WebDriverException: If something error happenes in the script. The message has the original error message. """ - class Result(object): + class Result: def __init__(self, response: Dict): self.result = response['result'] diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 054cf44e..4af21de9 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, Optional, TypeVar, Union +from typing import Dict, TypeVar, Union from selenium import webdriver diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index dd256a7a..4f8ff871 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -74,7 +74,7 @@ def push_file(self, destination_path: str, with open(source_path, 'rb') as f: file_data = f.read() except IOError: - message = 'source_path {} could not be found. Are you sure the file exists?'.format(source_path) + message = f'source_path "{source_path}" could not be found. Are you sure the file exists?' raise InvalidArgumentException(message) base64data = base64.b64encode(file_data).decode('utf-8') diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/webdriver/extensions/search_context/base_search_context.py index 404aa62a..317069f0 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/webdriver/extensions/search_context/base_search_context.py @@ -14,13 +14,13 @@ # pylint: disable=abstract-method -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Dict, List, Union if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -class BaseSearchContext(object): +class BaseSearchContext: """Used by each search context. Dummy find_element/s are for preventing pylint error""" def find_element(self, by: str, value: Union[str, Dict] = None) -> 'WebElement': diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index f6d90679..b0bd8c4d 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -13,7 +13,7 @@ # limitations under the License. -class MobileCommand(object): +class MobileCommand: # Common GET_SESSION = 'getSession' GET_ALL_SESSIONS = 'getAllSessions' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 13e9619f..3a0512bc 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -193,12 +193,7 @@ def _update_command_executor(self, keep_alive: bool) -> None: hostname = self.capabilities[direct_host] port = self.capabilities[direct_port] path = self.capabilities[direct_path] - executor = '{scheme}://{hostname}:{port}{path}'.format( - scheme=protocol, - hostname=hostname, - port=port, - path=path - ) + executor = f'{protocol}://{hostname}:{port}{path}' logger.info('Updated request endpoint to %s', executor) # Override command executor diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index cf5ea791..04fb63e8 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -44,7 +44,7 @@ def test_open_notifications(self): self.skipTest('Need to fix flaky test during running on CI.') for word in ['App', 'Notification', 'Status Bar', ':-|']: wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, - 'new UiSelector().text("{}")'.format(word)).click() + f'new UiSelector().text("{word}")').click() self.driver.open_notifications() sleep(1) diff --git a/test/functional/android/ime_tests.py b/test/functional/android/ime_tests.py index 86bf7449..6cac0e73 100644 --- a/test/functional/android/ime_tests.py +++ b/test/functional/android/ime_tests.py @@ -37,7 +37,6 @@ def test_active_ime_engine(self): def test_activate_ime_engine(self): engines = self.driver.available_ime_engines - active_engine = self.driver.active_ime_engine self.driver.activate_ime_engine(engines[-1]) self.assertEqual(self.driver.active_ime_engine, engines[-1]) diff --git a/test/functional/android/log_event_tests.py b/test/functional/android/log_event_tests.py index 8067e9f7..a64ff9a0 100644 --- a/test/functional/android/log_event_tests.py +++ b/test/functional/android/log_event_tests.py @@ -23,7 +23,7 @@ def test_log_event(self): vendor = 'appium' event = 'funEvent' self.driver.log_event(vendor, event) - assert '{}:{}'.format(vendor, event) in self.driver.get_events().keys() + assert f'{vendor}:{event}' in self.driver.get_events().keys() if __name__ == '__main__': diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index 1a16473b..b936ffc4 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -47,7 +47,7 @@ def test_set_text(self): def test_send_keys(self): for text in ['App', 'Activity', 'Custom Title']: wait_for_element(self.driver, MobileBy.XPATH, - "//android.widget.TextView[@text='{}']".format(text)).click() + f"//android.widget.TextView[@text='{text}']").click() el = wait_for_element(self.driver, MobileBy.ID, '{}:id/left_text_edit'.format(APIDEMO_PKG_NAME)) el.send_keys(' text') diff --git a/test/functional/ios/helper/desired_capabilities.py b/test/functional/ios/helper/desired_capabilities.py index 7a98f61a..12dc982a 100644 --- a/test/functional/ios/helper/desired_capabilities.py +++ b/test/functional/ios/helper/desired_capabilities.py @@ -41,7 +41,7 @@ def get_desired_capabilities(app=None): return desired_caps -class PytestXdistWorker(object): +class PytestXdistWorker: NUMBER = os.getenv('PYTEST_XDIST_WORKER') COUNT = os.getenv('PYTEST_XDIST_WORKER_COUNT') # Return 2 if `-n 2` is passed @@ -53,7 +53,7 @@ def gw(number): if number >= PytestXdistWorker.COUNT: return 'gw0' - return 'gw{}'.format(number) + return f'gw{number}' # If you run tests with pytest-xdist, you can run tests in parallel. diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 51c7bc58..3d0f113f 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -36,7 +36,7 @@ def test_all_sessions(self): desired_caps['deviceName'] = 'iPhone Xs Max' desired_caps['wdaLocalPort'] = port - class session_counts_is_two(object): + class session_counts_is_two: TIMEOUT = 10 def __call__(self, driver): diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index a531d053..ee6b6daf 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -20,8 +20,7 @@ def get_available_from_port_range(from_port, to_port): finally: sock.close() - raise NoAvailablePortError('No available port between {} and {}'.format( - from_port, to_port)) + raise NoAvailablePortError(f'No available port between {from_port} and {to_port}') def is_ci(): diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py index f7ff3ca0..4c1b2d07 100644 --- a/test/unit/helper/test_helper.py +++ b/test/unit/helper/test_helper.py @@ -28,7 +28,7 @@ def appium_command(command): Returns: str: A string of command URL """ - return '{}{}'.format(SERVER_URL_BASE, command) + return f'{SERVER_URL_BASE}{command}' def android_w3c_driver(): From 4a833f2c818aa830f8137bec7afebebf98fe8c52 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Sun, 26 Jan 2020 11:28:45 +0900 Subject: [PATCH 05/12] chore: Fix mypy errors under test folder (#487) * Fix mypy errors under test folder * Add mypy check for test folder to pre-commit * Add mypy check to ci --- .pre-commit-config.yaml | 3 +-- appium/webdriver/extensions/action_helpers.py | 2 +- appium/webdriver/extensions/applications.py | 5 +++- ci.sh | 2 +- test/functional/android/activities_tests.py | 8 +++--- test/functional/android/applications_tests.py | 20 +++++++------- test/functional/android/chrome_tests.py | 6 ++--- test/functional/android/common_tests.py | 6 ++--- .../android/context_switching_tests.py | 20 +++++++------- test/functional/android/device_time_tests.py | 2 +- test/functional/android/finger_print_tests.py | 2 +- .../android/helper/desired_capabilities.py | 10 ++++--- test/functional/android/helper/test_helper.py | 11 +++++--- test/functional/android/hw_actions_tests.py | 2 +- test/functional/android/ime_tests.py | 10 +++---- test/functional/android/keyboard_tests.py | 8 +++--- test/functional/android/location_tests.py | 4 +-- test/functional/android/log_event_tests.py | 2 +- test/functional/android/multi_action_tests.py | 6 ++--- .../android/network_connection_tests.py | 4 +-- test/functional/android/remote_fs_tests.py | 6 ++--- .../functional/android/screen_record_tests.py | 2 +- .../find_by_accessibility_id_tests.py | 8 +++--- .../search_context/find_by_image_tests.py | 10 +++---- .../find_by_uiautomator_tests.py | 10 +++---- test/functional/android/settings_tests.py | 4 +-- test/functional/android/touch_action_tests.py | 26 +++++++++---------- test/functional/android/webelement_tests.py | 6 ++--- test/functional/ios/applications_tests.py | 2 +- test/functional/ios/execute_driver_tests.py | 4 +-- .../ios/helper/desired_capabilities.py | 19 +++++++------- test/functional/ios/helper/test_helper.py | 4 +-- test/functional/ios/hw_actions_tests.py | 10 +++---- test/functional/ios/keyboard_tests.py | 10 +++---- test/functional/ios/remote_fs_tests.py | 2 +- test/functional/ios/safari_tests.py | 8 +++--- test/functional/ios/screen_record_tests.py | 2 +- .../find_by_element_webelement_tests.py | 2 +- .../find_by_ios_class_chain_tests.py | 4 +-- .../find_by_ios_predicate_tests.py | 12 ++++----- test/functional/ios/webdriver_tests.py | 16 +++++++----- test/functional/test_helper.py | 15 ++++++++--- test/unit/helper/test_helper.py | 13 +++++++--- 43 files changed, 177 insertions(+), 151 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8af654b..a4f7c00a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,5 @@ rev: 'v0.761' hooks: - id: mypy - files: ^appium/ - entry: mypy appium/ + entry: mypy appium/ test/ pass_filenames: false diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 98ddbf67..d03d36ee 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -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: Optional[int] = None) -> T: + def tap(self, 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 diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index 9ab8a295..3e1939cf 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -172,13 +172,16 @@ class for more details. } return self.execute(Command.QUERY_APP_STATE, data)['value'] - def app_strings(self, language: str = None, string_file: str = None) -> str: + def app_strings(self, language: str = None, string_file: str = None) -> Dict[str, str]: """Returns the application strings from the device for the specified language. Args: language (str): strings language code string_file (str): the name of the string file to query + + Returns: + Dict[str, str]: The key is string id and the value is the content. """ data = {} if language is not None: diff --git a/ci.sh b/ci.sh index a0b1d8fc..1303ff84 100755 --- a/ci.sh +++ b/ci.sh @@ -35,7 +35,7 @@ if [[ $? -ne 0 ]] ; then fi ( - python -m mypy appium + python -m mypy appium test ) if [[ $? -ne 0 ]] ; then EXIT_STATUS=1 diff --git a/test/functional/android/activities_tests.py b/test/functional/android/activities_tests.py index 7b1894ff..d13f2d52 100644 --- a/test/functional/android/activities_tests.py +++ b/test/functional/android/activities_tests.py @@ -19,25 +19,25 @@ class ActivitiesTests(BaseTestCase): - def test_current_activity(self): + def test_current_activity(self) -> None: activity = self.driver.current_activity self.assertEqual('.ApiDemos', activity) - def test_start_activity_this_app(self): + def test_start_activity_this_app(self) -> None: self.driver.start_activity(APIDEMO_PKG_NAME, ".ApiDemos") self._assert_activity_contains('Demos') self.driver.start_activity(APIDEMO_PKG_NAME, ".accessibility.AccessibilityNodeProviderActivity") self._assert_activity_contains('Node') - def test_start_activity_other_app(self): + def test_start_activity_other_app(self) -> None: self.driver.start_activity(APIDEMO_PKG_NAME, ".ApiDemos") self._assert_activity_contains('Demos') self.driver.start_activity("com.android.calculator2", ".Calculator") self._assert_activity_contains('Calculator') - def _assert_activity_contains(self, activity): + def _assert_activity_contains(self, activity: str) -> None: current = self.driver.current_activity self.assertTrue(activity in current) diff --git a/test/functional/android/applications_tests.py b/test/functional/android/applications_tests.py index 45ea52ca..b8b98ffb 100644 --- a/test/functional/android/applications_tests.py +++ b/test/functional/android/applications_tests.py @@ -23,33 +23,33 @@ class ApplicationsTests(BaseTestCase): - def test_background_app(self): + def test_background_app(self) -> None: self.driver.background_app(1) sleep(3) self.driver.launch_app() - def test_is_app_installed(self): + def test_is_app_installed(self) -> None: self.assertFalse(self.driver.is_app_installed('sdfsdf')) self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) - def test_install_app(self): + def test_install_app(self) -> None: self.skipTest('This causes the server to crash. no idea why') self.assertFalse(self.driver.is_app_installed('io.selendroid.testapp')) self.driver.install_app('/Users/isaac/code/python-client/test/apps/selendroid-test-app.apk') self.assertTrue(self.driver.is_app_installed('io.selendroid.testapp')) - def test_remove_app(self): + def test_remove_app(self) -> None: self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) self.driver.remove_app(APIDEMO_PKG_NAME) self.assertFalse(self.driver.is_app_installed(APIDEMO_PKG_NAME)) - def test_close_and_launch_app(self): + def test_close_and_launch_app(self) -> None: self.driver.close_app() self.driver.launch_app() activity = self.driver.current_activity self.assertEqual('.ApiDemos', activity) - def test_app_management(self): + def test_app_management(self) -> None: app_id = self.driver.current_package self.assertEqual(self.driver.query_app_state(app_id), ApplicationState.RUNNING_IN_FOREGROUND) @@ -60,19 +60,19 @@ def test_app_management(self): self.assertEqual(self.driver.query_app_state(app_id), ApplicationState.RUNNING_IN_FOREGROUND) - def test_app_strings(self): + def test_app_strings(self) -> None: strings = self.driver.app_strings() self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) - def test_app_strings_with_language(self): + def test_app_strings_with_language(self) -> None: strings = self.driver.app_strings('en') self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) - def test_app_strings_with_language_and_file(self): + def test_app_strings_with_language_and_file(self) -> None: strings = self.driver.app_strings('en', 'some_file') self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) - def test_reset(self): + def test_reset(self) -> None: self.driver.reset() self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) diff --git a/test/functional/android/chrome_tests.py b/test/functional/android/chrome_tests.py index 32f74216..971e4ebf 100644 --- a/test/functional/android/chrome_tests.py +++ b/test/functional/android/chrome_tests.py @@ -20,15 +20,15 @@ class ChromeTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: caps = get_desired_capabilities() caps['browserName'] = 'Chrome' self.driver = webdriver.Remote('http://localhost:4723/wd/hub', caps) - def tearDown(self): + def tearDown(self) -> None: self.driver.quit() - def test_find_single_element(self): + def test_find_single_element(self) -> None: self.driver.get('http://10.0.2.2:4723/test/guinea-pig') self.driver.find_element_by_link_text('i am a link').click() diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index 04fb63e8..4da8f793 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -30,15 +30,15 @@ class CommonTests(BaseTestCase): - def test_current_package(self): + def test_current_package(self) -> None: self.assertEqual(APIDEMO_PKG_NAME, self.driver.current_package) - def test_end_test_coverage(self): + def test_end_test_coverage(self) -> None: self.skipTest('Not sure how to set this up to run') self.driver.end_test_coverage(intent='android.intent.action.MAIN', path='') sleep(5) - def test_open_notifications(self): + def test_open_notifications(self) -> None: if is_ci(): # TODO Due to unexpected dialog, "System UI isn't responding" self.skipTest('Need to fix flaky test during running on CI.') diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index ed4dcef9..2b42e55d 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -24,37 +24,37 @@ @pytest.mark.skip(reason="Need to fix broken test") class ContextSwitchingTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('selendroid-test-app.apk') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - def test_contexts_list(self): + def tearDown(self) -> None: + self.driver.quit() + + def test_contexts_list(self) -> None: self._enter_webview() contexts = self.driver.contexts self.assertEqual(2, len(contexts)) - def test_move_to_correct_context(self): + def test_move_to_correct_context(self) -> None: self._enter_webview() self.assertEqual('WEBVIEW_io.selendroid.testapp', self.driver.current_context) - def test_actually_in_webview(self): + def test_actually_in_webview(self) -> None: self._enter_webview() self.driver.find_element_by_css_selector('input[type=submit]').click() el = self.driver.find_element_by_xpath("//h1[contains(., 'This is my way')]") self.assertIsNot(None, el) - def test_move_back_to_native_context(self): + def test_move_back_to_native_context(self) -> None: self._enter_webview() self.driver.switch_to.context(None) self.assertEqual('NATIVE_APP', self.driver.current_context) - def test_set_invalid_context(self): + def test_set_invalid_context(self) -> None: self.assertRaises(NoSuchContextException, self.driver.switch_to.context, 'invalid name') - def tearDown(self): - self.driver.quit() - - def _enter_webview(self): + def _enter_webview(self) -> None: btn = self.driver.find_element_by_name('buttonStartWebviewCD') btn.click() self.driver.switch_to.context('WEBVIEW') diff --git a/test/functional/android/device_time_tests.py b/test/functional/android/device_time_tests.py index deefdd36..736bf7c6 100644 --- a/test/functional/android/device_time_tests.py +++ b/test/functional/android/device_time_tests.py @@ -21,7 +21,7 @@ class DeviceTimeTests(BaseTestCase): - def test_device_time(self): + def test_device_time(self) -> None: date_time = self.driver.device_time # convert to date ought to work parse(date_time) diff --git a/test/functional/android/finger_print_tests.py b/test/functional/android/finger_print_tests.py index e77e72a4..cc0457c2 100644 --- a/test/functional/android/finger_print_tests.py +++ b/test/functional/android/finger_print_tests.py @@ -19,7 +19,7 @@ class FingerPrintTests(BaseTestCase): - def test_finger_print(self): + def test_finger_print(self) -> None: result = self.driver.finger_print(1) self.assertEqual(None, result) diff --git a/test/functional/android/helper/desired_capabilities.py b/test/functional/android/helper/desired_capabilities.py index eccb091a..7ddcd80a 100644 --- a/test/functional/android/helper/desired_capabilities.py +++ b/test/functional/android/helper/desired_capabilities.py @@ -13,17 +13,19 @@ # limitations under the License. import os - +from typing import Any, Dict, Optional # Returns abs path relative to this file and not cwd -def PATH(p): + + +def PATH(p: str) -> str: return os.path.abspath( os.path.join(os.path.dirname(__file__), '..', p) ) -def get_desired_capabilities(app=None): - desired_caps = { +def get_desired_capabilities(app: Optional[str] = None) -> Dict[str, Any]: + desired_caps: Dict[str, Any] = { 'platformName': 'Android', 'deviceName': 'Android Emulator', 'newCommandTimeout': 240, diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index 5813abc6..c8b4af27 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -16,6 +16,7 @@ import base64 import os import unittest +from typing import TYPE_CHECKING from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -25,6 +26,10 @@ from . import desired_capabilities +if TYPE_CHECKING: + from appium.webdriver.webelement import WebElement + from appium.webdriver.webdriver import WebDriver + # the emulator is sometimes slow and needs time to think SLEEPY_TIME = 10 @@ -32,7 +37,7 @@ APIDEMO_PKG_NAME = 'io.appium.android.apis' -def wait_for_element(driver, locator, value, timeout=SLEEPY_TIME): +def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int = SLEEPY_TIME) -> 'WebElement': """Wait until the element located Args: @@ -55,13 +60,13 @@ def wait_for_element(driver, locator, value, timeout=SLEEPY_TIME): class BaseTestCase(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) if is_ci(): self.driver.start_recording_screen() - def tearDown(self): + def tearDown(self) -> None: if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), self._testMethodName + '.mp4') diff --git a/test/functional/android/hw_actions_tests.py b/test/functional/android/hw_actions_tests.py index 3bb022f4..2a6b14e8 100644 --- a/test/functional/android/hw_actions_tests.py +++ b/test/functional/android/hw_actions_tests.py @@ -20,7 +20,7 @@ class HwActionsTests(BaseTestCase): - def test_lock(self): + def test_lock(self) -> None: self.driver.lock(-1) sleep(10) try: diff --git a/test/functional/android/ime_tests.py b/test/functional/android/ime_tests.py index 6cac0e73..ade988c6 100644 --- a/test/functional/android/ime_tests.py +++ b/test/functional/android/ime_tests.py @@ -23,25 +23,25 @@ class IMETests(BaseTestCase): - def test_available_ime_engines(self): + def test_available_ime_engines(self) -> None: engines = self.driver.available_ime_engines self.assertIsInstance(engines, list) self.assertTrue(ANDROID_LATIN in engines or GOOGLE_LATIN in engines) - def test_is_ime_active(self): + def test_is_ime_active(self) -> None: self.assertTrue(self.driver.is_ime_active()) - def test_active_ime_engine(self): + def test_active_ime_engine(self) -> None: engines = self.driver.available_ime_engines self.assertTrue(self.driver.active_ime_engine in engines) - def test_activate_ime_engine(self): + def test_activate_ime_engine(self) -> None: engines = self.driver.available_ime_engines self.driver.activate_ime_engine(engines[-1]) self.assertEqual(self.driver.active_ime_engine, engines[-1]) - def test_deactivate_ime_engine(self): + def test_deactivate_ime_engine(self) -> None: engines = self.driver.available_ime_engines self.driver.activate_ime_engine(engines[-1]) diff --git a/test/functional/android/keyboard_tests.py b/test/functional/android/keyboard_tests.py index 6c634da3..53a5d48c 100644 --- a/test/functional/android/keyboard_tests.py +++ b/test/functional/android/keyboard_tests.py @@ -19,12 +19,12 @@ class KeyboardTests(BaseTestCase): - def test_press_keycode(self): - # not sure how to test this. + def test_press_keycode(self) -> None: + # TODO not sure how to test this. self.driver.press_keycode(176) - def test_long_press_keycode(self): - # not sure how to test this. + def test_long_press_keycode(self) -> None: + # TODO not sure how to test this. self.driver.long_press_keycode(176) diff --git a/test/functional/android/location_tests.py b/test/functional/android/location_tests.py index 1584fd5a..68603385 100644 --- a/test/functional/android/location_tests.py +++ b/test/functional/android/location_tests.py @@ -19,8 +19,8 @@ class LocationTests(BaseTestCase): - def test_toggle_location_services(self): - self.driver.toggle_location_services() + def test_toggle_location_services(self) -> None: + self.driver.toggle_location_services() # TODO Add assert if __name__ == '__main__': diff --git a/test/functional/android/log_event_tests.py b/test/functional/android/log_event_tests.py index a64ff9a0..cc3c2b62 100644 --- a/test/functional/android/log_event_tests.py +++ b/test/functional/android/log_event_tests.py @@ -19,7 +19,7 @@ class LogEventTests(BaseTestCase): - def test_log_event(self): + def test_log_event(self) -> None: vendor = 'appium' event = 'funEvent' self.driver.log_event(vendor, event) diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 3d0be2b0..9904ccbf 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -23,7 +23,7 @@ class MultiActionTests(BaseTestCase): - def test_parallel_actions(self): + def test_parallel_actions(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') self.driver.scroll(el1, el2) @@ -55,7 +55,7 @@ def test_parallel_actions(self): ma.add(a1, a2) ma.perform() - def test_actions_with_waits(self): + def test_actions_with_waits(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') self.driver.scroll(el1, el2) @@ -95,7 +95,7 @@ def test_actions_with_waits(self): ma.add(a1, a2) ma.perform() - def test_driver_multi_tap(self): + def test_driver_multi_tap(self) -> None: el = self.driver.find_element_by_accessibility_id('Graphics') action = TouchAction(self.driver) action.tap(el).perform() diff --git a/test/functional/android/network_connection_tests.py b/test/functional/android/network_connection_tests.py index 5727e1d6..73d3816d 100644 --- a/test/functional/android/network_connection_tests.py +++ b/test/functional/android/network_connection_tests.py @@ -22,11 +22,11 @@ class NetworkConnectionTests(BaseTestCase): - def test_get_network_connection(self): + def test_get_network_connection(self) -> None: nc = self.driver.network_connection self.assertIsInstance(nc, int) - def test_set_network_connection(self): + def test_set_network_connection(self) -> None: if is_ci(): self.skipTest('Need to fix flaky test during running on CI') nc = self.driver.set_network_connection(ConnectionType.DATA_ONLY) diff --git a/test/functional/android/remote_fs_tests.py b/test/functional/android/remote_fs_tests.py index 1a7a0092..db6f3f1e 100644 --- a/test/functional/android/remote_fs_tests.py +++ b/test/functional/android/remote_fs_tests.py @@ -23,7 +23,7 @@ class RemoteFsTests(BaseTestCase): - def test_push_pull_file(self): + def test_push_pull_file(self) -> None: dest_path = '/data/local/tmp/test_push_file.txt' data = bytes('This is the contents of the file to push to the device.', 'utf-8') @@ -32,7 +32,7 @@ def test_push_pull_file(self): self.assertEqual(data, data_ret) - def test_pull_folder(self): + def test_pull_folder(self) -> None: data = bytes('random string data {}'.format(random.randint(0, 1000)), 'utf-8') dest_dir = '/data/local/tmp/' @@ -45,7 +45,7 @@ def test_pull_folder(self): for filename in ['1.txt', '2.txt']: self.assertTrue(filename in fzip.namelist()) - def test_push_file_with_src_path(self): + def test_push_file_with_src_path(self) -> None: test_files = ['test_image.jpg', 'test_file.txt'] for file_name in test_files: src_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', file_name) diff --git a/test/functional/android/screen_record_tests.py b/test/functional/android/screen_record_tests.py index 26f5f764..cad3b72b 100644 --- a/test/functional/android/screen_record_tests.py +++ b/test/functional/android/screen_record_tests.py @@ -20,7 +20,7 @@ class ScreenRecordTests(BaseTestCase): - def test_screen_record(self): + def test_screen_record(self) -> None: self.driver.start_recording_screen(timeLimit=10, forcedRestart=True) sleep(10) result = self.driver.stop_recording_screen() diff --git a/test/functional/android/search_context/find_by_accessibility_id_tests.py b/test/functional/android/search_context/find_by_accessibility_id_tests.py index 508edc21..b3a64507 100644 --- a/test/functional/android/search_context/find_by_accessibility_id_tests.py +++ b/test/functional/android/search_context/find_by_accessibility_id_tests.py @@ -23,18 +23,18 @@ class FindByAccessibilityIDTests(BaseTestCase): - def test_find_single_element(self): + def test_find_single_element(self) -> None: wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")').click() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Task Take out Trash') self.assertIsNotNone(el) - def test_find_multiple_elements(self): + def test_find_multiple_elements(self) -> None: els = self.driver.find_elements_by_accessibility_id('Accessibility') self.assertIsInstance(els, list) - def test_element_find_single_element(self): + def test_element_find_single_element(self) -> None: if is_ci(): self.skipTest('Need to fix flaky test during running on CI') wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() @@ -45,7 +45,7 @@ def test_element_find_single_element(self): sub_el = el.find_element_by_accessibility_id('Task Take out Trash') self.assertIsNotNone(sub_el) - def test_element_find_multiple_elements(self): + def test_element_find_multiple_elements(self) -> None: wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') el = self.driver.find_element_by_class_name('android.widget.ListView') diff --git a/test/functional/android/search_context/find_by_image_tests.py b/test/functional/android/search_context/find_by_image_tests.py index 9d628c28..8d3440f5 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -28,7 +28,7 @@ @pytest.mark.skip(reason="Need to fix broken test") class FindByImageTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) @@ -37,10 +37,10 @@ def setUp(self): "fixImageTemplateSize": True, "autoUpdateImageElementPosition": True}) - def tearDown(self): + def tearDown(self) -> None: self.driver.quit() - def test_find_based_on_image_template(self): + def test_find_based_on_image_template(self) -> None: image_path = desired_capabilities.PATH('file/find_by_image_success.png') print(image_path) with open(image_path, 'rb') as png_file: @@ -64,7 +64,7 @@ def test_find_based_on_image_template(self): el.click() self.driver.find_element_by_accessibility_id("Alarm") - def test_find_multiple_elements_by_image_just_returns_one(self): + def test_find_multiple_elements_by_image_just_returns_one(self) -> None: WebDriverWait(self.driver, 3).until( EC.presence_of_element_located((By.ACCESSIBILITY_ID, "App")) ) @@ -73,7 +73,7 @@ def test_find_multiple_elements_by_image_just_returns_one(self): els[0].click() self.driver.find_element_by_accessibility_id("Alarm") - def test_find_throws_no_such_element(self): + def test_find_throws_no_such_element(self) -> None: image_path = desired_capabilities.PATH('file/find_by_image_failure.png') with open(image_path, 'rb') as png_file: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') diff --git a/test/functional/android/search_context/find_by_uiautomator_tests.py b/test/functional/android/search_context/find_by_uiautomator_tests.py index 47430356..1e197e8e 100644 --- a/test/functional/android/search_context/find_by_uiautomator_tests.py +++ b/test/functional/android/search_context/find_by_uiautomator_tests.py @@ -21,27 +21,27 @@ @pytest.mark.skip(reason="Need to fix flaky test") class FindByUIAutomatorTests(BaseTestCase): - def test_find_single_element(self): + def test_find_single_element(self) -> None: el = self.driver.find_element_by_android_uiautomator('new UiSelector().text("Animation")') self.assertIsNotNone(el) - def test_find_multiple_elements(self): + def test_find_multiple_elements(self) -> None: els = self.driver.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') self.assertIsInstance(els, list) - def test_element_find_single_element(self): + def test_element_find_single_element(self) -> None: el = self.driver.find_element_by_class_name('android.widget.ListView') sub_el = el.find_element_by_android_uiautomator('new UiSelector().description("Animation")') self.assertIsNotNone(sub_el) - def test_element_find_multiple_elements(self): + def test_element_find_multiple_elements(self) -> None: el = self.driver.find_element_by_class_name('android.widget.ListView') sub_els = el.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') self.assertIsInstance(sub_els, list) - def test_scroll_into_view(self): + def test_scroll_into_view(self) -> None: el = self.driver.find_element_by_android_uiautomator( 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));') el.click() diff --git a/test/functional/android/settings_tests.py b/test/functional/android/settings_tests.py index a1ffe10b..98f3d667 100644 --- a/test/functional/android/settings_tests.py +++ b/test/functional/android/settings_tests.py @@ -19,11 +19,11 @@ class SettingsTests(BaseTestCase): - def test_get_settings(self): + def test_get_settings(self) -> None: settings = self.driver.get_settings() self.assertIsNotNone(settings) - def test_update_settings(self): + def test_update_settings(self) -> None: self.driver.update_settings({"waitForIdleTimeout": 10001}) settings = self.driver.get_settings() self.assertEqual(settings["waitForIdleTimeout"], 10001) diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 7bf7e30f..22c11bba 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -27,14 +27,14 @@ class TouchActionTests(BaseTestCase): - def test_tap(self): + def test_tap(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') action = TouchAction(self.driver) action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') self.assertIsNotNone(el) - def test_tap_x_y(self): + def test_tap_x_y(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') action = TouchAction(self.driver) action.tap(el, 100, 10).perform() @@ -42,7 +42,7 @@ def test_tap_x_y(self): el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') self.assertIsNotNone(el) - def test_tap_twice(self): + def test_tap_twice(self) -> None: el = self.driver.find_element_by_accessibility_id('Text') action = TouchAction(self.driver) action.tap(el).perform() @@ -56,7 +56,7 @@ def test_tap_twice(self): els = self.driver.find_elements_by_class_name('android.widget.TextView') self.assertEqual('This is a test\nThis is a test\n', els[1].get_attribute("text")) - def test_press_and_immediately_release(self): + def test_press_and_immediately_release(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') action = TouchAction(self.driver) action.press(el).release().perform() @@ -64,7 +64,7 @@ def test_press_and_immediately_release(self): el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') self.assertIsNotNone(el) - def test_press_and_immediately_release_x_y(self): + def test_press_and_immediately_release_x_y(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') action = TouchAction(self.driver) action.press(el, 100, 10).release().perform() @@ -72,7 +72,7 @@ def test_press_and_immediately_release_x_y(self): el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') self.assertIsNotNone(el) - def test_press_and_wait(self): + def test_press_and_wait(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') @@ -97,7 +97,7 @@ def test_press_and_wait(self): 'new UiSelector().text("Sample menu")') self.assertIsNotNone(el) - def test_press_and_moveto(self): + def test_press_and_moveto(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') @@ -107,7 +107,7 @@ def test_press_and_moveto(self): el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') self.assertIsNotNone(el) - def test_press_and_moveto_x_y(self): + def test_press_and_moveto_x_y(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('App') @@ -117,7 +117,7 @@ def test_press_and_moveto_x_y(self): el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') self.assertIsNotNone(el) - def test_long_press(self): + def test_long_press(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') @@ -142,7 +142,7 @@ def test_long_press(self): 'new UiSelector().text("Sample menu")') self.assertIsNotNone(el) - def test_long_press_x_y(self): + def test_long_press_x_y(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') @@ -167,7 +167,7 @@ def test_long_press_x_y(self): 'new UiSelector().text("Sample menu")') self.assertIsNotNone(el) - def test_drag_and_drop(self): + def test_drag_and_drop(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') self.driver.scroll(el1, el2) @@ -188,7 +188,7 @@ def test_drag_and_drop(self): el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) self.assertTrue('drag_dot_3' in el.text) - def test_driver_drag_and_drop(self): + def test_driver_drag_and_drop(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') self.driver.scroll(el1, el2) @@ -208,7 +208,7 @@ def test_driver_drag_and_drop(self): el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) self.assertTrue('drag_dot_3' in el.text) - def test_driver_swipe(self): + def test_driver_swipe(self) -> None: el = self.driver.find_element_by_accessibility_id('Views') action = TouchAction(self.driver) action.tap(el).perform() diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index b936ffc4..bb28782f 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -25,13 +25,13 @@ class WebelementTests(BaseTestCase): - def test_element_location_in_view(self): + def test_element_location_in_view(self) -> None: el = self.driver.find_element_by_accessibility_id('Content') loc = el.location_in_view self.assertIsNotNone(loc['x']) self.assertIsNotNone(loc['y']) - def test_set_text(self): + def test_set_text(self) -> None: self.driver.find_element_by_android_uiautomator( 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));').click() @@ -44,7 +44,7 @@ def test_set_text(self): self.assertEqual('new text', el.text) - def test_send_keys(self): + def test_send_keys(self) -> None: for text in ['App', 'Activity', 'Custom Title']: wait_for_element(self.driver, MobileBy.XPATH, f"//android.widget.TextView[@text='{text}']").click() diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index 067aec18..362ab42e 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -22,7 +22,7 @@ class WebDriverTests(BaseTestCase): - def test_app_management(self): + def test_app_management(self) -> None: # this only works in Xcode9+ if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: diff --git a/test/functional/ios/execute_driver_tests.py b/test/functional/ios/execute_driver_tests.py index 4f2251ec..cf696675 100644 --- a/test/functional/ios/execute_driver_tests.py +++ b/test/functional/ios/execute_driver_tests.py @@ -19,7 +19,7 @@ class ExecuteDriverTests(BaseTestCase): - def test_batch(self): + def test_batch(self) -> None: script = """ const status = await driver.status(); console.warn('warning message'); @@ -30,7 +30,7 @@ def test_batch(self): assert(response.result['build']) assert(response.logs['warn'] == ['warning message']) - def test_batch_combination_python_script(self): + def test_batch_combination_python_script(self) -> None: script = """ console.warn('warning message'); const element = await driver.findElement('accessibility id', 'Buttons'); diff --git a/test/functional/ios/helper/desired_capabilities.py b/test/functional/ios/helper/desired_capabilities.py index 12dc982a..633b8e2c 100644 --- a/test/functional/ios/helper/desired_capabilities.py +++ b/test/functional/ios/helper/desired_capabilities.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import os +from typing import Any, Dict, Optional # Returns abs path relative to this file and not cwd -def PATH(p): return os.path.abspath( +def PATH(p: str) -> str: return os.path.abspath( os.path.join(os.path.dirname(__file__), p) ) @@ -24,8 +25,8 @@ def PATH(p): return os.path.abspath( BUNDLE_ID = 'com.example.apple-samplecode.UICatalog' -def get_desired_capabilities(app=None): - desired_caps = { +def get_desired_capabilities(app: Optional[str] = None) -> Dict[str, Any]: + desired_caps: Dict[str, Any] = { 'deviceName': iphone_device_name(), 'platformName': 'iOS', 'platformVersion': '13.3', @@ -42,15 +43,15 @@ def get_desired_capabilities(app=None): class PytestXdistWorker: - NUMBER = os.getenv('PYTEST_XDIST_WORKER') - COUNT = os.getenv('PYTEST_XDIST_WORKER_COUNT') # Return 2 if `-n 2` is passed + NUMBER: Optional[str] = os.getenv('PYTEST_XDIST_WORKER') + COUNT: Optional[str] = os.getenv('PYTEST_XDIST_WORKER_COUNT') # Return 2 if `-n 2` is passed @staticmethod - def gw(number): + def gw(number: int) -> str: if PytestXdistWorker.COUNT is None: return '0' - if number >= PytestXdistWorker.COUNT: + if number >= int(PytestXdistWorker.COUNT): return 'gw0' return f'gw{number}' @@ -58,7 +59,7 @@ def gw(number): # If you run tests with pytest-xdist, you can run tests in parallel. -def wda_port(): +def wda_port() -> int: if PytestXdistWorker.NUMBER == PytestXdistWorker.gw(1): return 8101 @@ -68,7 +69,7 @@ def wda_port(): # Before running tests, you must have iOS simulators named 'iPhone 6s - 8100' and 'iPhone 6s - 8101' -def iphone_device_name(port=None): +def iphone_device_name() -> str: if PytestXdistWorker.NUMBER == PytestXdistWorker.gw(0): return 'iPhone 8 - 8100' elif PytestXdistWorker.NUMBER == PytestXdistWorker.gw(1): diff --git a/test/functional/ios/helper/test_helper.py b/test/functional/ios/helper/test_helper.py index 3329bbd6..1e66e46d 100644 --- a/test/functional/ios/helper/test_helper.py +++ b/test/functional/ios/helper/test_helper.py @@ -25,13 +25,13 @@ class BaseTestCase(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) if is_ci(): self.driver.start_recording_screen() - def tearDown(self): + def tearDown(self) -> None: if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), self._testMethodName + '.mp4') diff --git a/test/functional/ios/hw_actions_tests.py b/test/functional/ios/hw_actions_tests.py index f37d268d..9f43392f 100644 --- a/test/functional/ios/hw_actions_tests.py +++ b/test/functional/ios/hw_actions_tests.py @@ -18,7 +18,7 @@ class HwActionsTests(BaseTestCase): - def test_lock(self): + def test_lock(self) -> None: self.driver.lock(-1) try: self.assertTrue(self.driver.is_locked()) @@ -26,16 +26,16 @@ def test_lock(self): self.driver.unlock() self.assertFalse(self.driver.is_locked()) - def test_shake(self): - # what can we assert about this? + def test_shake(self) -> None: + # TODO what can we assert about this? self.driver.shake() - def test_touch_id(self): + def test_touch_id(self) -> None: # nothing to assert, just verify that it doesn't blow up self.driver.touch_id(True) self.driver.touch_id(False) - def test_toggle_touch_id_enrollment(self): + def test_toggle_touch_id_enrollment(self) -> None: # nothing to assert, just verify that it doesn't blow up self.driver.toggle_touch_id_enrollment() diff --git a/test/functional/ios/keyboard_tests.py b/test/functional/ios/keyboard_tests.py index 7ae62347..08ecfdfa 100644 --- a/test/functional/ios/keyboard_tests.py +++ b/test/functional/ios/keyboard_tests.py @@ -19,7 +19,7 @@ class KeyboardTests(BaseTestCase): - def test_hide_keyboard(self): + def test_hide_keyboard(self) -> None: self._move_to_textbox() el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] @@ -33,7 +33,7 @@ def test_hide_keyboard(self): self.assertFalse(el.is_displayed()) - def test_hide_keyboard_presskey_strategy(self): + def test_hide_keyboard_presskey_strategy(self) -> None: self._move_to_textbox() el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] @@ -47,7 +47,7 @@ def test_hide_keyboard_presskey_strategy(self): self.assertFalse(el.is_displayed()) - def test_hide_keyboard_no_key_name(self): + def test_hide_keyboard_no_key_name(self) -> None: self._move_to_textbox() el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] @@ -63,7 +63,7 @@ def test_hide_keyboard_no_key_name(self): # currently fails. self.assertFalse(el.is_displayed()) - def test_is_keyboard_shown(self): + def test_is_keyboard_shown(self) -> None: self._move_to_textbox() el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] @@ -71,7 +71,7 @@ def test_is_keyboard_shown(self): el.set_value('Testing') self.assertTrue(self.driver.is_keyboard_shown()) - def _move_to_textbox(self): + def _move_to_textbox(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Sliders') el2 = self.driver.find_element_by_accessibility_id('Buttons') self.driver.scroll(el1, el2) diff --git a/test/functional/ios/remote_fs_tests.py b/test/functional/ios/remote_fs_tests.py index 4c24b39f..66cad40e 100644 --- a/test/functional/ios/remote_fs_tests.py +++ b/test/functional/ios/remote_fs_tests.py @@ -20,7 +20,7 @@ class RemoteFsTests(BaseTestCase): - def test_push_file(self): + def test_push_file(self) -> None: file_name = 'test_image.jpg' source_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', file_name) destination_path = file_name diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index a0b5255f..08c73dd4 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -20,7 +20,7 @@ class SafariTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: desired_caps = get_desired_capabilities() desired_caps.update({ 'browserName': 'safari', @@ -30,15 +30,15 @@ def setUp(self): self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - def tearDown(self): + def tearDown(self) -> None: self.driver.quit() - def test_context(self): + def test_context(self) -> None: self.assertEqual('NATIVE_APP', self.driver.contexts[0]) self.assertTrue(self.driver.contexts[1].startswith('WEBVIEW_')) self.assertTrue('WEBVIEW_' in self.driver.current_context) - def test_get(self): + def test_get(self) -> None: self.driver.get("http://google.com") self.assertEqual('Google', self.driver.title) diff --git a/test/functional/ios/screen_record_tests.py b/test/functional/ios/screen_record_tests.py index 7ad79af6..33a5e98a 100644 --- a/test/functional/ios/screen_record_tests.py +++ b/test/functional/ios/screen_record_tests.py @@ -19,7 +19,7 @@ class ScreenRecordTests(BaseTestCase): - def test_screen_record(self): + def test_screen_record(self) -> None: self.driver.start_recording_screen() sleep(10) result = self.driver.stop_recording_screen() diff --git a/test/functional/ios/search_context/find_by_element_webelement_tests.py b/test/functional/ios/search_context/find_by_element_webelement_tests.py index 3da2fc42..2e81fec5 100644 --- a/test/functional/ios/search_context/find_by_element_webelement_tests.py +++ b/test/functional/ios/search_context/find_by_element_webelement_tests.py @@ -19,7 +19,7 @@ class FindByElementWebelementTests(BaseTestCase): - def test_find_element_by_path(self): + def test_find_element_by_path(self) -> None: el = self.driver.find_element_by_ios_predicate('wdName == "UICatalog"') self.assertEqual('UICatalog', el.get_attribute('name')) diff --git a/test/functional/ios/search_context/find_by_ios_class_chain_tests.py b/test/functional/ios/search_context/find_by_ios_class_chain_tests.py index 12537ad3..3c02fb2b 100644 --- a/test/functional/ios/search_context/find_by_ios_class_chain_tests.py +++ b/test/functional/ios/search_context/find_by_ios_class_chain_tests.py @@ -18,12 +18,12 @@ class FindByIOClassChainTests(BaseTestCase): - def test_find_element_by_path(self): + def test_find_element_by_path(self) -> None: els = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/**/XCUIElementTypeStaticText') self.assertEqual(35, len(els)) self.assertEqual('UICatalog', els[0].get_attribute('name')) - def test_find_multiple_elements_by_path(self): + def test_find_multiple_elements_by_path(self) -> None: el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*/*') self.assertEqual(2, len(el)) self.assertEqual('UICatalog', el[0].get_attribute('name')) diff --git a/test/functional/ios/search_context/find_by_ios_predicate_tests.py b/test/functional/ios/search_context/find_by_ios_predicate_tests.py index 6890b6b5..6f5bc6d9 100644 --- a/test/functional/ios/search_context/find_by_ios_predicate_tests.py +++ b/test/functional/ios/search_context/find_by_ios_predicate_tests.py @@ -18,23 +18,23 @@ class FindByIOSPredicateTests(BaseTestCase): - def test_find_element_by_name(self): + def test_find_element_by_name(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('wdName == "Buttons"') - def test_find_multiple_element_by_type(self): + def test_find_multiple_element_by_type(self) -> None: e = self.driver.find_elements_by_ios_predicate('wdType == "XCUIElementTypeStaticText"') self.assertNotEqual(len(e), 0) - def test_find_element_by_label(self): + def test_find_element_by_label(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('label == "Buttons"') - def test_find_element_by_value(self): + def test_find_element_by_value(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('wdValue == "Buttons"') - def test_find_element_by_isvisible(self): + def test_find_element_by_isvisible(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('wdValue == "Buttons" AND visible == 1') @@ -42,7 +42,7 @@ def test_find_element_by_isvisible(self): e = self.driver.find_elements_by_ios_predicate('wdValue == "Buttons" AND visible == 0') self.assertEqual(len(e), 0) - def test_find_element_by_isenabled(self): + def test_find_element_by_isenabled(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('wdValue == "Buttons" AND enabled == 1') diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 3d0f113f..7a7c5149 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +from typing import TYPE_CHECKING from selenium.webdriver.support.ui import WebDriverWait @@ -24,10 +25,13 @@ from ..test_helper import is_ci from .helper import desired_capabilities +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + class WebDriverTests(BaseTestCase): - def test_all_sessions(self): + def test_all_sessions(self) -> None: if is_ci(): # TODO Due to not created 2nd session somehow self.skipTest('Need to fix flaky test during running on CI.') @@ -39,7 +43,7 @@ def test_all_sessions(self): class session_counts_is_two: TIMEOUT = 10 - def __call__(self, driver): + def __call__(self, driver: 'WebDriver') -> bool: return len(driver.all_sessions) == 2 driver2 = None @@ -52,7 +56,7 @@ def __call__(self, driver): if driver2 is not None: driver2.quit() - def test_app_management(self): + def test_app_management(self) -> None: # this only works in Xcode9+ if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: @@ -66,7 +70,7 @@ def test_app_management(self): self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), ApplicationState.RUNNING_IN_FOREGROUND) - def test_clear(self): + def test_clear(self) -> None: self._move_to_textbox() el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] @@ -90,7 +94,7 @@ def test_clear(self): text = el.get_attribute('value') self.assertEqual(text, def_text) - def test_press_button(self): + def test_press_button(self) -> None: self.driver.press_button("Home") if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: @@ -98,7 +102,7 @@ def test_press_button(self): self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), ApplicationState.RUNNING_IN_FOREGROUND) - def _move_to_textbox(self): + def _move_to_textbox(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Sliders') el2 = self.driver.find_element_by_accessibility_id('Buttons') self.driver.scroll(el1, el2) diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index ee6b6daf..ae55b386 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -6,14 +6,21 @@ class NoAvailablePortError(Exception): pass -def get_available_from_port_range(from_port, to_port): +def get_available_from_port_range(from_port: int, to_port: int) -> int: """Returns available local port number. + + Args: + from_port (int): The start port to search + to_port (int): The end port to search + + Returns: + int: available local port number which are found first + """ - r = range(from_port, to_port) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - for port in r: + for port in range(from_port, to_port): try: if sock.connect_ex(('localhost', port)) != 0: return port @@ -23,7 +30,7 @@ def get_available_from_port_range(from_port, to_port): raise NoAvailablePortError(f'No available port between {from_port} and {to_port}') -def is_ci(): +def is_ci() -> bool: """Returns if current execution is running on CI Returns: diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py index 4c1b2d07..b33d3a92 100644 --- a/test/unit/helper/test_helper.py +++ b/test/unit/helper/test_helper.py @@ -13,6 +13,7 @@ # limitations under the License. import json +from typing import TYPE_CHECKING, Any, Dict import httpretty @@ -21,8 +22,12 @@ # :return: A string of test URL SERVER_URL_BASE = 'http://localhost:4723/wd/hub' +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + from httpretty.core import HTTPrettyRequestEmpty -def appium_command(command): + +def appium_command(command: str) -> str: """Return a command of Appium Returns: @@ -31,7 +36,7 @@ def appium_command(command): return f'{SERVER_URL_BASE}{command}' -def android_w3c_driver(): +def android_w3c_driver() -> 'WebDriver': """Return a W3C driver which is generated by a mock response for Android Returns: @@ -86,7 +91,7 @@ def android_w3c_driver(): return driver -def ios_w3c_driver(): +def ios_w3c_driver() -> 'WebDriver': """Return a W3C driver which is generated by a mock response for iOS Returns: @@ -127,6 +132,6 @@ def ios_w3c_driver(): return driver -def get_httpretty_request_body(request): +def get_httpretty_request_body(request: 'HTTPrettyRequestEmpty') -> Dict[str, Any]: """Returns utf-8 decoded request body""" return json.loads(request.body.decode('utf-8')) From a31659797b0d97220cc64e46cfe70572a5deef07 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Sun, 26 Jan 2020 17:35:31 +0900 Subject: [PATCH 06/12] chore: Remove unittest dependency (#488) * Removed unnecessary codes from calling super * Removed unittest dependency * Upgrade the dependencies to the latest * Removed unused args * Review comments --- Pipfile | 12 +++--- appium/webdriver/errorhandler.py | 2 +- appium/webdriver/webdriver.py | 2 +- test/functional/android/activities_tests.py | 13 ++---- test/functional/android/applications_tests.py | 43 ++++++++----------- test/functional/android/chrome_tests.py | 15 ++----- test/functional/android/common_tests.py | 26 +++++------ .../android/context_switching_tests.py | 24 ++++------- test/functional/android/device_time_tests.py | 9 +--- test/functional/android/finger_print_tests.py | 11 +---- test/functional/android/helper/test_helper.py | 9 ++-- test/functional/android/hw_actions_tests.py | 12 ++---- test/functional/android/ime_tests.py | 22 ++++------ test/functional/android/keyboard_tests.py | 9 +--- test/functional/android/location_tests.py | 9 +--- test/functional/android/log_event_tests.py | 9 +--- test/functional/android/multi_action_tests.py | 8 +--- .../android/network_connection_tests.py | 18 +++----- test/functional/android/remote_fs_tests.py | 14 ++---- .../functional/android/screen_record_tests.py | 10 +---- .../find_by_accessibility_id_tests.py | 20 +++------ .../search_context/find_by_image_tests.py | 34 ++++++--------- .../find_by_uiautomator_tests.py | 18 +++----- test/functional/android/settings_tests.py | 13 ++---- test/functional/android/touch_action_tests.py | 39 ++++++++--------- test/functional/android/webelement_tests.py | 17 +++----- test/functional/ios/applications_tests.py | 18 ++------ test/functional/ios/execute_driver_tests.py | 8 +--- test/functional/ios/helper/test_helper.py | 9 ++-- test/functional/ios/hw_actions_tests.py | 13 ++---- test/functional/ios/keyboard_tests.py | 22 ++++------ test/functional/ios/remote_fs_tests.py | 8 +--- test/functional/ios/safari_tests.py | 21 +++------ test/functional/ios/screen_record_tests.py | 10 +---- .../find_by_element_webelement_tests.py | 17 +++----- .../find_by_ios_class_chain_tests.py | 19 +++----- .../find_by_ios_predicate_tests.py | 15 ++----- test/functional/ios/webdriver_tests.py | 34 ++++++--------- test/unit/webdriver/webdriver_test.py | 4 +- 39 files changed, 197 insertions(+), 419 deletions(-) diff --git a/Pipfile b/Pipfile index d9795ba9..78bdce7b 100644 --- a/Pipfile +++ b/Pipfile @@ -4,18 +4,18 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=1.13" +pre-commit = "~=1.21" [packages] selenium = "~=3.141" -autopep8 = "~=1.4" +autopep8 = "~=1.5" -pytest = "~=4.0" -pytest-cov = "~=2.6" +pytest = "~=5.3" +pytest-cov = "~=2.8" -tox = "~=3.6" -tox-travis = "~=0.11" +tox = "~=3.14" +tox-travis = "~=0.12" httpretty = "~=0.9" python-dateutil = "~=2.8" diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index 38426992..11359e4d 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -23,7 +23,7 @@ class MobileErrorHandler(errorhandler.ErrorHandler): def check_response(self, response: Dict) -> None: try: - super(MobileErrorHandler, self).check_response(response) + super().check_response(response) except WebDriverException as wde: if wde.msg == 'No such context found.': raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 3a0512bc..f35f6479 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -148,7 +148,7 @@ class WebDriver( def __init__(self, command_executor: str = 'http://127.0.0.1:4444/wd/hub', desired_capabilities: Optional[Dict] = None, browser_profile: str = None, proxy: str = None, keep_alive: bool = True, direct_connection: bool = False): - super(WebDriver, self).__init__( + super().__init__( AppiumConnection(command_executor, keep_alive=keep_alive), desired_capabilities, browser_profile, diff --git a/test/functional/android/activities_tests.py b/test/functional/android/activities_tests.py index d13f2d52..7f9978be 100644 --- a/test/functional/android/activities_tests.py +++ b/test/functional/android/activities_tests.py @@ -13,15 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase -class ActivitiesTests(BaseTestCase): +class TestActivities(BaseTestCase): def test_current_activity(self) -> None: activity = self.driver.current_activity - self.assertEqual('.ApiDemos', activity) + assert '.ApiDemos' == activity def test_start_activity_this_app(self) -> None: self.driver.start_activity(APIDEMO_PKG_NAME, ".ApiDemos") @@ -39,9 +37,4 @@ def test_start_activity_other_app(self) -> None: def _assert_activity_contains(self, activity: str) -> None: current = self.driver.current_activity - self.assertTrue(activity in current) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ActivitiesTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert activity in current diff --git a/test/functional/android/applications_tests.py b/test/functional/android/applications_tests.py index b8b98ffb..118af363 100644 --- a/test/functional/android/applications_tests.py +++ b/test/functional/android/applications_tests.py @@ -13,15 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep +import pytest + from appium.webdriver.applicationstate import ApplicationState from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase -class ApplicationsTests(BaseTestCase): +class TestApplications(BaseTestCase): def test_background_app(self) -> None: self.driver.background_app(1) @@ -29,54 +30,46 @@ def test_background_app(self) -> None: self.driver.launch_app() def test_is_app_installed(self) -> None: - self.assertFalse(self.driver.is_app_installed('sdfsdf')) - self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) + assert not self.driver.is_app_installed('sdfsdf') + assert self.driver.is_app_installed(APIDEMO_PKG_NAME) + @pytest.mark.skip('This causes the server to crash. no idea why') def test_install_app(self) -> None: - self.skipTest('This causes the server to crash. no idea why') - self.assertFalse(self.driver.is_app_installed('io.selendroid.testapp')) + assert not self.driver.is_app_installed('io.selendroid.testapp') self.driver.install_app('/Users/isaac/code/python-client/test/apps/selendroid-test-app.apk') - self.assertTrue(self.driver.is_app_installed('io.selendroid.testapp')) + assert self.driver.is_app_installed('io.selendroid.testapp') def test_remove_app(self) -> None: - self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) + assert self.driver.is_app_installed(APIDEMO_PKG_NAME) self.driver.remove_app(APIDEMO_PKG_NAME) - self.assertFalse(self.driver.is_app_installed(APIDEMO_PKG_NAME)) + assert not self.driver.is_app_installed(APIDEMO_PKG_NAME) def test_close_and_launch_app(self) -> None: self.driver.close_app() self.driver.launch_app() activity = self.driver.current_activity - self.assertEqual('.ApiDemos', activity) + assert '.ApiDemos' == activity def test_app_management(self) -> None: app_id = self.driver.current_package - self.assertEqual(self.driver.query_app_state(app_id), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(app_id) == ApplicationState.RUNNING_IN_FOREGROUND self.driver.background_app(-1) - self.assertTrue(self.driver.query_app_state(app_id) < - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(app_id) < ApplicationState.RUNNING_IN_FOREGROUND self.driver.activate_app(app_id) - self.assertEqual(self.driver.query_app_state(app_id), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(app_id) == ApplicationState.RUNNING_IN_FOREGROUND def test_app_strings(self) -> None: strings = self.driver.app_strings() - self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) + assert u'You can\'t wipe my data, you are a monkey!' == strings[u'monkey_wipe_data'] def test_app_strings_with_language(self) -> None: strings = self.driver.app_strings('en') - self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) + assert u'You can\'t wipe my data, you are a monkey!' == strings[u'monkey_wipe_data'] def test_app_strings_with_language_and_file(self) -> None: strings = self.driver.app_strings('en', 'some_file') - self.assertEqual(u'You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data']) + assert u'You can\'t wipe my data, you are a monkey!' == strings[u'monkey_wipe_data'] def test_reset(self) -> None: self.driver.reset() - self.assertTrue(self.driver.is_app_installed(APIDEMO_PKG_NAME)) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ApplicationsTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert self.driver.is_app_installed(APIDEMO_PKG_NAME) diff --git a/test/functional/android/chrome_tests.py b/test/functional/android/chrome_tests.py index 971e4ebf..08767349 100644 --- a/test/functional/android/chrome_tests.py +++ b/test/functional/android/chrome_tests.py @@ -12,29 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from appium import webdriver from .helper.desired_capabilities import get_desired_capabilities -class ChromeTests(unittest.TestCase): - def setUp(self) -> None: +class TestChrome(object): + def setup_method(self) -> None: caps = get_desired_capabilities() caps['browserName'] = 'Chrome' self.driver = webdriver.Remote('http://localhost:4723/wd/hub', caps) - def tearDown(self) -> None: + def teardown_method(self) -> None: self.driver.quit() def test_find_single_element(self) -> None: self.driver.get('http://10.0.2.2:4723/test/guinea-pig') self.driver.find_element_by_link_text('i am a link').click() - self.assertTrue('I am some other page content' in self.driver.page_source) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ChromeTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert 'I am some other page content' in self.driver.page_source diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index 4da8f793..7768c376 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -13,9 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep +import pytest from selenium.common.exceptions import NoSuchElementException from appium.webdriver.common.mobileby import MobileBy @@ -28,28 +28,27 @@ ) -class CommonTests(BaseTestCase): +class TestCommon(BaseTestCase): def test_current_package(self) -> None: - self.assertEqual(APIDEMO_PKG_NAME, self.driver.current_package) + assert APIDEMO_PKG_NAME == self.driver.current_package + @pytest.mark.skip('Not sure how to set this up to run') def test_end_test_coverage(self) -> None: - self.skipTest('Not sure how to set this up to run') self.driver.end_test_coverage(intent='android.intent.action.MAIN', path='') sleep(5) + # TODO Due to unexpected dialog, "System UI isn't responding" + @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI.') def test_open_notifications(self) -> None: - if is_ci(): - # TODO Due to unexpected dialog, "System UI isn't responding" - self.skipTest('Need to fix flaky test during running on CI.') for word in ['App', 'Notification', 'Status Bar', ':-|']: wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, f'new UiSelector().text("{word}")').click() self.driver.open_notifications() sleep(1) - self.assertRaises(NoSuchElementException, - self.driver.find_element_by_android_uiautomator, 'new UiSelector().text(":-|")') + with pytest.raises(NoSuchElementException): + self.driver.find_element_by_android_uiautomator, 'new UiSelector().text(":-|")' els = self.driver.find_elements_by_class_name('android.widget.TextView') # sometimes numbers shift @@ -61,14 +60,9 @@ def test_open_notifications(self) -> None: title = True elif text == 'I am ok': body = True - self.assertTrue(title) - self.assertTrue(body) + assert title + assert body self.driver.keyevent(4) sleep(1) self.driver.find_element_by_android_uiautomator('new UiSelector().text(":-|")') - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(CommonTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index 2b42e55d..c358866a 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - import pytest from appium import webdriver @@ -23,43 +21,39 @@ @pytest.mark.skip(reason="Need to fix broken test") -class ContextSwitchingTests(unittest.TestCase): - def setUp(self) -> None: +class TestContextSwitching(object): + def setup_method(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('selendroid-test-app.apk') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - def tearDown(self) -> None: + def teardown_method(self) -> None: self.driver.quit() def test_contexts_list(self) -> None: self._enter_webview() contexts = self.driver.contexts - self.assertEqual(2, len(contexts)) + assert 2 == len(contexts) def test_move_to_correct_context(self) -> None: self._enter_webview() - self.assertEqual('WEBVIEW_io.selendroid.testapp', self.driver.current_context) + assert 'WEBVIEW_io.selendroid.testapp' == self.driver.current_context def test_actually_in_webview(self) -> None: self._enter_webview() self.driver.find_element_by_css_selector('input[type=submit]').click() el = self.driver.find_element_by_xpath("//h1[contains(., 'This is my way')]") - self.assertIsNot(None, el) + assert el is not None def test_move_back_to_native_context(self) -> None: self._enter_webview() self.driver.switch_to.context(None) - self.assertEqual('NATIVE_APP', self.driver.current_context) + assert 'NATIVE_APP' == self.driver.current_context def test_set_invalid_context(self) -> None: - self.assertRaises(NoSuchContextException, self.driver.switch_to.context, 'invalid name') + with pytest.raises(NoSuchContextException): + self.driver.switch_to.context('invalid name') def _enter_webview(self) -> None: btn = self.driver.find_element_by_name('buttonStartWebviewCD') btn.click() self.driver.switch_to.context('WEBVIEW') - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ContextSwitchingTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/device_time_tests.py b/test/functional/android/device_time_tests.py index 736bf7c6..b5d576fb 100644 --- a/test/functional/android/device_time_tests.py +++ b/test/functional/android/device_time_tests.py @@ -13,20 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from dateutil.parser import parse from .helper.test_helper import BaseTestCase -class DeviceTimeTests(BaseTestCase): +class TestDeviceTime(BaseTestCase): def test_device_time(self) -> None: date_time = self.driver.device_time # convert to date ought to work parse(date_time) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(DeviceTimeTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/finger_print_tests.py b/test/functional/android/finger_print_tests.py index cc0457c2..9b8871d9 100644 --- a/test/functional/android/finger_print_tests.py +++ b/test/functional/android/finger_print_tests.py @@ -13,17 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import BaseTestCase -class FingerPrintTests(BaseTestCase): +class TestFingerPrint(BaseTestCase): def test_finger_print(self) -> None: result = self.driver.finger_print(1) - self.assertEqual(None, result) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FingerPrintTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert result is None diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index c8b4af27..026ea9e3 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -15,7 +15,6 @@ import base64 import os -import unittest from typing import TYPE_CHECKING from selenium.webdriver.support import expected_conditions as EC @@ -58,18 +57,18 @@ def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int ) -class BaseTestCase(unittest.TestCase): +class BaseTestCase(): - def setUp(self) -> None: + def setup_method(self, method) -> None: # type: ignore desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) if is_ci(): self.driver.start_recording_screen() - def tearDown(self) -> None: + def teardown_method(self, method) -> None: # type: ignore if is_ci(): payload = self.driver.stop_recording_screen() - video_path = os.path.join(os.getcwd(), self._testMethodName + '.mp4') + video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') with open(video_path, "wb") as fd: fd.write(base64.b64decode(payload)) self.driver.quit() diff --git a/test/functional/android/hw_actions_tests.py b/test/functional/android/hw_actions_tests.py index 2a6b14e8..4646d254 100644 --- a/test/functional/android/hw_actions_tests.py +++ b/test/functional/android/hw_actions_tests.py @@ -13,23 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from .helper.test_helper import BaseTestCase -class HwActionsTests(BaseTestCase): +class TestHwActions(BaseTestCase): def test_lock(self) -> None: self.driver.lock(-1) sleep(10) try: - self.assertTrue(self.driver.is_locked()) + assert self.driver.is_locked() finally: self.driver.unlock() - self.assertFalse(self.driver.is_locked()) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(HwActionsTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert not self.driver.is_locked() diff --git a/test/functional/android/ime_tests.py b/test/functional/android/ime_tests.py index ade988c6..ea0f6ea0 100644 --- a/test/functional/android/ime_tests.py +++ b/test/functional/android/ime_tests.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from .helper.test_helper import BaseTestCase @@ -22,36 +21,31 @@ GOOGLE_LATIN = 'com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME' # Android O/P -class IMETests(BaseTestCase): +class TestIME(BaseTestCase): def test_available_ime_engines(self) -> None: engines = self.driver.available_ime_engines - self.assertIsInstance(engines, list) - self.assertTrue(ANDROID_LATIN in engines or GOOGLE_LATIN in engines) + assert isinstance(engines, list) + assert ANDROID_LATIN in engines or GOOGLE_LATIN in engines def test_is_ime_active(self) -> None: - self.assertTrue(self.driver.is_ime_active()) + assert self.driver.is_ime_active() def test_active_ime_engine(self) -> None: engines = self.driver.available_ime_engines - self.assertTrue(self.driver.active_ime_engine in engines) + assert self.driver.active_ime_engine in engines def test_activate_ime_engine(self) -> None: engines = self.driver.available_ime_engines self.driver.activate_ime_engine(engines[-1]) - self.assertEqual(self.driver.active_ime_engine, engines[-1]) + assert self.driver.active_ime_engine == engines[-1] def test_deactivate_ime_engine(self) -> None: engines = self.driver.available_ime_engines self.driver.activate_ime_engine(engines[-1]) - self.assertEqual(self.driver.active_ime_engine, engines[-1]) + assert self.driver.active_ime_engine == engines[-1] self.driver.deactivate_ime_engine() sleep(1) - self.assertNotEqual(self.driver.active_ime_engine, engines[-1]) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(IMETests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert self.driver.active_ime_engine != engines[-1] diff --git a/test/functional/android/keyboard_tests.py b/test/functional/android/keyboard_tests.py index 53a5d48c..5e1ebe6a 100644 --- a/test/functional/android/keyboard_tests.py +++ b/test/functional/android/keyboard_tests.py @@ -13,12 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import BaseTestCase -class KeyboardTests(BaseTestCase): +class TestKeyboard(BaseTestCase): def test_press_keycode(self) -> None: # TODO not sure how to test this. self.driver.press_keycode(176) @@ -26,8 +24,3 @@ def test_press_keycode(self) -> None: def test_long_press_keycode(self) -> None: # TODO not sure how to test this. self.driver.long_press_keycode(176) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(KeyboardTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/location_tests.py b/test/functional/android/location_tests.py index 68603385..30cd7880 100644 --- a/test/functional/android/location_tests.py +++ b/test/functional/android/location_tests.py @@ -13,16 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import BaseTestCase -class LocationTests(BaseTestCase): +class TestLocation(BaseTestCase): def test_toggle_location_services(self) -> None: self.driver.toggle_location_services() # TODO Add assert - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(LocationTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/log_event_tests.py b/test/functional/android/log_event_tests.py index cc3c2b62..9ad0b53d 100644 --- a/test/functional/android/log_event_tests.py +++ b/test/functional/android/log_event_tests.py @@ -13,19 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import BaseTestCase -class LogEventTests(BaseTestCase): +class TestLogEvent(BaseTestCase): def test_log_event(self) -> None: vendor = 'appium' event = 'funEvent' self.driver.log_event(vendor, event) assert f'{vendor}:{event}' in self.driver.get_events().keys() - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(LogEventTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 9904ccbf..24b4b404 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from appium.webdriver.common.mobileby import MobileBy @@ -22,7 +21,7 @@ from .helper.test_helper import BaseTestCase, wait_for_element -class MultiActionTests(BaseTestCase): +class TestMultiAction(BaseTestCase): def test_parallel_actions(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') el2 = self.driver.find_element_by_accessibility_id('Animation') @@ -117,8 +116,3 @@ def test_driver_multi_tap(self) -> None: # THE TEST MUST BE WATCHED TO CHECK IF IT WORKS self.driver.tap(positions) sleep(10) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(MultiActionTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/network_connection_tests.py b/test/functional/android/network_connection_tests.py index 73d3816d..e0a5fb7d 100644 --- a/test/functional/android/network_connection_tests.py +++ b/test/functional/android/network_connection_tests.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +import pytest from appium.webdriver.connectiontype import ConnectionType @@ -21,19 +21,13 @@ from .helper.test_helper import BaseTestCase -class NetworkConnectionTests(BaseTestCase): +class TestNetworkConnection(BaseTestCase): def test_get_network_connection(self) -> None: nc = self.driver.network_connection - self.assertIsInstance(nc, int) + assert isinstance(nc, int) + @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI') def test_set_network_connection(self) -> None: - if is_ci(): - self.skipTest('Need to fix flaky test during running on CI') nc = self.driver.set_network_connection(ConnectionType.DATA_ONLY) - self.assertIsInstance(nc, int) - self.assertEqual(nc, ConnectionType.DATA_ONLY) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(NetworkConnectionTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert isinstance(nc, int) + assert nc == ConnectionType.DATA_ONLY diff --git a/test/functional/android/remote_fs_tests.py b/test/functional/android/remote_fs_tests.py index db6f3f1e..a71fe30c 100644 --- a/test/functional/android/remote_fs_tests.py +++ b/test/functional/android/remote_fs_tests.py @@ -15,14 +15,13 @@ import base64 import os import random -import unittest from io import BytesIO from zipfile import ZipFile from .helper.test_helper import BaseTestCase -class RemoteFsTests(BaseTestCase): +class TestRemoteFs(BaseTestCase): def test_push_pull_file(self) -> None: dest_path = '/data/local/tmp/test_push_file.txt' data = bytes('This is the contents of the file to push to the device.', 'utf-8') @@ -30,7 +29,7 @@ def test_push_pull_file(self) -> None: self.driver.push_file(dest_path, base64.b64encode(data).decode('utf-8')) data_ret = base64.b64decode(self.driver.pull_file(dest_path)) - self.assertEqual(data, data_ret) + assert data == data_ret def test_pull_folder(self) -> None: data = bytes('random string data {}'.format(random.randint(0, 1000)), 'utf-8') @@ -43,7 +42,7 @@ def test_pull_folder(self) -> None: with ZipFile(BytesIO(base64.b64decode(folder))) as fzip: for filename in ['1.txt', '2.txt']: - self.assertTrue(filename in fzip.namelist()) + assert filename in fzip.namelist() def test_push_file_with_src_path(self) -> None: test_files = ['test_image.jpg', 'test_file.txt'] @@ -56,9 +55,4 @@ def test_push_file_with_src_path(self) -> None: self.driver.push_file(dest_path, source_path=src_path) new_data = base64.b64decode(self.driver.pull_file(dest_path)) - self.assertEqual(original_data, new_data) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(RemoteFsTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert original_data == new_data diff --git a/test/functional/android/screen_record_tests.py b/test/functional/android/screen_record_tests.py index cad3b72b..49618a89 100644 --- a/test/functional/android/screen_record_tests.py +++ b/test/functional/android/screen_record_tests.py @@ -13,20 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from .helper.test_helper import BaseTestCase -class ScreenRecordTests(BaseTestCase): +class TestScreenRecord(BaseTestCase): def test_screen_record(self) -> None: self.driver.start_recording_screen(timeLimit=10, forcedRestart=True) sleep(10) result = self.driver.stop_recording_screen() - self.assertTrue(len(result) > 0) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ScreenRecordTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert len(result) > 0 diff --git a/test/functional/android/search_context/find_by_accessibility_id_tests.py b/test/functional/android/search_context/find_by_accessibility_id_tests.py index b3a64507..6932dba2 100644 --- a/test/functional/android/search_context/find_by_accessibility_id_tests.py +++ b/test/functional/android/search_context/find_by_accessibility_id_tests.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +import pytest from appium.webdriver.common.mobileby import MobileBy from test.functional.android.helper.test_helper import ( @@ -22,37 +22,31 @@ from test.functional.test_helper import is_ci -class FindByAccessibilityIDTests(BaseTestCase): +class TestFindByAccessibilityID(BaseTestCase): def test_find_single_element(self) -> None: wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")').click() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Task Take out Trash') - self.assertIsNotNone(el) + assert el is not None def test_find_multiple_elements(self) -> None: els = self.driver.find_elements_by_accessibility_id('Accessibility') - self.assertIsInstance(els, list) + assert isinstance(els, list) + @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI') def test_element_find_single_element(self) -> None: - if is_ci(): - self.skipTest('Need to fix flaky test during running on CI') wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")').click() el = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') sub_el = el.find_element_by_accessibility_id('Task Take out Trash') - self.assertIsNotNone(sub_el) + assert sub_el is not None def test_element_find_multiple_elements(self) -> None: wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') el = self.driver.find_element_by_class_name('android.widget.ListView') sub_els = el.find_elements_by_accessibility_id('Animation') - self.assertIsInstance(sub_els, list) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByAccessibilityIDTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert isinstance(sub_els, list) diff --git a/test/functional/android/search_context/find_by_image_tests.py b/test/functional/android/search_context/find_by_image_tests.py index 8d3440f5..03b552c2 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -13,7 +13,6 @@ # limitations under the License. import base64 -import unittest import pytest from selenium.common.exceptions import NoSuchElementException, TimeoutException @@ -26,9 +25,9 @@ @pytest.mark.skip(reason="Need to fix broken test") -class FindByImageTests(unittest.TestCase): +class TestFindByImage(object): - def setUp(self) -> None: + def setup_method(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) @@ -37,7 +36,7 @@ def setUp(self) -> None: "fixImageTemplateSize": True, "autoUpdateImageElementPosition": True}) - def tearDown(self) -> None: + def teardown_method(self) -> None: self.driver.quit() def test_find_based_on_image_template(self) -> None: @@ -50,17 +49,17 @@ def test_find_based_on_image_template(self) -> None: EC.presence_of_element_located((By.IMAGE, b64_data)) ) size = el.size - self.assertIsNotNone(size['width']) - self.assertIsNotNone(size['height']) + assert size['width'] is not None + assert size['height'] is not None loc = el.location - self.assertIsNotNone(loc['x']) - self.assertIsNotNone(loc['y']) + assert loc['x'] is not None + assert loc['y'] is not None rect = el.rect - self.assertIsNotNone(rect['width']) - self.assertIsNotNone(rect['height']) - self.assertIsNotNone(rect['x']) - self.assertIsNotNone(rect['y']) - self.assertTrue(el.is_displayed()) + assert rect['width'] is not None + assert rect['height'] is not None + assert rect['x'] is not None + assert rect['y'] is not None + assert el.is_displayed() el.click() self.driver.find_element_by_accessibility_id("Alarm") @@ -78,14 +77,9 @@ def test_find_throws_no_such_element(self) -> None: with open(image_path, 'rb') as png_file: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): WebDriverWait(self.driver, 3).until( EC.presence_of_element_located((By.IMAGE, b64_data)) ) - with self.assertRaises(NoSuchElementException): + with pytest.raises(NoSuchElementException): self.driver.find_element_by_image(image_path) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByImageTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/android/search_context/find_by_uiautomator_tests.py b/test/functional/android/search_context/find_by_uiautomator_tests.py index 1e197e8e..2c23cfee 100644 --- a/test/functional/android/search_context/find_by_uiautomator_tests.py +++ b/test/functional/android/search_context/find_by_uiautomator_tests.py @@ -12,41 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - import pytest from test.functional.android.helper.test_helper import BaseTestCase @pytest.mark.skip(reason="Need to fix flaky test") -class FindByUIAutomatorTests(BaseTestCase): +class TestFindByUIAutomator(BaseTestCase): def test_find_single_element(self) -> None: el = self.driver.find_element_by_android_uiautomator('new UiSelector().text("Animation")') - self.assertIsNotNone(el) + assert el is not None def test_find_multiple_elements(self) -> None: els = self.driver.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') - self.assertIsInstance(els, list) + assert isinstance(els, list) def test_element_find_single_element(self) -> None: el = self.driver.find_element_by_class_name('android.widget.ListView') sub_el = el.find_element_by_android_uiautomator('new UiSelector().description("Animation")') - self.assertIsNotNone(sub_el) + assert sub_el is not None def test_element_find_multiple_elements(self) -> None: el = self.driver.find_element_by_class_name('android.widget.ListView') sub_els = el.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') - self.assertIsInstance(sub_els, list) + assert isinstance(sub_els, list) def test_scroll_into_view(self) -> None: el = self.driver.find_element_by_android_uiautomator( 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));') el.click() - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByUIAutomatorTests) - unittest.TextTestRunner(verbosity=2).run(suite) + # TODO Add assert diff --git a/test/functional/android/settings_tests.py b/test/functional/android/settings_tests.py index 98f3d667..ac600a3c 100644 --- a/test/functional/android/settings_tests.py +++ b/test/functional/android/settings_tests.py @@ -13,22 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from .helper.test_helper import BaseTestCase -class SettingsTests(BaseTestCase): +class TestSettings(BaseTestCase): def test_get_settings(self) -> None: settings = self.driver.get_settings() - self.assertIsNotNone(settings) + assert settings is not None def test_update_settings(self) -> None: self.driver.update_settings({"waitForIdleTimeout": 10001}) settings = self.driver.get_settings() - self.assertEqual(settings["waitForIdleTimeout"], 10001) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(SettingsTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert settings["waitForIdleTimeout"] == 10001 diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 22c11bba..59ee65a4 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - +import pytest from selenium.common.exceptions import NoSuchElementException from appium.webdriver.common.mobileby import MobileBy @@ -26,13 +25,13 @@ ) -class TouchActionTests(BaseTestCase): +class TestTouchAction(BaseTestCase): def test_tap(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') action = TouchAction(self.driver) action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') - self.assertIsNotNone(el) + assert el is not None def test_tap_x_y(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') @@ -40,7 +39,7 @@ def test_tap_x_y(self) -> None: action.tap(el, 100, 10).perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') - self.assertIsNotNone(el) + assert el is not None def test_tap_twice(self) -> None: el = self.driver.find_element_by_accessibility_id('Text') @@ -54,7 +53,7 @@ def test_tap_twice(self) -> None: action.tap(el, count=2).perform() els = self.driver.find_elements_by_class_name('android.widget.TextView') - self.assertEqual('This is a test\nThis is a test\n', els[1].get_attribute("text")) + assert 'This is a test\nThis is a test\n' == els[1].get_attribute("text") def test_press_and_immediately_release(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') @@ -62,7 +61,7 @@ def test_press_and_immediately_release(self) -> None: action.press(el).release().perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') - self.assertIsNotNone(el) + assert el is not None def test_press_and_immediately_release_x_y(self) -> None: el = self.driver.find_element_by_accessibility_id('Animation') @@ -70,7 +69,7 @@ def test_press_and_immediately_release_x_y(self) -> None: action.press(el, 100, 10).release().perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') - self.assertIsNotNone(el) + assert el is not None def test_press_and_wait(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -95,7 +94,7 @@ def test_press_and_wait(self) -> None: # 'Sample menu' only comes up with a long press, not a press el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') - self.assertIsNotNone(el) + assert el is not None def test_press_and_moveto(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -105,7 +104,7 @@ def test_press_and_moveto(self) -> None: action.press(el1).move_to(el2).release().perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') - self.assertIsNotNone(el) + assert el is not None def test_press_and_moveto_x_y(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -115,7 +114,7 @@ def test_press_and_moveto_x_y(self) -> None: action.press(el1).move_to(el2, 100, 100).release().perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') - self.assertIsNotNone(el) + assert el is not None def test_long_press(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -140,7 +139,7 @@ def test_long_press(self) -> None: # 'Sample menu' only comes up with a long press, not a tap el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') - self.assertIsNotNone(el) + assert el is not None def test_long_press_x_y(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -165,7 +164,7 @@ def test_long_press_x_y(self) -> None: # 'Sample menu' only comes up with a long press, not a tap el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') - self.assertIsNotNone(el) + assert el is not None def test_drag_and_drop(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -186,7 +185,7 @@ def test_drag_and_drop(self) -> None: action.long_press(dd3).move_to(dd2).release().perform() el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) - self.assertTrue('drag_dot_3' in el.text) + assert 'drag_dot_3' in el.text def test_driver_drag_and_drop(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Content') @@ -206,20 +205,16 @@ def test_driver_drag_and_drop(self) -> None: self.driver.drag_and_drop(dd3, dd2) el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) - self.assertTrue('drag_dot_3' in el.text) + assert 'drag_dot_3' in el.text def test_driver_swipe(self) -> None: el = self.driver.find_element_by_accessibility_id('Views') action = TouchAction(self.driver) action.tap(el).perform() - self.assertRaises(NoSuchElementException, self.driver.find_element_by_accessibility_id, 'ImageView') + with pytest.raises(NoSuchElementException): + self.driver.find_element_by_accessibility_id('ImageView') self.driver.swipe(100, 1000, 100, 100, 800) el = self.driver.find_element_by_accessibility_id('ImageView') - self.assertIsNotNone(el) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(TouchActionTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert el is not None diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index bb28782f..d9c484c4 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from appium.webdriver.common.mobileby import MobileBy from .helper.test_helper import ( @@ -24,12 +22,12 @@ ) -class WebelementTests(BaseTestCase): +class TestWebelement(BaseTestCase): def test_element_location_in_view(self) -> None: el = self.driver.find_element_by_accessibility_id('Content') loc = el.location_in_view - self.assertIsNotNone(loc['x']) - self.assertIsNotNone(loc['y']) + assert loc['x'] is not None + assert loc['y'] is not None def test_set_text(self) -> None: self.driver.find_element_by_android_uiautomator( @@ -42,7 +40,7 @@ def test_set_text(self) -> None: el.send_keys('original text') el.set_text('new text') - self.assertEqual('new text', el.text) + assert 'new text' == el.text def test_send_keys(self) -> None: for text in ['App', 'Activity', 'Custom Title']: @@ -52,9 +50,4 @@ def test_send_keys(self) -> None: el = wait_for_element(self.driver, MobileBy.ID, '{}:id/left_text_edit'.format(APIDEMO_PKG_NAME)) el.send_keys(' text') - self.assertEqual('Left is best text', el.text) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(WebelementTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert 'Left is best text' == el.text diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index 362ab42e..c3b3ef1c 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -12,31 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from appium.webdriver.applicationstate import ApplicationState from test.functional.ios.helper.test_helper import BaseTestCase from .helper import desired_capabilities -class WebDriverTests(BaseTestCase): +class TestWebDriver(BaseTestCase): def test_app_management(self) -> None: # this only works in Xcode9+ if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: return - self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND self.driver.background_app(-1) - self.assertTrue(self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND self.driver.activate_app(desired_capabilities.BUNDLE_ID) - self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), - ApplicationState.RUNNING_IN_FOREGROUND) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(WebDriverTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND diff --git a/test/functional/ios/execute_driver_tests.py b/test/functional/ios/execute_driver_tests.py index cf696675..8c3a64e8 100644 --- a/test/functional/ios/execute_driver_tests.py +++ b/test/functional/ios/execute_driver_tests.py @@ -13,12 +13,11 @@ # limitations under the License. import textwrap -import unittest from test.functional.ios.helper.test_helper import BaseTestCase -class ExecuteDriverTests(BaseTestCase): +class TestExecuteDriver(BaseTestCase): def test_batch(self) -> None: script = """ const status = await driver.status(); @@ -42,8 +41,3 @@ def test_batch_combination_python_script(self) -> None: r = response.result[0].rect assert(r == response.result[1]) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ExecuteDriverTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/ios/helper/test_helper.py b/test/functional/ios/helper/test_helper.py index 1e66e46d..f78912f6 100644 --- a/test/functional/ios/helper/test_helper.py +++ b/test/functional/ios/helper/test_helper.py @@ -15,7 +15,6 @@ import base64 import os -import unittest from appium import webdriver from test.functional.test_helper import is_ci @@ -23,18 +22,18 @@ from . import desired_capabilities -class BaseTestCase(unittest.TestCase): +class BaseTestCase(object): - def setUp(self) -> None: + def setup_method(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) if is_ci(): self.driver.start_recording_screen() - def tearDown(self) -> None: + def teardown_method(self, method) -> None: # type: ignore if is_ci(): payload = self.driver.stop_recording_screen() - video_path = os.path.join(os.getcwd(), self._testMethodName + '.mp4') + video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') with open(video_path, "wb") as fd: fd.write(base64.b64decode(payload)) self.driver.quit() diff --git a/test/functional/ios/hw_actions_tests.py b/test/functional/ios/hw_actions_tests.py index 9f43392f..86264b37 100644 --- a/test/functional/ios/hw_actions_tests.py +++ b/test/functional/ios/hw_actions_tests.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from test.functional.ios.helper.test_helper import BaseTestCase -class HwActionsTests(BaseTestCase): +class TestHwActions(BaseTestCase): def test_lock(self) -> None: self.driver.lock(-1) try: - self.assertTrue(self.driver.is_locked()) + assert self.driver.is_locked() finally: self.driver.unlock() - self.assertFalse(self.driver.is_locked()) + assert not self.driver.is_locked() def test_shake(self) -> None: # TODO what can we assert about this? @@ -38,8 +36,3 @@ def test_touch_id(self) -> None: def test_toggle_touch_id_enrollment(self) -> None: # nothing to assert, just verify that it doesn't blow up self.driver.toggle_touch_id_enrollment() - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(HwActionsTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/ios/keyboard_tests.py b/test/functional/ios/keyboard_tests.py index 08ecfdfa..0ee303e0 100644 --- a/test/functional/ios/keyboard_tests.py +++ b/test/functional/ios/keyboard_tests.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from test.functional.ios.helper.test_helper import BaseTestCase -class KeyboardTests(BaseTestCase): +class TestKeyboard(BaseTestCase): def test_hide_keyboard(self) -> None: self._move_to_textbox() @@ -27,11 +26,11 @@ def test_hide_keyboard(self) -> None: el.set_value('Testing') el = self.driver.find_element_by_class_name('UIAKeyboard') - self.assertTrue(el.is_displayed()) + assert el.is_displayed() self.driver.hide_keyboard(key_name='Done') - self.assertFalse(el.is_displayed()) + assert not el.is_displayed() def test_hide_keyboard_presskey_strategy(self) -> None: self._move_to_textbox() @@ -41,11 +40,11 @@ def test_hide_keyboard_presskey_strategy(self) -> None: el.set_value('Testing') el = self.driver.find_element_by_class_name('UIAKeyboard') - self.assertTrue(el.is_displayed()) + assert el.is_displayed() self.driver.hide_keyboard(strategy='pressKey', key='Done') - self.assertFalse(el.is_displayed()) + assert not el.is_displayed() def test_hide_keyboard_no_key_name(self) -> None: self._move_to_textbox() @@ -55,13 +54,13 @@ def test_hide_keyboard_no_key_name(self) -> None: el.set_value('Testing') el = self.driver.find_element_by_class_name('UIAKeyboard') - self.assertTrue(el.is_displayed()) + assert el.is_displayed() self.driver.hide_keyboard() sleep(10) # currently fails. - self.assertFalse(el.is_displayed()) + assert not el.is_displayed() def test_is_keyboard_shown(self) -> None: self._move_to_textbox() @@ -69,7 +68,7 @@ def test_is_keyboard_shown(self) -> None: el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] el.click() el.set_value('Testing') - self.assertTrue(self.driver.is_keyboard_shown()) + assert self.driver.is_keyboard_shown() def _move_to_textbox(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Sliders') @@ -78,8 +77,3 @@ def _move_to_textbox(self) -> None: # Click text fields self.driver.find_element_by_accessibility_id('Text Fields').click() - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(KeyboardTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/ios/remote_fs_tests.py b/test/functional/ios/remote_fs_tests.py index 66cad40e..89b42568 100644 --- a/test/functional/ios/remote_fs_tests.py +++ b/test/functional/ios/remote_fs_tests.py @@ -13,12 +13,11 @@ # limitations under the License. import os -import unittest from test.functional.ios.helper.test_helper import BaseTestCase -class RemoteFsTests(BaseTestCase): +class TestRemoteFs(BaseTestCase): def test_push_file(self) -> None: file_name = 'test_image.jpg' @@ -26,8 +25,3 @@ def test_push_file(self) -> None: destination_path = file_name self.driver.push_file(destination_path, source_path=source_path) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(RemoteFsTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 08c73dd4..23d907fc 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -12,15 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from appium import webdriver from .helper.desired_capabilities import get_desired_capabilities -class SafariTests(unittest.TestCase): - def setUp(self) -> None: +class TestSafari(object): + def setup_method(self) -> None: desired_caps = get_desired_capabilities() desired_caps.update({ 'browserName': 'safari', @@ -30,19 +28,14 @@ def setUp(self) -> None: self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - def tearDown(self) -> None: + def teardown_method(self) -> None: self.driver.quit() def test_context(self) -> None: - self.assertEqual('NATIVE_APP', self.driver.contexts[0]) - self.assertTrue(self.driver.contexts[1].startswith('WEBVIEW_')) - self.assertTrue('WEBVIEW_' in self.driver.current_context) + assert 'NATIVE_APP' == self.driver.contexts[0] + assert self.driver.contexts[1].startswith('WEBVIEW_') + assert 'WEBVIEW_' in self.driver.current_context def test_get(self) -> None: self.driver.get("http://google.com") - self.assertEqual('Google', self.driver.title) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(SafariTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert 'Google' == self.driver.title diff --git a/test/functional/ios/screen_record_tests.py b/test/functional/ios/screen_record_tests.py index 33a5e98a..b821f6d3 100644 --- a/test/functional/ios/screen_record_tests.py +++ b/test/functional/ios/screen_record_tests.py @@ -12,20 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from time import sleep from test.functional.ios.helper.test_helper import BaseTestCase -class ScreenRecordTests(BaseTestCase): +class TestScreenRecord(BaseTestCase): def test_screen_record(self) -> None: self.driver.start_recording_screen() sleep(10) result = self.driver.stop_recording_screen() - self.assertTrue(len(result) > 0) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(ScreenRecordTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert len(result) > 0 diff --git a/test/functional/ios/search_context/find_by_element_webelement_tests.py b/test/functional/ios/search_context/find_by_element_webelement_tests.py index 2e81fec5..96d8ed91 100644 --- a/test/functional/ios/search_context/find_by_element_webelement_tests.py +++ b/test/functional/ios/search_context/find_by_element_webelement_tests.py @@ -12,27 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from test.functional.ios.helper.test_helper import BaseTestCase -class FindByElementWebelementTests(BaseTestCase): +class TestFindByElementWebelement(BaseTestCase): def test_find_element_by_path(self) -> None: el = self.driver.find_element_by_ios_predicate('wdName == "UICatalog"') - self.assertEqual('UICatalog', el.get_attribute('name')) + assert 'UICatalog' == el.get_attribute('name') c_el = el.find_elements_by_ios_predicate('label == "Action Sheets"') - self.assertEqual('Action Sheets', c_el[0].get_attribute('name')) + assert 'Action Sheets' == c_el[0].get_attribute('name') c_el = el.find_elements_by_ios_class_chain('**/XCUIElementTypeStaticText') - self.assertEqual('UICatalog', c_el[0].get_attribute('name')) + assert 'UICatalog' == c_el[0].get_attribute('name') c_el = el.find_elements_by_accessibility_id('UICatalog') - self.assertEqual('UICatalog', c_el[0].get_attribute('name')) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByElementWebelementTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert 'UICatalog' == c_el[0].get_attribute('name') diff --git a/test/functional/ios/search_context/find_by_ios_class_chain_tests.py b/test/functional/ios/search_context/find_by_ios_class_chain_tests.py index 3c02fb2b..7ff0a24d 100644 --- a/test/functional/ios/search_context/find_by_ios_class_chain_tests.py +++ b/test/functional/ios/search_context/find_by_ios_class_chain_tests.py @@ -12,24 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from test.functional.ios.helper.test_helper import BaseTestCase -class FindByIOClassChainTests(BaseTestCase): +class TestFindByIOClassChain(BaseTestCase): def test_find_element_by_path(self) -> None: els = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/**/XCUIElementTypeStaticText') - self.assertEqual(35, len(els)) - self.assertEqual('UICatalog', els[0].get_attribute('name')) + assert 35 == len(els) + assert 'UICatalog' == els[0].get_attribute('name') def test_find_multiple_elements_by_path(self) -> None: el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*/*') - self.assertEqual(2, len(el)) - self.assertEqual('UICatalog', el[0].get_attribute('name')) - self.assertEqual(None, el[1].get_attribute('name')) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByIOClassChainTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert 2 == len(el) + assert 'UICatalog' == el[0].get_attribute('name') + assert el[1].get_attribute('name') is None diff --git a/test/functional/ios/search_context/find_by_ios_predicate_tests.py b/test/functional/ios/search_context/find_by_ios_predicate_tests.py index 6f5bc6d9..41908479 100644 --- a/test/functional/ios/search_context/find_by_ios_predicate_tests.py +++ b/test/functional/ios/search_context/find_by_ios_predicate_tests.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - from test.functional.ios.helper.test_helper import BaseTestCase -class FindByIOSPredicateTests(BaseTestCase): +class TestFindByIOSPredicate(BaseTestCase): def test_find_element_by_name(self) -> None: # Will throw exception if element is not found self.driver.find_element_by_ios_predicate('wdName == "Buttons"') def test_find_multiple_element_by_type(self) -> None: e = self.driver.find_elements_by_ios_predicate('wdType == "XCUIElementTypeStaticText"') - self.assertNotEqual(len(e), 0) + assert len(e) != 0 def test_find_element_by_label(self) -> None: # Will throw exception if element is not found @@ -40,7 +38,7 @@ def test_find_element_by_isvisible(self) -> None: # Should not find any elements e = self.driver.find_elements_by_ios_predicate('wdValue == "Buttons" AND visible == 0') - self.assertEqual(len(e), 0) + assert len(e) == 0 def test_find_element_by_isenabled(self) -> None: # Will throw exception if element is not found @@ -48,9 +46,4 @@ def test_find_element_by_isenabled(self) -> None: # Should not find any elements e = self.driver.find_elements_by_ios_predicate('wdValue == "Buttons" AND enabled == 0') - self.assertEqual(len(e), 0) - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(FindByIOSPredicateTests) - unittest.TextTestRunner(verbosity=2).run(suite) + assert len(e) == 0 diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 7a7c5149..629190ef 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest from typing import TYPE_CHECKING +import pytest from selenium.webdriver.support.ui import WebDriverWait from appium import webdriver @@ -29,12 +29,11 @@ from appium.webdriver.webdriver import WebDriver -class WebDriverTests(BaseTestCase): +class TestWebDriver(BaseTestCase): + # TODO Due to not created 2nd session somehow + @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI.') def test_all_sessions(self) -> None: - if is_ci(): - # TODO Due to not created 2nd session somehow - self.skipTest('Need to fix flaky test during running on CI.') port = get_available_from_port_range(8200, 8300) desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') desired_caps['deviceName'] = 'iPhone Xs Max' @@ -51,7 +50,7 @@ def __call__(self, driver: 'WebDriver') -> bool: driver2 = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) WebDriverWait( driver2, session_counts_is_two.TIMEOUT).until(session_counts_is_two()) - self.assertEqual(2, len(self.driver.all_sessions)) + assert len(self.driver.all_sessions) == 2 finally: if driver2 is not None: driver2.quit() @@ -61,14 +60,11 @@ def test_app_management(self) -> None: if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: return - self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND self.driver.background_app(-1) - self.assertTrue(self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND self.driver.activate_app(desired_capabilities.BUNDLE_ID) - self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND def test_clear(self) -> None: self._move_to_textbox() @@ -78,7 +74,7 @@ def test_clear(self) -> None: # Verify default text def_text = 'Placeholder text' text = el.get_attribute('value') - self.assertEqual(text, def_text) + assert text == def_text # Input some text, verify input_text = 'blah' @@ -87,20 +83,19 @@ def test_clear(self) -> None: # TODO Needs to get the element again to update value in the element. Remove below one line when it's fixed. el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] text = el.get_attribute('value') - self.assertEqual(text, input_text) + assert text == input_text # Clear text, verify el.clear() text = el.get_attribute('value') - self.assertEqual(text, def_text) + assert text == def_text def test_press_button(self) -> None: self.driver.press_button("Home") if float(desired_capabilities.get_desired_capabilities( desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: return - self.assertEqual(self.driver.query_app_state(desired_capabilities.BUNDLE_ID), - ApplicationState.RUNNING_IN_FOREGROUND) + assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND def _move_to_textbox(self) -> None: el1 = self.driver.find_element_by_accessibility_id('Sliders') @@ -109,8 +104,3 @@ def _move_to_textbox(self) -> None: # Click text fields self.driver.find_element_by_accessibility_id('Text Fields').click() - - -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(WebDriverTests) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 40e458d5..87e45d18 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -275,7 +275,7 @@ def exceptionCallback(request, uri, headers): class SubWebDriver(WebDriver): def __init__(self, command_executor, desired_capabilities, direct_connection=False): - super(SubWebDriver, self).__init__( + super().__init__( command_executor=command_executor, desired_capabilities=desired_capabilities, direct_connection=direct_connection @@ -284,7 +284,7 @@ def __init__(self, command_executor, desired_capabilities, direct_connection=Fal class SubSubWebDriver(SubWebDriver): def __init__(self, command_executor, desired_capabilities, direct_connection=False): - super(SubSubWebDriver, self).__init__( + super().__init__( command_executor=command_executor, desired_capabilities=desired_capabilities, direct_connection=direct_connection From d676eed8979ce61901baf9c00ac03eed2b4e44f7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2020 00:18:25 +0900 Subject: [PATCH 07/12] Update mock requirement from ~=3.0 to ~=4.0 (#502) Updates the requirements on [mock](https://github.com/testing-cabal/mock) to permit the latest version. - [Release notes](https://github.com/testing-cabal/mock/releases) - [Changelog](https://github.com/testing-cabal/mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/testing-cabal/mock/compare/3.0.0...4.0.0) Signed-off-by: dependabot-preview[bot] --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 78bdce7b..dc9ed100 100644 --- a/Pipfile +++ b/Pipfile @@ -19,7 +19,7 @@ tox-travis = "~=0.12" httpretty = "~=0.9" python-dateutil = "~=2.8" -mock = "~=3.0" +mock = "~=4.0" pylint = "~=2.4" astroid = "~=2.3" From 10c2062614d67853a2c340a597789231ce6f1b63 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Wed, 12 Feb 2020 00:02:31 +0900 Subject: [PATCH 08/12] Add 'from' to except (#503) --- appium/webdriver/appium_service.py | 2 +- appium/webdriver/errorhandler.py | 2 +- appium/webdriver/extensions/remote_fs.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index d2aedadb..8aecc145 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -104,7 +104,7 @@ def _get_main_script(self) -> Union[str, bytes]: '-e', 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)]).strip() except sp.CalledProcessError as e: - raise AppiumServiceError(e.output) + raise AppiumServiceError(e.output) from e return self._main_script @staticmethod diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index 11359e4d..8612d86a 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -26,6 +26,6 @@ def check_response(self, response: Dict) -> None: super().check_response(response) except WebDriverException as wde: if wde.msg == 'No such context found.': - raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) + raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) from wde else: raise wde diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index 4f8ff871..9176f710 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -73,9 +73,9 @@ def push_file(self, destination_path: str, try: with open(source_path, 'rb') as f: file_data = f.read() - except IOError: + except IOError as e: message = f'source_path "{source_path}" could not be found. Are you sure the file exists?' - raise InvalidArgumentException(message) + raise InvalidArgumentException(message) from e base64data = base64.b64encode(file_data).decode('utf-8') data = { From 58bec442a65a1a6af141ccd71c0002aff704cda5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 20 Feb 2020 08:31:13 +0900 Subject: [PATCH 09/12] Update pre-commit requirement from ~=1.21 to ~=2.1 (#506) Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v1.21.0...v2.1.0) Signed-off-by: dependabot-preview[bot] --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index dc9ed100..b07ffb04 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=1.21" +pre-commit = "~=2.1" [packages] selenium = "~=3.141" From 4bc867792f1cb512685bf48112bb37e18fa7367b Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Mon, 2 Mar 2020 23:29:03 +0900 Subject: [PATCH 10/12] doc: Add script to generate sphinx doc (#508) * Add quickstart template files * Update conf file * Update * Update settings * Change project name * Add script to generate docs * Changed header title * Add new line to usage section * Add py.typed file(PEP561) * Replace \n with new line * tweak * Use sphinx format for tables --- .gitignore | 3 +- appium/webdriver/common/multi_action.py | 6 + appium/webdriver/connectiontype.py | 22 ++- .../webdriver/extensions/android/network.py | 22 ++- appium/webdriver/extensions/android/power.py | 1 + appium/webdriver/extensions/device_time.py | 1 + appium/webdriver/extensions/execute_driver.py | 2 + appium/webdriver/extensions/log_event.py | 1 + appium/webdriver/py.typed | 0 appium/webdriver/webelement.py | 2 + docs/Makefile | 20 +++ docs/README.md | 21 +++ docs/conf.py | 59 +++++++ docs/generate.sh | 4 + docs/index.rst | 21 +++ docs/make.bat | 35 ++++ docs/webdriver.common.rst | 38 +++++ docs/webdriver.extensions.android.rst | 94 +++++++++++ docs/webdriver.extensions.rst | 158 ++++++++++++++++++ docs/webdriver.extensions.search_context.rst | 62 +++++++ docs/webdriver.rst | 102 +++++++++++ 21 files changed, 659 insertions(+), 15 deletions(-) create mode 100644 appium/webdriver/py.typed create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/conf.py create mode 100644 docs/generate.sh create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/webdriver.common.rst create mode 100644 docs/webdriver.extensions.android.rst create mode 100644 docs/webdriver.extensions.rst create mode 100644 docs/webdriver.extensions.search_context.rst create mode 100644 docs/webdriver.rst diff --git a/.gitignore b/.gitignore index c5b30b79..e4760485 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *._* *.log *.log.* +_*/ *.pyc *.egg-info @@ -23,4 +24,4 @@ __pycache__ venv* .tox -Pipfile.lock \ No newline at end of file +Pipfile.lock diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index e6a84cfe..692decad 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -45,8 +45,11 @@ def add(self, *touch_actions: 'TouchAction') -> None: Usage: a1 = TouchAction(driver) + a1.press(el1).move_to(el2).release() + a2 = TouchAction(driver) + a2.press(el2).move_to(el1).release() MultiAction(driver).add(a1, a2) @@ -62,8 +65,11 @@ def perform(self: T) -> T: Usage: a1 = TouchAction(driver) + a1.press(el1).move_to(el2).release() + a2 = TouchAction(driver) + a2.press(el2).move_to(el1).release() MultiAction(driver).add(a1, a2).perform() diff --git a/appium/webdriver/connectiontype.py b/appium/webdriver/connectiontype.py index 7d477680..7db69ba3 100644 --- a/appium/webdriver/connectiontype.py +++ b/appium/webdriver/connectiontype.py @@ -16,13 +16,21 @@ """ Connection types are specified here: https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile#120 - Value (Alias) | Data | Wifi | Airplane Mode - ------------------------------------------------- - 0 (None) | 0 | 0 | 0 - 1 (Airplane Mode) | 0 | 0 | 1 - 2 (Wifi only) | 0 | 1 | 0 - 4 (Data only) | 1 | 0 | 0 - 6 (All network on) | 1 | 1 | 0 + + +--------------------+------+------+---------------+ + | Value (Alias) | Data | Wifi | Airplane Mode | + +====================+======+======+===============+ + | 0 (None) | 0 | 0 | 0 | + +--------------------+------+------+---------------+ + | 1 (Airplane Mode) | 0 | 0 | 1 | + +--------------------+------+------+---------------+ + | 2 (Wifi only) | 0 | 1 | 0 | + +--------------------+------+------+---------------+ + | 4 (Data only) | 1 | 0 | 0 | + +--------------------+------+------+---------------+ + | 6 (All network on) | 1 | 1 | 0 | + +--------------------+------+------+---------------+ + """ diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 4f874687..d95bc440 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -50,13 +50,21 @@ def set_network_connection(self, connection_type: int) -> int: """Sets the network connection type. Android only. Possible values: - Value (Alias) | Data | Wifi | Airplane Mode - ------------------------------------------------- - 0 (None) | 0 | 0 | 0 - 1 (Airplane Mode) | 0 | 0 | 1 - 2 (Wifi only) | 0 | 1 | 0 - 4 (Data only) | 1 | 0 | 0 - 6 (All network on) | 1 | 1 | 0 + + +--------------------+------+------+---------------+ + | Value (Alias) | Data | Wifi | Airplane Mode | + +====================+======+======+===============+ + | 0 (None) | 0 | 0 | 0 | + +--------------------+------+------+---------------+ + | 1 (Airplane Mode) | 0 | 0 | 1 | + +--------------------+------+------+---------------+ + | 2 (Wifi only) | 0 | 1 | 0 | + +--------------------+------+------+---------------+ + | 4 (Data only) | 1 | 0 | 0 | + +--------------------+------+------+---------------+ + | 6 (All network on) | 1 | 1 | 0 | + +--------------------+------+------+---------------+ + These are available through the enumeration `appium.webdriver.ConnectionType` Args: diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index 827324e8..381e597b 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -52,6 +52,7 @@ def set_power_ac(self, ac_state: str) -> T: Usage: self.driver.set_power_ac(Power.AC_OFF) + self.driver.set_power_ac(Power.AC_ON) Returns: diff --git a/appium/webdriver/extensions/device_time.py b/appium/webdriver/extensions/device_time.py index d16f56b3..db57f19c 100644 --- a/appium/webdriver/extensions/device_time.py +++ b/appium/webdriver/extensions/device_time.py @@ -41,6 +41,7 @@ def get_device_time(self, format: Optional[str] = None) -> str: Usage: self.driver.get_device_time() + self.driver.get_device_time("YYYY-MM-DD") Return: diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index 3b3778c3..4e96b8fe 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -34,7 +34,9 @@ def execute_driver(self, script: str, script_type: str = 'webdriverio', timeout_ Usage: self.driver.execute_driver(script='return [];') + self.driver.execute_driver(script='return [];', script_type='webdriverio') + self.driver.execute_driver(script='return [];', script_type='webdriverio', timeout_ms=10000) Returns: diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index b22c2416..d7de2eca 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -32,6 +32,7 @@ def get_events(self, type: List[str] = None) -> Dict[str, Union[str, int]]: Usage: events = driver.get_events() + events = driver.get_events(['appium:funEvent']) Returns: diff --git a/appium/webdriver/py.typed b/appium/webdriver/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index ce9e8f80..2eb191aa 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -180,7 +180,9 @@ def location_in_view(self) -> Dict[str, int]: Usage: location = element.location_in_view + x = location['x'] + y = location['y'] Returns: diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..119c8515 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +### How to generate doc + +```bash +$cd python-client/docs +$bash generate.sh +``` + +### How to check generated doc + +```bash +$cd python-client/docs +$bash generate.sh +$cd python-client/docs/_build/html +$python -m http.server 1234 +``` + +Access to `http://localhost:1234` on web browser + + +### How to deploy generated doc +See https://github.com/ki4070ma/python-client-sphinx#how-to-deploy for now diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..7d93d15d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,59 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath('../appium')) + + +# -- Project information ----------------------------------------------------- + +project = 'Python client 1.0 beta' +copyright = '2020, Appium' +author = 'Appium' + +# The full version, including alpha/beta/rc tags +release = '1.0beta' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.githubpages' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/generate.sh b/docs/generate.sh new file mode 100644 index 00000000..219b7325 --- /dev/null +++ b/docs/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +rm -rf *rst _build +sphinx-apidoc -F -H 'Appium python client' -o . ../appium/webdriver +make html diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..b5db08cc --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +.. Appium python client documentation master file, created by + sphinx-quickstart on Sun Mar 1 17:05:15 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Appium python client's documentation! +================================================ + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + webdriver + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..6247f7e2 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/webdriver.common.rst b/docs/webdriver.common.rst new file mode 100644 index 00000000..1da461fb --- /dev/null +++ b/docs/webdriver.common.rst @@ -0,0 +1,38 @@ +webdriver.common package +======================== + +Submodules +---------- + +webdriver.common.mobileby module +-------------------------------- + +.. automodule:: webdriver.common.mobileby + :members: + :undoc-members: + :show-inheritance: + +webdriver.common.multi\_action module +------------------------------------- + +.. automodule:: webdriver.common.multi_action + :members: + :undoc-members: + :show-inheritance: + +webdriver.common.touch\_action module +------------------------------------- + +.. automodule:: webdriver.common.touch_action + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webdriver.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/webdriver.extensions.android.rst b/docs/webdriver.extensions.android.rst new file mode 100644 index 00000000..55a32a1f --- /dev/null +++ b/docs/webdriver.extensions.android.rst @@ -0,0 +1,94 @@ +webdriver.extensions.android package +==================================== + +Submodules +---------- + +webdriver.extensions.android.activities module +---------------------------------------------- + +.. automodule:: webdriver.extensions.android.activities + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.common module +------------------------------------------ + +.. automodule:: webdriver.extensions.android.common + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.display module +------------------------------------------- + +.. automodule:: webdriver.extensions.android.display + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.gsm module +--------------------------------------- + +.. automodule:: webdriver.extensions.android.gsm + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.nativekey module +--------------------------------------------- + +.. automodule:: webdriver.extensions.android.nativekey + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.network module +------------------------------------------- + +.. automodule:: webdriver.extensions.android.network + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.performance module +----------------------------------------------- + +.. automodule:: webdriver.extensions.android.performance + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.power module +----------------------------------------- + +.. automodule:: webdriver.extensions.android.power + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.sms module +--------------------------------------- + +.. automodule:: webdriver.extensions.android.sms + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.android.system\_bars module +------------------------------------------------ + +.. automodule:: webdriver.extensions.android.system_bars + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webdriver.extensions.android + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/webdriver.extensions.rst b/docs/webdriver.extensions.rst new file mode 100644 index 00000000..53002b3a --- /dev/null +++ b/docs/webdriver.extensions.rst @@ -0,0 +1,158 @@ +webdriver.extensions package +============================ + +Subpackages +----------- + +.. toctree:: + + webdriver.extensions.android + webdriver.extensions.search_context + +Submodules +---------- + +webdriver.extensions.action\_helpers module +------------------------------------------- + +.. automodule:: webdriver.extensions.action_helpers + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.applications module +---------------------------------------- + +.. automodule:: webdriver.extensions.applications + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.clipboard module +------------------------------------- + +.. automodule:: webdriver.extensions.clipboard + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.context module +----------------------------------- + +.. automodule:: webdriver.extensions.context + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.device\_time module +---------------------------------------- + +.. automodule:: webdriver.extensions.device_time + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.execute\_driver module +------------------------------------------- + +.. automodule:: webdriver.extensions.execute_driver + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.execute\_mobile\_command module +---------------------------------------------------- + +.. automodule:: webdriver.extensions.execute_mobile_command + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.hw\_actions module +--------------------------------------- + +.. automodule:: webdriver.extensions.hw_actions + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.images\_comparison module +---------------------------------------------- + +.. automodule:: webdriver.extensions.images_comparison + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.ime module +------------------------------- + +.. automodule:: webdriver.extensions.ime + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.keyboard module +------------------------------------ + +.. automodule:: webdriver.extensions.keyboard + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.location module +------------------------------------ + +.. automodule:: webdriver.extensions.location + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.log\_event module +-------------------------------------- + +.. automodule:: webdriver.extensions.log_event + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.remote\_fs module +-------------------------------------- + +.. automodule:: webdriver.extensions.remote_fs + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.screen\_record module +------------------------------------------ + +.. automodule:: webdriver.extensions.screen_record + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.session module +----------------------------------- + +.. automodule:: webdriver.extensions.session + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.settings module +------------------------------------ + +.. automodule:: webdriver.extensions.settings + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webdriver.extensions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/webdriver.extensions.search_context.rst b/docs/webdriver.extensions.search_context.rst new file mode 100644 index 00000000..cff8b87e --- /dev/null +++ b/docs/webdriver.extensions.search_context.rst @@ -0,0 +1,62 @@ +webdriver.extensions.search\_context package +============================================ + +Submodules +---------- + +webdriver.extensions.search\_context.android module +--------------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.android + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.search\_context.base\_search\_context module +----------------------------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.base_search_context + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.search\_context.custom module +-------------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.custom + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.search\_context.ios module +----------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.ios + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.search\_context.mobile module +-------------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.mobile + :members: + :undoc-members: + :show-inheritance: + +webdriver.extensions.search\_context.windows module +--------------------------------------------------- + +.. automodule:: webdriver.extensions.search_context.windows + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webdriver.extensions.search_context + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/webdriver.rst b/docs/webdriver.rst new file mode 100644 index 00000000..2b20f6b2 --- /dev/null +++ b/docs/webdriver.rst @@ -0,0 +1,102 @@ +webdriver package +================= + +Subpackages +----------- + +.. toctree:: + + webdriver.common + webdriver.extensions + +Submodules +---------- + +webdriver.appium\_connection module +----------------------------------- + +.. automodule:: webdriver.appium_connection + :members: + :undoc-members: + :show-inheritance: + +webdriver.appium\_service module +-------------------------------- + +.. automodule:: webdriver.appium_service + :members: + :undoc-members: + :show-inheritance: + +webdriver.applicationstate module +--------------------------------- + +.. automodule:: webdriver.applicationstate + :members: + :undoc-members: + :show-inheritance: + +webdriver.clipboard\_content\_type module +----------------------------------------- + +.. automodule:: webdriver.clipboard_content_type + :members: + :undoc-members: + :show-inheritance: + +webdriver.connectiontype module +------------------------------- + +.. automodule:: webdriver.connectiontype + :members: + :undoc-members: + :show-inheritance: + +webdriver.errorhandler module +----------------------------- + +.. automodule:: webdriver.errorhandler + :members: + :undoc-members: + :show-inheritance: + +webdriver.mobilecommand module +------------------------------ + +.. automodule:: webdriver.mobilecommand + :members: + :undoc-members: + :show-inheritance: + +webdriver.switch\_to module +--------------------------- + +.. automodule:: webdriver.switch_to + :members: + :undoc-members: + :show-inheritance: + +webdriver.webdriver module +-------------------------- + +.. automodule:: webdriver.webdriver + :members: + :undoc-members: + :show-inheritance: + +webdriver.webelement module +--------------------------- + +.. automodule:: webdriver.webelement + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webdriver + :members: + :undoc-members: + :show-inheritance: From 78926d2a33df2222b44a6d161700d0088a17c043 Mon Sep 17 00:00:00 2001 From: Mori Atsushi Date: Mon, 27 Apr 2020 00:26:53 +0900 Subject: [PATCH 11/12] Rebase python3 branch with master (#522) * Update pytest-cov requirement from ~=2.6 to ~=2.8 (#489) Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.6.0...v2.8.1) Signed-off-by: dependabot-preview[bot] * Update autopep8 requirement from ~=1.4 to ~=1.5 (#490) Updates the requirements on [autopep8](https://github.com/hhatto/autopep8) to permit the latest version. - [Release notes](https://github.com/hhatto/autopep8/releases) - [Commits](https://github.com/hhatto/autopep8/compare/v1.4...v1.5) Signed-off-by: dependabot-preview[bot] * Update tox-travis requirement from ~=0.11 to ~=0.12 (#491) Updates the requirements on [tox-travis](https://github.com/tox-dev/tox-travis) to permit the latest version. - [Release notes](https://github.com/tox-dev/tox-travis/releases) - [Changelog](https://github.com/tox-dev/tox-travis/blob/master/HISTORY.rst) - [Commits](https://github.com/tox-dev/tox-travis/compare/0.11...0.12) Signed-off-by: dependabot-preview[bot] * Update tox requirement from ~=3.6 to ~=3.14 (#494) Updates the requirements on [tox](https://github.com/tox-dev/tox) to permit the latest version. - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/3.6.0...3.14.3) Signed-off-by: dependabot-preview[bot] * chore: Fix find_by_images_tests.py (#495) * chore: Fix find_by_images_tests.py * Add installation opencv4nodejs * Fix typo * Add taking screen record to find_by_image_test * Fix errors on the emulator * Remove unused imports * feat: Add viewmatcher (#480) * Add android view matcher as strategy locator * Add docstring * Add functional test * Remove find_elements_by_android_data_matcher * Fix docstring * tweak docstring * Bump 0.50 * Update changelog for 0.50 * Fix flaky functional tests (#473) * Run all tests * Fix apk file path * Skip find_element_by_image test cases * Skip context switching test * Skip multi tap test on CI * Change strategy for waiting element * Add functions for same steps * Restore unexpected changes * Fix touch_action_tests * Fix * Fix Fix test_driver_swipe * fix * Create _move_to_[target_view] * [test_driver_swipe] Add wait * feat: Add idempotency key header to create session requests (#514) * feat: Override send_keys without file upload function (#515) * add send_keys_direct * override send_keys * tune * add unittest instead of functional test * tweak syntax * Bump 0.51 * Update changelog for 0.51 * test: Fix test_clear flaky functional test (#519) * test: Add unit test for set_value (setImmediateValue) (#518) * chore: Fix int - str comparison error in ios desired capabilities (#517) if number >= PytestXdistWorker.COUNT: TypeError: '>=' not supported between instances of 'int' and 'str' 2. Updated test case path and iPhone model in Readme file * fix: Handling of dictionary-values in WebElement.get_attribute() (#521) * Bump 0.52 * Update changelog for 0.52 * Fix mypy error * tweak * Add wait to test * Skip tap_twice test * review comments * Remove unnecessary import Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo Co-authored-by: Mykola Mokhnach Co-authored-by: Nrupesh Patel Co-authored-by: Venkatesh Co-authored-by: Hannes Hauer --- .gitignore | 4 + CHANGELOG.rst | 165 ++++++++++++++++++ README.md | 6 +- appium/version.py | 2 +- appium/webdriver/appium_connection.py | 11 +- appium/webdriver/appium_service.py | 2 +- appium/webdriver/common/mobileby.py | 1 + .../extensions/search_context/android.py | 46 ++++- appium/webdriver/webelement.py | 21 ++- ci-jobs/functional/run_android_test.yml | 2 + ci-jobs/functional/run_appium.yml | 3 + ci-jobs/functional_test.yml | 7 +- test/functional/android/applications_tests.py | 4 +- .../android/file/find_by_image_success.png | Bin 6341 -> 5226 bytes test/functional/android/multi_action_tests.py | 61 +++---- .../search_context/find_by_image_tests.py | 23 ++- .../find_by_view_matcher_tests.py | 69 ++++++++ test/functional/android/touch_action_tests.py | 88 ++++------ .../ios/helper/desired_capabilities.py | 2 +- test/functional/ios/webdriver_tests.py | 2 + test/unit/webdriver/webelement_test.py | 100 +++++++++++ 21 files changed, 495 insertions(+), 124 deletions(-) create mode 100644 test/functional/android/search_context/find_by_view_matcher_tests.py create mode 100644 test/unit/webdriver/webelement_test.py diff --git a/.gitignore b/.gitignore index e4760485..911a3616 100644 --- a/.gitignore +++ b/.gitignore @@ -15,13 +15,17 @@ MANIFEST build dist +# Cache .cache __pycache__ .idea .pytest_cache +.mypy_cache # Virtual Environments venv* .tox Pipfile.lock + +.coverage diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f1a8ae78..94cbed85 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,171 @@ Changelog ========= +v0.52 (2020-04-23) +------------------ + +Fix +~~~ +- Handling of dictionary-values in WebElement.get_attribute() (#521) + [Hannes Hauer] + +Other +~~~~~ +- Bump 0.52. [Kazuaki Matsuo] +- Chore: Fix int - str comparison error in ios desired capabilities + (#517) [Venkatesh] + + if number >= PytestXdistWorker.COUNT: +- Test: Add unit test for set_value (setImmediateValue) (#518) [Nrupesh + Patel] +- Test: Fix test_clear flaky functional test (#519) [Nrupesh Patel] +- Update changelog for 0.51. [Kazuaki Matsuo] + + +v0.51 (2020-04-12) +------------------ +- Bump 0.51. [Kazuaki Matsuo] +- Feat: Override send_keys without file upload function (#515) [Kazuaki + Matsuo] + + * add send_keys_direct + + * override send_keys + + * tune + + * add unittest instead of functional test + + * tweak syntax +- Feat: Add idempotency key header to create session requests (#514) + [Mykola Mokhnach] +- Fix flaky functional tests (#473) [Mori Atsushi] + + * Run all tests + + * Fix apk file path + + * Skip find_element_by_image test cases + + * Skip context switching test + + * Skip multi tap test on CI + + * Change strategy for waiting element + + * Add functions for same steps + + * Restore unexpected changes + + * Fix touch_action_tests + + * Fix + + * Fix + Fix test_driver_swipe + + * fix + + * Create _move_to_[target_view] + + * [test_driver_swipe] Add wait +- Update changelog for 0.50. [Kazuaki Matsuo] + + +v0.50 (2020-02-10) +------------------ +- Bump 0.50. [Kazuaki Matsuo] +- Feat: Add viewmatcher (#480) [Mori Atsushi] + + * Add android view matcher as strategy locator + + * Add docstring + + * Add functional test + + * Remove find_elements_by_android_data_matcher + + * Fix docstring + + * tweak docstring +- Chore: Fix find_by_images_tests.py (#495) [Mori Atsushi] + + * chore: Fix find_by_images_tests.py + + * Add installation opencv4nodejs + + * Fix typo + + * Add taking screen record to find_by_image_test + + * Fix errors on the emulator + + * Remove unused imports +- Update tox requirement from ~=3.6 to ~=3.14 (#494) [dependabot- + preview[bot]] + + Updates the requirements on [tox](https://github.com/tox-dev/tox) to permit the latest version. + - [Release notes](https://github.com/tox-dev/tox/releases) + - [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/3.6.0...3.14.3) +- Update tox-travis requirement from ~=0.11 to ~=0.12 (#491) + [dependabot-preview[bot]] + + Updates the requirements on [tox-travis](https://github.com/tox-dev/tox-travis) to permit the latest version. + - [Release notes](https://github.com/tox-dev/tox-travis/releases) + - [Changelog](https://github.com/tox-dev/tox-travis/blob/master/HISTORY.rst) + - [Commits](https://github.com/tox-dev/tox-travis/compare/0.11...0.12) +- Update autopep8 requirement from ~=1.4 to ~=1.5 (#490) [dependabot- + preview[bot]] + + Updates the requirements on [autopep8](https://github.com/hhatto/autopep8) to permit the latest version. + - [Release notes](https://github.com/hhatto/autopep8/releases) + - [Commits](https://github.com/hhatto/autopep8/compare/v1.4...v1.5) +- Update pytest-cov requirement from ~=2.6 to ~=2.8 (#489) [dependabot- + preview[bot]] + + Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) + - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.6.0...v2.8.1) +- Chore: add try/catch in release script (#479) [Kazuaki Matsuo] + + * Add m and try/catch in pushing + + * fix error message + + * remove -m since it does not work for this usage +- [CI] Run with iOS 13.3 and Xcode 11.3 (#477) [Mori Atsushi] + + * [CI] Run with iOS 13.3 and Xcode 11.3 + + * Skip the case which has problem on Xcode 11.3 + + * Update FyndByIOClassChainTests along to iOS13 + + * Update FyndByElementWebelementTests along to iOS13 + + * Update KeyboardTests along to iOS13 + + * Update webdriver_tests along to iOS13 + + * Run test_find_element_by_isvisible with simpleIsVisibleCheck caps + + * Run test_hide_keyboard_no_key_name + + * Remove unused codes + + * [Readme] py.test -> pytest +- Ci: Take screen record as evidence (#481) [Mori Atsushi] + + * Take screen record for android + + * Take screen record for iOS + + * Save screen record for iOS +- Update changelog for 0.49. [Kazuaki Matsuo] + + v0.49 (2019-12-24) ------------------ - Bump 0.49. [Kazuaki Matsuo] diff --git a/README.md b/README.md index 50834ce6..d54e88ed 100644 --- a/README.md +++ b/README.md @@ -96,18 +96,18 @@ $ pytest -n 2 test/unit ### Functional ``` -$ pytest test/functional/ios/find_by_ios_class_chain_tests.py +$ pytest test/functional/ios/search_context/find_by_ios_class_chain_tests.py ``` ### In parallel for iOS -1. Create simulators named 'iPhone 6s - 8100' and 'iPhone 6s - 8101' +1. Create simulators named 'iPhone 8 - 8100' and 'iPhone 8 - 8101' 2. Install test libraries via pip ``` $ pip install pytest pytest-xdist ``` 3. Run tests ``` - $ pytest -n 2 test/functional/ios/find_by_ios_class_chain_tests.py + $ pytest -n 2 test/functional/ios/search_context/find_by_ios_class_chain_tests.py ``` # Release diff --git a/appium/version.py b/appium/version.py index fda2e626..04e6d68c 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '0.49' +version = '0.52' diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index f89998af..f643a168 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -12,19 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict +import uuid +from typing import TYPE_CHECKING, Any, Dict from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.helper import library_version +if TYPE_CHECKING: + from urllib.parse import ParseResult + class AppiumConnection(RemoteConnection): @classmethod - def get_remote_connection_headers(cls, parsed_url: str, keep_alive: bool = True) -> Dict[str, Any]: + def get_remote_connection_headers(cls, parsed_url: 'ParseResult', 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']) + if parsed_url.path.endswith('/session'): + # https://github.com/appium/appium-base-driver/pull/400 + headers['X-Idempotency-Key'] = str(uuid.uuid4()) return headers diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 8aecc145..ca190d88 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -170,7 +170,7 @@ def start(self, **kwargs: Any) -> sp.Popen: if not self.is_running or (timeout_ms > 0 and not poll_url(host, port, STATUS_URL, timeout_ms)): error_msg = f'Appium has failed to start on {host}:{port} within {timeout_ms}ms timeout' if error_msg is not None: - if stderr == sp.PIPE: + if stderr == sp.PIPE and self._process.stderr is not None: err_output = self._process.stderr.read() if err_output: error_msg += f'\nOriginal error: {str(err_output)}' diff --git a/appium/webdriver/common/mobileby.py b/appium/webdriver/common/mobileby.py index 34970a7b..2034275f 100644 --- a/appium/webdriver/common/mobileby.py +++ b/appium/webdriver/common/mobileby.py @@ -22,6 +22,7 @@ class MobileBy(By): ANDROID_UIAUTOMATOR = '-android uiautomator' ANDROID_VIEWTAG = '-android viewtag' ANDROID_DATA_MATCHER = '-android datamatcher' + ANDROID_VIEW_MATCHER = '-android viewmatcher' WINDOWS_UI_AUTOMATION = '-windows uiautomation' ACCESSIBILITY_ID = 'accessibility id' IMAGE = '-image' diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 4197eb99..a4f3a958 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, Optional +from typing import TYPE_CHECKING, Any, List, Optional from appium.webdriver.common.mobileby import MobileBy @@ -28,8 +28,42 @@ class AndroidSearchContext(BaseSearchContext): """Define search context for Android""" + def find_element_by_android_view_matcher( + self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None) -> 'WebElement': + """Finds element by [onView](https://developer.android.com/training/testing/espresso/basics) in Android + + It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver). + + Args: + name (:obj:`str`, optional): The name of a method to invoke. + The method must return a Hamcrest + [Matcher](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html) + args (:obj:`Any`, optional): The args provided to the method + className (:obj:`str`, optional): The class name that the method is part of (defaults to `org.hamcrest.Matchers`). + Can be fully qualified by having the androidx.test.espresso.matcher. prefix. + If the prefix is not provided then it is going to be added implicitly. + (e.g.: `class=CursorMatchers` fully qualified is `class=androidx.test.espresso.matcher.CursorMatchers` + + Returns: + `appium.webdriver.webelement.WebElement`: The found element + + Raises: + TypeError - Raises a TypeError if the arguments are not validated for JSON format + + Usage: + driver.find_element_by_android_view_matcher(name='withText', args=['Accessibility'], className='ViewMatchers') + + # To enable auto completion in PyCharm(IDE) + :rtype: `appium.webdriver.webelement.WebElement` + """ + + return self.find_element( + by=MobileBy.ANDROID_VIEW_MATCHER, + value=self._build_data_matcher(name=name, args=args, className=className) + ) + def find_element_by_android_data_matcher( - self, name: Optional[str] = None, args: Optional[str] = None, className: Optional[str] = None) -> 'WebElement': + self, name: Optional[str] = None, args: Optional[Any] = 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). @@ -38,7 +72,7 @@ def find_element_by_android_data_matcher( name (:obj:`str`, optional): The name of a method to invoke. The method must return a Hamcrest [Matcher](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html) - args (:obj:`str`, optional): The args provided to the method + args (:obj:`Any`, optional): The args provided to the method className (:obj:`str`, optional): The class name that the method is part of (defaults to `org.hamcrest.Matchers`). Can be fully qualified, or simple, and simple defaults to `androidx.test.espresso.matcher` package (e.g.: `class=CursorMatchers` fully qualified is `class=androidx.test.espresso.matcher.CursorMatchers` @@ -62,7 +96,7 @@ def find_element_by_android_data_matcher( ) def find_elements_by_android_data_matcher( - self, name: Optional[str] = None, args: Optional[str] = None, className: Optional[str] = None) -> List['WebElement']: + self, name: Optional[str] = None, args: Optional[Any] = 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). @@ -70,7 +104,7 @@ def find_elements_by_android_data_matcher( name (:obj:`str`, optional): The name of a method to invoke. The method must return a Hamcrest [Matcher](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html) - args (:obj:`str`, optional): The args provided to the method + args (:obj:`Any`, optional): The args provided to the method className (:obj:`str`, optional): The class name that the method is part of (defaults to `org.hamcrest.Matchers`). Can be fully qualified, or simple, and simple defaults to `androidx.test.espresso.matcher` package (e.g.: `class=CursorMatchers` fully qualified is `class=androidx.test.espresso.matcher.CursorMatchers` @@ -89,7 +123,7 @@ def find_elements_by_android_data_matcher( value=self._build_data_matcher(name=name, args=args, className=className) ) - def _build_data_matcher(self, name: Optional[str] = None, args: Optional[str] + def _build_data_matcher(self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None) -> str: result = {} diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 2eb191aa..42af922b 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -15,6 +15,7 @@ from typing import Dict, List, Optional, TypeVar, Union from selenium.webdriver.common.by import By +from selenium.webdriver.common.utils import keys_to_typing from selenium.webdriver.remote.command import Command as RemoteCommand from .extensions.search_context import AppiumWebElementSearchContext @@ -24,7 +25,7 @@ class WebElement(AppiumWebElementSearchContext): - def get_attribute(self, name: str) -> Optional[str]: + def get_attribute(self, name: str) -> Optional[Union[str, Dict]]: """Gets the given attribute or property of the element. Override for Appium @@ -56,6 +57,9 @@ def get_attribute(self, name: str) -> Optional[str]: if attributeValue is None: return None + if isinstance(attributeValue, dict): + return attributeValue + # Convert to str along to the spec if not isinstance(attributeValue, str): attributeValue = str(attributeValue) @@ -205,3 +209,18 @@ def set_value(self, value: str) -> T: } self._execute(Command.SET_IMMEDIATE_VALUE, data) return self + + # Override + def send_keys(self, *value: str) -> T: + """Simulates typing into the element. + + Args: + value (str): A string for typing. + + Returns: + `appium.webdriver.webelement.WebElement` + """ + keys = keys_to_typing(value) + self._execute(RemoteCommand.SEND_KEYS_TO_ELEMENT, + {'text': ''.join(keys), 'value': keys}) + return self diff --git a/ci-jobs/functional/run_android_test.yml b/ci-jobs/functional/run_android_test.yml index cedf2ac7..1786e8dc 100644 --- a/ci-jobs/functional/run_android_test.yml +++ b/ci-jobs/functional/run_android_test.yml @@ -7,6 +7,8 @@ jobs: CI: ${{ parameters.ci }} steps: - template: ./run_appium.yml + parameters: + OPENCV: ${{ parameters.opencv }} - script: bash ci-jobs/functional/start-emulator.sh displayName: Create and run Emulator - script: | diff --git a/ci-jobs/functional/run_appium.yml b/ci-jobs/functional/run_appium.yml index 42ebadd3..dbc773b1 100644 --- a/ci-jobs/functional/run_appium.yml +++ b/ci-jobs/functional/run_appium.yml @@ -5,6 +5,9 @@ steps: displayName: Install Node 11.x - script: npm install -g appium@beta --chromedriver_version='2.44' displayName: Install appium +- script: npm install -g opencv4nodejs + condition: and(succeeded(), eq('${{ parameters.opencv }}', true)) + displayName: Install opencv4nodejs - task: UsePythonVersion@0 inputs: versionSpec: '3.x' diff --git a/ci-jobs/functional_test.yml b/ci-jobs/functional_test.yml index cc807502..200b7f20 100644 --- a/ci-jobs/functional_test.yml +++ b/ci-jobs/functional_test.yml @@ -5,8 +5,6 @@ parameters: xcodeForIOS: 11.3 CI: true -# [Android] Need to fix and add flaky tests for activities_tests, find_by_uiautomator_tests - jobs: - template: ./functional/run_ios_test.yml parameters: @@ -32,6 +30,7 @@ jobs: testFiles: 'device_time_tests.py search_context/find_by_*.py' sdkVer: ${{ parameters.androidSdkVer }} CI: ${{ parameters.ci }} + OPENCV: true - template: ./functional/run_android_test.yml parameters: name: 'func_test_android2' @@ -69,7 +68,7 @@ jobs: name: 'func_test_android6' vmImage: ${{ parameters.vmImage }} pytestOpt: ${{ parameters.pytestOpt }} - testFiles: 'common_tests.py' + testFiles: 'common_tests.py multi_action_tests.py webelement_tests.py' sdkVer: ${{ parameters.androidSdkVer }} CI: ${{ parameters.ci }} - template: ./functional/run_android_test.yml @@ -85,6 +84,6 @@ jobs: name: 'func_test_android8' vmImage: ${{ parameters.vmImage }} pytestOpt: ${{ parameters.pytestOpt }} - testFiles: 'network_connection_tests.py log_event_tests.py' + testFiles: 'network_connection_tests.py log_event_tests.py activities_tests.py hw_actions_tests.py touch_action_tests.py' sdkVer: ${{ parameters.androidSdkVer }} CI: ${{ parameters.ci }} diff --git a/test/functional/android/applications_tests.py b/test/functional/android/applications_tests.py index 118af363..ea2216a8 100644 --- a/test/functional/android/applications_tests.py +++ b/test/functional/android/applications_tests.py @@ -13,12 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os from time import sleep import pytest from appium.webdriver.applicationstate import ApplicationState +from .helper.desired_capabilities import PATH from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase @@ -36,7 +38,7 @@ def test_is_app_installed(self) -> None: @pytest.mark.skip('This causes the server to crash. no idea why') def test_install_app(self) -> None: assert not self.driver.is_app_installed('io.selendroid.testapp') - self.driver.install_app('/Users/isaac/code/python-client/test/apps/selendroid-test-app.apk') + self.driver.install_app(PATH(os.path.join('../..', 'apps', 'selendroid-test-app.apk'))) assert self.driver.is_app_installed('io.selendroid.testapp') def test_remove_app(self) -> None: diff --git a/test/functional/android/file/find_by_image_success.png b/test/functional/android/file/find_by_image_success.png index a7f06ca9f90655f31e0e9fd9b135109705865f99..39435fd3a9c6d79c0dbd2bd4127a09d3264ea0a6 100644 GIT binary patch literal 5226 zcmcgwi91wp*p_4+OJivWQA3uo@0#qDv4xB!W6h|sW*1Tt8fykKktN#*5tD`?OUW=K z*=4LD*+oRQ@BFUsZ}`6Vy3Tp8>zs4F=Y8(yexCb22Wx3@gOgo|oq>UY6Jcy*4b1Jp z;ls)ZobBz>sldc`-`F7tnE!X=xl1ZDFz^W@jPz~7bJv{c{x-v+&XF76BmL?It=UUa z?8Y$$(#}#$=}FdXa%YT~%)c4&V!5G09ltUj#VAOcSUM|Sk9qM5)m>^+Yj5&?g4)d> zC*b%>?{VR`Y_dw^?gBh2d)lF{?&mN3lHkbSZ)SIGM_03V);7YNDsj~-W_mLh3@vuf zu$HhfWiaK&xZ)nG@R)(`Ge1Eve$`V%v;-fN^#AfutavAsPL@Ep zC(oAo(*NLKJ%79k2u#+gz;)`2JSNjEtde9;;KlUk3n$JsMN;JyofsCkE+qsG>YTxf zM@uJOM1YMY5jaeabmR3H7S}E%jM!sCA|{8p6iCq{>iF;)i=)b09=FU~6&J;$X(Si| zS0tqvG&9oPY7oQXE~ShOYt$E`lS7;~mY1LS^RY6`sO#riKcq1xbLva#V>Wq@-}}<> z=;6^(mRNnoauO+UYo4YG8z#Wo#&|rye9t6eqk2d)DF4aEoj*&1^WOgBb+m;*!T66i zf*7SgTcVh25+vrL2tEAlanUD7Vb3y(q}0^Z!ZxO|(BDZg{Jii&!CoD2f9;9m<0tbO z=^u8|1eL_AD^4KC&G*_ay(So@B4*e}+7v;9suGBbii&{{Cp_8#-_+Ek;QV2a?%%r9 zSY07wS}psVn|qVDXDRT=E<#`JniZ(sRhT9Pf!;_N^KroY(km*!C$OH2@otZy2v>D^ zT*0RJqnPqf&+mXon0o+NdKTCd+JT)V*BV`%-HAAgZY`3UespqG%lc0OY^fx}n5Pm_ zUx!C$6R{GPN5J<$NBnH(&ATA&*W97K3_U%Se0@WY4k}IM;)OG6APpVq+FtkWHAEk2 z54r#RlHFKMCp$Shp65!~f0U&^D=I2xu4nymU7=O46pUp;a7ZedXEYc*V*KQQzgBqh z3=`B8Chh-9ve{(2q*;?wOOi$cy^}!T+#rqBc1WbSxK_yWP|A6Ye^EMAdpo<)^h-Cw zr*m2L6Ah-c1+t0^tj}&@QL@*p+SowFY#4agkPALMmQ^RFW@qK7;j#OPFNiXh zq7)x@6AI2Dz9*B_)zvSGQ-YTUfPon##u_pYs*ft@BV=X8+0h*0r3R4M#P7qyXKshXMUlu!zj{ngy7Bx} zb7XUK^W@}YbnEouHrmtk-%+#?;UNgss67Yd`2O=+hQuOaov4O)?`~P~nGfRSon(5p zI21nI-(7VWR5dKLl;Vpx{M}hgeMp+JSKM=)jy~{(31mPq)MP=9bMj_Tu(`Q;q2=r5 z=Bq*}@$Iv#t3CyN8g8GS;&8a4q9Q%9$83dqFhLf3{7$dN^vo!5xvU^sTPP*}O4$0B zY!!mM)AG_1w!_`8{)&o9(Ba;12?+@m&o7(%Q4O5o#o2gBpc$i>3=jbex2Q)l0;h`Z z&ZK2r?je1+Kl|y+moJX>eh6If=FCTjcb@ufdyyif`9KPg-8-vE3N61@SA!|2O1ro6 z($e3X!ZunS9vBqzKoeG9!FmRX=8;RKgzoO{>#4$QJ*;3ws^ZqWKAl*F_ny5u(zl2< zNF>tEPD4#iFut&&!k3=;Lk0$mTF9~kK*}KyI*k4s1Qx1 z9OA<*+U1$gHE!+NSFY$D{TT%I%ynMfXXv$+mX?;Fpy1W3SO2S#fP>w&TV)oQ99tCX z4MqrRjzq$rqaSSl?5!yT5EM%BoBBX0E^c27O%u5iy0tPvwy34qD?`0Z;}@-8hCV`KKV zwm-M`-D_XCLBx2ZIv|6poC{hdM1#U_Sl-Kr(I>ZVUPap2sQOK4-mh$KKANBe0jv>7 zx^^+$mrh<-Sg3Mp5_n3?E%Bwd$8*LCAOG&WP-xk;7D~Y5Epj!_nk5=t?CtG6r{D|^ zS+ z04ByVIO0n7w-@iC(IFur5@4)TQ)pIJmbi&KB7vDqCBeGD;TQ*7m;eV8BR@aC_h_{o z3^t@{jw>;`=$7Mo>((G%&T)BVg|q!xhr7$eo#CQuN!QavQ)~sEr4&g4WH`9LE2cea;yj%j~STtE~UL#daL zkPwg)Kp29ZTquDOAc0KD%a_aEyaCnkDYPCA^!MxQYl34TY;sDVcJf*{Jk^dM=ST)x zK~d2~XhIwJH7gTS)9J{KW`L1;Dz_aSxrH_LQsMDR)J>3l$o|%Xf9ugMP)LQD&%idw zJfNyrE<{<{+e4B%%m)91sj{;2pyb1T2|5B0Dg=HZ9PURCMI!lOPR`EG7>x9KctbPU z_brM7{u}J??;jGPhST8lvPv|@qs<35^^nLxRSlo9nVpE*-S7cLP=gUrpl@6L$*jI| zgXkXbU9u(^-Jrf8VgQsZNkDh>^z{7HLw(9uY@Vz6duP<;Mr!v$;M#Pn%V@KRS|vf`VvP=w!ioD;padd;4c0UH7}b28|c)~ z%8pQctp^QPd#BR`$CuXmGAGAxqCtO0)8sk3q!z==(V6F@8BYxsR?q%ZyFTJ*85K^fe18c>c0J$OajA!a@C(C<(?Y-J!zw=QC{N zTB7!qMQ0|J!2LSmv7O=A2{F|5(9m6=5K2AXIeUA{26OBx9g$T^@(~a7HO${NH5~x2 z-J^1_vlDL?a1~yB8>i$6x$fYQ-8uI$aecChAmQyB&R>~?G-7|u#Nbi7$JKWy`kIyJ zmlrW8zB{YS!4Jgz{<^hIHjb7iTtLzdi`hb%gvJm!6{v>)RAxgyw8^EIHCd7d?oCU(WofB*seM*)ig8Y^nl63yISIy4rOFuo z>uJw|K_}}lnVKpRd3^W)iR8sTrLSFw#A#oL3B1&K7@-IO1S4#HqP(igsoqb-fUG}7 zz@0HdC#~Eyfr4!mVjDrK+*(&Xw99{RWOPHeBbuK{r0a+|K_!3tvYu=h3ubivc@Cc; z`}zCpM*Ml5x7w{4wYLc*_*Z2Z1qCQw4$%zYK`BtCer{fD_iqVS38JAcF0yT9#x@Zka`%7Y6g-2lld<|LH28snjwBv&+T|8HsppBe&B| z@Ud9kMbqdRp4@-=`J$U(T7VD@hltv$9$=961S@M~9u0;(?90FUTz05K4BUX-pN9%_ zd7K&_A8)H!US0;2tTslYx3&atsX21D2JIjsBBDqF6o5jZ0E?q5%iA-?Z*Nlgu2BLP z644HR^>oE#KzQ*!=57cUQ;Xs?P`L-#T{!WJFWnEwyO)=HayC&ba(A^O>0IYrKtbOq zou1^%-hHY)<|qS?_We$h5K56rBt1Gg)|ie80%YSuB-iF{YFNm}+d5(Qe|{0Hr>EH5 zijF?kJ9iUnIi{TBFdD*l%9qX+X8>DbO%+s$AGn>&tCG+SKM4bQ+29iAJF^pmWNFh6{FQo-abAkpmxM$Jsj|zbASi<@Sx~*MP=p6 zXDsjUa1?$4kat9eB&RC(VF0wV{9DqiQ_Y~+`wcBEq6+&_I%XX%>h@Xc(bnBV{MAwh zawWpW{Zhkafq{XZefj1}>PT@ZzRRulR+)*3?4-x9O+h4hz2Brk3}~F{D22eK3Tp-K zEF&9sJ32Zp-AEOlzl3Uy7U2>uI3J67=X>z0zZihM!PBhcvJR+SjGf40jVYu3{oF^T zQg?SlAx(?^*|py?~1Vp>-kVg98H7pawv)pBR z{>IDa-NG6YA+Vmr&N;xr#KVnob->-H3lR7-s#M~S?R@}VB^VexG&BTKj_ynamRP;# z=a+iDxT4~dAz-flOZ;NNBT*2u+bYm+TJH51>BHT7NTF-vJCkA4PL09k_4R~hC_u1} zuA4mytsv%D2oOri84dyazxCz=(374p^^$bukYUvn>j50}eSLia`~jF)`0ADG@=&>r zqlC*|%ywH8>~`HfK)R)dMYMzO9sSz@Yzw#W1*{R-=R4qTmAyya^cUZVgy)CDr~3P? zTwGl2?C#RZeT#t48FTb+R*U%aOW*dkRVJWeQGn+xw0uTfUR(3^_P+A)-=CYhM*vNT znD5`e1J8H$^({_HQ+=~d&g)8Ng$n7XR8%xpj}8xCEA9`6S7XLW=pM!x25X(oIF@WC z1QBqI=gys*n|Tai9JKhgH(RBQd%um6n3!06BW+}8NLV{qiidUY=-3_xz@nDcdAd^; zu%kc_eL4rLGY_N@INNH}eml3Wjh)@b_jh+cKRXMA2WTG9%?ii9ZTxNB9rrdR$j2=j zJu=NMt1CQf>ieGQeE`)S3jW?4sgWN*LG|gLz~8?v$}=3@i9rc$ep%VU69~Wu-sf2$ zvH2=@zn<=Vv^Se@>3Xte^x+<0G5{~wK3hXyi{D!S_Pw}BAQ0X<)^o|V@+s~nU~)EE zPohIZLlq%_6*jQ5vx9;^hAcPcXJl-T-AM!NPxIc7q%r4DorizK@=W}t?|_-umN0a3 zQc_Zx`OBYy6u9V3H#hOtl~|HFcW#1h#O$ZIbMh#?Mg_p2oQR|e1_lL9KUmC*<2WaJ zCG<^IRaIFT3u*E10iaueU}%J`qdX+jd4;-=IJB06!Un*lt0BuahK7bfyM%(>lHI2D zftGQ&zg_U)r(n2~JRwOwe%d@sUDZ7qaTxzA;UAUu4OX21J)pDA}>L&22^%H&K z?1dqYU4jm#0M3h6D6cR%{cEiaa{o*`C_vz8Vt`1{*3k{4&3C`OO#IEOIHS1p;BDzI zmW(c`GlfZOox#%edj>z5hz5LaYK!t_m>U`U5!(2BO{y!+6tRitU{1G?g-ocH@ebe- z$yfH6ZNwtIrXPKK`B{YCu_D-XazWtR^gsc9qE_HVB1qOh6C1^Su&s+KxoKy3;j#9I zmYOW4VAcpp1UT-_hb!oyyVB`GrB@Rk*P+230eiz1q9Nl6hC^0E zmlq5fF!ozK{IU;%*#xhE<*q!iJSywcgOfl-iyRya-821LSJl()O%EQNv{_S zN%|T_FqXR5_`im|UTFj@$g9zWkK8MUWnfGX6z{X?np_3&)JfQWLuZPcdHaWWKvkXX zR@eE>)dhk}YKObj>Mi|mChKt|3fGyO*|Toe-O3p!>RoBXU`p=9zpnG7kA&=~OWKAB zMR1+FDL#zq&G3sKmeT5WGVBM=N$Tuy6ORLj*XB4fm zCR5RFR%@9+DCu{K#LYWTWPKs=qbn&L?l4N(K@8VHaV&tqz=$!w`-$$GzUdc!bMudIF0wED0&4?7S)(LmJmksosdK) zF5FMHoJLQiPuOm4YHpEec_(%{U!LC>pE8V!NgjWBOjW-1<0aQgJtcNtS|LudB6zvy z*d_gI4wLc8>KEAxrd#;jrwMQIhZ*(8+$B|#XIJ&!&YIr$pW#1*83$Z4 km_)h$_xykGdu)%}TREiTfg#H7zdweEYZgY;2Ch&32L!nQs{jB1 literal 6341 zcmd55z~ZN@D1iMx;9i285x87?5V@1|&b+B?SZo z2`LHhD4hQ}>#TJ@+;4Y2ylcPD{GQ&wXYV&cQ(ch&|2{qd03c9$ET;tkV9cS{Lb$ik zPr0ZiF96^!tDUT@rjo2Iy{4;^jh%xv0Pr~CWy)>+r~ROSjbvtK=2|K2H>d^dO`T`9 zK)f?Mt&J66{3MfEkTX0jBk?1%I5t~5;05NbFb=ZYJza7# zw`70#Cwop|F#4NSrOf?mylexh(VBh40Yn653@5oF80fG8o*R)5yZ}Vr+4BzGnG}3a zcH`=>A-FTf!)eOLFAz9nQL3>*}ST)A{-B@2-IK;n!h^} z$Fa0W?;k4mP{{Kk)ZM;sa&vK}{~~PYr$suo`Z^DX)f3W}<~iCKooW021*`rk^VBn= z6gHJwIEj1uUV{hC)*3PK{A33PPrS7Spj>@=Az~q|8Ckf{WxU{DbPZadK}vH#fRZFeo$sQ3oO?}G=edO6N>#rh|vrO$0K%LAWzdf~nJ3!wL0{yIBw-_=|1~u>Ji01X;g>(+>%M?xv*5`-xGHlMsLt zghA40u6FyWG>E9~NZ@BX*jj~c34qs!?PD&D)#y&q4Q9 z_AJGs=)jJUkF@M8l6GbZ@2fEphU-$j>X?dvm?0zb?t_jjg$)@qg!#+*OJ=KL!v1iE zE^|v^-WRsP+pB&JgtT~B7?qIK%!a9Wv&Ely8n`>CFqAz?y`zl1gR!45lmCP8D4y?K znrQCJ^KZ^7wx0KI2RP8v0Ty_Cq{nsgqBbE#|{pd)N$@|)-aS=_GVpLY39JsJH7z~XV& zzKh#Z@vE4a5G}`g&!}w`zJGNN&9cU-%dOrQFtsiH)#;s6uS$Zi`*#k$Muj}m5tC&c zB>l>Zwd^45dV-L#TUAi&0%I^!03yR|dHd>|tKPBWloiRPOk z5ehX*1j!AfU;>5HtVAOj{T|~IM|3f=Kfa$B5l%$OcgG;$1;bjOH}^^gobgnis0<2 zQT3lVvtbyjWLD-Zsp9JqRh71!WCQKY)6}J;CGEwPhNXB{n;iPAlfk}S5yCXEm1F1D z&{aHP@~qI24=GFA`zAzhRRTyl5Fya&!?Dt}c%iCQG^qxlc?p2|h|=%=g&iv}FtxXJsJ@ zTlDS;5`~4u6eYqXsNy%p;o6bf54Al?sC70qy^23+Owt?0xAc!eX}xZFQF@6sM)*X` z%kRFm)hcCceSuU4SxZ=RbmusgxXo9riG?Ceo~i--f_1KI<oa2qi^l;s=|rJjuMr{N?--F+9{j;UG!N{qR{J4b#GQ>;%E++II5RM)*}+Y{OIjGL!_ zkPnuSW$NUSty+e3l7xyY+|2{zapvhLD8@9!Z1(u|$gJx0^hRg#yQc;YQm-4XE7?>; z1S&N|3_?qk(v{kZUfKoOCD^^*6z7jjdutFn^RPy@#uQ<7=(1ijXg}y=6wf8bg$=Uj z9M*5HudPR7o?y|USH5+7>%_^X&;tY@5M&L(fj+Gt{ONY~uR z?s@;ccZhnDS$M5Iojk?bBV~c&XA=a^i+!_wTYY0Lv2KUTzL8Ck4elHmThDu{SV7@y z^HWbzsF%8TC)P8gHlqhZCh|z6#iP!B9ij&@bI)l+H4VDkxp}QU+xY1D<*WC2%&y=t zJjbYHj@*;bGvayK^ZKwx#aF$z;;1rbGkG&r#74Y<%|XrU-jAO9{`5E*K3w>5Ss&M; zQ8(h41+yyc+3R(nJM5d9NLhXVxqduuGiANLyJCE?D{Iwk?Ke+5X8?I6(~@1bnz`;$ zW_yZstaM_wtn?3qh0>YQWOGho&IW-7E;$Zn2B>e}rRMnP;OMK-1)loKGz+d})L!@= z*|N}dr~~)=S6@UYt-gP+Vr41>x&XJCXccA^lo``|a#nj{jYM`GU&y~;D`vPaSCglw zuozVpnFc!SMXsEt?!R2k>Vk9+M~agu#-$V2i|(5EXkJvGisDV;Rih^5j=CZuK1cjW z3QGEz1W8h49Z_$M&&^^~wNrhN&r-1acqg5R&*BJG9^pu$!eW{QeN<%rsj>f85`u*; zEmtChe@*H>0~b5oa~7s9@HTQhWmtVNeqq4cWy;xX4-;8}{NiKgC-U1a)Jegsr$%*g z@MYQ;On>Q+Y{fp}a`M)X_#?9!M-rlt=dfcbbuD$1a%D%uU5kWdHX}FJ;<1UKq8&?7 zmxu<$tCQN#CU|%L9%>NmY#uzig=*DQ#z$(4FXR z{!CCP=t__ztN-;3WB@vOVL)hedw2nIJ1t4#t$X>*{7R$AVaNK;D~+@)@!uniy}1Xu z^>-3M0%TO65e~nfD%0Rc51u_pOr*Nw3vx6?fr+`F8GWxtep#u=Z~k;>9cxWyot5I8 zlJWcV=G42kdz?f1vvq^MGX-m1Yq@JvdZ!iBI^_nY`d7AV4KI&MQcINeT1>>Pj~el( zB8TkR?4kCMDTQ*lF|5_`tZ8H$XS+h@md+%cXSx#Ec^=aoLz9jv_}Ni#-ni~;9JHLA zZq!*fsb`?86AKoV7ZvsF-h~+KJTYmRGe4=mU#p-y z-U3_69C#jlA~-oa-pMtMb zaCS}&Z+_L~-gIY~nuJ=|mGh|;tVRj327Jvl5UUjnqN5SkMV3~`&woOQ9iHY*vX^

}C9{`MY~#k+16c*uCGet9n5d8Lb~%y_XFq-W_k_O*t*3BJ&PeeyrCRiPW2Z8E*UH z4_h$l`10xb{ga2gRPzsj#y79b zrKAKjFW>>VZ~(uok$$P!&FSp;xy>36aW7bP7ZypoX#xyS6X`k)xB%aSKfBV_5|muv z($WDm$y1gg`fn(?hn0=AkyZ>o6ck7+TtE?C05~x)@G1ri%dAco=7cfIr{=}_g8Yoe zEvh5~LVS5t*pIl;LtpiY+7+(@`H`jY|x5JN)H zOSrY01w9h(;D~@A#Tox1An5fUF_@A5FT~AWoKas*lU~-z)tdehw*WT}qXa%ZJ-wJK z)CQs@r*NZ=eiLW3b#rrufWe-gp4^`N+)l1AFt3P+2$+Wt%*V%tMsOj#9NjFCT#g8) zYa{=%BWI1Ua@fO)xj z!2j4rtBU=RLbR+AP7dyW%Pe{RRKG_}|{HwPc;(&aT!71X@jk_eSMEW&e%W zaJ92W=l93WKluNY{Wo6I*2&EYU29i6D9#YlO0Dv!}Bqyzn#MpV=TS7Jf3LD5k3>yH+5V3`9x#9G}F7#|c z+5C$4E(kLs?qx(2CfTlI=6RlOphYbNubz)#Z7^OD-cp?NWW-7wk_9 zz9I28$jP+OgreuDDkPi4`?luT#`lg#}(+781bVL{!afuNQg5 z_a4`^k0Eh%l2;MNzAQI2#S5J8KJE-7%+bnM&OvMp=eTcA?)N-;Hi3J2e)79`+A?fo zKo}e}$V=-wd)n;A4iAPGk7OdEu6wYgN785F0Bj5;pbE9<4DH^ZDbcIwfyw!ZdVHs- zxD59kKsrsv9y+l5X7+@tn2pRLIj@`97c8}lrxgwlzD3Xx&mh~8#H!KJ6GPQGnj@>y z(f?K>E7p`_0gUq_Nv`-q@a-^elO_}it2*iqIXCJ_oG)$z?*~B)P0(M%$ z{#QQ8(>;rC<8L)cC$wEgv!!`CV=mG4l0Dk`jMV{g-LSJyP@a#R491FY-OI9@)jATN<9p&ak<^+N+3>#cQR|@chK_bi1MrlTodZBE1ei zr{HwoVzK49C#f-t>O-c4uQ&2&OGm%X!GLJXfD{*f#2Nu_3Z)6@Vo5B&M=eY= zXQ7&0$s3eeom3^=kVKHha5c|=gh54Im3Z64yj+m=dN;E%#79jtnnQ2%x4hLU(Oo_} z+~BpRSf=5%l$(Dgad`yr&qYsJBV8ub)lZzkH};tWje8vS#gw9D3PKPCte_MnE+JA!Wi&R_rgSBH&j=D(I(#EJ^xI^*TuBKd^1m4_^ct8r`RR;Iy0v*;Pb zNNN$XJl5;lRcQA-D9DK(Z3Qv_lNxgkpVrg}iaC7KLWoKJP2}K|o z>de6i;H13=hl|+ydPJjX7Zk}g?z{KU zKmgMzF@#yaGqsWnQ@?3RXT__!5XW+WI2y`h*65lqCyYnJ^s&Zf=$C3KCBnL>XJ@Xy z_xNe}{i-$oI=k@#Ul2=yze)->7tpP{?x)a7Pgv1SF}35HP@#ouE%*93H=SYZFVkE*xp1lyo;J0Xl}8tScuhqWF#IgF}aw zH=z6}g{M3&P!Bs@C+pKGN9IYRnP8J{Aq ziklk-zI^sll;MR#FOi{`MPe~qG{*0ASjMhWpU6z*2tM+nUGj!Qzp(<1#pbfS?ui-7 z^fRmR?kZc7jJvlckD>^2o8fbHr+NF)Ly5>E4a!l;aUuAF>%(1ACVSAI&qwky+`?24 znTISK%#nxUVeua;ho7p@I#x@Y&k&gwZGWT;j1E&9I2|K#+xG1|+uM+<&bJ$4l8mf60eRHxr zP5z$uw?L$iFuLms+ayG<$4j@AR25gKQ@@1rpBtCd6B12_s9)PPhkvvFKdPQmRZ!tB U%6O@h|M`?ql2?~2moX3eALyHPApigX diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 24b4b404..6739bf59 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -14,33 +14,19 @@ from time import sleep +import pytest + from appium.webdriver.common.mobileby import MobileBy from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction -from .helper.test_helper import BaseTestCase, wait_for_element +from .helper.test_helper import BaseTestCase, is_ci, wait_for_element class TestMultiAction(BaseTestCase): def test_parallel_actions(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - self.driver.scroll(el1, el2) - - el = self.driver.find_element_by_accessibility_id('Views') - action = TouchAction(self.driver) - action.tap(el).perform() - - # simulate a swipe/scroll - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.press(el).move_to(x=100, y=-1000).release().perform() - el = self.driver.find_element_by_accessibility_id('Layouts') - action.press(el).move_to(x=100, y=-1000).release().perform() - - el = self.driver.find_element_by_accessibility_id('Splitting Touches across Views') - action.tap(el).perform() + self._move_to_splitting_touches_accros_views() - wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') els = self.driver.find_elements_by_class_name('android.widget.ListView') a1 = TouchAction() a1.press(els[0]) \ @@ -55,24 +41,8 @@ def test_parallel_actions(self) -> None: ma.perform() def test_actions_with_waits(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - self.driver.scroll(el1, el2) + self._move_to_splitting_touches_accros_views() - el = self.driver.find_element_by_accessibility_id('Views') - action = TouchAction(self.driver) - action.tap(el).perform() - - # simulate a swipe/scroll - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.press(el).move_to(x=100, y=-1000).release().perform() - el = self.driver.find_element_by_accessibility_id('Layouts') - action.press(el).move_to(x=100, y=-1000).release().perform() - - el = self.driver.find_element_by_accessibility_id('Splitting Touches across Views') - action.tap(el).perform() - - wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') els = self.driver.find_elements_by_class_name('android.widget.ListView') a1 = TouchAction() a1.press(els[0]) \ @@ -94,6 +64,27 @@ def test_actions_with_waits(self) -> None: ma.add(a1, a2) ma.perform() + def _move_to_splitting_touches_accros_views(self) -> None: + el1 = self.driver.find_element_by_accessibility_id('Content') + el2 = self.driver.find_element_by_accessibility_id('Animation') + self.driver.scroll(el1, el2) + + el = self.driver.find_element_by_accessibility_id('Views') + action = TouchAction(self.driver) + action.tap(el).perform() + + # simulate a swipe/scroll + el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') + action.press(el).move_to(x=100, y=-1000).release().perform() + el = self.driver.find_element_by_accessibility_id('Layouts') + action.press(el).move_to(x=100, y=-1000).release().perform() + + el = self.driver.find_element_by_accessibility_id('Splitting Touches across Views') + action.tap(el).perform() + + wait_for_element(self.driver, MobileBy.ID, 'io.appium.android.apis:id/list1') + + @pytest.mark.skipif(condition=is_ci(), reason='Skip since the test must be watched to check if it works') def test_driver_multi_tap(self) -> None: el = self.driver.find_element_by_accessibility_id('Graphics') action = TouchAction(self.driver) diff --git a/test/functional/android/search_context/find_by_image_tests.py b/test/functional/android/search_context/find_by_image_tests.py index 03b552c2..24dbde03 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -21,14 +21,16 @@ from selenium.webdriver.support.ui import WebDriverWait from appium import webdriver +from appium.webdriver.common.mobileby import MobileBy from test.functional.android.helper import desired_capabilities +from ..helper.test_helper import wait_for_element + -@pytest.mark.skip(reason="Need to fix broken test") class TestFindByImage(object): def setup_method(self) -> None: - desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk') + desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # relax template matching @@ -45,9 +47,7 @@ def test_find_based_on_image_template(self) -> None: with open(image_path, 'rb') as png_file: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') - el = WebDriverWait(self.driver, 3).until( - EC.presence_of_element_located((By.IMAGE, b64_data)) - ) + el = wait_for_element(self.driver, MobileBy.IMAGE, b64_data) size = el.size assert size['width'] is not None assert size['height'] is not None @@ -61,16 +61,14 @@ def test_find_based_on_image_template(self) -> None: assert rect['y'] is not None assert el.is_displayed() el.click() - self.driver.find_element_by_accessibility_id("Alarm") + wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "Alarm") def test_find_multiple_elements_by_image_just_returns_one(self) -> None: - WebDriverWait(self.driver, 3).until( - EC.presence_of_element_located((By.ACCESSIBILITY_ID, "App")) - ) + wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "App") image_path = desired_capabilities.PATH('file/find_by_image_success.png') els = self.driver.find_elements_by_image(image_path) els[0].click() - self.driver.find_element_by_accessibility_id("Alarm") + wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "Alarm") def test_find_throws_no_such_element(self) -> None: image_path = desired_capabilities.PATH('file/find_by_image_failure.png') @@ -78,8 +76,7 @@ def test_find_throws_no_such_element(self) -> None: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') with pytest.raises(TimeoutException): - WebDriverWait(self.driver, 3).until( - EC.presence_of_element_located((By.IMAGE, b64_data)) - ) + wait_for_element(self.driver, MobileBy.IMAGE, b64_data, timeout=3) + with pytest.raises(NoSuchElementException): self.driver.find_element_by_image(image_path) diff --git a/test/functional/android/search_context/find_by_view_matcher_tests.py b/test/functional/android/search_context/find_by_view_matcher_tests.py new file mode 100644 index 00000000..5bb0a47d --- /dev/null +++ b/test/functional/android/search_context/find_by_view_matcher_tests.py @@ -0,0 +1,69 @@ +#!/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. + +import os +import unittest + +import pytest +from selenium.common.exceptions import WebDriverException + +from appium import webdriver +from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.extensions.search_context.android import ( + AndroidSearchContext +) +from test.functional.android.helper.test_helper import ( + BaseTestCase, + desired_capabilities, + is_ci +) + + +class TestFindByViewMatcher(BaseTestCase): + + # Override + def setup_method(self, method) -> None: # type: ignore + desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') + desired_caps['automationName'] = 'Espresso' + self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + if is_ci(): + self.driver.start_recording_screen() + + def test_find_single_element(self) -> None: + el = self.driver.find_element_by_android_view_matcher( + name='withText', args=['Accessibility'], className='ViewMatchers') + assert el.text == 'Accessibility' + + def test_find_single_element_ful_class_name(self) -> None: + el = self.driver.find_element_by_android_view_matcher( + name='withText', args=['Accessibility'], className='androidx.test.espresso.matcher.ViewMatchers') + assert el.text == 'Accessibility' + + def test_find_single_element_using_hamcrest_matcher(self) -> None: + el = self.driver.find_element_by_android_view_matcher( + name='withText', + args={ + 'name': 'containsString', + 'args': 'Animati', + 'class': 'org.hamcrest.Matchers'}, + className='ViewMatchers') + assert el.text == 'Animation' + + # androidx.test.espresso.AmbiguousViewMatcherException: + # 'with text: a string containing "Access"' matches multiple views in the hierarchy. + def test_find_multiple_elements(self) -> None: + value = AndroidSearchContext()._build_data_matcher( + name='withSubstring', args=['Access'], className='ViewMatchers') + with pytest.raises(WebDriverException): + self.driver.find_elements(by=MobileBy.ANDROID_VIEW_MATCHER, value=value) diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 59ee65a4..42f22fb6 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -21,6 +21,7 @@ from .helper.test_helper import ( APIDEMO_PKG_NAME, BaseTestCase, + is_ci, wait_for_element ) @@ -41,6 +42,7 @@ def test_tap_x_y(self) -> None: el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') assert el is not None + @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI.') def test_tap_twice(self) -> None: el = self.driver.find_element_by_accessibility_id('Text') action = TouchAction(self.driver) @@ -72,20 +74,8 @@ def test_press_and_immediately_release_x_y(self) -> None: assert el is not None def test_press_and_wait(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - + self._move_to_custom_adapter() action = TouchAction(self.driver) - action.press(el1).move_to(el2).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Custom Adapter') - action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') @@ -117,20 +107,8 @@ def test_press_and_moveto_x_y(self) -> None: assert el is not None def test_long_press(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - + self._move_to_custom_adapter() action = TouchAction(self.driver) - action.press(el1).move_to(el2).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Custom Adapter') - action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') @@ -141,21 +119,10 @@ def test_long_press(self) -> None: 'new UiSelector().text("Sample menu")') assert el is not None + @pytest.mark.skipif(condition=is_ci(), reason='Skip since this check is low robust due to hard-coded position.') def test_long_press_x_y(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - + self._move_to_custom_adapter() action = TouchAction(self.driver) - action.press(el1).move_to(el2).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.tap(el).perform() - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Custom Adapter') - action.tap(el).perform() # the element "People Names" is located at 430:310 (top left corner) # location can be changed by phone resolusion, OS version @@ -167,13 +134,8 @@ def test_long_press_x_y(self) -> None: assert el is not None def test_drag_and_drop(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - self.driver.scroll(el1, el2) - - el = self.driver.find_element_by_accessibility_id('Views') + self._move_to_views() action = TouchAction(self.driver) - action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Drag and Drop') action.tap(el).perform() @@ -184,17 +146,12 @@ def test_drag_and_drop(self) -> None: # dnd is stimulated by longpress-move_to-release action.long_press(dd3).move_to(dd2).release().perform() - el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) - assert 'drag_dot_3' in el.text + el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) + assert 'Dropped!' in el.text def test_driver_drag_and_drop(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Content') - el2 = self.driver.find_element_by_accessibility_id('Animation') - self.driver.scroll(el1, el2) - - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') + self._move_to_views() action = TouchAction(self.driver) - action.tap(el).perform() el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Drag and Drop') action.tap(el).perform() @@ -204,8 +161,8 @@ def test_driver_drag_and_drop(self) -> None: self.driver.drag_and_drop(dd3, dd2) - el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_text'.format(APIDEMO_PKG_NAME)) - assert 'drag_dot_3' in el.text + el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) + assert 'Dropped!' in el.text def test_driver_swipe(self) -> None: el = self.driver.find_element_by_accessibility_id('Views') @@ -216,5 +173,24 @@ def test_driver_swipe(self) -> None: self.driver.find_element_by_accessibility_id('ImageView') self.driver.swipe(100, 1000, 100, 100, 800) - el = self.driver.find_element_by_accessibility_id('ImageView') + el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'ImageView') assert el is not None + + def _move_to_views(self) -> None: + el1 = self.driver.find_element_by_accessibility_id('Content') + el2 = self.driver.find_element_by_accessibility_id('Animation') + self.driver.scroll(el1, el2) + + el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') + action = TouchAction(self.driver) + action.tap(el).perform() + + def _move_to_custom_adapter(self) -> None: + self._move_to_views() + action = TouchAction(self.driver) + + el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') + action.tap(el).perform() + + el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Custom Adapter') + action.tap(el).perform() diff --git a/test/functional/ios/helper/desired_capabilities.py b/test/functional/ios/helper/desired_capabilities.py index 633b8e2c..50810667 100644 --- a/test/functional/ios/helper/desired_capabilities.py +++ b/test/functional/ios/helper/desired_capabilities.py @@ -66,7 +66,7 @@ def wda_port() -> int: return 8100 -# Before running tests, you must have iOS simulators named 'iPhone 6s - 8100' and 'iPhone 6s - 8101' +# Before running tests, you must have iOS simulators named 'iPhone 8 - 8100' and 'iPhone 8 - 8101' def iphone_device_name() -> str: diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 629190ef..5f3cecbf 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -80,6 +80,8 @@ def test_clear(self) -> None: input_text = 'blah' el.click() el.send_keys(input_text) + self.driver.hide_keyboard() + # TODO Needs to get the element again to update value in the element. Remove below one line when it's fixed. el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] text = el.get_attribute('value') diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py new file mode 100644 index 00000000..88347b57 --- /dev/null +++ b/test/unit/webdriver/webelement_test.py @@ -0,0 +1,100 @@ +#!/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. + +import json +import os +import tempfile + +import httpretty + +from appium.webdriver.webelement import WebElement as MobileWebElement +from test.unit.helper.test_helper import ( + android_w3c_driver, + appium_command, + get_httpretty_request_body +) + + +class TestWebElement(object): + + @httpretty.activate + def test_set_value(self): + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/appium/element/element_id/value') + ) + + element = MobileWebElement(driver, 'element_id', w3c=True) + value = 'happy testing' + element.set_value(value) + + d = get_httpretty_request_body(httpretty.last_request()) + assert d['value'] == [value] + + @httpretty.activate + def test_send_key(self): + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/element/element_id/value') + ) + + element = MobileWebElement(driver, 'element_id', w3c=True) + element.send_keys('happy testing') + + d = get_httpretty_request_body(httpretty.last_request()) + assert d['text'] == ''.join(d['value']) + + @httpretty.activate + def test_send_key_with_file(self): + driver = android_w3c_driver() + # Should not send this file + tmp_f = tempfile.NamedTemporaryFile() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/element/element_id/value') + ) + + try: + element = MobileWebElement(driver, 'element_id', w3c=True) + element.send_keys(tmp_f.name) + finally: + tmp_f.close() + + d = get_httpretty_request_body(httpretty.last_request()) + assert d['text'] == ''.join(d['value']) + + @httpretty.activate + def test_get_attribute_with_dict(self): + driver = android_w3c_driver() + rect_dict = { + 'y': 200, + 'x': 100, + 'width': 300, + 'height': 56 + } + httpretty.register_uri( + httpretty.GET, + appium_command('/session/1234567890/element/element_id/attribute/rect'), + body=json.dumps({"value": rect_dict}) + ) + + element = MobileWebElement(driver, 'element_id', w3c=True) + ef = element.get_attribute('rect') + + d = httpretty.last_request() + + assert isinstance(ef, dict) + assert ef == rect_dict From e6754c8cc814400176f120d672794619054333ea Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 28 Apr 2020 14:43:39 +0900 Subject: [PATCH 12/12] chore: Update readme and gitchangelog section role (#524) (#525) * chore: tweak changelog filter * address stoping Python 2 support * 2 instead of 2.0... * tweak readme --- .gitchangelog.rc | 11 +- CHANGELOG.rst | 794 ++++++++++++++++++++++++++++++----------------- README.md | 14 +- 3 files changed, 527 insertions(+), 292 deletions(-) diff --git a/.gitchangelog.rc b/.gitchangelog.rc index abf569d9..45092650 100644 --- a/.gitchangelog.rc +++ b/.gitchangelog.rc @@ -83,15 +83,14 @@ ignore_regexps = [ ## section_regexps = [ ('New', [ - r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', - ]), - ('Changes', [ - r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + r'^([nN]ew|[fF]eat|[aA]dd)\s*(:|)\s*([^\n]*)$', ]), ('Fix', [ - r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + r'^[fF]ix\s*(:|)\s*([^\n]*)$', + ]), + ('Test', [ + r'^([tT]est|[cC][iI])\s*(:|)\s*([^\n]*)$', ]), - ('Other', None ## Match all lines ), diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94cbed85..5b756414 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= +(unreleased) +------------ +- Chore: tweak changelog filter. [Kazuaki Matsuo] +- Update changelog for 0.52. [Kazuaki Matsuo] + + v0.52 (2020-04-23) ------------------ @@ -10,6 +16,12 @@ Fix - Handling of dictionary-values in WebElement.get_attribute() (#521) [Hannes Hauer] +Test +~~~~ +- Test: Add unit test for set_value (setImmediateValue) (#518) [Nrupesh + Patel] +- Test: Fix test_clear flaky functional test (#519) [Nrupesh Patel] + Other ~~~~~ - Bump 0.52. [Kazuaki Matsuo] @@ -17,15 +29,14 @@ Other (#517) [Venkatesh] if number >= PytestXdistWorker.COUNT: -- Test: Add unit test for set_value (setImmediateValue) (#518) [Nrupesh - Patel] -- Test: Fix test_clear flaky functional test (#519) [Nrupesh Patel] - Update changelog for 0.51. [Kazuaki Matsuo] v0.51 (2020-04-12) ------------------ -- Bump 0.51. [Kazuaki Matsuo] + +New +~~~ - Feat: Override send_keys without file upload function (#515) [Kazuaki Matsuo] @@ -40,6 +51,9 @@ v0.51 (2020-04-12) * tweak syntax - Feat: Add idempotency key header to create session requests (#514) [Mykola Mokhnach] + +Fix +~~~ - Fix flaky functional tests (#473) [Mori Atsushi] * Run all tests @@ -70,12 +84,18 @@ v0.51 (2020-04-12) * Create _move_to_[target_view] * [test_driver_swipe] Add wait + +Other +~~~~~ +- Bump 0.51. [Kazuaki Matsuo] - Update changelog for 0.50. [Kazuaki Matsuo] v0.50 (2020-02-10) ------------------ -- Bump 0.50. [Kazuaki Matsuo] + +New +~~~ - Feat: Add viewmatcher (#480) [Mori Atsushi] * Add android view matcher as strategy locator @@ -89,6 +109,20 @@ v0.50 (2020-02-10) * Fix docstring * tweak docstring + +Test +~~~~ +- Ci: Take screen record as evidence (#481) [Mori Atsushi] + + * Take screen record for android + + * Take screen record for iOS + + * Save screen record for iOS + +Other +~~~~~ +- Bump 0.50. [Kazuaki Matsuo] - Chore: Fix find_by_images_tests.py (#495) [Mori Atsushi] * chore: Fix find_by_images_tests.py @@ -157,20 +191,32 @@ v0.50 (2020-02-10) * Remove unused codes * [Readme] py.test -> pytest -- Ci: Take screen record as evidence (#481) [Mori Atsushi] - - * Take screen record for android - - * Take screen record for iOS - - * Save screen record for iOS - Update changelog for 0.49. [Kazuaki Matsuo] v0.49 (2019-12-24) ------------------ -- Bump 0.49. [Kazuaki Matsuo] + +New +~~~ - Add IME unittest (#475) [Mori Atsushi] +- Add new locator strategy find_elements_by_windows_uiautomation and + test. [Manoj Kumar] +- Add new locator strategy find_element_by_windows_uiautomation. [Manoj + Kumar] + +Fix +~~~ +- Fix functional test broken by previous commit. [Manoj Kumar] +- Fix CI (Failed iOS) (#460) [Mori Atsushi] + + * Fix CI (Failed iOS) + + * Fix variable name + +Other +~~~~~ +- Bump 0.49. [Kazuaki Matsuo] - Move session/execute_mobile commands to mixin class (#471) [Mori Atsushi] @@ -294,7 +340,6 @@ v0.49 (2019-12-24) * Revert some changes * Fix typo -- Fix functional test broken by previous commit. [Manoj Kumar] - Move commands from webdriver as mixins class (#459) [Manoj Kumar] * move to mixins class @@ -302,41 +347,17 @@ v0.49 (2019-12-24) * Create common class with its tests * incorporating PR comments -- Fix CI (Failed iOS) (#460) [Mori Atsushi] - - * Fix CI (Failed iOS) - - * Fix variable name -- Add new locator strategy find_elements_by_windows_uiautomation and - test. [Manoj Kumar] -- Add new locator strategy find_element_by_windows_uiautomation. [Manoj - Kumar] - Update changelog for 0.48. [Kazuaki Matsuo] - Bump 0.48. [Kazuaki Matsuo] v0.48 (2019-10-22) ------------------ + +New +~~~ - Add docs on start activity with args. [Manoj Kumar] - Add unit tests Activate app. [Manoj Kumar] -- Test: Add unit tests for application_tests (#454) [Manoj Kumar] - - * Add unit tests for application_tests - - * change body values to be empty -- Fix docstring, add getting available port number (#448) [Kazuaki - Matsuo] - - * fix docstring, add getting available port number - - * add WebDriverWait - - * define custom wait - - * move get available port in another module - - * follow python wait condition name -- Test: add Unit tests currentPackage (#453) [Manoj Kumar] - Add unit tests for keyboard API (#452) [Manoj Kumar] * Add Unit tests for Keyboard API @@ -344,8 +365,6 @@ v0.48 (2019-10-22) * incorporating review comments * change per review comment -- Test: add unit test unlock (#450) [Manoj Kumar] -- Docs: Minor fix in README (#445) [Aliakbar] - Feat: Adding getAllSessions (#446) [Manoj Kumar] * Adding getAllSessions @@ -353,32 +372,22 @@ v0.48 (2019-10-22) * adjust per lint * fix comments -- AndroidKey class for Key Codes added. (#443) [Aliakbar] - - * AndroidKey class for Key Codes added. - - AndroidKey enum from java client ported. Instead of using unreadable numbers in code we can use these constant in order to write more readable code. - - * Android native key test - - Test for native key module which contains key codes for android keys. - - * Fixed # sign in comment instead of * - - * Change returns +- Add downloads badge (#441) [Mori Atsushi] - Instead of `if` and two return statements. +Fix +~~~ +- Fix docstring, add getting available port number (#448) [Kazuaki + Matsuo] - * Used AndroidKey.XXX instead of numbers in tests + * fix docstring, add getting available port number - * Make fuctions similar to is_gamepad_button + * add WebDriverWait - Used a similar sentence format for similar functions as is_gamepad_button. + * define custom wait - * Make function names as is in java-client + * move get available port in another module - * Underscore in the beginning of constant removed -- Add downloads badge (#441) [Mori Atsushi] + * follow python wait condition name - Fix CI fails (Updated iOS ver) (#440) [Mori Atsushi] * Updated iOS ver to fix CI fails @@ -400,7 +409,6 @@ v0.48 (2019-10-22) * Add guide for docstrings * Delete unnecessary codes -- Run unittest with python3.8 (#433) [Mori Atsushi] - Fix android flaky tests (#413) [Mori Atsushi] * Fix android flaky tests @@ -416,12 +424,52 @@ v0.48 (2019-10-22) * Don't run flaky tests on CI * Rename class name + +Test +~~~~ +- Test: Add unit tests for application_tests (#454) [Manoj Kumar] + + * Add unit tests for application_tests + + * change body values to be empty +- Test: add Unit tests currentPackage (#453) [Manoj Kumar] +- Test: add unit test unlock (#450) [Manoj Kumar] - Ci: try run all scripts and exit 1 when something fails (#431) [Kazuaki Matsuo] * try run all scripts and exit 1 when something fails * ignore link in Python 3.7 because of runtime error + +Other +~~~~~ +- Docs: Minor fix in README (#445) [Aliakbar] +- AndroidKey class for Key Codes added. (#443) [Aliakbar] + + * AndroidKey class for Key Codes added. + + AndroidKey enum from java client ported. Instead of using unreadable numbers in code we can use these constant in order to write more readable code. + + * Android native key test + + Test for native key module which contains key codes for android keys. + + * Fixed # sign in comment instead of * + + * Change returns + + Instead of `if` and two return statements. + + * Used AndroidKey.XXX instead of numbers in tests + + * Make fuctions similar to is_gamepad_button + + Used a similar sentence format for similar functions as is_gamepad_button. + + * Make function names as is in java-client + + * Underscore in the beginning of constant removed +- Run unittest with python3.8 (#433) [Mori Atsushi] - Bump 0.47. [Kazuaki Matsuo] - Update changelog for 0.47. [Kazuaki Matsuo] @@ -429,16 +477,8 @@ v0.48 (2019-10-22) v0.47 (2019-08-22) ------------------ -Fix +New ~~~ -- CI doesn't fail even if autopep8 makes changes (#422) [Mori Atsushi] - - * Fix: CI doesn't fail even if autopep8 makes changes - - * Fix: CI failure - -Other -~~~~~ - Add events property (#429) [Dan Graham] * add GET_SESSION @@ -462,13 +502,6 @@ Other * Fix docstring - Add videoFilters option documentation (#419) [Mykola Mokhnach] -- Change altitude optional as arg for set_location (#415) [Mori Atsushi] - - * Change altitude optional as arg for set_location - - * Add comments - - * review comments - Add remote_fs unittest (#410) [Mori Atsushi] * Add test_push_file unittest @@ -476,6 +509,24 @@ Other * Add test_pull_file unittest * Add remote_fs error cases unittest + +Fix +~~~ +- CI doesn't fail even if autopep8 makes changes (#422) [Mori Atsushi] + + * Fix: CI doesn't fail even if autopep8 makes changes + + * Fix: CI failure + +Other +~~~~~ +- Change altitude optional as arg for set_location (#415) [Mori Atsushi] + + * Change altitude optional as arg for set_location + + * Add comments + + * review comments - Update docstring (#407) [Mori Atsushi] * Remove import error on pycharm @@ -505,7 +556,9 @@ v0.46 (2019-06-27) v0.45 (2019-06-26) ------------------ -- Bump 0.45. [Kazuaki Matsuo] + +New +~~~ - Add execute driver (#406) [Kazuaki Matsuo] * add execute driver @@ -525,8 +578,12 @@ v0.45 (2019-06-26) * Add autocompletion for pycharm * Removed flaky tests from running -- Moving reset method from WebDriver to Applications (#399) [Mayura] - Add unit test for open_notifications (#398) [tabatask] + +Other +~~~~~ +- Bump 0.45. [Kazuaki Matsuo] +- Moving reset method from WebDriver to Applications (#399) [Mayura] - Run android functional tests on ci (#396) [Mori Atsushi] * Add android functional test to ci @@ -638,19 +695,6 @@ Fix * Fix: installed selenium4 when setup.py install * Keep existing comparison operator - -Other -~~~~~ -- Bump 0.44. [Kazuaki Matsuo] -- Support get_display_density (#388) [Mori Atsushi] - - * Support get_display_density - - * Add get_display_density unittest - - * Add api doc - - * Add return description to api doc - Fix ios functional tests failed (#385) [Mori Atsushi] * Fix safari test(iOS) @@ -668,6 +712,19 @@ Other * Fix tests failed along to replaced test app * review comments + +Other +~~~~~ +- Bump 0.44. [Kazuaki Matsuo] +- Support get_display_density (#388) [Mori Atsushi] + + * Support get_display_density + + * Add get_display_density unittest + + * Add api doc + + * Add return description to api doc - Support set_network_speed (#386) [Mori Atsushi] * Support set_nework_speed @@ -684,7 +741,9 @@ Other v0.43 (2019-05-18) ------------------ -- Bump 0.43. [Kazuaki Matsuo] + +New +~~~ - Add assertions for w3c (#384) [Kazuaki Matsuo] - Add isort to pre-commit (#379) [Mori Atsushi] @@ -699,44 +758,23 @@ v0.43 (2019-05-18) * Add check to ci.sh * Use exit code for condition check in ci.sh -- [RD-34891] Assign w3c property on the command executor. (#382) - [Erustus Agutu] -- Get rid of sessionId (#383) [Kazuaki Matsuo] -- Divide functional appium tests into each module(android) (#378) [Mori + +Fix +~~~ +- Fix functional tests failed (android, push_file) (#375) [Mori Atsushi] - * Move non test files + * Fix: test_push_file - * Divide appium_tests into each module tests(android) + * Move remove_fs tests - * Skip contexts, find_by_image tests + * Move teardown process - * Removed unnecessary codes -- Introduced pipfile (#376) [Mori Atsushi] + * Delete selendroid test - * Added Pipfile + * tweak - Just created by pipenv install -r ci-requirements.txt - - * Introduced pipenv - - * Add Pipfile.lock to gitignore - - * Cover any minor versions for packages -- Fix functional tests failed (android, push_file) (#375) [Mori - Atsushi] - - * Fix: test_push_file - - * Move remove_fs tests - - * Move teardown process - - * Delete selendroid test - - * tweak - - * Update along to review comments + * Update along to review comments * Replace double quote with single quote under android dir @@ -773,6 +811,34 @@ v0.43 (2019-05-18) * Add SLEEPY_TIME * Remove set with sleep and find_element + +Other +~~~~~ +- Bump 0.43. [Kazuaki Matsuo] +- [RD-34891] Assign w3c property on the command executor. (#382) + [Erustus Agutu] +- Get rid of sessionId (#383) [Kazuaki Matsuo] +- Divide functional appium tests into each module(android) (#378) [Mori + Atsushi] + + * Move non test files + + * Divide appium_tests into each module tests(android) + + * Skip contexts, find_by_image tests + + * Removed unnecessary codes +- Introduced pipfile (#376) [Mori Atsushi] + + * Added Pipfile + + Just created by pipenv install -r ci-requirements.txt + + * Introduced pipenv + + * Add Pipfile.lock to gitignore + + * Cover any minor versions for packages - Move android commands to android package (#371) [Mori Atsushi] * Reorder mobilecommands @@ -787,7 +853,15 @@ v0.43 (2019-05-18) v0.42 (2019-05-10) ------------------ -- Bump 0.42. [Kazuaki Matsuo] + +New +~~~ +- Add return value. [Atsushi Mori] +- Add set_power_ac unittest. [Atsushi Mori] +- Added set_power_capacity unittest. [Atsushi Mori] + +Fix +~~~ - Fix functional tests failed (android, appium_tests) (#366) [Mori Atsushi] @@ -802,6 +876,26 @@ v0.42 (2019-05-10) * Update along to review comments * Add return value to wait_for_element +- Fix poll_url in Python 3 (#370) [Kazuaki Matsuo] +- Fix functional tests failed (#364) [Mori Atsushi] + + * Fix test failed: element_location_in_view, set_text + + * Fix test failed: test_push_file + + * Merge test_pull_test into test_push_test + + * Fix test failed: test_pull_folder + + * Enable running by both py2 and py3 + + * Removed unnecessary codes + + * Remove magic number + +Other +~~~~~ +- Bump 0.42. [Kazuaki Matsuo] - Support get_performance_data, get_performance_data_types (#368) [Mori Atsushi] @@ -814,7 +908,6 @@ v0.42 (2019-05-10) * Tweak * Update api doc -- Fix poll_url in Python 3 (#370) [Kazuaki Matsuo] - Support set_gsm_voice (#367) [Mori Atsushi] * Support set_gsm_voice @@ -822,21 +915,6 @@ v0.42 (2019-05-10) * Add set_gsm_voice unittest * Fix typo -- Fix functional tests failed (#364) [Mori Atsushi] - - * Fix test failed: element_location_in_view, set_text - - * Fix test failed: test_push_file - - * Merge test_pull_test into test_push_test - - * Fix test failed: test_pull_folder - - * Enable running by both py2 and py3 - - * Removed unnecessary codes - - * Remove magic number - Support get_system_bars (#363) [Mori Atsushi] * Support get_system_bars @@ -889,11 +967,8 @@ v0.42 (2019-05-10) - Update api doc. [Atsushi Mori] - Define AC_OFF, AC_ON as const. [Atsushi Mori] - Skip pylint warnings. [Atsushi Mori] -- Add return value. [Atsushi Mori] - Update api doc. [Atsushi Mori] -- Add set_power_ac unittest. [Atsushi Mori] - Support set_power_ac. [Atsushi Mori] -- Added set_power_capacity unittest. [Atsushi Mori] - Support set_power_capacity. [Atsushi Mori] - Update changelog for 0.41. [Kazuaki Matsuo] - Bump 0.41. [Kazuaki Matsuo] @@ -901,12 +976,9 @@ v0.42 (2019-05-10) v0.41 (2019-04-23) ------------------ -- Fix True/False in image settings, add boolean value in settings test - (#352) [Kazuaki Matsuo] - - * Fix True/False in image settings, add boolean value in settings test - * use is for boolean +New +~~~ - Add send sms support (#351) [Mori Atsushi] * Support sendSms function @@ -918,18 +990,31 @@ v0.41 (2019-04-23) * Revert unexpected changes * Update api doc -- Make keep alive True by default (#348) [Kazuaki Matsuo] -- Move settings to mixin classes (#347) [Mori Atsushi] - Add pixelFormat in docstring (#346) [Kazuaki Matsuo] - Add fingerprint unittest (#345) [Mori Atsushi] - Add shake unittest (#344) [Mori Atsushi] + +Fix +~~~ +- Fix True/False in image settings, add boolean value in settings test + (#352) [Kazuaki Matsuo] + + * Fix True/False in image settings, add boolean value in settings test + + * use is for boolean + +Other +~~~~~ +- Make keep alive True by default (#348) [Kazuaki Matsuo] +- Move settings to mixin classes (#347) [Mori Atsushi] - Update changelog for 0.40. [Kazuaki Matsuo] v0.40 (2019-03-14) ------------------ -- Bump 0.40. [Kazuaki Matsuo] -- Update missing changelog in 0.39. [Kazuaki Matsuo] + +Fix +~~~ - Fix RuntimeError: maximum recursion depth exceeded in cmp happened (#343) [Kazuaki Matsuo] @@ -941,9 +1026,17 @@ v0.40 (2019-03-14) * use issubclass to ensure the class is sub +Other +~~~~~ +- Bump 0.40. [Kazuaki Matsuo] +- Update missing changelog in 0.39. [Kazuaki Matsuo] + v0.39 (2019-02-27) ------------------ + +New +~~~ - Add direct connect flag to be able to handle directConnectXxxxc (#338) [Kazuaki Matsuo] @@ -969,12 +1062,10 @@ v0.39 (2019-02-27) * add zero case * defines search context for driver and element -- Update changelog for 0.38. [Kazuaki Matsuo] -- Bump 0.38. [Kazuaki Matsuo] - -v0.38 (2019-02-11) ------------------- +Other +~~~~~ +- Update changelog for 0.38. [Kazuaki Matsuo] - Bump 0.38. [Kazuaki Matsuo] - Remove io.open from getting version code (#334) [Kazuaki Matsuo] @@ -985,8 +1076,9 @@ v0.38 (2019-02-11) v0.37 (2019-02-10) ------------------ -- Cast set_location arguments to float (#332) [Mykola Mokhnach] -- Fix passing options to screen record commands (#330) [Mykola Mokhnach] + +New +~~~ - Add AppiumConnection to customise user agent (#327) [Kazuaki Matsuo] - Add a test for reset (#326) [Kazuaki Matsuo] - Add a simple class to control Appium execution from the client code @@ -999,6 +1091,14 @@ v0.37 (2019-02-10) * fix typo - Add a test case using another session id (#320) [Kazuaki Matsuo] + +Fix +~~~ +- Fix passing options to screen record commands (#330) [Mykola Mokhnach] + +Other +~~~~~ +- Cast set_location arguments to float (#332) [Mykola Mokhnach] - Update changelog for 0.36. [Kazuaki MATSUO] - Bump 0.36. [Kazuaki MATSUO] @@ -1012,7 +1112,9 @@ v0.36 (2019-01-18) v0.35 (2019-01-17) ------------------ -- Bump 0.35. [Kazuaki MATSUO] + +New +~~~ - Add location unittest (#317) [Mori Atsushi] * Add test_location @@ -1025,17 +1127,6 @@ v0.35 (2019-01-17) * Add settings unittest * Remove unused import -- Move device_time to a mixin class (#314) [Mori Atsushi] -- Define getting httpretty request body decoded by utf-8 (#313) [Kazuaki - Matsuo] - - * define httpretty_last_request_body - - * replace the order - - * update - - * rename - Added format to device_time as argument (#312) [Mori Atsushi] - Add devicetime unittest (#309) [Mori Atsushi] @@ -1085,27 +1176,47 @@ v0.35 (2019-01-17) * 10 -> 9 * Modify based on comment -- Move action and keyboard helpers to mixin classes (#307) [Mykola - Mokhnach] - Add precommit (#304) [Kazuaki Matsuo] * add pre-commit hook + +Fix +~~~ - Fixing broken pypi long description rendering (#303) [Prabhash] reference: https://packaging.python.org/guides/making-a-pypi-friendly-readme Tested at https://pypi.org/project/delayed-assert +- Fix overridden mixin method call (#297) [Mykola Mokhnach] + +Other +~~~~~ +- Bump 0.35. [Kazuaki MATSUO] +- Move device_time to a mixin class (#314) [Mori Atsushi] +- Define getting httpretty request body decoded by utf-8 (#313) [Kazuaki + Matsuo] + + * define httpretty_last_request_body + + * replace the order + + * update + + * rename +- Move action and keyboard helpers to mixin classes (#307) [Mykola + Mokhnach] - Extract more webdriver methods into specialized mixin classes (#302) [Mykola Mokhnach] - Move specialized method groups to mixin classes (#301) [Mykola Mokhnach] -- Fix overridden mixin method call (#297) [Mykola Mokhnach] - Update changelog for 0.34. [Kazuaki MATSUO] v0.34 (2018-12-18) ------------------ -- Bump 0.34. [Kazuaki MATSUO] + +Fix +~~~ - Fix missing package, missing commands and a test (#296) [Kazuaki Matsuo] @@ -1114,27 +1225,33 @@ v0.34 (2018-12-18) * add tests for context to make sure it loads * move command definition from extensions to root + +Other +~~~~~ +- Bump 0.34. [Kazuaki MATSUO] - Update changelog for 0.33. [Kazuaki MATSUO] v0.33 (2018-12-18) ------------------ -- Bump 0.33. [Kazuaki MATSUO] -- Move read version (#294) [Kazuaki Matsuo] + +New +~~~ - Add newline in release script because of autopep8 (#292) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 0.33. [Kazuaki MATSUO] +- Move read version (#294) [Kazuaki Matsuo] - Update changelog for 0.32. [Kazuaki MATSUO] v0.32 (2018-12-18) ------------------ -- Bump 0.32. [Kazuaki MATSUO] -- Split driver methods into mixin classes (#291) [Mykola Mokhnach] -- Run with tox on travis (#290) [Kazuaki Matsuo] - - * run with tox on travis - * update readme +New +~~~ - Add unit tests for isLocked Library (#288) [Venkatesh Singh] * Add unit tests for isLocked Lib @@ -1143,6 +1260,27 @@ v0.32 (2018-12-18) - Add unit test for lock lib (#287) [Venkatesh Singh] * Add unit test for lock lib + +Fix +~~~ +- Fixed few failing tests in appium_tests.py (#278) + [RajeshkumarAyyadurai] + + * fixed few failing tests in appium_tests.py + + * updated few tests in appium_tests.py by removing uiautomator strategy +- Fixed failing tests in find_by_accessibility_id_tests.py. + [RajeshkumarAyyadurai] + +Other +~~~~~ +- Bump 0.32. [Kazuaki MATSUO] +- Split driver methods into mixin classes (#291) [Mykola Mokhnach] +- Run with tox on travis (#290) [Kazuaki Matsuo] + + * run with tox on travis + + * update readme - Improve pytest, adding pytest.ini and set default arguments (#284) [Kazuaki Matsuo] - Extract bytes and add a test for set clipboard (#282) [Kazuaki Matsuo] @@ -1162,14 +1300,6 @@ v0.32 (2018-12-18) * remove docgen since we can use markdown format in pypi - Release automation (#276) [Kazuaki Matsuo] -- Fixed few failing tests in appium_tests.py (#278) - [RajeshkumarAyyadurai] - - * fixed few failing tests in appium_tests.py - - * updated few tests in appium_tests.py by removing uiautomator strategy -- Fixed failing tests in find_by_accessibility_id_tests.py. - [RajeshkumarAyyadurai] - Updated requirements.txt file with version (#275) [RajeshkumarAyyadurai] @@ -1211,32 +1341,31 @@ v0.31 (2018-11-21) v0.30 (2018-10-31) ------------------ -- V0.30. [Kazuaki MATSUO] + +New +~~~ +- Add release section in readme. [Kazuaki MATSUO] + +Fix +~~~ - Fix python3 set_clipboard error (#267) [Kazuaki Matsuo] * fix python3 set_clipboard error * apply formatter -- Add release section in readme. [Kazuaki MATSUO] + +Other +~~~~~ +- V0.30. [Kazuaki MATSUO] v0.29 (2018-10-30) ------------------ -- V0.29. [Kazuaki MATSUO] + +New +~~~ - Add an endpoint for pressing buttons (#262) [Alex] - Add custom locator strategy (#260) [Jonathan Lipps] -- Bump selenium 3.14.1, call RemoteCommand without workaround (#259) - [Kazuaki Matsuo] - - * bump selenium 3.14.1, call RemoteCommand without workaround - - * make attributeValue check safe - - * define str = basestring for Python 2 - - * apply formatter - - * add missing value check - Add a duration for scroll for ios (#256) [Kazuaki Matsuo] * add a duration for scroll for ios @@ -1250,8 +1379,6 @@ v0.29 (2018-10-30) * skip wait if duration is none * add comment -- Update obsolete link for mobile json wire protocol spec. (#257) - [Andrei Petre] - Add finger print (#252) [Kazuaki Matsuo] * add fingre print @@ -1272,20 +1399,6 @@ v0.29 (2018-10-30) * define PytestXdistWorker * use gw0 if the number of worker is over the count of workers -- Remove always_match and use first_match instead (#246) [Kazuaki - Matsuo] - - remove always_match and use first_match instead -- Use normal element for find image by (#236) [Kazuaki Matsuo] - - * use normal element - - * get rid of png - - * get rid of imagelement.py - - * apply formatter -- Typo fix: finiding -> finding (#245) [Andrew Fuller] - Add autopep8 (#243) [Kazuaki Matsuo] * apply autopep8 @@ -1327,23 +1440,58 @@ v0.29 (2018-10-30) * tweak docstring * add find element by viewtag section in readme + +Other +~~~~~ +- V0.29. [Kazuaki MATSUO] +- Bump selenium 3.14.1, call RemoteCommand without workaround (#259) + [Kazuaki Matsuo] + + * bump selenium 3.14.1, call RemoteCommand without workaround + + * make attributeValue check safe + + * define str = basestring for Python 2 + + * apply formatter + + * add missing value check +- Update obsolete link for mobile json wire protocol spec. (#257) + [Andrei Petre] +- Remove always_match and use first_match instead (#246) [Kazuaki + Matsuo] + + remove always_match and use first_match instead +- Use normal element for find image by (#236) [Kazuaki Matsuo] + + * use normal element + + * get rid of png + + * get rid of imagelement.py + + * apply formatter +- Typo fix: finiding -> finding (#245) [Andrew Fuller] - Tweak PyPi URLs and add a badge (#232) [Kazuaki Matsuo] v0.28 (2018-07-13) ------------------ -- V0.28. [Isaac Murchie] + +Fix +~~~ - Fix base64 encoded string (#231) [Kazuaki Matsuo] +Other +~~~~~ +- V0.28. [Isaac Murchie] + v0.27 (2018-07-10) ------------------ -- V0.27. [Isaac Murchie] -- Set None as default value to lock device (#227) [Miguel Hernández] - - * Set 0 as default value to lock device - * Set None as default value instead of 0 +New +~~~ - Add support for is keyboard shown command. [Jonathan Lipps] - Add find by image commands and tests (#224) [Jonathan Lipps] @@ -1366,8 +1514,6 @@ v0.27 (2018-07-10) * Add wrappers for OpenCV-based image comparison * Tune some docs -- Avoid setting coordinates to null for touch actions (#214) [Mykola - Mokhnach] - Add clipboard handlers (#209) [Mykola Mokhnach] * Add clipboard handlers @@ -1375,7 +1521,6 @@ v0.27 (2018-07-10) * Fix documentation * fix options notation -- Change QUERY_APP_STATE request type to POST (#205) [Mykola Mokhnach] - Add applications management endpoint handlers (#204) [Mykola Mokhnach] - Add methods for start/stop screen record API endpoints (#201) [Mykola Mokhnach] @@ -1407,6 +1552,18 @@ v0.27 (2018-07-10) * update the docset - Add endpoints for lock/unlock. [Mykola Mokhnach] +Other +~~~~~ +- V0.27. [Isaac Murchie] +- Set None as default value to lock device (#227) [Miguel Hernández] + + * Set 0 as default value to lock device + + * Set None as default value instead of 0 +- Avoid setting coordinates to null for touch actions (#214) [Mykola + Mokhnach] +- Change QUERY_APP_STATE request type to POST (#205) [Mykola Mokhnach] + v0.26 (2018-01-09) ------------------ @@ -1415,19 +1572,28 @@ v0.26 (2018-01-09) v0.25 (2018-01-09) ------------------ + +New +~~~ +- Add method for getting current package. [Isaac Murchie] +- Add tests for ios class chain and rename methods a bit. [Kazuaki + MATSUO] +- Add class chain. [Kazuaki MATSUO] +- Add toggleTouchIdEnrollment. [Dan Graham] + +Fix +~~~ +- Fix typos in the README. [Mel Shafer] + +Other +~~~~~ - V0.25. [Isaac Murchie] - Only if key_name, key, and strategy are None do we need to set the strategy to 'tapOutside'. This change allows setting just the strategy to some other value, like 'swipeDown'. (#181) [Daniel Freer] -- Fix typos in the README. [Mel Shafer] - Correct a wording. [Kazuaki MATSUO] -- Add method for getting current package. [Isaac Murchie] - Create README.md. [Kazuaki Matsuo] - Append class chain related descriptions. [Kazuaki MATSUO] -- Add tests for ios class chain and rename methods a bit. [Kazuaki - MATSUO] -- Add class chain. [Kazuaki MATSUO] -- Add toggleTouchIdEnrollment. [Dan Graham] - Update README to include instructions for using iOS predicates. [Emil Petersen] - Update docs for UIAutomation selector to include version requirement. @@ -1436,23 +1602,35 @@ v0.25 (2018-01-09) v0.24 (2016-12-20) ------------------ -- V0.24. [Isaac Murchie] -- DontStopAppOnReset instead of stopAppOnReset. [s.zubov] + +New +~~~ - Added test cases for clear and find elements by ios predicate string. [ben.zhou] - Added clear to driver. Added find elements by ios predicate string. [ben.zhou] +Other +~~~~~ +- V0.24. [Isaac Murchie] +- DontStopAppOnReset instead of stopAppOnReset. [s.zubov] + v0.23 (2016-11-10) ------------------ -- V0.23. [Isaac Murchie] + +New +~~~ - Added touchId to driver (#143) [Dan Graham] * Added touchId to driver Wrote a test for it (still need help running Python tests though). Updated capabilities to use iOS 10.1 +Other +~~~~~ +- V0.23. [Isaac Murchie] + v0.22 (2016-03-16) ------------------ @@ -1462,11 +1640,20 @@ v0.22 (2016-03-16) v0.21 (2016-01-20) ------------------ -- V0.21. [Isaac Murchie] + +New +~~~ - Add device_time property. [Isaac Murchie] + +Fix +~~~ - Fix saucetestcase to run under Python3. [Ling Lin] The module 'new' was removed. Instead of new.newclass, use type(). + +Other +~~~~~ +- V0.21. [Isaac Murchie] - Update README.md. [tophercf] smallest win in history @@ -1486,41 +1673,65 @@ v0.19 (2015-10-09) v0.18 (2015-10-07) ------------------ -- V0.18. [Isaac Murchie] -- Remove dependency on enum. [Isaac Murchie] + +New +~~~ +- Add string file argument to driver.app_strings. [Isaac Murchie] +- Add wait_activity method for webdriver. [zhaoqifa] +- Add el.location_in_view method. [Isaac Murchie] + +Fix +~~~ - Fixed typographical error, changed accomodate to accommodate in README. [orthographic-pedant] +- Fix bug with monkeypatching. [Isaac Murchie] +- Fix to issue #71. [James Salt] +- Fix start_activity for Python 3.x. [Artur Tanistra] +- Fix start_activity for Python3. [Isaac Murchie] + +Other +~~~~~ +- V0.18. [Isaac Murchie] +- Remove dependency on enum. [Isaac Murchie] - Bump version. [Isaac Murchie] -- Add string file argument to driver.app_strings. [Isaac Murchie] - Use WebDriverWait to implement wait_activity. [zhaoqifa] -- Add wait_activity method for webdriver. [zhaoqifa] - Make tap duration be handled as ms, not s. [Isaac Murchie] - Bump version. [Isaac Murchie] -- Fix bug with monkeypatching. [Isaac Murchie] - Bump version. [Isaac Murchie] - Move monkeypatched set_value into WebElement. [Isaac Murchie] -- Add el.location_in_view method. [Isaac Murchie] -- Fix to issue #71. [James Salt] -- Fix start_activity for Python 3.x. [Artur Tanistra] -- Fix start_activity for Python3. [Isaac Murchie] v0.14 (2015-03-06) ------------------ -- Bump version. [Isaac Murchie] + +Fix +~~~ - Fix issue with single tap. [Isaac Murchie] -- Bump version. [Isaac Murchie] - Fix handling of sauce test case so ImportError is suppressed. [Isaac Murchie] +Other +~~~~~ +- Bump version. [Isaac Murchie] +- Bump version. [Isaac Murchie] + v0.12 (2015-01-13) ------------------ -- Bump version. [Isaac Murchie] + +New +~~~ - Add base class for Sauce tests. [Isaac Murchie] - Add remaining optional arguments to start_activity method. [Isaac Murchie] + +Fix +~~~ - Fix package names for starting activity. [Isaac Murchie] + +Other +~~~~~ +- Bump version. [Isaac Murchie] - Update README.md. [Mikhail Martin] Missing dot causes errors. @@ -1529,8 +1740,14 @@ v0.12 (2015-01-13) v0.11 (2014-11-14) ------------------ -- Bump version. [Isaac Murchie] + +New +~~~ - Add toggle_location_services. [Isaac Murchie] + +Other +~~~~~ +- Bump version. [Isaac Murchie] - Update webdriver.py. [urtow] Start_y - y-coordinate for start, not end @@ -1538,15 +1755,21 @@ v0.11 (2014-11-14) v0.10 (2014-09-24) ------------------ + +New +~~~ +- Added start_activity and tests. [Eric Millin] +- Added 'keyevent' since it is needed for Selendroid. [Payman Delshad] +- Add set_text method for Android. [Isaac Murchie] + +Other +~~~~~ - Bump version. [Isaac Murchie] - Removed complex_find, added get_settings, update_settings. [Jonah Stiennon] -- Added start_activity and tests. [Eric Millin] - Make long_press works with 'duration' parameter. [ianxiaohanxu] Add a new parameter 'duration = None' to _get_opts -- Added 'keyevent' since it is needed for Selendroid. [Payman Delshad] -- Add set_text method for Android. [Isaac Murchie] - Typo fix! [Cass] - Update README.md. [Johan Lundstroem] @@ -1558,25 +1781,49 @@ v0.10 (2014-09-24) v0.9 (2014-07-07) ----------------- -- Bump version. [Isaac Murchie] + +New +~~~ - Add some more tests, fix others. [Isaac Murchie] - Add ConnectionType enum. [Isaac Murchie] - Add methods for Android ime access. [Isaac Murchie] - Add network connection methods. [Isaac Murchie] -- Bump version. [Isaac Murchie] -- Change call to single-gesture tap. [Isaac Murchie] - Add strategy to hide_keyboard. [Isaac Murchie] - Add necessary ios attributes. [Brad Pitcher] - Add pull_file method. [Isaac Murchie] - Add support for open_notifications. [Isaac Murchie] +- Add optional argument 'language' to app_strings. [Isaac Murchie] +- Add context method for simplicity. [Isaac Murchie] +- Add find methods to WebElement. [Isaac Murchie] +- Add reset and hide_keyboard. [Isaac Murchie] +- Add PyPi packaging setup. [Isaac Murchie] +- Add miscellaneous methods. [Isaac Murchie] +- Add touch and multi touch. [Isaac Murchie] +- Add accessibility id locator strategy. [Isaac Murchie] +- Add Android UIAutomator locator strategy. [Isaac Murchie] +- Add iOS UIAutomation locator strategy. [Isaac Murchie] +- Add context methods. [Isaac Murchie] + +Fix +~~~ - Fix for #23: Re-add 'keyevent' temporarily. [Payman Delshad] - Fix keycode command. [Isaac Murchie] +- Fix for Python 3. [Isaac Murchie] +- Fix typos with context. [Alexander Bayandin] +- Fix typo in README (resolve #12) [Alexander Bayandin] +- Fix timing. [Isaac Murchie] +- Fix setup for egg distro, and add install instructions. [Isaac + Murchie] + +Other +~~~~~ +- Bump version. [Isaac Murchie] +- Bump version. [Isaac Murchie] +- Change call to single-gesture tap. [Isaac Murchie] - Bump version. [Isaac Murchie] -- Add optional argument 'language' to app_strings. [Isaac Murchie] - Renamed keyevent to press_keycode and added long_press_keycode. [Payman Delshad] - Bump version. [Isaac Murchie] -- Fix for Python 3. [Isaac Murchie] - Numerous fixes. [Alexander Bayandin] 1. fix comparation with None @@ -1584,26 +1831,11 @@ v0.9 (2014-07-07) 3. fix imports order (according to pep8) 4. style fixes (according to pep8) 5. another minor fixes -- Fix typos with context. [Alexander Bayandin] -- Fix typo in README (resolve #12) [Alexander Bayandin] -- Add context method for simplicity. [Isaac Murchie] -- Fix timing. [Isaac Murchie] - Update zoom/pinch signatures. [Isaac Murchie] - Remove tag name, use class. [Isaac Murchie] - Don't send multitouch for single finger tap. [Isaac Murchie] -- Add find methods to WebElement. [Isaac Murchie] - Miscellaneous fixes. [Isaac Murchie] -- Add reset and hide_keyboard. [Isaac Murchie] -- Fix setup for egg distro, and add install instructions. [Isaac - Murchie] -- Add PyPi packaging setup. [Isaac Murchie] -- Add miscellaneous methods. [Isaac Murchie] -- Add touch and multi touch. [Isaac Murchie] - Update desired caps. [Isaac Murchie] -- Add accessibility id locator strategy. [Isaac Murchie] -- Add Android UIAutomator locator strategy. [Isaac Murchie] -- Add iOS UIAutomation locator strategy. [Isaac Murchie] -- Add context methods. [Isaac Murchie] - Basic module structure. [Isaac Murchie] diff --git a/README.md b/README.md index d54e88ed..94319fbb 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ An extension library for adding [Selenium 3.0 draft](https://dvcs.w3.org/hg/webd functionality to the Python language bindings, for use with the mobile testing framework [Appium](https://appium.io). +# Notice + +**Since v1.0.0 only Python 3 is supported** + # Getting the Appium Python client There are three ways to install and use the Appium Python client. @@ -545,11 +549,11 @@ and the list of additional arguments that can be passed are, ```python 'app_wait_package' - 'app_wait_activity' - 'intent_action' - 'intent_category' - 'intent_flags' - 'optional_intent_arguments' + 'app_wait_activity' + 'intent_action' + 'intent_category' + 'intent_flags' + 'optional_intent_arguments' 'dont_stop_app_on_reset' ```