From 36b4990bd8c34ce32cf013754058b3eb928d2186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Aug 2021 11:50:19 +0900 Subject: [PATCH 001/548] chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1b911868c..12fb77bb6 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=0.1" mock = "~=4.0" -pylint = "~=2.8" +pylint = "~=2.10" astroid = "~=2.5" isort = "~=5.8" From 76ef1e77b6cb48ac62bee02e5b8003c5fa50a3e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 02:53:44 +0900 Subject: [PATCH 002/548] chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - [Release notes](https://github.com/PyCQA/astroid/releases) - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) --- updated-dependencies: - dependency-name: astroid dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 12fb77bb6..4e497f92a 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ types-python-dateutil = "~=0.1" mock = "~=4.0" pylint = "~=2.10" -astroid = "~=2.5" +astroid = "~=2.7" isort = "~=5.8" mypy = "~=0.812" From 5f603cecb9464335f4e1b99e21a880dff12a958a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 11:21:51 +0900 Subject: [PATCH 003/548] chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 4e497f92a..f361aea57 100644 --- a/Pipfile +++ b/Pipfile @@ -25,4 +25,4 @@ pylint = "~=2.10" astroid = "~=2.7" isort = "~=5.8" -mypy = "~=0.812" +mypy = "~=0.910" From f8d3a38639557081700a273c5dcc641e42112aba Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 30 Aug 2021 00:02:44 -0700 Subject: [PATCH 004/548] feat: do not raise an error in case method is already defined (#632) --- appium/webdriver/webdriver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 733054364..5712cf86a 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -292,7 +292,7 @@ def __init__( instance = extension(self.execute) method_name = instance.method_name() if hasattr(WebDriver, method_name): - raise ValueError(f'{method_name} is already defined.') + logger.debug(f"Overriding the method '{method_name}'") # add a new method named 'instance.method_name()' and call it setattr(WebDriver, method_name, getattr(instance, method_name)) From 31eda777d0761689b84a1503f6b7c896f1df361a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Sep 2021 10:55:30 +0900 Subject: [PATCH 005/548] chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 (#634) 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/v2.13.0...v2.15.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index f361aea57..68ecfc694 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.13" +pre-commit = "~=2.15" [packages] selenium = "~=3.141" From e85d7d402f93f24d21e116040a06d6ee95d2e5a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Sep 2021 10:55:52 +0900 Subject: [PATCH 006/548] chore(deps): update types-python-dateutil requirement (#633) Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 68ecfc694..eefde70fe 100644 --- a/Pipfile +++ b/Pipfile @@ -18,7 +18,7 @@ tox = "~=3.24" httpretty = "~=1.0" python-dateutil = "~=2.8" -types-python-dateutil = "~=0.1" +types-python-dateutil = "~=2.8" mock = "~=4.0" pylint = "~=2.10" From 954cd7a418736716d1cd3ffa45632d3f453a2623 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 26 Sep 2021 19:21:42 -0700 Subject: [PATCH 007/548] Bump 1.3.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index ee965d3a0..dc59fbaf3 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '1.2.0' +version = '1.3.0' From 484db8a2af6a87ff49a0be7a65e2405b5bce37c0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 26 Sep 2021 19:22:27 -0700 Subject: [PATCH 008/548] Update changelog for 1.3.0 --- CHANGELOG.rst | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16f0b7a93..fcb0e6626 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,126 @@ Changelog ========= +v1.3.0 (2021-09-27) +------------------- + +New +~~~ +- Feat: do not raise an error in case method is already defined (#632) + [Kazuaki Matsuo] +- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] + + * feat: add satellites in set_location + + * fix review +- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] + + * chore: add placeholder + + * move to extention way + + * revert pytest + + * add todo + + * call method_name instead of wrapper + + * remove types + + * rename a method + + * add examples + + * add types-python-dateutil as error message + + * add example more + + * tweak naming + + * Explicit Dict + +Other +~~~~~ +- Bump 1.3.0. [Kazuaki Matsuo] +- Chore(deps): update types-python-dateutil requirement (#633) + [dependabot[bot]] + + Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. + - [Release notes](https://github.com/python/typeshed/releases) + - [Commits](https://github.com/python/typeshed/commits) + + --- + updated-dependencies: + - dependency-name: types-python-dateutil + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 + (#634) [dependabot[bot]] + + 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/v2.13.0...v2.15.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) + [dependabot[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.23.0...3.24.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Update changelog for 1.2.0. [Kazuaki Matsuo] + + v1.2.0 (2021-06-07) ------------------- From 4643402c287dd75ef0ed68dee1e15229116cc00d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 15:09:58 +0900 Subject: [PATCH 009/548] chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) 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.12.0...v3.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index eefde70fe..5b7c2b517 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ selenium = "~=3.141" black = "==20.8b1" pytest = "~=6.2" -pytest-cov = "~=2.12" +pytest-cov = "~=3.0" tox = "~=3.24" From 9baa3786480c68f00e6bb1a6ea8c5b79fc4c0d62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 15:10:15 +0900 Subject: [PATCH 010/548] chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5b7c2b517..24de9d00a 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.10" +pylint = "~=2.11" astroid = "~=2.7" isort = "~=5.8" From c7d4193a26c766da66fa16ecb89fc698a781826c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 15 Oct 2021 17:33:11 +0900 Subject: [PATCH 011/548] feat: Change base selenium client version to selenium 4 (#636) - Changed base selenium client version to v4 - No longer forceMjsonwp works - Add strict_ssl option in webdriver.Remote --- CHANGELOG.rst | 116 ++++++++++++++++++ Pipfile | 2 +- README.md | 40 +++--- appium/version.py | 2 +- appium/webdriver/appium_connection.py | 20 ++- appium/webdriver/extensions/action_helpers.py | 2 +- appium/webdriver/mobilecommand.py | 4 + appium/webdriver/webdriver.py | 98 ++++++++------- ci-jobs/functional/run_appium.yml | 2 + setup.py | 2 +- test/functional/android/helper/test_helper.py | 7 ++ test/functional/ios/safari_tests.py | 9 +- test/unit/webdriver/log_test.py | 47 +++++++ .../webdriver/search_context/android_test.py | 6 +- .../webdriver/search_context/windows_test.py | 2 +- test/unit/webdriver/webdriver_test.py | 35 ------ test/unit/webdriver/webelement_test.py | 8 +- 17 files changed, 289 insertions(+), 113 deletions(-) create mode 100644 test/unit/webdriver/log_test.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fcb0e6626..db6516a60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,122 @@ Changelog ========= +(unreleased) +------------ + +New +~~~ +- Feat: do not raise an error in case method is already defined (#632) + [Kazuaki Matsuo] +- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] + + * feat: add satellites in set_location + + * fix review +- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] + + * chore: add placeholder + + * move to extention way + + * revert pytest + + * add todo + + * call method_name instead of wrapper + + * remove types + + * rename a method + + * add examples + + * add types-python-dateutil as error message + + * add example more + + * tweak naming + + * Explicit Dict +- Add types-python-dateutil in 4 branch. [Kazuaki Matsuo] +- Add test to check log path. [Kazuaki Matsuo] + +Fix +~~~ +- Fix tests. [Kazuaki Matsuo] +- Remove w3c argument in MobileWebElement (#598) [Kazuaki Matsuo] + + * Bump 2.0.0.b1 + + * Update changelog for 2.0.0.b1 + + * remove w3c +- Fix format with black. [Kazuaki Matsuo] +- Fix lint, CI and tests. [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.0.0.rc4. [Kazuaki Matsuo] +- Tweak tests. [Kazuaki Matsuo] +- Remove w3c flag. [Kazuaki Matsuo] +- Remove w3c=True from MobileWebElement argument. [Kazuaki Matsuo] +- Bump selenium version. [Kazuaki Matsuo] +- Update changelog for 2.0.0.rc3. [Kazuaki Matsuo] +- Bump 2.0.0.rc3. [Kazuaki Matsuo] +- Use 4.0.0.rc3. [Kazuaki Matsuo] +- Update changelog for 2.0.0.rc2. [Kazuaki Matsuo] +- Bump 2.0.0.rc2. [Kazuaki Matsuo] +- Try rc2. [Kazuaki Matsuo] +- Update changelog for 2.0.0.rc1. [Kazuaki Matsuo] +- Bump 2.0.0.rc1. [Kazuaki Matsuo] +- Try rc1. [Kazuaki Matsuo] +- Update changelog for 2.0.0.b5. [Kazuaki Matsuo] +- Bump 2.0.0.b5. [Kazuaki Matsuo] +- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) + [dependabot[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.23.0...3.24.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Override log. [Kazuaki Matsuo] +- Bump selenium version in tox. [Kazuaki Matsuo] +- Update changelog for 2.0.0.b4. [Kazuaki Matsuo] +- Bump 2.0.0.b4. [Kazuaki Matsuo] +- Use python selenium client v4.0.0.b4. [Kazuaki Matsuo] +- Update changelog for 2.0.0.b3. [Kazuaki Matsuo] +- Bump 2.0.0.b3. [Kazuaki Matsuo] +- Tweak some lines. [Kazuaki Matsuo] +- Update selenium in pipfile. [Kazuaki Matsuo] +- Bump selenium 4.0.0.b3. [Kazuaki Matsuo] +- Update pipfile. [Kazuaki Matsuo] +- Update changelog for 2.0.0.b2. [Kazuaki Matsuo] +- Bump 2.0.0.b2. [Kazuaki Matsuo] +- Bump base selenium version to 4.0.0.b2. [Kazuaki Matsuo] +- Bump v2.0.0.a0. [Kazuaki Matsuo] +- Update changelog for v2.0.0.a0. [Kazuaki Matsuo] +- Bump selenium. [Kazuaki Matsuo] + + v1.3.0 (2021-09-27) ------------------- diff --git a/Pipfile b/Pipfile index 24de9d00a..d4e0eda45 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.15" [packages] -selenium = "~=3.141" +selenium = "~=4.0.0" black = "==20.8b1" diff --git a/README.md b/README.md index 0b2f8d1bd..fbfa9e869 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,15 @@ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -An extension library for adding [Selenium 3.0 draft](https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html) and [Mobile JSON Wire Protocol Specification draft](https://github.com/SeleniumHQ/mobile-spec/blob/master/spec-draft.md) -functionality to the Python language bindings, for use with the mobile testing -framework [Appium](https://appium.io). +An extension library for adding [WebDriver Protocol](https://www.w3.org/TR/webdriver/) and Appium commands to the Selenium Python language binding for use with the mobile testing framework [Appium](https://appium.io). ## Notice -Since **v1.0.0**, only Python 3 is supported +Since **v1.0.0**, only Python 3.7+ is supported. -### developing version -[selenium-4](https://github.com/appium/python-client/tree/selenium-4) branch is a developing branch to switch base selenium client version from v3 to v4. The branch is available as pre-release versioning like `2.0.0.a0` via pypi. - -Main differences since current v1 is the v2 can connect to invalid SSL environment like self-certificated server. Please take a look at the branch's README for more details. +Since **v2.0.0**, the base selenium client version is v4. +The version only works in W3C WebDriver protocol format. +If you would like to use the old protocol (MJSONWP), please use v1 Appium Python client. ## Getting the Appium Python client @@ -52,12 +49,8 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). ## Usage -The Appium Python Client is fully compliant with the Selenium 3.0 specification -draft, with some helpers to make mobile testing in Python easier. The majority of -the usage remains as it has been for Selenium 2 (WebDriver), and as the [official -Selenium Python bindings](https://pypi.org/project/selenium/) begins to -implement the new specification that implementation will be used underneath, so -test code can be written that is utilizable with both bindings. +The Appium Python Client is fully compliant with the WebDriver Protocol +including several helpers to make mobile testing in Python easier. To use the new functionality now, and to use the superset of functions, instead of including the Selenium `webdriver` module in your test code, use that from @@ -133,6 +126,25 @@ desired_caps = dict( self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, direct_connection=True) ``` +## Relax SSL validation + +`strict_ssl` option allows you to send commands to an invalid certificate host like a self-signed one. + +```python +import unittest +from appium import webdriver + +desired_caps = dict( + platformName='iOS', + platformVersion='13.4', + automationName='xcuitest', + deviceName='iPhone Simulator', + app=PATH('../../apps/UICatalog.app.zip') +) + +self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, strict_ssl=False) +``` + ## Documentation - https://appium.github.io/python-client-sphinx/ is detailed documentation diff --git a/appium/version.py b/appium/version.py index dc59fbaf3..9dd833aa4 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '1.3.0' +version = '2.0.0.rc4' diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 36fa34327..eb218b994 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -13,8 +13,9 @@ # limitations under the License. import uuid -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, Union +import urllib3 from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.helper import library_version @@ -24,6 +25,23 @@ class AppiumConnection(RemoteConnection): + def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyManager]: + # https://github.com/SeleniumHQ/selenium/blob/0e0194b0e52a34e7df4b841f1ed74506beea5c3e/py/selenium/webdriver/remote/remote_connection.py#L134 + pool_manager_init_args = {'timeout': self._timeout} + # pylint: disable=E1101 + if self._ca_certs: + pool_manager_init_args['cert_reqs'] = 'CERT_REQUIRED' + pool_manager_init_args['ca_certs'] = self._ca_certs + else: + # This line is necessary to disable certificate verification + pool_manager_init_args['cert_reqs'] = 'CERT_NONE' + + return ( + urllib3.PoolManager(**pool_manager_init_args) + if self._proxy_url is None + else urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args) + ) + @classmethod def get_remote_connection_headers(cls, parsed_url: 'ParseResult', keep_alive: bool = True) -> Dict[str, Any]: """Override get_remote_connection_headers in RemoteConnection""" diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 0a0fc4c25..dcb38e6d2 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -45,7 +45,7 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: """ # XCUITest x W3C spec has no duration by default in server side - if self.w3c and duration is None: + if duration is None: duration = 600 action = TouchAction(self) diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index b0bd8c4d5..966d7e4c1 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -107,3 +107,7 @@ class MobileCommand: # iOS TOUCH_ID = 'touchId' TOGGLE_TOUCH_ID_ENROLLMENT = 'toggleTouchIdEnrollment' + + # To override selenium commands + GET_LOG = 'getLog' + GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 5712cf86a..ab5998ae5 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -78,7 +78,6 @@ _OSS_W3C_CONVERSION = {'acceptSslCerts': 'acceptInsecureCerts', 'version': 'browserVersion', 'platform': 'platformName'} _EXTENSION_CAPABILITY = ':' -_FORCE_MJSONWP = 'forceMjsonwp' # override # Add appium prefix for the non-W3C capabilities @@ -262,8 +261,16 @@ def __init__( keep_alive: bool = True, direct_connection: bool = False, extensions: List[T] = [], + strict_ssl: bool = True, ): + if strict_ssl is False: + # pylint: disable=E1101 + import urllib3 + + AppiumConnection.set_certificate_bundle_path(None) + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__( AppiumConnection(command_executor, keep_alive=keep_alive), desired_capabilities, browser_profile, proxy ) @@ -314,17 +321,17 @@ def _update_command_executor(self, keep_alive: bool) -> None: direct_port = 'directConnectPort' direct_path = 'directConnectPath' - if not {direct_protocol, direct_host, direct_port, direct_path}.issubset(set(self.capabilities)): + if not {direct_protocol, direct_host, direct_port, direct_path}.issubset(set(self.caps)): message = 'Direct connect capabilities from server were:\n' for key in [direct_protocol, direct_host, direct_port, direct_path]: - message += '{}: \'{}\'\n'.format(key, self.capabilities.get(key, '')) + message += '{}: \'{}\'\n'.format(key, self.caps.get(key, '')) logger.warning(message) return - protocol = self.capabilities[direct_protocol] - hostname = self.capabilities[direct_host] - port = self.capabilities[direct_port] - path = self.capabilities[direct_path] + protocol = self.caps[direct_protocol] + hostname = self.caps[direct_host] + port = self.caps[direct_port] + path = self.caps[direct_path] executor = f'{protocol}://{hostname}:{port}{path}' logger.info('Updated request endpoint to %s', executor) @@ -332,6 +339,7 @@ def _update_command_executor(self, keep_alive: bool) -> None: self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) self._addCommands() + # https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277 def start_session(self, capabilities: Dict, browser_profile: Optional[str] = None) -> None: """Creates a new session with the desired capabilities. @@ -358,30 +366,18 @@ def start_session(self, capabilities: Dict, browser_profile: Optional[str] = Non if 'sessionId' not in response: response = response['value'] self.session_id = response['sessionId'] - self.capabilities = response.get('value') + self.caps = response.get('value') # if capabilities is none we are probably speaking to # a W3C endpoint - if self.capabilities is None: - self.capabilities = response.get('capabilities') + if self.caps is None: + self.caps = response.get('capabilities') # Double check to see if we have a W3C Compliant browser - self.w3c = response.get('status') is None - self.command_executor.w3c = self.w3c + self.command_executor.w3c = True def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: """Manage capabilities whether W3C format or MJSONWP format""" - if _FORCE_MJSONWP in capabilities: - logger.warning( - "[Deprecated] 'forceMjsonwp' capability will be dropped after switching base selenium client from v3 to v4 " - "to follow W3C spec capabilities. Appium 2.0 will also support only W3C session creation capabilities." - ) - force_mjsonwp = capabilities[_FORCE_MJSONWP] - del capabilities[_FORCE_MJSONWP] - - if force_mjsonwp != False: - return {'desiredCapabilities': capabilities} - w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} @@ -398,18 +394,17 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> Mobil """ # TODO: If we need, we should enable below converter for Web context - # if self.w3c: - # if by == By.ID: - # by = By.CSS_SELECTOR - # value = '[id="%s"]' % value - # elif by == By.TAG_NAME: - # by = By.CSS_SELECTOR - # elif by == By.CLASS_NAME: - # by = By.CSS_SELECTOR - # value = ".%s" % value - # elif by == By.NAME: - # by = By.CSS_SELECTOR - # value = '[name="%s"]' % value + # if by == By.ID: + # by = By.CSS_SELECTOR + # value = '[id="%s"]' % value + # elif by == By.TAG_NAME: + # by = By.CSS_SELECTOR + # elif by == By.CLASS_NAME: + # by = By.CSS_SELECTOR + # value = ".%s" % value + # elif by == By.NAME: + # by = By.CSS_SELECTOR + # value = '[name="%s"]' % value return self.execute(RemoteCommand.FIND_ELEMENT, {'using': by, 'value': value})['value'] @@ -425,25 +420,24 @@ def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> Unio :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ # TODO: If we need, we should enable below converter for Web context - # if self.w3c: - # if by == By.ID: - # by = By.CSS_SELECTOR - # value = '[id="%s"]' % value - # elif by == By.TAG_NAME: - # by = By.CSS_SELECTOR - # elif by == By.CLASS_NAME: - # by = By.CSS_SELECTOR - # value = ".%s" % value - # elif by == By.NAME: - # by = By.CSS_SELECTOR - # value = '[name="%s"]' % value + # if by == By.ID: + # by = By.CSS_SELECTOR + # value = '[id="%s"]' % value + # elif by == By.TAG_NAME: + # by = By.CSS_SELECTOR + # elif by == By.CLASS_NAME: + # by = By.CSS_SELECTOR + # value = ".%s" % value + # elif by == By.NAME: + # by = By.CSS_SELECTOR + # value = '[name="%s"]' % value # Return empty list if driver returns null # See https://github.com/SeleniumHQ/selenium/issues/4555 return self.execute(RemoteCommand.FIND_ELEMENTS, {'using': by, 'value': value})['value'] or [] - def create_web_element(self, element_id: Union[int, str], w3c: bool = False) -> MobileWebElement: + def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """Creates a web element with the specified element_id. Overrides method in Selenium WebDriver in order to always give them @@ -451,12 +445,11 @@ def create_web_element(self, element_id: Union[int, str], w3c: bool = False) -> Args: element_id: The element id to create a web element - w3c: Whether the element is W3C or MJSONWP Returns: `MobileWebElement` """ - return MobileWebElement(self, element_id, w3c) + return MobileWebElement(self, element_id) def set_value(self, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. @@ -515,3 +508,8 @@ def _addCommands(self) -> None: 'GET', '/session/$sessionId/element/$id/location_in_view', ) + + # override for Appium 1.x + # Appium 2.0 and Appium 1.22 work with `/se/log` and `/se/log/types` + self.command_executor._commands[Command.GET_LOG] = ('POST', '/session/$sessionId/log') + self.command_executor._commands[Command.GET_AVAILABLE_LOG_TYPES] = ('GET', '/session/$sessionId/log/types') diff --git a/ci-jobs/functional/run_appium.yml b/ci-jobs/functional/run_appium.yml index 333449d7e..a866957c4 100644 --- a/ci-jobs/functional/run_appium.yml +++ b/ci-jobs/functional/run_appium.yml @@ -13,6 +13,8 @@ steps: versionSpec: '3.x' - script: brew install ffmpeg displayName: Resolve dependencies (Appium server) +# - script: pip install trio==0.17.0 +# displayName: Install trio - script: python setup.py install displayName: Install python language bindings for Appium - script: | diff --git a/setup.py b/setup.py index a8c8867cb..7eb4e8bc2 100644 --- a/setup.py +++ b/setup.py @@ -47,5 +47,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium >= 3.14.1, < 4'], + install_requires=['selenium ~= 4.0.0'], ) diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index 25f14ce2c..1f726f55e 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -22,6 +22,13 @@ from . import desired_capabilities +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + from appium.webdriver.webelement import WebElement + +# the emulator is sometimes slow and needs time to think +SLEEPY_TIME = 10 + # The package name of ApiDemos-debug.apk.zip APIDEMO_PKG_NAME = 'io.appium.android.apis' diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 4c25920e8..a62cf2852 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time + from appium import webdriver from .helper.desired_capabilities import get_desired_capabilities @@ -34,4 +36,9 @@ def test_context(self) -> None: def test_get(self) -> None: self.driver.get("http://google.com") - assert 'Google' == self.driver.title + for _ in range(5): + time.sleep(0.5) + if 'Google' == self.driver.title: + return + + assert False, 'The title was wrong' diff --git a/test/unit/webdriver/log_test.py b/test/unit/webdriver/log_test.py new file mode 100644 index 000000000..3c63f657c --- /dev/null +++ b/test/unit/webdriver/log_test.py @@ -0,0 +1,47 @@ +#!/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 httpretty + +from appium.webdriver.webdriver import WebDriver +from test.unit.helper.test_helper import appium_command, get_httpretty_request_body, ios_w3c_driver + + +class TestWebDriverLog(object): + @httpretty.activate + def test_get_log_types(self): + driver = ios_w3c_driver() + httpretty.register_uri( + httpretty.GET, + appium_command('/session/1234567890/log/types'), + body=json.dumps({'value': ['syslog']}), + ) + log_types = driver.log_types + assert log_types == ['syslog'] + + @httpretty.activate + def test_get_log(self): + driver = ios_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/log'), + body=json.dumps({'value': ['logs as array']}), + ) + log_types = driver.get_log('syslog') + assert log_types == ['logs as array'] + + d = get_httpretty_request_body(httpretty.last_request()) + assert {'type': 'syslog'} == d diff --git a/test/unit/webdriver/search_context/android_test.py b/test/unit/webdriver/search_context/android_test.py index badee09f6..94aefe081 100644 --- a/test/unit/webdriver/search_context/android_test.py +++ b/test/unit/webdriver/search_context/android_test.py @@ -73,7 +73,7 @@ def test_find_elements_by_android_data_matcher_no_value(self): @httpretty.activate def test_find_element_by_android_data_matcher(self): driver = android_w3c_driver() - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/element'), @@ -94,7 +94,7 @@ def test_find_element_by_android_data_matcher(self): @httpretty.activate def test_find_elements_by_android_data_matcher(self): driver = android_w3c_driver() - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/elements'), @@ -113,7 +113,7 @@ def test_find_elements_by_android_data_matcher(self): @httpretty.activate def test_find_elements_by_android_data_matcher_no_value(self): driver = android_w3c_driver() - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/elements'), body='{"value": []}' ) diff --git a/test/unit/webdriver/search_context/windows_test.py b/test/unit/webdriver/search_context/windows_test.py index 5e0b3c4a6..99296df18 100644 --- a/test/unit/webdriver/search_context/windows_test.py +++ b/test/unit/webdriver/search_context/windows_test.py @@ -22,7 +22,7 @@ class TestWebDriverWindowsSearchContext(object): @httpretty.activate def test_find_element_by_windows_uiautomation(self): driver = android_w3c_driver() - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/element'), diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index d9cfea29b..db0281a10 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -61,42 +61,8 @@ def test_create_session(self): assert request_json.get('desiredCapabilities') is not None assert driver.session_id == 'session-id' - assert driver.w3c assert driver.command_executor.w3c - @httpretty.activate - def test_create_session_forceMjsonwp(self): - httpretty.register_uri( - httpretty.POST, - 'http://localhost:4723/wd/hub/session', - body='{ "capabilities": {"deviceName": "Android Emulator"}, "status": 0, "sessionId": "session-id"}', - ) - - desired_caps = { - 'platformName': 'Android', - 'deviceName': 'Android Emulator', - 'app': 'path/to/app', - 'automationName': 'UIAutomator2', - 'forceMjsonwp': True, - } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - - # This tests counts the same request twice on Azure only for now (around 20th May, 2021). Local running works. - # Should investigate the cause. - # assert len(httpretty.HTTPretty.latest_requests) == 1 - - request = httpretty.HTTPretty.latest_requests[0] - assert request.headers['content-type'] == 'application/json;charset=UTF-8' - assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] - - request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) - assert request_json.get('capabilities') is None - assert request_json.get('desiredCapabilities') is not None - - assert driver.session_id == 'session-id' - assert driver.w3c is False - assert driver.command_executor.w3c is False - @httpretty.activate def test_create_session_change_session_id(self): httpretty.register_uri( @@ -255,7 +221,6 @@ def exceptionCallback(request, uri, headers): driver = ios_w3c_driver() httpretty.register_uri(httpretty.GET, appium_command('/session/1234567890'), body=exceptionCallback) events = driver.events - mock_warning.assert_called_once() assert events == {} @httpretty.activate diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py index f9b38922a..59464fd89 100644 --- a/test/unit/webdriver/webelement_test.py +++ b/test/unit/webdriver/webelement_test.py @@ -28,7 +28,7 @@ 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) + element = MobileWebElement(driver, 'element_id') value = 'happy testing' element.set_value(value) @@ -40,7 +40,7 @@ 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 = MobileWebElement(driver, 'element_id') element.send_keys('happy testing') d = get_httpretty_request_body(httpretty.last_request()) @@ -54,7 +54,7 @@ def test_send_key_with_file(self): httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/element/element_id/value')) try: - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') element.send_keys(tmp_f.name) finally: tmp_f.close() @@ -72,7 +72,7 @@ def test_get_attribute_with_dict(self): body=json.dumps({"value": rect_dict}), ) - element = MobileWebElement(driver, 'element_id', w3c=True) + element = MobileWebElement(driver, 'element_id') ef = element.get_attribute('rect') d = httpretty.last_request() From 658cadd065411caf5299450a610fe9fd725cdceb Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 21 Oct 2021 03:59:15 +0900 Subject: [PATCH 012/548] chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) chore: cleanup no longer needed code in w3c, bump dev Pipfile --- Pipfile | 6 ++-- appium/webdriver/webdriver.py | 24 ++++++-------- appium/webdriver/webelement.py | 46 +++++++++++++-------------- test/unit/webdriver/webdriver_test.py | 2 -- 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/Pipfile b/Pipfile index d4e0eda45..e1b163082 100644 --- a/Pipfile +++ b/Pipfile @@ -16,13 +16,13 @@ pytest-cov = "~=3.0" tox = "~=3.24" -httpretty = "~=1.0" +httpretty = "~=1.1" python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" pylint = "~=2.11" -astroid = "~=2.7" -isort = "~=5.8" +astroid = "~=2.8" +isort = "~=5.9" mypy = "~=0.910" diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index ab5998ae5..326e1be10 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -23,7 +23,6 @@ from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.logger import logger -from appium.webdriver.command_method import CommandMethod from appium.webdriver.common.mobileby import MobileBy from .appium_connection import AppiumConnection @@ -83,31 +82,31 @@ # Add appium prefix for the non-W3C capabilities -def _make_w3c_caps(caps: Dict) -> Dict[str, List[Dict[str, Any]]]: +def _make_w3c_caps(caps: Dict) -> Dict[str, Union[Dict[str, Any], List[Dict[str, Any]]]]: appium_prefix = 'appium:' caps = copy.deepcopy(caps) profile = caps.get('firefox_profile') - first_match = {} + always_match = {} if caps.get('proxy') and caps['proxy'].get('proxyType'): caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower() for k, v in caps.items(): if v and k in _OSS_W3C_CONVERSION: - first_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v + always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v if k in _W3C_CAPABILITY_NAMES or _EXTENSION_CAPABILITY in k: - first_match[k] = v + always_match[k] = v else: if not k.startswith(appium_prefix): - first_match[appium_prefix + k] = v + always_match[appium_prefix + k] = v if profile: - moz_opts = first_match.get('moz:firefoxOptions', {}) + moz_opts = always_match.get('moz:firefoxOptions', {}) # If it's already present, assume the caller did that intentionally. if 'profile' not in moz_opts: # Don't mutate the original capabilities. new_opts = copy.deepcopy(moz_opts) new_opts['profile'] = profile - first_match['moz:firefoxOptions'] = new_opts - return {'firstMatch': [first_match]} + always_match['moz:firefoxOptions'] = new_opts + return {'alwaysMatch': always_match, 'firstMatch': [{}]} T = TypeVar('T', bound='WebDriver') @@ -259,7 +258,7 @@ def __init__( browser_profile: str = None, proxy: str = None, keep_alive: bool = True, - direct_connection: bool = False, + direct_connection: bool = True, extensions: List[T] = [], strict_ssl: bool = True, ): @@ -334,7 +333,7 @@ def _update_command_executor(self, keep_alive: bool) -> None: path = self.caps[direct_path] executor = f'{protocol}://{hostname}:{port}{path}' - logger.info('Updated request endpoint to %s', executor) + logger.debug('Updated request endpoint to %s', executor) # Override command executor self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) self._addCommands() @@ -373,9 +372,6 @@ def start_session(self, capabilities: Dict, browser_profile: Optional[str] = Non if self.caps is None: self.caps = response.get('capabilities') - # Double check to see if we have a W3C Compliant browser - self.command_executor.w3c = True - def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: """Manage capabilities whether W3C format or MJSONWP format""" w3c_caps = _make_w3c_caps(capabilities) diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index c0ef235f1..9542377f2 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -95,18 +95,17 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> T: `appium.webdriver.webelement.WebElement` """ # TODO: If we need, we should enable below converter for Web context - # if self._w3c: - # if by == By.ID: - # by = By.CSS_SELECTOR - # value = '[id="%s"]' % value - # elif by == By.TAG_NAME: - # by = By.CSS_SELECTOR - # elif by == By.CLASS_NAME: - # by = By.CSS_SELECTOR - # value = ".%s" % value - # elif by == By.NAME: - # by = By.CSS_SELECTOR - # value = '[name="%s"]' % value + # if by == By.ID: + # by = By.CSS_SELECTOR + # value = '[id="%s"]' % value + # elif by == By.TAG_NAME: + # by = By.CSS_SELECTOR + # elif by == By.CLASS_NAME: + # by = By.CSS_SELECTOR + # value = ".%s" % value + # elif by == By.NAME: + # by = By.CSS_SELECTOR + # value = '[name="%s"]' % value return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] @@ -128,18 +127,17 @@ def find_elements(self, by: str = By.ID, value: Union[str, Dict] = None) -> List :obj:`list` of :obj:`appium.webdriver.webelement.WebElement` """ # TODO: If we need, we should enable below converter for Web context - # if self._w3c: - # if by == By.ID: - # by = By.CSS_SELECTOR - # value = '[id="%s"]' % value - # elif by == By.TAG_NAME: - # by = By.CSS_SELECTOR - # elif by == By.CLASS_NAME: - # by = By.CSS_SELECTOR - # value = ".%s" % value - # elif by == By.NAME: - # by = By.CSS_SELECTOR - # value = '[name="%s"]' % value + # if by == By.ID: + # by = By.CSS_SELECTOR + # value = '[id="%s"]' % value + # elif by == By.TAG_NAME: + # by = By.CSS_SELECTOR + # elif by == By.CLASS_NAME: + # by = By.CSS_SELECTOR + # value = ".%s" % value + # elif by == By.NAME: + # by = By.CSS_SELECTOR + # value = '[name="%s"]' % value return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index db0281a10..6565277aa 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -20,7 +20,6 @@ from appium import version as appium_version from appium import webdriver -from appium.webdriver.command_method import CommandMethod from appium.webdriver.webdriver import ExtensionBase, WebDriver from test.unit.helper.test_helper import ( android_w3c_driver, @@ -61,7 +60,6 @@ def test_create_session(self): assert request_json.get('desiredCapabilities') is not None assert driver.session_id == 'session-id' - assert driver.command_executor.w3c @httpretty.activate def test_create_session_change_session_id(self): From a2d3df1aa65654727aa8becc104e4c1207c84ab2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 06:07:25 +0900 Subject: [PATCH 013/548] chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 (#637) Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3dab8f14e..1f90be955 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx >= 3.0, <4.0 -sphinx_rtd_theme < 1.0 +sphinx_rtd_theme < 2.0 From 63954cd80136ecfe0af9312ce506f2ae6889635b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 20 Oct 2021 23:33:14 -0700 Subject: [PATCH 014/548] update gitchangelog once --- CHANGELOG.rst | 114 +++++++++++++------------------------------------- 1 file changed, 29 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index db6516a60..86a1df560 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,115 +7,59 @@ Changelog New ~~~ -- Feat: do not raise an error in case method is already defined (#632) +- Feat: Change base selenium client version to selenium 4 (#636) [Kazuaki Matsuo] -- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] - - * feat: add satellites in set_location - - * fix review -- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] - * chore: add placeholder - - * move to extention way - - * revert pytest - - * add todo + - Changed base selenium client version to v4 + - No longer forceMjsonwp works + - Add strict_ssl option in webdriver.Remote - * call method_name instead of wrapper - - * remove types - - * rename a method - - * add examples - - * add types-python-dateutil as error message - - * add example more - - * tweak naming - - * Explicit Dict -- Add types-python-dateutil in 4 branch. [Kazuaki Matsuo] -- Add test to check log path. [Kazuaki Matsuo] - -Fix -~~~ -- Fix tests. [Kazuaki Matsuo] -- Remove w3c argument in MobileWebElement (#598) [Kazuaki Matsuo] - - * Bump 2.0.0.b1 +Other +~~~~~ +- Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 + (#637) [Kazuaki Matsuo, dependabot[bot]] - * Update changelog for 2.0.0.b1 + Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. + - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) + - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) + - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) - * remove w3c -- Fix format with black. [Kazuaki Matsuo] -- Fix lint, CI and tests. [Kazuaki Matsuo] + --- + updated-dependencies: + - dependency-name: sphinx-rtd-theme + dependency-type: direct:production + ... +- Chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) + [Kazuaki Matsuo] -Other -~~~~~ -- Bump 2.0.0.rc4. [Kazuaki Matsuo] -- Tweak tests. [Kazuaki Matsuo] -- Remove w3c flag. [Kazuaki Matsuo] -- Remove w3c=True from MobileWebElement argument. [Kazuaki Matsuo] -- Bump selenium version. [Kazuaki Matsuo] -- Update changelog for 2.0.0.rc3. [Kazuaki Matsuo] -- Bump 2.0.0.rc3. [Kazuaki Matsuo] -- Use 4.0.0.rc3. [Kazuaki Matsuo] -- Update changelog for 2.0.0.rc2. [Kazuaki Matsuo] -- Bump 2.0.0.rc2. [Kazuaki Matsuo] -- Try rc2. [Kazuaki Matsuo] -- Update changelog for 2.0.0.rc1. [Kazuaki Matsuo] -- Bump 2.0.0.rc1. [Kazuaki Matsuo] -- Try rc1. [Kazuaki Matsuo] -- Update changelog for 2.0.0.b5. [Kazuaki Matsuo] -- Bump 2.0.0.b5. [Kazuaki Matsuo] -- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) + chore: cleanup no longer needed code in w3c, bump dev Pipfile +- Chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) [dependabot[bot]] Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... -- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) +- Chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) [dependabot[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.23.0...3.24.0) + 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.12.0...v3.0.0) --- updated-dependencies: - - dependency-name: tox + - dependency-name: pytest-cov dependency-type: direct:production ... -- Override log. [Kazuaki Matsuo] -- Bump selenium version in tox. [Kazuaki Matsuo] -- Update changelog for 2.0.0.b4. [Kazuaki Matsuo] -- Bump 2.0.0.b4. [Kazuaki Matsuo] -- Use python selenium client v4.0.0.b4. [Kazuaki Matsuo] -- Update changelog for 2.0.0.b3. [Kazuaki Matsuo] -- Bump 2.0.0.b3. [Kazuaki Matsuo] -- Tweak some lines. [Kazuaki Matsuo] -- Update selenium in pipfile. [Kazuaki Matsuo] -- Bump selenium 4.0.0.b3. [Kazuaki Matsuo] -- Update pipfile. [Kazuaki Matsuo] -- Update changelog for 2.0.0.b2. [Kazuaki Matsuo] -- Bump 2.0.0.b2. [Kazuaki Matsuo] -- Bump base selenium version to 4.0.0.b2. [Kazuaki Matsuo] -- Bump v2.0.0.a0. [Kazuaki Matsuo] -- Update changelog for v2.0.0.a0. [Kazuaki Matsuo] -- Bump selenium. [Kazuaki Matsuo] +- Update changelog for 1.3.0. [Kazuaki Matsuo] v1.3.0 (2021-09-27) From 2df9031ea83701decd855f0bc8c33c6aaa09a0e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:20:34 +0900 Subject: [PATCH 015/548] chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 (#603) Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1f90be955..426e81603 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx >= 3.0, <4.0 +sphinx >= 4.0, < 5.0 sphinx_rtd_theme < 2.0 From 7aa460aa49ba8b403b7109e2f279111e0479511a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 23 Oct 2021 08:47:48 -0700 Subject: [PATCH 016/548] Bump 2.0.0.rc5 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 9dd833aa4..1413f2807 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.0.0.rc4' +version = '2.0.0.rc5' From 268a7ddf47b40de9546daa487d776de244ea034b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 23 Oct 2021 08:47:58 -0700 Subject: [PATCH 017/548] Update changelog for 2.0.0.rc5 --- CHANGELOG.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 86a1df560..37630b5b7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,15 @@ New Other ~~~~~ +- Bump 2.0.0.rc5. [Kazuaki Matsuo] +- Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 + (#603) [Kazuaki Matsuo, dependabot[bot]] + + Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. + - [Release notes](https://github.com/sphinx-doc/sphinx/releases) + - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) + - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) +- Update gitchangelog once. [Kazuaki Matsuo] - Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 (#637) [Kazuaki Matsuo, dependabot[bot]] From 8ec5441d2b5074180eafd8cc7dc511e1d8615496 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 1 Nov 2021 01:16:32 +0900 Subject: [PATCH 018/548] chore: deprecate -windows uiautomation (#649) * chore: add Deprecated for -windows uiautomation * chore: add logger --- appium/webdriver/common/mobileby.py | 1 + appium/webdriver/extensions/search_context/windows.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/common/mobileby.py b/appium/webdriver/common/mobileby.py index 2034275fc..fa69d123a 100644 --- a/appium/webdriver/common/mobileby.py +++ b/appium/webdriver/common/mobileby.py @@ -23,6 +23,7 @@ class MobileBy(By): ANDROID_VIEWTAG = '-android viewtag' ANDROID_DATA_MATCHER = '-android datamatcher' ANDROID_VIEW_MATCHER = '-android viewmatcher' + # Deprecated WINDOWS_UI_AUTOMATION = '-windows uiautomation' ACCESSIBILITY_ID = 'accessibility id' IMAGE = '-image' diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index b1d4f4918..c89eb8860 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext @@ -30,7 +31,7 @@ class WindowsSearchContext(BaseSearchContext): """Define search context for Windows""" def find_element_by_windows_uiautomation(self: T, win_uiautomation: str) -> 'WebElement': - """Finds an element by windows uiautomation + """[Deprecated] Finds an element by windows uiautomation Args: win_uiautomation: The element name in the windows UIAutomation selector @@ -42,10 +43,13 @@ def find_element_by_windows_uiautomation(self: T, win_uiautomation: str) -> 'Web `appium.webdriver.webelement.WebElement`: The found element """ + logger.warning( + "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." + ) return self.find_element(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) def find_elements_by_windows_uiautomation(self: T, win_uiautomation: str) -> List['WebElement']: - """Finds elements by windows uiautomation + """[Deprecated] Finds elements by windows uiautomation Args: win_uiautomation: The element name in the windows UIAutomation selector @@ -56,4 +60,7 @@ def find_elements_by_windows_uiautomation(self: T, win_uiautomation: str) -> Lis Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + logger.warning( + "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." + ) return self.find_elements(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) From 1a54fe9010d2305ab9ec2b6f2a6382279832f42d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 2 Nov 2021 14:23:00 +0900 Subject: [PATCH 019/548] chore: adding deprecation mark in touch actions and multi touch (#648) * chore: add deprecation mark in touch actions and multi touch * chore: add deprecated mark in MultiAction class * docs: update readme --- README.md | 14 +++ appium/webdriver/common/multi_action.py | 8 ++ appium/webdriver/common/touch_action.py | 8 ++ appium/webdriver/extensions/action_helpers.py | 86 +++++++++++++------ appium/webdriver/webdriver.py | 4 +- 5 files changed, 93 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index fbfa9e869..a877fefe2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,20 @@ Since **v2.0.0**, the base selenium client version is v4. The version only works in W3C WebDriver protocol format. If you would like to use the old protocol (MJSONWP), please use v1 Appium Python client. +### Quick migration guide from v1 to v2 +- Update base Selenium Python binding version to v4 +- Removed `forceMjsonwp` since Selenium v4/this vlient v2 expect only W3C WebDriver protocol +- Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend +- Adds `strict_ssl` to relax SSL error such as self-signed ones +- Deprecated + - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. + - e.g. + - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) + - https://www.selenium.dev/documentation/support_packages/mouse_and_keyboard_actions_in_detail/ + - https://www.youtube.com/watch?v=oAJ7jwMNFVU + - https://appiumpro.com/editions/30-ios-specific-touch-action-methods + - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api + ## Getting the Appium Python client There are three ways to install and use the Appium Python client. diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 6b2d1574c..9804a1edb 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -21,6 +21,7 @@ import copy from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: @@ -32,7 +33,14 @@ class MultiAction: + """ + Deprecated. + Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + """ + def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: + logger.warning("[Deprecated] 'MultiAction' action is deprecated. Please use W3C actions instead.") + self._driver = driver self._element = element self._touch_actions: List['TouchAction'] = [] diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 038217b6c..85cc9b84b 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -26,6 +26,7 @@ import copy from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: @@ -36,7 +37,14 @@ class TouchAction: + """ + Deprecated. + Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + """ + def __init__(self, driver: Optional['WebDriver'] = None): + logger.warning("[Deprecated] 'TouchAction' action is deprecated. Please use W3C actions instead.") + self._driver = driver self._actions: List = [] diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index dcb38e6d2..e0a3186a1 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -15,6 +15,8 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, TypeVar, Union from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.actions.mouse_button import MouseButton from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction @@ -35,7 +37,7 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: origin_el: the element from which to being scrolling destination_el: the element to scroll to duration: a duration after pressing originalEl and move the element to destinationEl. - Default is 600 ms for W3C spec. Zero for MJSONWP. + Default is 600 ms for W3C spec. Usage: driver.scroll(el1, el2) @@ -48,11 +50,26 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: if duration is None: duration = 600 - action = TouchAction(self) - if duration is None: - action.press(origin_el).move_to(destination_el).release().perform() + # w3c action requires seconds + duration_sec = duration / 1000 + + actions = ActionChains(self) + dest_el_rect = destination_el.rect + + # https://github.com/SeleniumHQ/selenium/blob/3c82c868d4f2a7600223a1b3817301d0b04d28e4/py/selenium/webdriver/common/actions/pointer_actions.py#L83 + if duration_sec is None: + actions.w3c_actions.pointer_action.move_to(origin_el) + actions.w3c_actions.pointer_action.pointer_down() + actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) + actions.w3c_actions.pointer_action.release() + actions.perform() else: - action.press(origin_el).wait(duration).move_to(destination_el).release().perform() + actions.w3c_actions.pointer_action.move_to(origin_el) + actions.w3c_actions.pointer_action.pointer_down() + actions.w3c_actions.pointer_action.pause(duration_sec) + actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) + actions.w3c_actions.pointer_action.release() + actions.perform() return self def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: @@ -65,8 +82,11 @@ def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ - action = TouchAction(self) - action.long_press(origin_el).move_to(destination_el).release().perform() + actions = ActionChains(self) + actions.w3c_actions.pointer_action.click_and_hold(origin_el) + actions.w3c_actions.pointer_action.move_to(destination_el) + actions.w3c_actions.pointer_action.release() + actions.perform() return self def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> T: @@ -85,27 +105,38 @@ def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = Non Union['WebDriver', 'ActionHelpers']: Self instance """ if len(positions) == 1: - action = TouchAction(self) + actions = ActionChains(self) x = positions[0][0] y = positions[0][1] + actions.w3c_actions.pointer_action.move_to_location(x, y) + actions.w3c_actions.pointer_action.pointer_down() if duration: - action.long_press(x=x, y=y, duration=duration).release() + actions.w3c_actions.pointer_action.pause(duration / 1000) else: - action.tap(x=x, y=y) - action.perform() + actions.w3c_actions.pointer_action.pause(100) + actions.w3c_actions.pointer_action.release() + actions.perform() else: - ma = MultiAction(self) + finger = 0 + actions = ActionChains(self) + actions.w3c_actions.devices = [] + for position in positions: + finger += 1 x = position[0] y = position[1] - action = TouchAction(self) + + # https://github.com/SeleniumHQ/selenium/blob/64447d4b03f6986337d1ca8d8b6476653570bcc1/py/selenium/webdriver/common/actions/pointer_input.py#L24 + new_input = actions.w3c_actions.add_pointer_input('touch', f'finger{finger}') + new_input.create_pointer_move(x=x, y=y) + new_input.create_pointer_down(MouseButton.LEFT) if duration: - action.long_press(x=x, y=y, duration=duration).release() + new_input.create_pause(duration / 1000) else: - action.press(x=x, y=y).release() - ma.add(action) + new_input.create_pause(0.1) + new_input.create_pointer_up(MouseButton.LEFT) + actions.perform() - ma.perform() return self def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: @@ -124,11 +155,13 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ - # `swipe` is something like press-wait-move_to-release, which the server - # will translate into the correct action - action = TouchAction(self) - action.press(x=start_x, y=start_y).wait(ms=duration).move_to(x=end_x, y=end_y).release() - action.perform() + actions = ActionChains(self) + actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) + actions.w3c_actions.pointer_action.pointer_down() + actions.w3c_actions.pointer_action.pause(duration / 1000) + actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) + actions.w3c_actions.pointer_action.release() + actions.perform() return self def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: @@ -146,7 +179,10 @@ def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ - action = TouchAction(self) - action.press(x=start_x, y=start_y).move_to(x=end_x, y=end_y).release() - action.perform() + actions = ActionChains(self) + actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) + actions.w3c_actions.pointer_action.pointer_down() + actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) + actions.w3c_actions.pointer_action.release() + actions.perform() return self diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 326e1be10..98b11b55c 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -323,8 +323,8 @@ def _update_command_executor(self, keep_alive: bool) -> None: if not {direct_protocol, direct_host, direct_port, direct_path}.issubset(set(self.caps)): message = 'Direct connect capabilities from server were:\n' for key in [direct_protocol, direct_host, direct_port, direct_path]: - message += '{}: \'{}\'\n'.format(key, self.caps.get(key, '')) - logger.warning(message) + message += '{}: \'{}\' '.format(key, self.caps.get(key, '')) + logger.debug(message) return protocol = self.caps[direct_protocol] From 4bc118b2158712f972dd43136d32241f05dc94c7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 2 Nov 2021 00:43:20 -0700 Subject: [PATCH 020/548] docs: update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a877fefe2..93a37c117 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ The version only works in W3C WebDriver protocol format. If you would like to use the old protocol (MJSONWP), please use v1 Appium Python client. ### Quick migration guide from v1 to v2 -- Update base Selenium Python binding version to v4 -- Removed `forceMjsonwp` since Selenium v4/this vlient v2 expect only W3C WebDriver protocol +- Updated base Selenium Python binding version to v4 +- Removed `forceMjsonwp` since Selenium v4 abd Appium Python client v2 expect only W3C WebDriver protocol - Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend -- Adds `strict_ssl` to relax SSL error such as self-signed ones +- Added `strict_ssl` to relax SSL error such as self-signed ones - Deprecated - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. - e.g. @@ -30,6 +30,7 @@ If you would like to use the old protocol (MJSONWP), please use v1 Appium Python - https://www.youtube.com/watch?v=oAJ7jwMNFVU - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api + - `launch_app`, `close_app` and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details ## Getting the Appium Python client From e682ff9881c6a13456d403251567370ef6786fdb Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 2 Nov 2021 00:44:11 -0700 Subject: [PATCH 021/548] Bump 2.0.0.rc6 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 1413f2807..1ecc78232 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.0.0.rc5' +version = '2.0.0.rc6' From 0ed77d759ad7fb0e690a09d51509354360be5dde Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 2 Nov 2021 00:44:24 -0700 Subject: [PATCH 022/548] Update changelog for 2.0.0.rc6 --- CHANGELOG.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 37630b5b7..55d79f142 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,22 @@ New Other ~~~~~ +- Bump 2.0.0.rc6. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: adding deprecation mark in touch actions and multi touch (#648) + [Kazuaki Matsuo] + + * chore: add deprecation mark in touch actions and multi touch + + * chore: add deprecated mark in MultiAction class + + * docs: update readme +- Chore: deprecate -windows uiautomation (#649) [Kazuaki Matsuo] + + * chore: add Deprecated for -windows uiautomation + + * chore: add logger +- Update changelog for 2.0.0.rc5. [Kazuaki Matsuo] - Bump 2.0.0.rc5. [Kazuaki Matsuo] - Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 (#603) [Kazuaki Matsuo, dependabot[bot]] From a195bf0affb813ad1729a90b2096620b27eb5478 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Nov 2021 10:06:10 -0700 Subject: [PATCH 023/548] chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) --- updated-dependencies: - dependency-name: isort dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e1b163082..3ed315698 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,6 @@ mock = "~=4.0" pylint = "~=2.11" astroid = "~=2.8" -isort = "~=5.9" +isort = "~=5.10" mypy = "~=0.910" From f4d5489ae576a3ad126aa99bca7531a89c153d6a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 4 Nov 2021 01:45:05 -0700 Subject: [PATCH 024/548] chore: add Python 3.9 as metadata --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7eb4e8bc2..4ec0e3505 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Environment :: Console', 'Environment :: MacOS X', 'Environment :: Win32 (MS Windows)', From 45b389d6e183561a57fef9c7c8ea28d15121093d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 5 Nov 2021 17:10:31 +0900 Subject: [PATCH 025/548] docs: update readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 93a37c117..8dd2354a9 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,12 @@ The version only works in W3C WebDriver protocol format. If you would like to use the old protocol (MJSONWP), please use v1 Appium Python client. ### Quick migration guide from v1 to v2 -- Updated base Selenium Python binding version to v4 -- Removed `forceMjsonwp` since Selenium v4 abd Appium Python client v2 expect only W3C WebDriver protocol -- Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend -- Added `strict_ssl` to relax SSL error such as self-signed ones +- Enhancement + - Updated base Selenium Python binding version to v4 + - Removed `forceMjsonwp` since Selenium v4 and Appium Python client v2 expect only W3C WebDriver protocol + - Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend + - Please check each behavior. Their behaviors could slightly differ. + - Added `strict_ssl` to relax SSL error such as self-signed ones - Deprecated - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. - e.g. From 9871231bb89b089a1ac93bf7cbc35439a5783ea5 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 9 Nov 2021 03:41:47 +0900 Subject: [PATCH 026/548] ci: set pipenv==2021.5.29 to prevent dependencies error (#651) * ci: add --pre * specify pipenv as same as the previous ok case * set 2021.5.29 in tox as well --- ci-jobs/functional/run_appium.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-jobs/functional/run_appium.yml b/ci-jobs/functional/run_appium.yml index a866957c4..eda98b939 100644 --- a/ci-jobs/functional/run_appium.yml +++ b/ci-jobs/functional/run_appium.yml @@ -18,7 +18,7 @@ steps: - script: python setup.py install displayName: Install python language bindings for Appium - script: | - pip install pipenv + pip install pipenv==2021.5.29 pipenv lock --clear pipenv install --system displayName: Resolve dependencies (Python) diff --git a/tox.ini b/tox.ini index ae9069ebf..0c7978f99 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = [testenv] deps = - pipenv + pipenv == 2021.5.29 commands = pipenv lock --clear pipenv install From e0358ffceb16490d68437187b5eba171a28cc8ed Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 8 Nov 2021 23:29:49 -0800 Subject: [PATCH 027/548] Bump 2.0.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 1ecc78232..37962c77f 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.0.0.rc6' +version = '2.0.0' From b0a299154ec7fde0fdc815208aa0be5754bb5a5a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 8 Nov 2021 23:29:58 -0800 Subject: [PATCH 028/548] Update changelog for 2.0.0 --- CHANGELOG.rst | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55d79f142..7ab70571d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog ========= -(unreleased) ------------- +v2.0.0 (2021-11-09) +------------------- New ~~~ @@ -14,8 +14,36 @@ New - No longer forceMjsonwp works - Add strict_ssl option in webdriver.Remote +Test +~~~~ +- Ci: set pipenv==2021.5.29 to prevent dependencies error (#651) + [Kazuaki Matsuo] + + * ci: add --pre + + * specify pipenv as same as the previous ok case + + * set 2021.5.29 in tox as well + Other ~~~~~ +- Bump 2.0.0. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: add Python 3.9 as metadata. [Kazuaki Matsuo] +- Chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) + [dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) + + --- + updated-dependencies: + - dependency-name: isort + dependency-type: direct:production + ... +- Update changelog for 2.0.0.rc6. [Kazuaki Matsuo] - Bump 2.0.0.rc6. [Kazuaki Matsuo] - Docs: update readme. [Kazuaki Matsuo] - Chore: adding deprecation mark in touch actions and multi touch (#648) From 2a462becfe31dc4e18559ba246b0856fd3eb2488 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 17 Nov 2021 10:17:58 -0800 Subject: [PATCH 029/548] chore: tweak keyword in metadata --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4ec0e3505..e215ef5fc 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ description='Python client for Appium', long_description=io.open(os.path.join(os.path.dirname('__file__'), 'README.md'), encoding='utf-8').read(), long_description_content_type='text/markdown', - keywords=['appium', 'selenium', 'selenium 3', 'python client', 'mobile automation'], + keywords=['appium', 'selenium', 'selenium 4', 'python client', 'mobile automation'], author='Isaac Murchie', author_email='isaac@saucelabs.com', maintainer='Kazuaki Matsuo, Mykola Mokhnach, Mori Atsushi', From ace98dc0dd9be7cd468ddfd775f5f55d24f7fb1d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 24 Nov 2021 01:01:15 -0800 Subject: [PATCH 030/548] chore: relax selenium version control (#656) --- Pipfile | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 3ed315698..8c8c15e6a 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.15" [packages] -selenium = "~=4.0.0" +selenium = "~=4.0" black = "==20.8b1" diff --git a/setup.py b/setup.py index e215ef5fc..c822c44f5 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium ~= 4.0.0'], + install_requires=['selenium ~= 4.0'], ) From d7cb6b59598fb594b56d77c47abb02f4f07fa452 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 24 Nov 2021 10:43:21 -0800 Subject: [PATCH 031/548] chore: add deprecated mark for find_element_by* (#657) --- .../extensions/search_context/android.py | 54 ++++++++++++++++--- .../extensions/search_context/custom.py | 17 +++++- .../extensions/search_context/ios.py | 49 ++++++++++++++--- .../extensions/search_context/mobile.py | 18 ++++++- appium/webdriver/webdriver.py | 24 ++++++--- appium/webdriver/webelement.py | 19 +++---- 6 files changed, 145 insertions(+), 36 deletions(-) diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 87a87e3d9..659a4cd8f 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -17,6 +17,7 @@ import json from typing import TYPE_CHECKING, Any, List, Optional, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext @@ -33,7 +34,10 @@ class AndroidSearchContext(BaseSearchContext): def find_element_by_android_view_matcher( self: T, 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 + """ + [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEW_MATCHER' instead. + + 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). @@ -57,6 +61,8 @@ def find_element_by_android_view_matcher( driver.find_element_by_android_view_matcher(name='withText', args=['Accessibility'], className='ViewMatchers') """ + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEW_MATCHER' instead.") + return self.find_element( by=MobileBy.ANDROID_VIEW_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) @@ -64,7 +70,10 @@ def find_element_by_android_view_matcher( def find_element_by_android_data_matcher( self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> 'WebElement': - """Finds element by + """ + [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_DATA_MATCHER' instead. + + 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). @@ -89,6 +98,8 @@ def find_element_by_android_data_matcher( driver.find_element_by_android_data_matcher(name='hasEntry', args=['title', 'Animation']) """ + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_DATA_MATCHER' instead.") + return self.find_element( by=MobileBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) @@ -96,7 +107,10 @@ def find_element_by_android_data_matcher( def find_elements_by_android_data_matcher( self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> List['WebElement']: - """Finds elements by + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_DATA_MATCHER' instead. + + 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). @@ -117,6 +131,8 @@ def find_elements_by_android_data_matcher( driver.find_elements_by_android_data_matcher(name='hasEntry', args=['title', 'Animation']) """ + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_DATA_MATCHER' instead.") + return self.find_elements( by=MobileBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) @@ -133,7 +149,10 @@ def _build_data_matcher( return json.dumps(result) def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement': - """Finds element by uiautomator in Android. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_UIAUTOMATOR' instead. + + Finds element by uiautomator in Android. Args: uia_string: The element name in the Android UIAutomator library @@ -144,10 +163,16 @@ def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement Returns: `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_UIAUTOMATOR' instead.") + return self.find_element(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebElement']: - """Finds elements by uiautomator in Android. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_UIAUTOMATOR' instead. + + Finds elements by uiautomator in Android. Args: uia_string: The element name in the Android UIAutomator library @@ -158,10 +183,16 @@ def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebE Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_UIAUTOMATOR' instead.") + return self.find_elements(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': - """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead. + + 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). @@ -174,10 +205,16 @@ def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': Returns: `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead.") + return self.find_element(by=MobileBy.ANDROID_VIEWTAG, value=tag) def find_elements_by_android_viewtag(self: T, tag: str) -> List['WebElement']: - """Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_VIEWTAG' instead. + + 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). @@ -190,4 +227,7 @@ def find_elements_by_android_viewtag(self: T, tag: str) -> List['WebElement']: Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead.") + return self.find_elements(by=MobileBy.ANDROID_VIEWTAG, value=tag) diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index e202728e6..f58594e99 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext @@ -30,7 +31,10 @@ class CustomSearchContext(BaseSearchContext): """Define search context for custom plugin""" def find_element_by_custom(self: T, selector: str) -> 'WebElement': - """Finds an element in conjunction with a custom element finding plugin + """ + [Deprecated] Please use 'find_element' with 'MobileBy.CUSTOM' instead. + + Finds an element in conjunction with a custom element finding plugin Args: selector: a string of the form "module:selector", where "module" is @@ -45,10 +49,16 @@ def find_element_by_custom(self: T, selector: str) -> 'WebElement': `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.CUSTOM' instead.") + return self.find_element(by=MobileBy.CUSTOM, value=selector) def find_elements_by_custom(self: T, selector: str) -> List['WebElement']: - """Finds elements in conjunction with a custom element finding plugin + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.CUSTOM' instead. + + Finds elements in conjunction with a custom element finding plugin Args: selector: a string of the form "module:selector", where "module" is @@ -62,4 +72,7 @@ def find_elements_by_custom(self: T, selector: str) -> List['WebElement']: Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.CUSTOM' instead.") + return self.find_elements(by=MobileBy.CUSTOM, value=selector) diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index b97bfa9c6..826b4a797 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext @@ -30,7 +31,10 @@ class iOSSearchContext(BaseSearchContext): """Define search context for iOS""" def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': - """Finds an element by uiautomation in iOS. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.IOS_UIAUTOMATION' instead. + + Finds an element by uiautomation in iOS. Args: uia_string: The element name in the iOS UIAutomation library @@ -42,10 +46,16 @@ def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_UIAUTOMATION' instead.") + return self.find_element(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElement']: - """Finds elements by uiautomation in iOS. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_UIAUTOMATION' instead. + + Finds elements by uiautomation in iOS. Args: uia_string: The element name in the iOS UIAutomation library @@ -57,10 +67,16 @@ def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElem :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_UIAUTOMATION' instead.") + return self.find_elements(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement': - """Find an element by ios predicate string. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.IOS_PREDICATE' instead. + + Find an element by ios predicate string. Args: predicate_string: The predicate string @@ -72,10 +88,16 @@ def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_PREDICATE' instead.") + return self.find_element(by=MobileBy.IOS_PREDICATE, value=predicate_string) def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebElement']: - """Finds elements by ios predicate string. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_PREDICATE' instead. + + Finds elements by ios predicate string. Args: predicate_string: The predicate string @@ -86,10 +108,16 @@ def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebE Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_PREDICATE' instead.") + return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string) def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebElement': - """Find an element by ios class chain string. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.IOS_CLASS_CHAIN' instead. + + Find an element by ios class chain string. Args: class_chain_string: The class chain string @@ -100,10 +128,16 @@ def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebEle Returns: `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_CLASS_CHAIN' instead.") + return self.find_element(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) def find_elements_by_ios_class_chain(self: T, class_chain_string: str) -> List['WebElement']: - """Finds elements by ios class chain string. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_CLASS_CHAIN' instead. + + Finds elements by ios class chain string. Args: class_chain_string: The class chain string @@ -114,4 +148,7 @@ def find_elements_by_ios_class_chain(self: T, class_chain_string: str) -> List[' Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_CLASS_CHAIN' instead.") + return self.find_elements(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index 52b16d71f..14ce7508a 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -17,6 +17,7 @@ import base64 from typing import TYPE_CHECKING, List, TypeVar, Union +from appium.common.logger import logger from appium.webdriver.common.mobileby import MobileBy from .base_search_context import BaseSearchContext @@ -31,7 +32,10 @@ class MobileSearchContext(BaseSearchContext): """Define search context for Mobile(Android, iOS)""" def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElement': - """Finds an element by accessibility id. + """ + [Deprecated] Please use 'find_element' with 'MobileBy.ACCESSIBILITY_ID' instead. + + Finds an element by accessibility id. Args: accessibility_id: A string corresponding to a recursive element search using the @@ -44,10 +48,16 @@ def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElem `appium.webdriver.webelement.WebElement`: The found element """ + + logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ACCESSIBILITY_ID' instead.") + return self.find_element(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['WebElement']: - """Finds elements by accessibility id. + """ + [Deprecated] Please use 'find_elements' with 'MobileBy.ACCESSIBILITY_ID' instead. + + Finds elements by accessibility id. Args: accessibility_id: a string corresponding to a recursive element search using the @@ -60,6 +70,9 @@ def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['W :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ + + logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ACCESSIBILITY_ID' instead.") + return self.find_elements(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) def find_element_by_image(self: T, img_path: str) -> 'WebElement': @@ -73,6 +86,7 @@ def find_element_by_image(self: T, img_path: str) -> 'WebElement': Returns: `appium.webdriver.webelement.WebElement`: The found element """ + with open(img_path, 'rb') as i_file: b64_data = base64.b64encode(i_file.read()).decode('UTF-8') diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 98b11b55c..f5661bc10 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -377,13 +377,16 @@ def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} - def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> MobileWebElement: - """'Private' method used by the find_element_by_* methods. + def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: + """ + Find an element given a MobileBy strategy and locator - Override for Appium + Args: + by: The strategy + value: The locator Usage: - Use the corresponding find_element_by_* instead of this. + driver.find_element(by=MobileBy.ACCESSIBILITY_ID, value='accessibility_id') Returns: `appium.webdriver.webelement.WebElement`: The found element @@ -404,13 +407,18 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> Mobil return self.execute(RemoteCommand.FIND_ELEMENT, {'using': by, 'value': value})['value'] - 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. + def find_elements( + self, by: str = MobileBy.ID, value: Union[str, Dict] = None + ) -> Union[List[MobileWebElement], List]: + """ + Find elements given a MobileBy strategy and locator - Override for Appium + Args: + by: The strategy + value: The locator Usage: - Use the corresponding find_elements_by_* instead of this. + driver.find_elements(by=MobileBy.ACCESSIBILITY_ID, value='accessibility_id') Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 9542377f2..17456dd58 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -14,10 +14,11 @@ 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 appium.webdriver.common.mobileby import MobileBy + from .extensions.search_context import AppiumWebElementSearchContext from .mobilecommand import MobileCommand as Command @@ -77,8 +78,8 @@ def is_displayed(self) -> bool: """ return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> T: - """Find an element given a By strategy and locator + def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> T: + """Find an element given a MobileBy strategy and locator Override for Appium @@ -89,7 +90,7 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> T: value: The locator Usage: - element = element.find_element(By.ID, 'foo') + element = element.find_element(MobileBy.ID, 'foo') Returns: `appium.webdriver.webelement.WebElement` @@ -109,19 +110,15 @@ def find_element(self, by: str = By.ID, value: Union[str, Dict] = None) -> T: return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - 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 - - Prefer the find_elements_by_* methods when possible. + def find_elements(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> List[T]: + """Find elements given a MobileBy strategy and locator Args: by: The strategy value: The locator Usage: - element = element.find_elements(By.CLASS_NAME, 'foo') + element = element.find_elements(MobileBy.CLASS_NAME, 'foo') Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement` From b70422b67f5254523ed360e1d196df0df04feab4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 26 Nov 2021 14:13:12 -0800 Subject: [PATCH 032/548] feat: add AppiumBy instead of MobileBy (#659) * feat: add AppiumBy instead of MobileBy * add class description * use deprecated:: --- appium/webdriver/common/appiumby.py | 30 +++++++++++ appium/webdriver/common/mobileby.py | 22 +++----- appium/webdriver/common/multi_action.py | 4 +- appium/webdriver/common/touch_action.py | 4 +- .../extensions/search_context/android.py | 50 +++++++++++-------- .../extensions/search_context/custom.py | 16 +++--- .../extensions/search_context/ios.py | 44 +++++++++------- .../extensions/search_context/mobile.py | 20 ++++---- .../extensions/search_context/windows.py | 6 +-- appium/webdriver/webdriver.py | 32 ++++++------ appium/webdriver/webelement.py | 14 +++--- .../android/appium_service_tests.py | 4 +- test/functional/android/common_tests.py | 4 +- test/functional/android/multi_action_tests.py | 8 +-- .../find_by_accessibility_id_tests.py | 16 +++--- .../search_context/find_by_image_tests.py | 12 ++--- .../find_by_view_matcher_tests.py | 4 +- test/functional/android/touch_action_tests.py | 48 +++++++++--------- test/functional/android/webelement_tests.py | 12 ++--- test/functional/mac/execute_script_test.py | 4 +- test/functional/mac/webelement_test.py | 4 +- test/functional/test_helper.py | 2 +- 22 files changed, 200 insertions(+), 160 deletions(-) create mode 100644 appium/webdriver/common/appiumby.py diff --git a/appium/webdriver/common/appiumby.py b/appium/webdriver/common/appiumby.py new file mode 100644 index 000000000..c4f4e3f3d --- /dev/null +++ b/appium/webdriver/common/appiumby.py @@ -0,0 +1,30 @@ +#!/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. + +from selenium.webdriver.common.by import By + + +class AppiumBy(By): + IOS_PREDICATE = '-ios predicate string' + IOS_UIAUTOMATION = '-ios uiautomation' + IOS_CLASS_CHAIN = '-ios class chain' + ANDROID_UIAUTOMATOR = '-android uiautomator' + ANDROID_VIEWTAG = '-android viewtag' + ANDROID_DATA_MATCHER = '-android datamatcher' + ANDROID_VIEW_MATCHER = '-android viewmatcher' + # Deprecated + WINDOWS_UI_AUTOMATION = '-windows uiautomation' + ACCESSIBILITY_ID = 'accessibility id' + IMAGE = '-image' + CUSTOM = '-custom' diff --git a/appium/webdriver/common/mobileby.py b/appium/webdriver/common/mobileby.py index fa69d123a..79b937022 100644 --- a/appium/webdriver/common/mobileby.py +++ b/appium/webdriver/common/mobileby.py @@ -12,19 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from selenium.webdriver.common.by import By +from appium.webdriver.common.appiumby import AppiumBy -class MobileBy(By): - IOS_PREDICATE = '-ios predicate string' - IOS_UIAUTOMATION = '-ios uiautomation' - IOS_CLASS_CHAIN = '-ios class chain' - ANDROID_UIAUTOMATOR = '-android uiautomator' - ANDROID_VIEWTAG = '-android viewtag' - ANDROID_DATA_MATCHER = '-android datamatcher' - ANDROID_VIEW_MATCHER = '-android viewmatcher' - # Deprecated - WINDOWS_UI_AUTOMATION = '-windows uiautomation' - ACCESSIBILITY_ID = 'accessibility id' - IMAGE = '-image' - CUSTOM = '-custom' +class MobileBy(AppiumBy): + """ + deprecated:: 2.1.0 + Please use 'from appium.webdriver.common.appiumby import AppiumBy' instead of 'MobileBy'. + """ + + pass diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 9804a1edb..f26bf498a 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -34,8 +34,8 @@ class MultiAction: """ - Deprecated. - Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + deprecated:: 2.0.0 + Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ """ def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 85cc9b84b..f1a15f327 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -38,8 +38,8 @@ class TouchAction: """ - Deprecated. - Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + deprecated:: 2.0.0 + Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ """ def __init__(self, driver: Optional['WebDriver'] = None): diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 659a4cd8f..87497bef1 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, TypeVar, Union from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .base_search_context import BaseSearchContext @@ -35,7 +35,8 @@ def find_element_by_android_view_matcher( self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEW_MATCHER' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.ANDROID_VIEW_MATCHER' instead. Finds element by [onView](https://developer.android.com/training/testing/espresso/basics) in Android @@ -61,17 +62,18 @@ def find_element_by_android_view_matcher( driver.find_element_by_android_view_matcher(name='withText', args=['Accessibility'], className='ViewMatchers') """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEW_MATCHER' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEW_MATCHER' instead.") return self.find_element( - by=MobileBy.ANDROID_VIEW_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) + by=AppiumBy.ANDROID_VIEW_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) def find_element_by_android_data_matcher( self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_DATA_MATCHER' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.ANDROID_DATA_MATCHER' instead. Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android @@ -98,17 +100,18 @@ def find_element_by_android_data_matcher( driver.find_element_by_android_data_matcher(name='hasEntry', args=['title', 'Animation']) """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_DATA_MATCHER' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_DATA_MATCHER' instead.") return self.find_element( - by=MobileBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) + by=AppiumBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) def find_elements_by_android_data_matcher( self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_DATA_MATCHER' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.ANDROID_DATA_MATCHER' instead. Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android @@ -131,10 +134,10 @@ def find_elements_by_android_data_matcher( driver.find_elements_by_android_data_matcher(name='hasEntry', args=['title', 'Animation']) """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_DATA_MATCHER' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_DATA_MATCHER' instead.") return self.find_elements( - by=MobileBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) + by=AppiumBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) ) def _build_data_matcher( @@ -150,7 +153,8 @@ def _build_data_matcher( def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_UIAUTOMATOR' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. Finds element by uiautomator in Android. @@ -164,13 +168,13 @@ def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement `appium.webdriver.webelement.WebElement`: The found element """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_UIAUTOMATOR' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead.") - return self.find_element(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) + return self.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_UIAUTOMATOR' instead. + [Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. Finds elements by uiautomator in Android. @@ -184,13 +188,14 @@ def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebE :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_UIAUTOMATOR' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead.") - return self.find_elements(by=MobileBy.ANDROID_UIAUTOMATOR, value=uia_string) + return self.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead. Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. @@ -206,13 +211,14 @@ def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': `appium.webdriver.webelement.WebElement`: The found element """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead.") - return self.find_element(by=MobileBy.ANDROID_VIEWTAG, value=tag) + return self.find_element(by=AppiumBy.ANDROID_VIEWTAG, value=tag) def find_elements_by_android_viewtag(self: T, tag: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.ANDROID_VIEWTAG' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.ANDROID_VIEWTAG' instead. Finds element by [View#tags](https://developer.android.com/reference/android/view/View#tags) in Android. @@ -228,6 +234,6 @@ def find_elements_by_android_viewtag(self: T, tag: str) -> List['WebElement']: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ANDROID_VIEWTAG' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead.") - return self.find_elements(by=MobileBy.ANDROID_VIEWTAG, value=tag) + return self.find_elements(by=AppiumBy.ANDROID_VIEWTAG, value=tag) diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index f58594e99..623769588 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .base_search_context import BaseSearchContext @@ -32,7 +32,8 @@ class CustomSearchContext(BaseSearchContext): def find_element_by_custom(self: T, selector: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.CUSTOM' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.CUSTOM' instead. Finds an element in conjunction with a custom element finding plugin @@ -50,13 +51,14 @@ def find_element_by_custom(self: T, selector: str) -> 'WebElement': """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.CUSTOM' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.CUSTOM' instead.") - return self.find_element(by=MobileBy.CUSTOM, value=selector) + return self.find_element(by=AppiumBy.CUSTOM, value=selector) def find_elements_by_custom(self: T, selector: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.CUSTOM' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.CUSTOM' instead. Finds elements in conjunction with a custom element finding plugin @@ -73,6 +75,6 @@ def find_elements_by_custom(self: T, selector: str) -> List['WebElement']: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.CUSTOM' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.CUSTOM' instead.") - return self.find_elements(by=MobileBy.CUSTOM, value=selector) + return self.find_elements(by=AppiumBy.CUSTOM, value=selector) diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index 826b4a797..fddfff876 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .base_search_context import BaseSearchContext @@ -32,7 +32,8 @@ class iOSSearchContext(BaseSearchContext): def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.IOS_UIAUTOMATION' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.IOS_UIAUTOMATION' instead. Finds an element by uiautomation in iOS. @@ -47,13 +48,14 @@ def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_UIAUTOMATION' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_UIAUTOMATION' instead.") - return self.find_element(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) + return self.find_element(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_UIAUTOMATION' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.IOS_UIAUTOMATION' instead. Finds elements by uiautomation in iOS. @@ -68,13 +70,14 @@ def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElem """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_UIAUTOMATION' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_UIAUTOMATION' instead.") - return self.find_elements(by=MobileBy.IOS_UIAUTOMATION, value=uia_string) + return self.find_elements(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.IOS_PREDICATE' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.IOS_PREDICATE' instead. Find an element by ios predicate string. @@ -89,13 +92,14 @@ def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_PREDICATE' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_PREDICATE' instead.") - return self.find_element(by=MobileBy.IOS_PREDICATE, value=predicate_string) + return self.find_element(by=AppiumBy.IOS_PREDICATE, value=predicate_string) def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_PREDICATE' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.IOS_PREDICATE' instead. Finds elements by ios predicate string. @@ -109,13 +113,14 @@ def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebE :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_PREDICATE' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_PREDICATE' instead.") - return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string) + return self.find_elements(by=AppiumBy.IOS_PREDICATE, value=predicate_string) def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.IOS_CLASS_CHAIN' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.IOS_CLASS_CHAIN' instead. Find an element by ios class chain string. @@ -129,13 +134,14 @@ def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebEle `appium.webdriver.webelement.WebElement`: The found element """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.IOS_CLASS_CHAIN' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_CLASS_CHAIN' instead.") - return self.find_element(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) + return self.find_element(by=AppiumBy.IOS_CLASS_CHAIN, value=class_chain_string) def find_elements_by_ios_class_chain(self: T, class_chain_string: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.IOS_CLASS_CHAIN' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.IOS_CLASS_CHAIN' instead. Finds elements by ios class chain string. @@ -149,6 +155,6 @@ def find_elements_by_ios_class_chain(self: T, class_chain_string: str) -> List[' :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.IOS_CLASS_CHAIN' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_CLASS_CHAIN' instead.") - return self.find_elements(by=MobileBy.IOS_CLASS_CHAIN, value=class_chain_string) + return self.find_elements(by=AppiumBy.IOS_CLASS_CHAIN, value=class_chain_string) diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index 14ce7508a..a3070f555 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .base_search_context import BaseSearchContext @@ -33,7 +33,8 @@ class MobileSearchContext(BaseSearchContext): def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElement': """ - [Deprecated] Please use 'find_element' with 'MobileBy.ACCESSIBILITY_ID' instead. + deprecated:: 2.1.0 + Please use 'find_element' with 'AppiumBy.ACCESSIBILITY_ID' instead. Finds an element by accessibility id. @@ -49,13 +50,14 @@ def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElem """ - logger.warning("[Deprecated] Please use 'find_element' with 'MobileBy.ACCESSIBILITY_ID' instead.") + logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ACCESSIBILITY_ID' instead.") - return self.find_element(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) + return self.find_element(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['WebElement']: """ - [Deprecated] Please use 'find_elements' with 'MobileBy.ACCESSIBILITY_ID' instead. + deprecated:: 2.1.0 + Please use 'find_elements' with 'AppiumBy.ACCESSIBILITY_ID' instead. Finds elements by accessibility id. @@ -71,9 +73,9 @@ def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['W """ - logger.warning("[Deprecated] Please use 'find_elements' with 'MobileBy.ACCESSIBILITY_ID' instead.") + logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ACCESSIBILITY_ID' instead.") - return self.find_elements(by=MobileBy.ACCESSIBILITY_ID, value=accessibility_id) + return self.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) def find_element_by_image(self: T, img_path: str) -> 'WebElement': """Finds a portion of a screenshot by an image. @@ -90,7 +92,7 @@ def find_element_by_image(self: T, img_path: str) -> 'WebElement': with open(img_path, 'rb') as i_file: b64_data = base64.b64encode(i_file.read()).decode('UTF-8') - return self.find_element(by=MobileBy.IMAGE, value=b64_data) + return self.find_element(by=AppiumBy.IMAGE, value=b64_data) def find_elements_by_image(self: T, img_path: str) -> List['WebElement']: """Finds a portion of a screenshot by an image. @@ -107,4 +109,4 @@ def find_elements_by_image(self: T, img_path: str) -> List['WebElement']: with open(img_path, 'rb') as i_file: b64_data = base64.b64encode(i_file.read()).decode('UTF-8') - return self.find_elements(by=MobileBy.IMAGE, value=b64_data) + return self.find_elements(by=AppiumBy.IMAGE, value=b64_data) diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index c89eb8860..7608c026b 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, List, TypeVar, Union from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .base_search_context import BaseSearchContext @@ -46,7 +46,7 @@ def find_element_by_windows_uiautomation(self: T, win_uiautomation: str) -> 'Web logger.warning( "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." ) - return self.find_element(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) + return self.find_element(by=AppiumBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) def find_elements_by_windows_uiautomation(self: T, win_uiautomation: str) -> List['WebElement']: """[Deprecated] Finds elements by windows uiautomation @@ -63,4 +63,4 @@ def find_elements_by_windows_uiautomation(self: T, win_uiautomation: str) -> Lis logger.warning( "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." ) - return self.find_elements(by=MobileBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) + return self.find_elements(by=AppiumBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index f5661bc10..8f257a209 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -23,7 +23,7 @@ from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.logger import logger -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection from .errorhandler import MobileErrorHandler @@ -283,15 +283,15 @@ def __init__( self._update_command_executor(keep_alive=keep_alive) # add new method to the `find_by_*` pantheon - By.IOS_UIAUTOMATION = MobileBy.IOS_UIAUTOMATION - By.IOS_PREDICATE = MobileBy.IOS_PREDICATE - By.IOS_CLASS_CHAIN = MobileBy.IOS_CLASS_CHAIN - By.ANDROID_UIAUTOMATOR = MobileBy.ANDROID_UIAUTOMATOR - By.ANDROID_VIEWTAG = MobileBy.ANDROID_VIEWTAG - By.WINDOWS_UI_AUTOMATION = MobileBy.WINDOWS_UI_AUTOMATION - By.ACCESSIBILITY_ID = MobileBy.ACCESSIBILITY_ID - By.IMAGE = MobileBy.IMAGE - By.CUSTOM = MobileBy.CUSTOM + By.IOS_UIAUTOMATION = AppiumBy.IOS_UIAUTOMATION + By.IOS_PREDICATE = AppiumBy.IOS_PREDICATE + By.IOS_CLASS_CHAIN = AppiumBy.IOS_CLASS_CHAIN + By.ANDROID_UIAUTOMATOR = AppiumBy.ANDROID_UIAUTOMATOR + By.ANDROID_VIEWTAG = AppiumBy.ANDROID_VIEWTAG + By.WINDOWS_UI_AUTOMATION = AppiumBy.WINDOWS_UI_AUTOMATION + By.ACCESSIBILITY_ID = AppiumBy.ACCESSIBILITY_ID + By.IMAGE = AppiumBy.IMAGE + By.CUSTOM = AppiumBy.CUSTOM self._extensions = extensions for extension in self._extensions: @@ -377,16 +377,16 @@ def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: w3c_caps = _make_w3c_caps(capabilities) return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} - def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: """ - Find an element given a MobileBy strategy and locator + Find an element given a AppiumBy strategy and locator Args: by: The strategy value: The locator Usage: - driver.find_element(by=MobileBy.ACCESSIBILITY_ID, value='accessibility_id') + driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='accessibility_id') Returns: `appium.webdriver.webelement.WebElement`: The found element @@ -408,17 +408,17 @@ def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> return self.execute(RemoteCommand.FIND_ELEMENT, {'using': by, 'value': value})['value'] def find_elements( - self, by: str = MobileBy.ID, value: Union[str, Dict] = None + self, by: str = AppiumBy.ID, value: Union[str, Dict] = None ) -> Union[List[MobileWebElement], List]: """ - Find elements given a MobileBy strategy and locator + Find elements given a AppiumBy strategy and locator Args: by: The strategy value: The locator Usage: - driver.find_elements(by=MobileBy.ACCESSIBILITY_ID, value='accessibility_id') + driver.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value='accessibility_id') Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 17456dd58..3f5f3a7c1 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -17,7 +17,7 @@ from selenium.webdriver.common.utils import keys_to_typing from selenium.webdriver.remote.command import Command as RemoteCommand -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from .extensions.search_context import AppiumWebElementSearchContext from .mobilecommand import MobileCommand as Command @@ -78,8 +78,8 @@ def is_displayed(self) -> bool: """ return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> T: - """Find an element given a MobileBy strategy and locator + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> T: + """Find an element given a AppiumBy strategy and locator Override for Appium @@ -90,7 +90,7 @@ def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> value: The locator Usage: - element = element.find_element(MobileBy.ID, 'foo') + element = element.find_element(AppiumBy.ID, 'foo') Returns: `appium.webdriver.webelement.WebElement` @@ -110,15 +110,15 @@ def find_element(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - def find_elements(self, by: str = MobileBy.ID, value: Union[str, Dict] = None) -> List[T]: - """Find elements given a MobileBy strategy and locator + def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> List[T]: + """Find elements given a AppiumBy strategy and locator Args: by: The strategy value: The locator Usage: - element = element.find_elements(MobileBy.CLASS_NAME, 'foo') + element = element.find_elements(AppiumBy.CLASS_NAME, 'foo') Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement` diff --git a/test/functional/android/appium_service_tests.py b/test/functional/android/appium_service_tests.py index cd22584c4..806bdb290 100644 --- a/test/functional/android/appium_service_tests.py +++ b/test/functional/android/appium_service_tests.py @@ -14,7 +14,7 @@ # limitations under the License. from appium.webdriver.appium_service import AppiumService -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.android.helper.test_helper import BaseTestCase from test.functional.test_helper import wait_for_element @@ -33,7 +33,7 @@ def setup_class(cls) -> None: def test_appium_service(self) -> None: assert self.service.is_running assert self.service.is_listening - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Accessibility') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Accessibility') assert el is not None @classmethod diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index e435050d7..7bec30b5e 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -18,7 +18,7 @@ import pytest from selenium.common.exceptions import NoSuchElementException -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.test_helper import wait_for_element from ..test_helper import is_ci @@ -38,7 +38,7 @@ def test_end_test_coverage(self) -> None: @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI.') def test_open_notifications(self) -> None: for word in ['App', 'Notification', 'Status Bar', ':-|']: - wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, f'new UiSelector().text("{word}")').click() + wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, f'new UiSelector().text("{word}")').click() self.driver.open_notifications() sleep(1) diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 897f531f9..16618bcaf 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -16,7 +16,7 @@ import pytest -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction from test.functional.test_helper import wait_for_element @@ -63,7 +63,7 @@ def _move_to_splitting_touches_accros_views(self) -> None: action.tap(el).perform() # simulate a swipe/scroll - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Expandable Lists') + el = wait_for_element(self.driver, AppiumBy.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() @@ -71,7 +71,7 @@ def _move_to_splitting_touches_accros_views(self) -> None: 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') + wait_for_element(self.driver, AppiumBy.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: @@ -79,7 +79,7 @@ def test_driver_multi_tap(self) -> None: action = TouchAction(self.driver) action.tap(el).perform() - wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.TextView') + wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.TextView') els = self.driver.find_elements_by_class_name('android.widget.TextView') self.driver.scroll(els[len(els) - 1], els[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 03825a81f..262fb841f 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 @@ -14,18 +14,18 @@ import pytest -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.android.helper.test_helper import BaseTestCase, is_ci from test.functional.test_helper import wait_for_element 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, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() wait_for_element( - self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")' + self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")' ).click() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Task Take out Trash') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Task Take out Trash') assert el is not None def test_find_multiple_elements(self) -> None: @@ -34,17 +34,17 @@ def test_find_multiple_elements(self) -> None: @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI') def test_element_find_single_element(self) -> None: - wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() + wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility")').click() wait_for_element( - self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")' + self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Accessibility Node Querying")' ).click() - el = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.ListView') + el = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.ListView') sub_el = el.find_element_by_accessibility_id('Task Take out Trash') 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') + wait_for_element(self.driver, AppiumBy.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') 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 6ac10bd5b..907dceaad 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -18,7 +18,7 @@ from selenium.common.exceptions import NoSuchElementException, TimeoutException from appium import webdriver -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.android.helper import desired_capabilities from test.functional.test_helper import wait_for_element @@ -41,7 +41,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 = wait_for_element(self.driver, MobileBy.IMAGE, b64_data) + el = wait_for_element(self.driver, AppiumBy.IMAGE, b64_data) size = el.size assert size['width'] is not None assert size['height'] is not None @@ -55,14 +55,14 @@ def test_find_based_on_image_template(self) -> None: assert rect['y'] is not None assert el.is_displayed() el.click() - wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "Alarm") + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, "Alarm") def test_find_multiple_elements_by_image_just_returns_one(self) -> None: - wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "App") + wait_for_element(self.driver, AppiumBy.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() - wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, "Alarm") + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, "Alarm") def test_find_throws_no_such_element(self) -> None: image_path = desired_capabilities.PATH('file/find_by_image_failure.png') @@ -70,7 +70,7 @@ def test_find_throws_no_such_element(self) -> None: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') with pytest.raises(TimeoutException): - wait_for_element(self.driver, MobileBy.IMAGE, b64_data, timeout_sec=3) + wait_for_element(self.driver, AppiumBy.IMAGE, b64_data, timeout_sec=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 index b96de9234..bcff5c602 100644 --- a/test/functional/android/search_context/find_by_view_matcher_tests.py +++ b/test/functional/android/search_context/find_by_view_matcher_tests.py @@ -19,7 +19,7 @@ from selenium.common.exceptions import WebDriverException from appium import webdriver -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.extensions.search_context.android import AndroidSearchContext from test.functional.android.helper.test_helper import BaseTestCase, desired_capabilities, is_ci @@ -61,4 +61,4 @@ def test_find_multiple_elements(self) -> None: name='withSubstring', args=['Access'], className='ViewMatchers' ) with pytest.raises(WebDriverException): - self.driver.find_elements(by=MobileBy.ANDROID_VIEW_MATCHER, value=value) + self.driver.find_elements(by=AppiumBy.ANDROID_VIEW_MATCHER, value=value) diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 4737dea87..6fd148ea7 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -15,7 +15,7 @@ import pytest from selenium.common.exceptions import NoSuchElementException -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.common.touch_action import TouchAction from test.functional.test_helper import wait_for_element @@ -27,7 +27,7 @@ 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') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') assert el is not None def test_tap_x_y(self) -> None: @@ -35,7 +35,7 @@ def test_tap_x_y(self) -> None: action = TouchAction(self.driver) action.tap(el, 100, 10).perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') + el = wait_for_element(self.driver, AppiumBy.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.') @@ -44,10 +44,10 @@ def test_tap_twice(self) -> None: action = TouchAction(self.driver) action.tap(el).perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'LogTextBox') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'LogTextBox') action.tap(el).perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Add') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Add') action.tap(el, count=2).perform() els = self.driver.find_elements_by_class_name('android.widget.TextView') @@ -58,7 +58,7 @@ def test_press_and_immediately_release(self) -> None: action = TouchAction(self.driver) action.press(el).release().perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') assert el is not None def test_press_and_immediately_release_x_y(self) -> None: @@ -66,18 +66,18 @@ def test_press_and_immediately_release_x_y(self) -> None: action = TouchAction(self.driver) action.press(el, 100, 10).release().perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Bouncing Balls') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') assert el is not None def test_press_and_wait(self) -> None: self._move_to_custom_adapter() action = TouchAction(self.driver) - el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') + el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') action.press(el).wait(2000).perform() # '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")') + el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') assert el is not None def test_press_and_moveto(self) -> None: @@ -87,7 +87,7 @@ def test_press_and_moveto(self) -> None: action = TouchAction(self.driver) action.press(el1).move_to(el2).release().perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') assert el is not None def test_press_and_moveto_x_y(self) -> None: @@ -97,18 +97,18 @@ def test_press_and_moveto_x_y(self) -> None: action = TouchAction(self.driver) action.press(el1).move_to(el2, 100, 100).release().perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') assert el is not None def test_long_press(self) -> None: self._move_to_custom_adapter() action = TouchAction(self.driver) - el = wait_for_element(self.driver, MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') + el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') action.long_press(el).perform() # '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")') + el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, '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.') @@ -121,38 +121,38 @@ def test_long_press_x_y(self) -> None: action.long_press(x=430, y=310).perform() # '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")') + el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') assert el is not None def test_drag_and_drop(self) -> None: self._move_to_views() action = TouchAction(self.driver) - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Drag and Drop') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Drag and Drop') action.tap(el).perform() - dd3 = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) + dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) dd2 = self.driver.find_element_by_id('{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) # 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_result_text'.format(APIDEMO_PKG_NAME)) + el = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) assert 'Dropped!' in el.text def test_driver_drag_and_drop(self) -> None: self._move_to_views() action = TouchAction(self.driver) - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Drag and Drop') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Drag and Drop') action.tap(el).perform() - dd3 = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) + dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) dd2 = self.driver.find_element_by_id('{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) self.driver.drag_and_drop(dd3, dd2) - el = wait_for_element(self.driver, MobileBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) + el = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) assert 'Dropped!' in el.text def test_driver_swipe(self) -> None: @@ -164,7 +164,7 @@ def test_driver_swipe(self) -> None: self.driver.find_element_by_accessibility_id('ImageView') self.driver.swipe(100, 1000, 100, 100, 800) - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'ImageView') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'ImageView') assert el is not None def _move_to_views(self) -> None: @@ -172,7 +172,7 @@ def _move_to_views(self) -> None: el2 = self.driver.find_element_by_accessibility_id('Animation') self.driver.scroll(el1, el2) - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Views') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') action = TouchAction(self.driver) action.tap(el).perform() @@ -180,8 +180,8 @@ 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') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Expandable Lists') action.tap(el).perform() - el = wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Custom Adapter') + el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, '1. Custom Adapter') action.tap(el).perform() diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index 825c071b3..0cbdbf23a 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.test_helper import wait_for_element from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase @@ -31,10 +31,10 @@ def test_set_text(self) -> None: 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));' ).click() - wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, 'Controls').click() - wait_for_element(self.driver, MobileBy.ACCESSIBILITY_ID, '1. Light Theme').click() + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Controls').click() + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, '1. Light Theme').click() - el = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'android.widget.EditText') + el = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.EditText') el.send_keys('original text') el.set_text('new text') @@ -42,9 +42,9 @@ def test_set_text(self) -> None: 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() + wait_for_element(self.driver, AppiumBy.XPATH, f"//android.widget.TextView[@text='{text}']").click() - el = wait_for_element(self.driver, MobileBy.ID, '{}:id/left_text_edit'.format(APIDEMO_PKG_NAME)) + el = wait_for_element(self.driver, AppiumBy.ID, '{}:id/left_text_edit'.format(APIDEMO_PKG_NAME)) el.send_keys(' text') assert 'Left is best text' == el.text diff --git a/test/functional/mac/execute_script_test.py b/test/functional/mac/execute_script_test.py index 03dadc8ea..95144e3dd 100644 --- a/test/functional/mac/execute_script_test.py +++ b/test/functional/mac/execute_script_test.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.mac.helper.test_helper import BaseTestCase from test.functional.test_helper import wait_for_element class TestExecuteScript(BaseTestCase): def test_sending_custom_keys(self) -> None: - edit_field = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'XCUIElementTypeTextView') + edit_field = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'XCUIElementTypeTextView') flagsShift = 1 << 1 self.driver.execute_script( 'macos: keys', diff --git a/test/functional/mac/webelement_test.py b/test/functional/mac/webelement_test.py index d30dea4b3..f2fc4687a 100644 --- a/test/functional/mac/webelement_test.py +++ b/test/functional/mac/webelement_test.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from appium.webdriver.common.mobileby import MobileBy +from appium.webdriver.common.appiumby import AppiumBy from test.functional.mac.helper.test_helper import BaseTestCase from test.functional.test_helper import wait_for_element class TestWebElement(BaseTestCase): def test_clear_text_field(self) -> None: - edit_field = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'XCUIElementTypeTextView') + edit_field = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'XCUIElementTypeTextView') edit_field.send_keys('helloworld') assert edit_field.text == 'helloworld' edit_field.clear() diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index fa6bef3a8..817f833ea 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -84,7 +84,7 @@ def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout_sec: Args: driver: WebDriver instance locator: Locator like WebDriver, Mobile JSON Wire Protocol - (e.g. `appium.webdriver.common.mobileby.MobileBy.ACCESSIBILITY_ID`) + (e.g. `appium.webdriver.common.appiumby.AppiumBy.ACCESSIBILITY_ID`) value: Query value to locator timeout_sec: Maximum time to wait the element. If time is over, `TimeoutException` is thrown From b14dc4285348a671da0f8b013ac72a77841ff04d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 26 Nov 2021 18:50:13 -0800 Subject: [PATCH 033/548] Bump 2.1.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 37962c77f..dc58ef8aa 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.0.0' +version = '2.1.0' From fb3dc832696e01e4419630aa57fa72a0c080939d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 26 Nov 2021 18:50:21 -0800 Subject: [PATCH 034/548] Update changelog for 2.1.0 --- CHANGELOG.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7ab70571d..807bd1d32 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,29 @@ Changelog ========= +v2.1.0 (2021-11-27) +------------------- + +New +~~~ +- Feat: add AppiumBy instead of MobileBy (#659) [Kazuaki Matsuo] + + * feat: add AppiumBy instead of MobileBy + + * add class description + + * use deprecated:: + +Other +~~~~~ +- Bump 2.1.0. [Kazuaki Matsuo] +- Chore: add deprecated mark for find_element_by* (#657) [Kazuaki + Matsuo] +- Chore: relax selenium version control (#656) [Kazuaki Matsuo] +- Chore: tweak keyword in metadata. [Kazuaki Matsuo] +- Update changelog for 2.0.0. [Kazuaki Matsuo] + + v2.0.0 (2021-11-09) ------------------- From e5cb9abee0272399289d698fba06a7ddd5fbd039 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 28 Nov 2021 17:54:55 -0800 Subject: [PATCH 035/548] ci: remove ==2021.5.29 (#653) * ci: remove ==2021.5.29 * bump black --- Pipfile | 2 +- ci-jobs/functional/run_appium.yml | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 8c8c15e6a..e6a857235 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.15" [packages] selenium = "~=4.0" -black = "==20.8b1" +black = "==21.11b1" pytest = "~=6.2" pytest-cov = "~=3.0" diff --git a/ci-jobs/functional/run_appium.yml b/ci-jobs/functional/run_appium.yml index eda98b939..a866957c4 100644 --- a/ci-jobs/functional/run_appium.yml +++ b/ci-jobs/functional/run_appium.yml @@ -18,7 +18,7 @@ steps: - script: python setup.py install displayName: Install python language bindings for Appium - script: | - pip install pipenv==2021.5.29 + pip install pipenv pipenv lock --clear pipenv install --system displayName: Resolve dependencies (Python) diff --git a/tox.ini b/tox.ini index 0c7978f99..ae9069ebf 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = [testenv] deps = - pipenv == 2021.5.29 + pipenv commands = pipenv lock --clear pipenv install From 758b2cd37f075a08f492575e7b938206f2828fd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 14:35:13 +0900 Subject: [PATCH 036/548] chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - [Release notes](https://github.com/PyCQA/astroid/releases) - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) --- updated-dependencies: - dependency-name: astroid dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e6a857235..c5db7370d 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ types-python-dateutil = "~=2.8" mock = "~=4.0" pylint = "~=2.11" -astroid = "~=2.8" +astroid = "~=2.9" isort = "~=5.10" mypy = "~=0.910" From 6c7c80c0c7211b9d5a2034a52d576738e53bdccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:36:59 +0900 Subject: [PATCH 037/548] chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c5db7370d..531182f94 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.11" +pylint = "~=2.12" astroid = "~=2.9" isort = "~=5.10" From 1b94c5fa3568b90dc90d1d4bb5634a573d07aa5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 15:47:17 +0900 Subject: [PATCH 038/548] chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 (#663) 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/v2.15.0...v2.16.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 531182f94..57d92ece8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.15" +pre-commit = "~=2.16" [packages] selenium = "~=4.0" From 02d6c8c5d21a1b3b135a5904edfa6acd03f1ac18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 09:55:03 -0800 Subject: [PATCH 039/548] chore(deps): bump black from 21.11b1 to 21.12b0 (#664) Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 57d92ece8..1bab4814c 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.16" [packages] selenium = "~=4.0" -black = "==21.11b1" +black = "==21.12b0" pytest = "~=6.2" pytest-cov = "~=3.0" From 6b21a6713ff34eb5b8fca71fc24105ff6d2e1c0f Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 24 Dec 2021 15:16:57 -0800 Subject: [PATCH 040/548] feat: use 'touch' pointer action (#670) * chore: specify touch * comment out touch in drag_and_drop * fix mypy * add desctiption of touch action --- README.md | 20 +++++++++++++++++++ appium/webdriver/extensions/action_helpers.py | 10 ++++++++-- .../extensions/search_context/android.py | 2 +- appium/webdriver/webdriver.py | 4 +++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8dd2354a9..e02474c9f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,26 @@ If you would like to use the old protocol (MJSONWP), please use v1 Appium Python - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api - `launch_app`, `close_app` and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details +#### MultiAction/TouchAction to W3C actions + +On UIA2, some elements can be handled with `touch` pointer action insead of the default `mouse` pointer action in the Selenium Python cleint. +For example, the below action builder is to replace the default one with the `touch` pointer action. + +```python +from selenium.webdriver.common.actions import interaction +from selenium.webdriver.common.actions.action_builder import ActionBuilder + +actions = ActionChains(driver) +# override as 'touch' pointer action +actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) +actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) +actions.w3c_actions.pointer_action.pointer_down() +actions.w3c_actions.pointer_action.pause(2) +actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) +actions.w3c_actions.pointer_action.release() +actions.perform() +``` + ## Getting the Appium Python client There are three ways to install and use the Appium Python client. diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index e0a3186a1..b2dc00d91 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -16,10 +16,11 @@ from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.actions import interaction +from selenium.webdriver.common.actions.action_builder import ActionBuilder from selenium.webdriver.common.actions.mouse_button import MouseButton +from selenium.webdriver.common.actions.pointer_input import PointerInput -from appium.webdriver.common.multi_action import MultiAction -from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.webelement import WebElement if TYPE_CHECKING: @@ -54,6 +55,7 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: duration_sec = duration / 1000 actions = ActionChains(self) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) dest_el_rect = destination_el.rect # https://github.com/SeleniumHQ/selenium/blob/3c82c868d4f2a7600223a1b3817301d0b04d28e4/py/selenium/webdriver/common/actions/pointer_actions.py#L83 @@ -83,6 +85,7 @@ def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> Union['WebDriver', 'ActionHelpers']: Self instance """ actions = ActionChains(self) + # 'mouse' pointer action actions.w3c_actions.pointer_action.click_and_hold(origin_el) actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() @@ -106,6 +109,7 @@ def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = Non """ if len(positions) == 1: actions = ActionChains(self) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) x = positions[0][0] y = positions[0][1] actions.w3c_actions.pointer_action.move_to_location(x, y) @@ -156,6 +160,7 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: Union['WebDriver', 'ActionHelpers']: Self instance """ actions = ActionChains(self) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.pause(duration / 1000) @@ -180,6 +185,7 @@ def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: Union['WebDriver', 'ActionHelpers']: Self instance """ actions = ActionChains(self) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 87497bef1..94192ea0b 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=Union[BaseSearchContext, 'AndroidSearchContext']) +T = TypeVar('T', bound='AndroidSearchContext') class AndroidSearchContext(BaseSearchContext): diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 8f257a209..372feaf19 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -493,7 +493,9 @@ def _addCommands(self) -> None: # https://github.com/appium/python-client/issues/342 for mixin_class in filter(lambda x: not issubclass(x, WebDriver), self.__class__.__mro__): if hasattr(mixin_class, self._addCommands.__name__): - getattr(mixin_class, self._addCommands.__name__, None)(self) + get_atter = getattr(mixin_class, self._addCommands.__name__, None) + if get_atter: + get_atter(self) self.command_executor._commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') self.command_executor._commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') From c109728986a3a583fe037780c88bdaa458e663c4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 24 Dec 2021 15:19:24 -0800 Subject: [PATCH 041/548] Bump 2.1.1 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index dc58ef8aa..c9e52937a 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.1.0' +version = '2.1.1' From 61bd71b1b168a4ab5d592ecc66abd9a1c57e49a0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 24 Dec 2021 15:19:33 -0800 Subject: [PATCH 042/548] Update changelog for 2.1.1 --- CHANGELOG.rst | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 807bd1d32..77971e2bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,87 @@ Changelog ========= +v2.1.1 (2021-12-24) +------------------- + +New +~~~ +- Feat: use 'touch' pointer action (#670) [Kazuaki Matsuo] + + * chore: specify touch + + * comment out touch in drag_and_drop + + * fix mypy + + * add desctiption of touch action + +Test +~~~~ +- Ci: remove ==2021.5.29 (#653) [Kazuaki Matsuo] + + * ci: remove ==2021.5.29 + + * bump black + +Other +~~~~~ +- Bump 2.1.1. [Kazuaki Matsuo] +- Chore(deps): bump black from 21.11b1 to 21.12b0 (#664) + [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/commits) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 + (#663) [dependabot[bot]] + + 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/v2.15.0...v2.16.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Update changelog for 2.1.0. [Kazuaki Matsuo] + + v2.1.0 (2021-11-27) ------------------- From 24b50d8138ea6ae008b0557991c0b5dcd75a15d0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 30 Dec 2021 10:51:02 -0800 Subject: [PATCH 043/548] fix: default duration in tap (#673) --- appium/webdriver/extensions/action_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index b2dc00d91..f1e4f8996 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -117,7 +117,7 @@ def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = Non if duration: actions.w3c_actions.pointer_action.pause(duration / 1000) else: - actions.w3c_actions.pointer_action.pause(100) + actions.w3c_actions.pointer_action.pause(0.1) actions.w3c_actions.pointer_action.release() actions.perform() else: From 6f68e115f276a8d90f837fe684a9da1bf58d7f5c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 30 Dec 2021 10:51:34 -0800 Subject: [PATCH 044/548] Bump 2.1.2 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index c9e52937a..c07bdebc4 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.1.1' +version = '2.1.2' From e3b55e1407519e0cf2b1b48787801b839699ea96 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 30 Dec 2021 10:51:54 -0800 Subject: [PATCH 045/548] Update changelog for 2.1.2 --- CHANGELOG.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 77971e2bc..4fe6ab2c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,19 @@ Changelog ========= +v2.1.2 (2021-12-30) +------------------- + +Fix +~~~ +- Default duration in tap (#673) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.1.2. [Kazuaki Matsuo] +- Update changelog for 2.1.1. [Kazuaki Matsuo] + + v2.1.1 (2021-12-24) ------------------- From 7dbf4f2f7ce43f60eded19fa247bb2177b65bafd Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 1 Jan 2022 00:39:55 -0800 Subject: [PATCH 046/548] test: update tests to use find_element(by...) (#674) * test: update find element/s methods * fix arguments * fix default value --- README.md | 6 ++- appium/webdriver/webdriver.py | 2 +- docs/roadmap.md | 36 ------------------ test/functional/android/chrome_tests.py | 3 +- test/functional/android/common_tests.py | 6 +-- .../android/context_switching_tests.py | 7 ++-- test/functional/android/multi_action_tests.py | 22 +++++------ .../find_by_accessibility_id_tests.py | 10 ++--- .../find_by_uiautomator_tests.py | 23 ++++++++---- .../find_by_view_matcher_tests.py | 37 +++++++++++-------- test/functional/android/touch_action_tests.py | 32 ++++++++-------- test/functional/android/webelement_tests.py | 7 ++-- test/functional/ios/keyboard_tests.py | 17 +++++---- .../find_by_element_webelement_tests.py | 9 +++-- .../find_by_ios_class_chain_tests.py | 7 +++- .../find_by_ios_predicate_tests.py | 17 +++++---- test/functional/ios/webdriver_tests.py | 11 +++--- .../webdriver/search_context/android_test.py | 23 ++++++++---- .../webdriver/search_context/windows_test.py | 4 +- 19 files changed, 139 insertions(+), 140 deletions(-) delete mode 100644 docs/roadmap.md diff --git a/README.md b/README.md index e02474c9f..89e64e6cc 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ environment: # Android environment import unittest from appium import webdriver +from appium.webdriver.common.appiumby import AppiumBy desired_caps = dict( platformName='Android', @@ -115,7 +116,7 @@ desired_caps = dict( app=PATH('../../../apps/selendroid-test-app.apk') ) self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) -el = self.driver.find_element_by_accessibility_id('item') +el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() ``` @@ -123,6 +124,7 @@ el.click() # iOS environment import unittest from appium import webdriver +from appium.webdriver.common.appiumby import AppiumBy desired_caps = dict( platformName='iOS', @@ -133,7 +135,7 @@ desired_caps = dict( ) self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) -el = self.driver.find_element_by_accessibility_id('item') +el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() ``` diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 372feaf19..9890ce622 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -189,7 +189,7 @@ def add_command(self): driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, extensions=[YourCustomCommand]) - element = driver.find_element_by_accessibility_id('id') + element = driver.find_elemeent(by=AppiumBy.ACCESSIBILITY_ID, value='id') # Then, the driver calls a get request to `session/$sessionId/path/to/your/custom/$id/url` # with replacing the `$id` with the given `element.id` diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index cf61486a7..000000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,36 +0,0 @@ -Appium Python Client Plan -========================= - -This library will be a simple extension of the official Python bindings, through -subclassing, to add the new methods. I would like to maintain the same package -structure, so that switching to the Appium library would be a matter of changing -the import. - -The official client allows for three ways to interact with the server: with the -`selenium` class, with the `webdriver.Remote` class, and with specific browser -classes, which subclass `webdriver.Remote` in `webdriver.*` classes. It seems -like we would not need to update the browser classes for our use case, and the -first is for RC, which we don't support. Thus we can subclass the official -`webdriver.Remote` classes and add the new methods. Otherwise we would need to -use composition, since we have to change the base class and subclasses. - -Usage will remain as it currently is, using the first two methods from above, -other than importing from Appium: - -```python -from appium import webdriver - -desired_caps = {} -# ... - -driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) - -print driver.get_window_size() -elem = driver.find_element_by_name('Graphics') -elem.click() -driver.quit() -``` - -As Selenium catches up, the methods can be seemlessly removed from the Appium -client. Any methods outside of the spec can remain and be used without issue, -should the user choose. diff --git a/test/functional/android/chrome_tests.py b/test/functional/android/chrome_tests.py index 08767349e..f7e0ae913 100644 --- a/test/functional/android/chrome_tests.py +++ b/test/functional/android/chrome_tests.py @@ -13,6 +13,7 @@ # limitations under the License. from appium import webdriver +from appium.webdriver.common.appiumby import AppiumBy from .helper.desired_capabilities import get_desired_capabilities @@ -28,6 +29,6 @@ def teardown_method(self) -> None: 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.driver.find_element(by=AppiumBy.LINK_TEXT, value='i am a link').click() 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 7bec30b5e..6fdbd3e1f 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -43,9 +43,9 @@ def test_open_notifications(self) -> None: self.driver.open_notifications() sleep(1) with pytest.raises(NoSuchElementException): - self.driver.find_element_by_android_uiautomator, 'new UiSelector().text(":-|")' + self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().text(":-|")') - els = self.driver.find_elements_by_class_name('android.widget.TextView') + els = self.driver.find_element(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') # sometimes numbers shift title = False body = False @@ -60,4 +60,4 @@ def test_open_notifications(self) -> None: self.driver.keyevent(4) sleep(1) - self.driver.find_element_by_android_uiautomator('new UiSelector().text(":-|")') + self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().text(":-|")') diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index c358866ac..e5ae1e8a0 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -16,6 +16,7 @@ from appium import webdriver from appium.common.exceptions import NoSuchContextException +from appium.webdriver.common.appiumby import AppiumBy from .helper import desired_capabilities @@ -40,8 +41,8 @@ def test_move_to_correct_context(self) -> None: 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.driver.find_element(by=AppiumBy.CSS_SELECTOR, value='input[type=submit]').click() + el = self.driver.find_element(by=AppiumBy.XPATH, value="//h1[contains(., 'This is my way')]") assert el is not None def test_move_back_to_native_context(self) -> None: @@ -54,6 +55,6 @@ def test_set_invalid_context(self) -> None: self.driver.switch_to.context('invalid name') def _enter_webview(self) -> None: - btn = self.driver.find_element_by_name('buttonStartWebviewCD') + btn = self.driver.find_element(by=AppiumBy.NAME, value='buttonStartWebviewCD') btn.click() self.driver.switch_to.context('WEBVIEW') diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 16618bcaf..e2946c15e 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -28,7 +28,7 @@ class TestMultiAction(BaseTestCase): def test_parallel_actions(self) -> None: self._move_to_splitting_touches_accros_views() - els = self.driver.find_elements_by_class_name('android.widget.ListView') + els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') a1 = TouchAction() a1.press(els[0]).move_to(x=10, y=0).move_to(x=10, y=-75).move_to(x=10, y=-600).release() @@ -42,7 +42,7 @@ def test_parallel_actions(self) -> None: def test_actions_with_waits(self) -> None: self._move_to_splitting_touches_accros_views() - els = self.driver.find_elements_by_class_name('android.widget.ListView') + els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') a1 = TouchAction() a1.press(els[0]).move_to(x=10, y=0).move_to(x=10, y=-75).wait(1000).move_to(x=10, y=-600).release() @@ -54,40 +54,40 @@ def test_actions_with_waits(self) -> None: 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') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') self.driver.scroll(el1, el2) - el = self.driver.find_element_by_accessibility_id('Views') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Views') action = TouchAction(self.driver) action.tap(el).perform() # simulate a swipe/scroll el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Expandable Lists') action.press(el).move_to(x=100, y=-1000).release().perform() - el = self.driver.find_element_by_accessibility_id('Layouts') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Layouts') action.press(el).move_to(x=100, y=-1000).release().perform() - el = self.driver.find_element_by_accessibility_id('Splitting Touches across Views') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Splitting Touches across Views') action.tap(el).perform() wait_for_element(self.driver, AppiumBy.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') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Graphics') action = TouchAction(self.driver) action.tap(el).perform() wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.TextView') - els = self.driver.find_elements_by_class_name('android.widget.TextView') + els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') self.driver.scroll(els[len(els) - 1], els[0]) - els = self.driver.find_elements_by_class_name('android.widget.TextView') + els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') if els[len(els) - 1].get_attribute('name') != 'Xfermodes': self.driver.scroll(els[len(els) - 1], els[0]) - el = self.driver.find_element_by_accessibility_id('Touch Paint') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Touch Paint') action.tap(el).perform() positions = [(100, 200), (100, 400)] 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 262fb841f..0b6d7e2d6 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 @@ -15,6 +15,7 @@ import pytest from appium.webdriver.common.appiumby import AppiumBy +from appium.webdriver.webelement import WebElement from test.functional.android.helper.test_helper import BaseTestCase, is_ci from test.functional.test_helper import wait_for_element @@ -29,7 +30,7 @@ def test_find_single_element(self) -> None: assert el is not None def test_find_multiple_elements(self) -> None: - els = self.driver.find_elements_by_accessibility_id('Accessibility') + els = self.driver.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value='Accessibility') assert isinstance(els, list) @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI') @@ -40,12 +41,11 @@ def test_element_find_single_element(self) -> None: ).click() el = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.ListView') - sub_el = el.find_element_by_accessibility_id('Task Take out Trash') + sub_el = el.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Task Take out Trash') # type: WebElement assert sub_el is not None def test_element_find_multiple_elements(self) -> None: wait_for_element(self.driver, AppiumBy.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') + el = self.driver.find_element(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') + sub_els = el.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') # type: list assert isinstance(sub_els, list) 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 be0ab39f7..5232ee2ba 100644 --- a/test/functional/android/search_context/find_by_uiautomator_tests.py +++ b/test/functional/android/search_context/find_by_uiautomator_tests.py @@ -14,34 +14,41 @@ import pytest +from appium.webdriver.common.appiumby import AppiumBy +from appium.webdriver.webelement import WebElement from test.functional.android.helper.test_helper import BaseTestCase @pytest.mark.skip(reason="Need to fix flaky test") class TestFindByUIAutomator(BaseTestCase): def test_find_single_element(self) -> None: - el = self.driver.find_element_by_android_uiautomator('new UiSelector().text("Animation")') + el = self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().text("Animation")') assert el is not None def test_find_multiple_elements(self) -> None: - els = self.driver.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') + els = self.driver.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().clickable(true)') assert isinstance(els, list) def test_element_find_single_element(self) -> None: - el = self.driver.find_element_by_class_name('android.widget.ListView') + el = self.driver.find_element(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') - sub_el = el.find_element_by_android_uiautomator('new UiSelector().description("Animation")') + sub_el = el.find_element( + by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().description("Animation")' + ) # type: WebElement 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') + el = self.driver.find_element(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') - sub_els = el.find_elements_by_android_uiautomator('new UiSelector().clickable(true)') + sub_els = el.find_elements( + by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().clickable(true)' + ) # type: 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 = self.driver.find_element( + by=AppiumBy.ANDROID_UIAUTOMATOR, + value='new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));', ) el.click() # TODO Add assert 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 index bcff5c602..d71fda4bd 100644 --- a/test/functional/android/search_context/find_by_view_matcher_tests.py +++ b/test/functional/android/search_context/find_by_view_matcher_tests.py @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import unittest +import json import pytest from selenium.common.exceptions import WebDriverException @@ -25,7 +24,6 @@ class TestFindByViewMatcher(BaseTestCase): - # Override def setup_method(self, method) -> None: # type: ignore desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') @@ -35,30 +33,39 @@ def setup_method(self, method) -> None: # type: ignore 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' + el = self.driver.find_element( + by=AppiumBy.ANDROID_VIEW_MATCHER, + value=json.dumps({'name': 'withText', 'args': ['Accessibility'], 'class': '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' + el = self.driver.find_element( + by=AppiumBy.ANDROID_VIEW_MATCHER, + value=json.dumps( + {'name': 'withText', 'args': ['Accessibility'], 'class': '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', + el = self.driver.find_element( + by=AppiumBy.ANDROID_VIEW_MATCHER, + value=json.dumps( + { + 'name': 'withText', + 'args': {'name': 'containsString', 'args': 'Animati', 'class': 'org.hamcrest.Matchers'}, + 'class': '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=AppiumBy.ANDROID_VIEW_MATCHER, value=value) + self.driver.find_elements( + by=AppiumBy.ANDROID_VIEW_MATCHER, + value=json.dumps({'name': 'withSubstring', 'args': ['Access'], 'class': 'ViewMatchers'}), + ) diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 6fd148ea7..a97b992e0 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -24,14 +24,14 @@ class TestTouchAction(BaseTestCase): def test_tap(self) -> None: - el = self.driver.find_element_by_accessibility_id('Animation') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') action = TouchAction(self.driver) action.tap(el).perform() el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') assert el is not None def test_tap_x_y(self) -> None: - el = self.driver.find_element_by_accessibility_id('Animation') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') action = TouchAction(self.driver) action.tap(el, 100, 10).perform() @@ -40,7 +40,7 @@ def test_tap_x_y(self) -> 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') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Text') action = TouchAction(self.driver) action.tap(el).perform() @@ -50,11 +50,11 @@ def test_tap_twice(self) -> None: el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Add') action.tap(el, count=2).perform() - els = self.driver.find_elements_by_class_name('android.widget.TextView') + els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') 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') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') action = TouchAction(self.driver) action.press(el).release().perform() @@ -62,7 +62,7 @@ def test_press_and_immediately_release(self) -> None: assert el is not None def test_press_and_immediately_release_x_y(self) -> None: - el = self.driver.find_element_by_accessibility_id('Animation') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') action = TouchAction(self.driver) action.press(el, 100, 10).release().perform() @@ -81,8 +81,8 @@ def test_press_and_wait(self) -> None: assert el is not None 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') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') action = TouchAction(self.driver) action.press(el1).move_to(el2).release().perform() @@ -91,8 +91,8 @@ def test_press_and_moveto(self) -> None: assert el is not None 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') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='App') action = TouchAction(self.driver) action.press(el1).move_to(el2, 100, 100).release().perform() @@ -132,7 +132,7 @@ def test_drag_and_drop(self) -> None: action.tap(el).perform() dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) - dd2 = self.driver.find_element_by_id('{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) + dd2 = self.driver.find_element(by=AppiumBy.ID, value='{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) # dnd is stimulated by longpress-move_to-release action.long_press(dd3).move_to(dd2).release().perform() @@ -148,7 +148,7 @@ def test_driver_drag_and_drop(self) -> None: action.tap(el).perform() dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) - dd2 = self.driver.find_element_by_id('{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) + dd2 = self.driver.find_element(by=AppiumBy.ID, value='{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) self.driver.drag_and_drop(dd3, dd2) @@ -156,20 +156,20 @@ def test_driver_drag_and_drop(self) -> None: assert 'Dropped!' in el.text def test_driver_swipe(self) -> None: - el = self.driver.find_element_by_accessibility_id('Views') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Views') action = TouchAction(self.driver) action.tap(el).perform() with pytest.raises(NoSuchElementException): - self.driver.find_element_by_accessibility_id('ImageView') + self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='ImageView') self.driver.swipe(100, 1000, 100, 100, 800) el = wait_for_element(self.driver, AppiumBy.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') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') self.driver.scroll(el1, el2) el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index 0cbdbf23a..989521e5a 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -21,14 +21,15 @@ class TestWebelement(BaseTestCase): def test_element_location_in_view(self) -> None: - el = self.driver.find_element_by_accessibility_id('Content') + el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') loc = el.location_in_view 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( - 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));' + self.driver.find_element( + by=AppiumBy.ANDROID_UIAUTOMATOR, + value='new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));', ).click() wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Controls').click() diff --git a/test/functional/ios/keyboard_tests.py b/test/functional/ios/keyboard_tests.py index 5769b5f03..ba0bd05d4 100644 --- a/test/functional/ios/keyboard_tests.py +++ b/test/functional/ios/keyboard_tests.py @@ -18,6 +18,7 @@ import pytest from selenium.common.exceptions import NoSuchElementException +from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase if TYPE_CHECKING: @@ -28,7 +29,7 @@ class TestKeyboard(BaseTestCase): def test_hide_keyboard(self) -> None: self._move_to_textbox() - el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() el.set_value('Testing') @@ -42,7 +43,7 @@ def test_hide_keyboard(self) -> None: def test_hide_keyboard_presskey_strategy(self) -> None: self._move_to_textbox() - el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() el.set_value('Testing') @@ -56,7 +57,7 @@ def test_hide_keyboard_presskey_strategy(self) -> None: def test_hide_keyboard_no_key_name(self) -> None: self._move_to_textbox() - el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() el.set_value('Testing') @@ -70,18 +71,18 @@ def test_hide_keyboard_no_key_name(self) -> None: def test_is_keyboard_shown(self) -> None: self._move_to_textbox() - el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() el.set_value('Testing') assert self.driver.is_keyboard_shown() def _get_keyboard_el(self) -> 'WebElement': - return self.driver.find_element_by_class_name('XCUIElementTypeKeyboard') + return self.driver.find_element(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeKeyboard') def _move_to_textbox(self) -> None: - el1 = self.driver.find_element_by_accessibility_id('Sliders') - el2 = self.driver.find_element_by_accessibility_id('Buttons') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Sliders') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Buttons') self.driver.scroll(el1, el2) # Click text fields - self.driver.find_element_by_accessibility_id('Text Fields').click() + self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Text Fields').click() 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 62f08bc7f..94ee552ea 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,19 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase class TestFindByElementWebelement(BaseTestCase): def test_find_element_by_path(self) -> None: - el = self.driver.find_element_by_ios_predicate('wdName == "UICatalog"') + el = self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='wdName == "UICatalog"') assert 'UICatalog' == el.get_attribute('name') - c_el = el.find_elements_by_ios_predicate('label == "Action Sheets"') + c_el = el.find_elements(by=AppiumBy.IOS_PREDICATE, value='label == "Action Sheets"') # type: list assert 'Action Sheets' == c_el[0].get_attribute('name') - c_el = el.find_elements_by_ios_class_chain('**/XCUIElementTypeStaticText') + c_el = el.find_elements(by=AppiumBy.IOS_CLASS_CHAIN, value='**/XCUIElementTypeStaticText') assert 'UICatalog' == c_el[0].get_attribute('name') - c_el = el.find_elements_by_accessibility_id('UICatalog') + c_el = el.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value='UICatalog') 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 7ff0a24db..395490652 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,17 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase class TestFindByIOClassChain(BaseTestCase): def test_find_element_by_path(self) -> None: - els = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/**/XCUIElementTypeStaticText') + els = self.driver.find_elements( + by=AppiumBy.IOS_CLASS_CHAIN, value='XCUIElementTypeWindow/**/XCUIElementTypeStaticText' + ) 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/*/*/*') + el = self.driver.find_elements(by=AppiumBy.IOS_CLASS_CHAIN, value='XCUIElementTypeWindow/*/*/*') 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 419084799..f98b8f30d 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,38 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. +from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import 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"') + self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='wdName == "Buttons"') def test_find_multiple_element_by_type(self) -> None: - e = self.driver.find_elements_by_ios_predicate('wdType == "XCUIElementTypeStaticText"') + e = self.driver.find_elements(by=AppiumBy.IOS_PREDICATE, value='wdType == "XCUIElementTypeStaticText"') assert len(e) != 0 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"') + self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='label == "Buttons"') 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"') + self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='wdValue == "Buttons"') 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') + self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='wdValue == "Buttons" AND visible == 1') # Should not find any elements - e = self.driver.find_elements_by_ios_predicate('wdValue == "Buttons" AND visible == 0') + e = self.driver.find_elements(by=AppiumBy.IOS_PREDICATE, value='wdValue == "Buttons" AND visible == 0') assert len(e) == 0 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') + self.driver.find_element(by=AppiumBy.IOS_PREDICATE, value='wdValue == "Buttons" AND enabled == 1') # Should not find any elements - e = self.driver.find_elements_by_ios_predicate('wdValue == "Buttons" AND enabled == 0') + e = self.driver.find_elements(by=AppiumBy.IOS_PREDICATE, value='wdValue == "Buttons" AND enabled == 0') assert len(e) == 0 diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index fcb9bc999..b132a2810 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -19,6 +19,7 @@ from appium import webdriver from appium.webdriver.applicationstate import ApplicationState +from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase from test.functional.test_helper import get_available_from_port_range, wait_for_condition @@ -71,7 +72,7 @@ def test_app_management(self) -> None: def test_clear(self) -> None: self._move_to_textbox() - el = self.driver.find_elements_by_class_name('XCUIElementTypeTextField')[0] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] # Verify default text def_text = 'Placeholder text' @@ -85,7 +86,7 @@ def test_clear(self) -> None: 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] + el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] text = el.get_attribute('value') assert text == input_text @@ -101,9 +102,9 @@ def test_press_button(self) -> None: 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') - el2 = self.driver.find_element_by_accessibility_id('Buttons') + el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Sliders') + el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Buttons') self.driver.scroll(el1, el2) # Click text fields - self.driver.find_element_by_accessibility_id('Text Fields').click() + self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Text Fields').click() diff --git a/test/unit/webdriver/search_context/android_test.py b/test/unit/webdriver/search_context/android_test.py index 94aefe081..9932945ba 100644 --- a/test/unit/webdriver/search_context/android_test.py +++ b/test/unit/webdriver/search_context/android_test.py @@ -16,6 +16,7 @@ import httpretty +from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.webelement import WebElement as MobileWebElement from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body @@ -29,8 +30,9 @@ def test_find_element_by_android_data_matcher(self): appium_command('/session/1234567890/element'), body='{"value": {"element-6066-11e4-a52e-4f735466cecf": "element-id"}}', ) - el = driver.find_element_by_android_data_matcher( - name='title', args=['title', 'Animation'], className='class name' + el = driver.find_element( + by=AppiumBy.ANDROID_DATA_MATCHER, + value=json.dumps({'name': 'title', 'args': ['title', 'Animation'], 'class': 'class name'}), ) d = get_httpretty_request_body(httpretty.last_request()) @@ -49,7 +51,9 @@ def test_find_elements_by_android_data_matcher(self): appium_command('/session/1234567890/elements'), body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "element-id1"}, {"element-6066-11e4-a52e-4f735466cecf": "element-id2"}]}', ) - els = driver.find_elements_by_android_data_matcher(name='title', args=['title', 'Animation']) + els = driver.find_elements( + by=AppiumBy.ANDROID_DATA_MATCHER, value=json.dumps({'name': 'title', 'args': ['title', 'Animation']}) + ) d = get_httpretty_request_body(httpretty.last_request()) assert d['using'] == '-android datamatcher' @@ -63,7 +67,7 @@ def test_find_elements_by_android_data_matcher(self): def test_find_elements_by_android_data_matcher_no_value(self): driver = android_w3c_driver() httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/elements'), body='{"value": []}') - els = driver.find_elements_by_android_data_matcher() + els = driver.find_elements(by=AppiumBy.ANDROID_DATA_MATCHER, value='{}') d = get_httpretty_request_body(httpretty.last_request()) assert d['using'] == '-android datamatcher' @@ -79,8 +83,9 @@ def test_find_element_by_android_data_matcher(self): appium_command('/session/1234567890/element/element_id/element'), body='{"value": {"element-6066-11e4-a52e-4f735466cecf": "child-element-id"}}', ) - el = element.find_element_by_android_data_matcher( - name='title', args=['title', 'Animation'], className='class name' + el = element.find_element( + by=AppiumBy.ANDROID_DATA_MATCHER, + value=json.dumps({'name': 'title', 'args': ['title', 'Animation'], 'class': 'class name'}), ) d = get_httpretty_request_body(httpretty.last_request()) @@ -100,7 +105,9 @@ def test_find_elements_by_android_data_matcher(self): appium_command('/session/1234567890/element/element_id/elements'), body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "child-element-id1"}, {"element-6066-11e4-a52e-4f735466cecf": "child-element-id2"}]}', ) - els = element.find_elements_by_android_data_matcher(name='title', args=['title', 'Animation']) + els = element.find_elements( + by=AppiumBy.ANDROID_DATA_MATCHER, value=json.dumps({'name': 'title', 'args': ['title', 'Animation']}) + ) d = get_httpretty_request_body(httpretty.last_request()) assert d['using'] == '-android datamatcher' @@ -117,7 +124,7 @@ def test_find_elements_by_android_data_matcher_no_value(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/elements'), body='{"value": []}' ) - els = element.find_elements_by_android_data_matcher() + els = element.find_elements(by=AppiumBy.ANDROID_DATA_MATCHER, value='{}') d = get_httpretty_request_body(httpretty.last_request()) assert d['using'] == '-android datamatcher' diff --git a/test/unit/webdriver/search_context/windows_test.py b/test/unit/webdriver/search_context/windows_test.py index 99296df18..2fcc099ef 100644 --- a/test/unit/webdriver/search_context/windows_test.py +++ b/test/unit/webdriver/search_context/windows_test.py @@ -13,7 +13,9 @@ # limitations under the License. import httpretty +from selenium.webdriver.common import by +from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.webelement import WebElement as MobileWebElement from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body @@ -28,7 +30,7 @@ def test_find_element_by_windows_uiautomation(self): appium_command('/session/1234567890/element/element_id/element'), body='{"value": {"element-6066-11e4-a52e-4f735466cecf": "win-element-id"}}', ) - el = element.find_element_by_windows_uiautomation('win_element') + el = element.find_element(by=AppiumBy.WINDOWS_UI_AUTOMATION, value='win_element') d = get_httpretty_request_body(httpretty.last_request()) assert d['using'] == '-windows uiautomation' From 72f942d2152ee1b0d7539f3bf2705b50d01133f7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 7 Jan 2022 01:17:05 -0800 Subject: [PATCH 047/548] chore: bump mypy (#675) --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1bab4814c..a7175e69e 100644 --- a/Pipfile +++ b/Pipfile @@ -25,4 +25,4 @@ pylint = "~=2.12" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.910" +mypy = "~=0.930" From 895cde1aa674aaab2f958ae251de0daefd049c02 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 14 Jan 2022 13:51:13 +0100 Subject: [PATCH 048/548] refactor: Update types descriptions for mixin classes (#677) --- Pipfile | 1 + appium/common/exceptions.py | 2 - appium/common/helper.py | 4 +- appium/protocols/__init__.py | 13 ++++ appium/protocols/protocol.py | 22 ++++++ appium/protocols/webdriver/__init__.py | 13 ++++ .../webdriver/can_execute_commands.py | 26 +++++++ .../webdriver/can_execute_scripts.py | 34 +++++++++ .../webdriver/can_find_elements.py} | 12 ++-- appium/webdriver/appium_connection.py | 2 +- appium/webdriver/appium_service.py | 6 +- appium/webdriver/common/mobileby.py | 2 - appium/webdriver/common/multi_action.py | 6 +- appium/webdriver/common/touch_action.py | 29 ++++---- appium/webdriver/errorhandler.py | 3 +- appium/webdriver/extensions/action_helpers.py | 29 ++++---- .../extensions/android/activities.py | 27 ++++--- appium/webdriver/extensions/android/common.py | 29 ++++---- .../webdriver/extensions/android/display.py | 23 ++---- appium/webdriver/extensions/android/gsm.py | 34 ++++----- .../webdriver/extensions/android/network.py | 36 +++++----- .../extensions/android/performance.py | 30 +++----- appium/webdriver/extensions/android/power.py | 28 ++++---- appium/webdriver/extensions/android/sms.py | 22 +++--- .../extensions/android/system_bars.py | 23 +++--- appium/webdriver/extensions/applications.py | 72 +++++++++---------- appium/webdriver/extensions/clipboard.py | 33 ++++----- appium/webdriver/extensions/context.py | 30 ++++---- appium/webdriver/extensions/device_time.py | 26 +++---- appium/webdriver/extensions/execute_driver.py | 22 +++--- .../extensions/execute_mobile_command.py | 15 ++-- appium/webdriver/extensions/hw_actions.py | 54 +++++++------- .../webdriver/extensions/images_comparison.py | 26 +++---- appium/webdriver/extensions/ime.py | 38 +++++----- appium/webdriver/extensions/keyboard.py | 47 ++++++------ appium/webdriver/extensions/location.py | 32 ++++----- appium/webdriver/extensions/log_event.py | 25 +++---- appium/webdriver/extensions/remote_fs.py | 34 +++++---- appium/webdriver/extensions/screen_record.py | 26 +++---- .../extensions/search_context/__init__.py | 9 +-- .../extensions/search_context/android.py | 32 ++++----- .../extensions/search_context/custom.py | 15 ++-- .../extensions/search_context/ios.py | 23 +++--- .../extensions/search_context/mobile.py | 19 ++--- .../extensions/search_context/windows.py | 15 ++-- appium/webdriver/extensions/session.py | 29 +++----- appium/webdriver/extensions/settings.py | 25 +++---- appium/webdriver/switch_to.py | 12 +++- appium/webdriver/webdriver.py | 57 ++++++++------- appium/webdriver/webelement.py | 38 +++++----- test/functional/ios/keyboard_tests.py | 1 - test/functional/ios/safari_tests.py | 2 +- 52 files changed, 591 insertions(+), 622 deletions(-) create mode 100644 appium/protocols/__init__.py create mode 100644 appium/protocols/protocol.py create mode 100644 appium/protocols/webdriver/__init__.py create mode 100644 appium/protocols/webdriver/can_execute_commands.py create mode 100644 appium/protocols/webdriver/can_execute_scripts.py rename appium/{webdriver/extensions/search_context/base_search_context.py => protocols/webdriver/can_find_elements.py} (79%) diff --git a/Pipfile b/Pipfile index a7175e69e..335f4bf2d 100644 --- a/Pipfile +++ b/Pipfile @@ -15,6 +15,7 @@ pytest = "~=6.2" pytest-cov = "~=3.0" tox = "~=3.24" +typing-extensions = "~=4.0" httpretty = "~=1.1" python-dateutil = "~=2.8" diff --git a/appium/common/exceptions.py b/appium/common/exceptions.py index e4ca08c76..3fe1712e8 100644 --- a/appium/common/exceptions.py +++ b/appium/common/exceptions.py @@ -24,5 +24,3 @@ class NoSuchContextException(InvalidSwitchToTargetException): print(driver.contexts) """ - - pass diff --git a/appium/common/helper.py b/appium/common/helper.py index f464e6bac..874c453fa 100644 --- a/appium/common/helper.py +++ b/appium/common/helper.py @@ -26,9 +26,7 @@ def extract_const_attributes(cls: type) -> Dict[str, Any]: Returns: dict with constants attributes and values in the class """ - return dict( - [(attr, value) for attr, value in vars(cls).items() if not callable(getattr(cls, attr)) and attr.isupper()] - ) + return {attr: value for attr, value in vars(cls).items() if not callable(getattr(cls, attr)) and attr.isupper()} def library_version() -> str: diff --git a/appium/protocols/__init__.py b/appium/protocols/__init__.py new file mode 100644 index 000000000..cc173e9d7 --- /dev/null +++ b/appium/protocols/__init__.py @@ -0,0 +1,13 @@ +#!/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. diff --git a/appium/protocols/protocol.py b/appium/protocols/protocol.py new file mode 100644 index 000000000..06daa903b --- /dev/null +++ b/appium/protocols/protocol.py @@ -0,0 +1,22 @@ +#!/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 sys + +if sys.version_info >= (3, 8): + # noinspection PyUnresolvedReferences + from typing import Protocol +else: + # noinspection PyUnresolvedReferences + from typing_extensions import Protocol diff --git a/appium/protocols/webdriver/__init__.py b/appium/protocols/webdriver/__init__.py new file mode 100644 index 000000000..cc173e9d7 --- /dev/null +++ b/appium/protocols/webdriver/__init__.py @@ -0,0 +1,13 @@ +#!/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. diff --git a/appium/protocols/webdriver/can_execute_commands.py b/appium/protocols/webdriver/can_execute_commands.py new file mode 100644 index 000000000..febd82ac3 --- /dev/null +++ b/appium/protocols/webdriver/can_execute_commands.py @@ -0,0 +1,26 @@ +#!/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. + +from typing import Dict + +from selenium.webdriver.remote.remote_connection import RemoteConnection + +from ..protocol import Protocol + + +class CanExecuteCommands(Protocol): + command_executor: RemoteConnection + + def execute(self, driver_command: str, params: Dict = None) -> Dict: + ... diff --git a/appium/protocols/webdriver/can_execute_scripts.py b/appium/protocols/webdriver/can_execute_scripts.py new file mode 100644 index 000000000..661a38806 --- /dev/null +++ b/appium/protocols/webdriver/can_execute_scripts.py @@ -0,0 +1,34 @@ +#!/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. + +from typing import Any, List, Optional + +from ..protocol import Protocol + + +class CanExecuteScripts(Protocol): + def pin_script(self, script: str, script_key: Optional[Any] = None) -> Any: + ... + + def unpin(self, script_key: Any) -> None: + ... + + def get_pinned_scripts(self) -> List[str]: + ... + + def execute_script(self, script: str, *args: Any) -> Any: + ... + + def execute_async_script(self, script: str, *args: Any) -> Any: + ... diff --git a/appium/webdriver/extensions/search_context/base_search_context.py b/appium/protocols/webdriver/can_find_elements.py similarity index 79% rename from appium/webdriver/extensions/search_context/base_search_context.py rename to appium/protocols/webdriver/can_find_elements.py index 317069f03..53c1c049a 100644 --- a/appium/webdriver/extensions/search_context/base_search_context.py +++ b/appium/protocols/webdriver/can_find_elements.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - from typing import TYPE_CHECKING, Dict, List, Union +from ..protocol import Protocol + if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -class BaseSearchContext: - """Used by each search context. Dummy find_element/s are for preventing pylint error""" - +class CanFindElements(Protocol): def find_element(self, by: str, value: Union[str, Dict] = None) -> 'WebElement': - raise NotImplementedError + ... def find_elements(self, by: str, value: Union[str, Dict] = None) -> List['WebElement']: - raise NotImplementedError + ... diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index eb218b994..74b6c7d10 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -46,7 +46,7 @@ def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyMan 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']) + headers['User-Agent'] = f'appium/python {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()) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index 52c5b4a4f..e3e817a10 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -30,7 +30,7 @@ def find_executable(executable: str) -> Optional[str]: path = os.environ['PATH'] paths = path.split(os.pathsep) - base, ext = os.path.splitext(executable) + _, ext = os.path.splitext(executable) if sys.platform == 'win32' and not ext: executable = executable + '.exe' @@ -53,7 +53,7 @@ def poll_url(host: str, port: int, path: str, timeout_ms: int) -> bool: resp = conn.request('HEAD', f'http://{host}:{port}{path}') if resp.status < 400: return True - except Exception: + except urllib3.exceptions.HTTPError: pass time.sleep(1.0) return False @@ -99,7 +99,7 @@ def _get_main_script(self) -> Union[str, bytes]: if not hasattr(self, '_main_script'): try: self._main_script = sp.check_output( - [self._get_node(), '-e', 'console.log(require.resolve("{}"))'.format(MAIN_SCRIPT_PATH)] + [self._get_node(), '-e', f'console.log(require.resolve("{MAIN_SCRIPT_PATH}"))'] ).strip() except sp.CalledProcessError as e: raise AppiumServiceError(e.output) from e diff --git a/appium/webdriver/common/mobileby.py b/appium/webdriver/common/mobileby.py index 79b937022..986ad736d 100644 --- a/appium/webdriver/common/mobileby.py +++ b/appium/webdriver/common/mobileby.py @@ -20,5 +20,3 @@ class MobileBy(AppiumBy): deprecated:: 2.1.0 Please use 'from appium.webdriver.common.appiumby import AppiumBy' instead of 'MobileBy'. """ - - pass diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index f26bf498a..818c63d61 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,7 +19,7 @@ # chaining as the spec requires. import copy -from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command @@ -29,8 +29,6 @@ from appium.webdriver.webdriver import WebDriver from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound='MultiAction') - class MultiAction: """ @@ -67,7 +65,7 @@ def add(self, *touch_actions: 'TouchAction') -> None: self._touch_actions.append(copy.copy(touch_action)) - def perform(self: T) -> T: + def perform(self) -> 'MultiAction': """Perform the actions stored in the object. Usage: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index f1a15f327..ba36efff5 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,7 +24,7 @@ # pylint: disable=no-self-use import copy -from typing import TYPE_CHECKING, Dict, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command @@ -33,8 +33,6 @@ from appium.webdriver.webdriver import WebDriver from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound='TouchAction') - class TouchAction: """ @@ -49,12 +47,12 @@ def __init__(self, driver: Optional['WebDriver'] = None): self._actions: List = [] def tap( - self: T, + self, element: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None, count: int = 1, - ) -> T: + ) -> 'TouchAction': """Perform a tap action on the element Args: @@ -72,12 +70,12 @@ def tap( return self def press( - self: T, + self, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None, pressure: Optional[float] = None, - ) -> T: + ) -> 'TouchAction': """Begin a chain with a press down action at a particular element or point Args: @@ -85,7 +83,8 @@ def press( x: x coordiate to press. If y is used, x must also be set y: y coordiate to press. If x is used, y must also be set pressure: [iOS Only] press as force touch. Read the description of `force` property on Apple's UITouch class - (https://developer.apple.com/documentation/uikit/uitouch?language=objc) for more details on possible value ranges. + (https://developer.apple.com/documentation/uikit/uitouch?language=objc) for + more details on possible value ranges. Returns: `TouchAction`: Self instance @@ -95,12 +94,12 @@ def press( return self def long_press( - self: T, + self, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None, duration: int = 1000, - ) -> T: + ) -> 'TouchAction': """Begin a chain with a press down that lasts `duration` milliseconds Args: @@ -116,7 +115,7 @@ def long_press( return self - def wait(self: T, ms: int = 0) -> T: + def wait(self, ms: int = 0) -> 'TouchAction': """Pause for `ms` milliseconds. Args: @@ -134,7 +133,9 @@ def wait(self: T, ms: int = 0) -> T: return self - def move_to(self: T, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None) -> T: + def move_to( + self, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None + ) -> 'TouchAction': """Move the pointer from the previous point to the element or point specified Args: @@ -149,7 +150,7 @@ def move_to(self: T, el: Optional['WebElement'] = None, x: Optional[int] = None, return self - def release(self: T) -> T: + def release(self) -> 'TouchAction': """End the action by lifting the pointer off the screen Returns: @@ -159,7 +160,7 @@ def release(self: T) -> T: return self - def perform(self: T) -> T: + def perform(self) -> 'TouchAction': """Perform the action by sending the commands to the server to be operated upon Returns: diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index 8612d86a3..fd582645b 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -27,5 +27,4 @@ def check_response(self, response: Dict) -> None: except WebDriverException as wde: if wde.msg == 'No such context found.': raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) from wde - else: - raise wde + raise wde diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index f1e4f8996..fd079e025 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -12,9 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List, Optional, Tuple, TypeVar, Union +from typing import TYPE_CHECKING, List, Optional, Tuple -from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.actions import interaction from selenium.webdriver.common.actions.action_builder import ActionBuilder @@ -24,14 +23,11 @@ from appium.webdriver.webelement import WebElement if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'ActionHelpers']) - -class ActionHelpers(webdriver.Remote): - def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> T: +class ActionHelpers: + def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> 'WebDriver': """Scrolls from one element to another Args: @@ -72,9 +68,9 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return self # type: ignore - def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: + def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': """Drag the origin element to the destination element Args: @@ -90,9 +86,9 @@ def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return self # type: ignore - def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> T: + def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> 'WebDriver': """Taps on an particular place with up to five fingers, holding for a certain time @@ -140,10 +136,9 @@ def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = Non new_input.create_pause(0.1) new_input.create_pointer_up(MouseButton.LEFT) actions.perform() + return self # type: ignore - return self - - def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: + def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> 'WebDriver': """Swipe from one point to another point, for an optional duration. Args: @@ -167,9 +162,9 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return self # type: ignore - def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: + def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDriver': """Flick from one point to another point. Args: @@ -191,4 +186,4 @@ def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return self # type: ignore diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index cdc0da7c2..ae25178bf 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,23 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union +from typing import TYPE_CHECKING -from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Activities']) - -class Activities(webdriver.Remote): - def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> T: +class Activities(CanExecuteCommands): + def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'WebDriver': """Opens an arbitrary activity during a test. If the activity belongs to another application, that application is started and the activity is opened. @@ -61,10 +58,10 @@ def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> if key in opts: data[value] = opts[key] self.execute(Command.START_ACTIVITY, data) - return self + return self # type: ignore @property - def current_activity(self: T) -> str: + def current_activity(self) -> str: """Retrieves the current activity running on the device. Returns: @@ -72,7 +69,7 @@ def current_activity(self: T) -> str: """ return self.execute(Command.GET_CURRENT_ACTIVITY)['value'] - def wait_activity(self: T, activity: str, timeout: int, interval: int = 1) -> bool: + 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. @@ -91,14 +88,14 @@ def wait_activity(self: T, activity: str, timeout: int, interval: int = 1) -> bo except TimeoutException: return False - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_CURRENT_ACTIVITY] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_CURRENT_ACTIVITY] = ( 'GET', '/session/$sessionId/appium/device/current_activity', ) - self.command_executor._commands[Command.START_ACTIVITY] = ( + commands[Command.START_ACTIVITY] = ( 'POST', '/session/$sessionId/appium/device/start_activity', ) diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index 625b9a00e..a6eeabf5c 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -12,21 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING, Any +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Common']) - -class Common(webdriver.Remote): - def end_test_coverage(self: T, intent: str, path: str) -> Any: # TODO Check return type +class Common(CanExecuteCommands): + 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. @@ -45,31 +41,32 @@ def end_test_coverage(self: T, intent: str, path: str) -> Any: # TODO Check ret } return self.execute(Command.END_TEST_COVERAGE, data)['value'] - def open_notifications(self: T) -> T: + def open_notifications(self) -> 'WebDriver': """Open notification shade in Android (API Level 18 and above) Returns: Union['WebDriver', 'Common']: Self instance """ self.execute(Command.OPEN_NOTIFICATIONS, {}) - return self + return self # type: ignore @property - def current_package(self: T) -> str: + def current_package(self) -> str: """Retrieves the current package running on the device.""" return self.execute(Command.GET_CURRENT_PACKAGE)['value'] - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_CURRENT_PACKAGE] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_CURRENT_PACKAGE] = ( 'GET', '/session/$sessionId/appium/device/current_package', ) - self.command_executor._commands[Command.END_TEST_COVERAGE] = ( + commands[Command.END_TEST_COVERAGE] = ( 'POST', '/session/$sessionId/appium/app/end_test_coverage', ) - self.command_executor._commands[Command.OPEN_NOTIFICATIONS] = ( + commands[Command.OPEN_NOTIFICATIONS] = ( 'POST', '/session/$sessionId/appium/device/open_notifications', ) diff --git a/appium/webdriver/extensions/android/display.py b/appium/webdriver/extensions/android/display.py index 8c3629d7e..9822182aa 100644 --- a/appium/webdriver/extensions/android/display.py +++ b/appium/webdriver/extensions/android/display.py @@ -12,21 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union - -from selenium import webdriver - +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver - -T = TypeVar('T', bound=Union['WebDriver', 'Display']) - -class Display(webdriver.Remote): - def get_display_density(self: T) -> int: +class Display(CanExecuteCommands): + def get_display_density(self) -> int: """Get the display density, Android only Returns: @@ -40,10 +31,10 @@ def get_display_density(self: T) -> int: """ return self.execute(Command.GET_DISPLAY_DENSITY)['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_DISPLAY_DENSITY] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + 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 af4f853dd..1df33ac7a 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING from appium.common.helper import extract_const_attributes from appium.common.logger import logger +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Gsm']) - class GsmCallActions: CALL = 'call' @@ -52,8 +48,8 @@ class GsmVoiceState: ON = 'on' -class Gsm(webdriver.Remote): - def make_gsm_call(self: T, phone_number: str, action: str) -> T: +class Gsm(CanExecuteCommands): + def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': """Make GSM call (Emulator only) Android only. @@ -76,9 +72,9 @@ def make_gsm_call(self: T, phone_number: str, action: str) -> T: f'(e.g. {GsmCallActions.__name__}.CALL)' ) self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) - return self + return self # type: ignore - def set_gsm_signal(self: T, strength: int) -> T: + def set_gsm_signal(self, strength: int) -> 'WebDriver': """Set GSM signal strength (Emulator only) Android only. @@ -100,9 +96,9 @@ def set_gsm_signal(self: T, strength: int) -> T: f'(e.g. {GsmSignalStrength.__name__}.GOOD)' ) self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) - return self + return self # type: ignore - def set_gsm_voice(self: T, state: str) -> T: + def set_gsm_voice(self, state: str) -> 'WebDriver': """Set GSM voice state (Emulator only) Android only. @@ -124,14 +120,14 @@ def set_gsm_voice(self: T, state: str) -> T: f'(e.g. {GsmVoiceState.__name__}.HOME)' ) self.execute(Command.SET_GSM_VOICE, {'state': state}) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - 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] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.MAKE_GSM_CALL] = ('POST', '/session/$sessionId/appium/device/gsm_call') + commands[Command.SET_GSM_SIGNAL] = ( 'POST', '/session/$sessionId/appium/device/gsm_signal', ) - self.command_executor._commands[Command.SET_GSM_VOICE] = ('POST', '/session/$sessionId/appium/device/gsm_voice') + commands[Command.SET_GSM_VOICE] = ('POST', '/session/$sessionId/appium/device/gsm_voice') diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 68c4d9eea..70a32802d 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING from appium.common.helper import extract_const_attributes from appium.common.logger import logger +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Network']) - class NetSpeed: GSM = 'gsm' # GSM/CSD (up: 14.4(kbps), down: 14.4(kbps)) @@ -39,9 +35,9 @@ class NetSpeed: FULL = 'full' # No limit, the default (up: 0.0, down: 0.0) -class Network(webdriver.Remote): +class Network(CanExecuteCommands): @property - def network_connection(self: T) -> int: + def network_connection(self) -> int: """Returns an integer bitmask specifying the network connection type. Android only. @@ -49,7 +45,7 @@ def network_connection(self: T) -> int: """ return self.execute(Command.GET_NETWORK_CONNECTION, {})['value'] - def set_network_connection(self: T, connection_type: int) -> int: + def set_network_connection(self, connection_type: int) -> int: """Sets the network connection type. Android only. Possible values: @@ -79,16 +75,16 @@ def set_network_connection(self: T, connection_type: int) -> int: data = {'parameters': {'type': connection_type}} return self.execute(Command.SET_NETWORK_CONNECTION, data)['value'] - def toggle_wifi(self: T) -> T: + def toggle_wifi(self) -> 'WebDriver': """Toggle the wifi on the device, Android only. Returns: Union['WebDriver', 'Network']: Self instance """ self.execute(Command.TOGGLE_WIFI, {}) - return self + return self # type: ignore - def set_network_speed(self: T, speed_type: str) -> T: + def set_network_speed(self, speed_type: str) -> 'WebDriver': """Set the network speed emulation. Android Emulator only. @@ -111,21 +107,21 @@ def set_network_speed(self: T, speed_type: str) -> T: ) self.execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - 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] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.TOGGLE_WIFI] = ('POST', '/session/$sessionId/appium/device/toggle_wifi') + commands[Command.GET_NETWORK_CONNECTION] = ( 'GET', '/session/$sessionId/network_connection', ) - self.command_executor._commands[Command.SET_NETWORK_CONNECTION] = ( + commands[Command.SET_NETWORK_CONNECTION] = ( 'POST', '/session/$sessionId/network_connection', ) - self.command_executor._commands[Command.SET_NETWORK_SPEED] = ( + commands[Command.SET_NETWORK_SPEED] = ( 'POST', '/session/$sessionId/appium/device/network_speed', ) diff --git a/appium/webdriver/extensions/android/performance.py b/appium/webdriver/extensions/android/performance.py index 7ab13bf08..ee144b608 100644 --- a/appium/webdriver/extensions/android/performance.py +++ b/appium/webdriver/extensions/android/performance.py @@ -12,23 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, List, TypeVar, Union - -from selenium import webdriver +from typing import Dict, List, Union +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver - -T = TypeVar('T', bound=Union['WebDriver', 'Performance']) - -class Performance(webdriver.Remote): - def get_performance_data( - self: T, package_name: str, data_type: str, data_read_timeout: int = None - ) -> List[List[str]]: +class Performance(CanExecuteCommands): + 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. @@ -52,7 +43,7 @@ def get_performance_data( data['dataReadTimeout'] = data_read_timeout return self.execute(Command.GET_PERFORMANCE_DATA, data)['value'] - def get_performance_data_types(self: T) -> List: + 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. @@ -65,15 +56,14 @@ def get_performance_data_types(self: T) -> List: """ return self.execute(Command.GET_PERFORMANCE_DATA_TYPES)['value'] - # pylint: disable=protected-access - - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_PERFORMANCE_DATA] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_PERFORMANCE_DATA] = ( 'POST', '/session/$sessionId/appium/getPerformanceData', ) - self.command_executor._commands[Command.GET_PERFORMANCE_DATA_TYPES] = ( + commands[Command.GET_PERFORMANCE_DATA_TYPES] = ( 'POST', '/session/$sessionId/appium/performanceData/types', ) diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index a6021ab16..a74d5e82f 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -12,24 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Power']) - -class Power(webdriver.Remote): +class Power(CanExecuteCommands): AC_OFF, AC_ON = 'off', 'on' - def set_power_capacity(self: T, percent: int) -> T: + def set_power_capacity(self, percent: int) -> 'WebDriver': """Emulate power capacity change on the connected emulator. Android only. @@ -44,9 +40,9 @@ def set_power_capacity(self: T, percent: int) -> T: Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_CAPACITY, {'percent': percent}) - return self + return self # type: ignore - def set_power_ac(self: T, ac_state: str) -> T: + def set_power_ac(self, ac_state: str) -> 'WebDriver': """Emulate power state change on the connected emulator. Android only. @@ -62,13 +58,13 @@ def set_power_ac(self: T, ac_state: str) -> T: Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_AC, {'state': ac_state}) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.SET_POWER_CAPACITY] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.SET_POWER_CAPACITY] = ( 'POST', '/session/$sessionId/appium/device/power_capacity', ) - self.command_executor._commands[Command.SET_POWER_AC] = ('POST', '/session/$sessionId/appium/device/power_ac') + commands[Command.SET_POWER_AC] = ('POST', '/session/$sessionId/appium/device/power_ac') diff --git a/appium/webdriver/extensions/android/sms.py b/appium/webdriver/extensions/android/sms.py index 92aff47ba..0fc10f398 100644 --- a/appium/webdriver/extensions/android/sms.py +++ b/appium/webdriver/extensions/android/sms.py @@ -12,21 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Sms']) - -class Sms(webdriver.Remote): - def send_sms(self: T, phone_number: str, message: str) -> T: +class Sms(CanExecuteCommands): + def send_sms(self, phone_number: str, message: str) -> 'WebDriver': """Emulate send SMS event on the connected emulator. Android only. @@ -42,9 +38,9 @@ def send_sms(self: T, phone_number: str, message: str) -> T: Union['WebDriver', 'Sms']: Self instance """ self.execute(Command.SEND_SMS, {'phoneNumber': phone_number, 'message': message}) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.SEND_SMS] = ('POST', '/session/$sessionId/appium/device/send_sms') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + 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 c0779b392..b894fe7c5 100644 --- a/appium/webdriver/extensions/android/system_bars.py +++ b/appium/webdriver/extensions/android/system_bars.py @@ -12,21 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, TypeVar, Union - -from selenium import webdriver +from typing import Dict, Union +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver - -T = TypeVar('T', bound=Union['WebDriver', 'SystemBars']) - -class SystemBars(webdriver.Remote): - def get_system_bars(self: T) -> Dict[str, Dict[str, Union[int, bool]]]: +class SystemBars(CanExecuteCommands): + 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. @@ -48,10 +41,10 @@ def get_system_bars(self: T) -> Dict[str, Dict[str, Union[int, bool]]]: """ return self.execute(Command.GET_SYSTEM_BARS)['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_SYSTEM_BARS] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + 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 c20599e92..e545d77a0 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command @@ -22,11 +22,9 @@ # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Applications']) - -class Applications(webdriver.Remote): - def background_app(self: T, seconds: int) -> T: +class Applications(CanExecuteCommands): + def background_app(self, seconds: int) -> 'WebDriver': """Puts the application in the background on the device for a certain duration. Args: @@ -39,9 +37,9 @@ def background_app(self: T, seconds: int) -> T: 'seconds': seconds, } self.execute(Command.BACKGROUND, data) - return self + return self # type: ignore - def is_app_installed(self: T, bundle_id: str) -> bool: + def is_app_installed(self, bundle_id: str) -> bool: """Checks whether the application specified by `bundle_id` is installed on the device. Args: @@ -55,7 +53,7 @@ def is_app_installed(self: T, bundle_id: str) -> bool: } return self.execute(Command.IS_APP_INSTALLED, data)['value'] - def install_app(self: T, app_path: str, **options: Any) -> T: + def install_app(self, app_path: str, **options: Any) -> 'WebDriver': """Install the application found at `app_path` on the device. Args: @@ -81,9 +79,9 @@ def install_app(self: T, app_path: str, **options: Any) -> T: if options: data.update({'options': options}) self.execute(Command.INSTALL_APP, data) - return self + return self # type: ignore - def remove_app(self: T, app_id: str, **options: Any) -> T: + def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': """Remove the specified application from the device. Args: @@ -104,18 +102,18 @@ def remove_app(self: T, app_id: str, **options: Any) -> T: if options: data.update({'options': options}) self.execute(Command.REMOVE_APP, data) - return self + return self # type: ignore - def launch_app(self: T) -> T: + def launch_app(self) -> 'WebDriver': """Start on the device the application specified in the desired capabilities. Returns: Union['WebDriver', 'Applications']: Self instance """ self.execute(Command.LAUNCH_APP) - return self + return self # type: ignore - def close_app(self: T) -> T: + def close_app(self) -> 'WebDriver': """Stop the running application, specified in the desired capabilities, on the device. @@ -123,9 +121,9 @@ def close_app(self: T) -> T: Union['WebDriver', 'Applications']: Self instance """ self.execute(Command.CLOSE_APP) - return self + return self # type: ignore - def terminate_app(self: T, app_id: str, **options: Any) -> bool: + def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. Args: @@ -145,7 +143,7 @@ def terminate_app(self: T, app_id: str, **options: Any) -> bool: data.update({'options': options}) return self.execute(Command.TERMINATE_APP, data)['value'] - def activate_app(self: T, app_id: str) -> T: + def activate_app(self, app_id: str) -> 'WebDriver': """Activates the application if it is not running or is running in the background. @@ -159,9 +157,9 @@ def activate_app(self: T, app_id: str) -> T: 'appId': app_id, } self.execute(Command.ACTIVATE_APP, data) - return self + return self # type: ignore - def query_app_state(self: T, app_id: str) -> int: + def query_app_state(self, app_id: str) -> int: """Queries the state of the application. Args: @@ -176,7 +174,7 @@ class for more details. } return self.execute(Command.QUERY_APP_STATE, data)['value'] - def app_strings(self: T, language: str = None, string_file: str = None) -> Dict[str, 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. @@ -194,38 +192,38 @@ def app_strings(self: T, language: str = None, string_file: str = None) -> Dict[ data['stringFile'] = string_file return self.execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self: T) -> T: + def reset(self) -> 'WebDriver': """Resets the current application on the device. Returns: Union['WebDriver', 'Applications']: Self instance """ self.execute(Command.RESET) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.BACKGROUND] = ('POST', '/session/$sessionId/appium/app/background') - self.command_executor._commands[Command.IS_APP_INSTALLED] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.BACKGROUND] = ('POST', '/session/$sessionId/appium/app/background') + commands[Command.IS_APP_INSTALLED] = ( 'POST', '/session/$sessionId/appium/device/app_installed', ) - self.command_executor._commands[Command.INSTALL_APP] = ('POST', '/session/$sessionId/appium/device/install_app') - self.command_executor._commands[Command.REMOVE_APP] = ('POST', '/session/$sessionId/appium/device/remove_app') - self.command_executor._commands[Command.TERMINATE_APP] = ( + commands[Command.INSTALL_APP] = ('POST', '/session/$sessionId/appium/device/install_app') + commands[Command.REMOVE_APP] = ('POST', '/session/$sessionId/appium/device/remove_app') + commands[Command.TERMINATE_APP] = ( 'POST', '/session/$sessionId/appium/device/terminate_app', ) - self.command_executor._commands[Command.ACTIVATE_APP] = ( + commands[Command.ACTIVATE_APP] = ( 'POST', '/session/$sessionId/appium/device/activate_app', ) - self.command_executor._commands[Command.QUERY_APP_STATE] = ( + commands[Command.QUERY_APP_STATE] = ( 'POST', '/session/$sessionId/appium/device/app_state', ) - self.command_executor._commands[Command.GET_APP_STRINGS] = ('POST', '/session/$sessionId/appium/app/strings') - self.command_executor._commands[Command.RESET] = ('POST', '/session/$sessionId/appium/app/reset') - self.command_executor._commands[Command.LAUNCH_APP] = ('POST', '/session/$sessionId/appium/app/launch') - self.command_executor._commands[Command.CLOSE_APP] = ('POST', '/session/$sessionId/appium/app/close') + commands[Command.GET_APP_STRINGS] = ('POST', '/session/$sessionId/appium/app/strings') + commands[Command.RESET] = ('POST', '/session/$sessionId/appium/app/reset') + commands[Command.LAUNCH_APP] = ('POST', '/session/$sessionId/appium/app/launch') + commands[Command.CLOSE_APP] = ('POST', '/session/$sessionId/appium/app/close') diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 979542a15..587f5316d 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,10 +13,9 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional, TypeVar, Union - -from selenium import webdriver +from typing import TYPE_CHECKING, Optional +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command @@ -25,13 +24,11 @@ # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Clipboard']) - -class Clipboard(webdriver.Remote): +class Clipboard(CanExecuteCommands): def set_clipboard( - self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None - ) -> T: + self, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None + ) -> 'WebDriver': """Set the content of the system clipboard Args: @@ -50,9 +47,9 @@ def set_clipboard( if label: options['label'] = label self.execute(Command.SET_CLIPBOARD, options) - return self + return self # type: ignore - def set_clipboard_text(self: T, text: str, label: Optional[str] = None) -> T: + def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriver': """Copies the given text to the system clipboard Args: @@ -64,9 +61,9 @@ def set_clipboard_text(self: T, text: str, label: Optional[str] = None) -> T: """ self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) - return self + return self # type: ignore - def get_clipboard(self: T, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: + def get_clipboard(self, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: """Receives the content of the system clipboard Args: @@ -79,7 +76,7 @@ def get_clipboard(self: T, content_type: str = ClipboardContentType.PLAINTEXT) - base64_str = self.execute(Command.GET_CLIPBOARD, {'contentType': content_type})['value'] return base64.b64decode(base64_str) - def get_clipboard_text(self: T) -> str: + def get_clipboard_text(self) -> str: """Receives the text of the system clipboard Returns: @@ -87,14 +84,14 @@ def get_clipboard_text(self: T) -> str: """ return self.get_clipboard(ClipboardContentType.PLAINTEXT).decode('UTF-8') - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.SET_CLIPBOARD] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.SET_CLIPBOARD] = ( 'POST', '/session/$sessionId/appium/device/set_clipboard', ) - self.command_executor._commands[Command.GET_CLIPBOARD] = ( + commands[Command.GET_CLIPBOARD] = ( 'POST', '/session/$sessionId/appium/device/get_clipboard', ) diff --git a/appium/webdriver/extensions/context.py b/appium/webdriver/extensions/context.py index f949f2abd..356604fe4 100644 --- a/appium/webdriver/extensions/context.py +++ b/appium/webdriver/extensions/context.py @@ -12,22 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import List -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Context']) - - -class Context(webdriver.Remote): +class Context(CanExecuteCommands): @property - def contexts(self: T) -> List[str]: + def contexts(self) -> List[str]: """Returns the contexts within the current session. Usage: @@ -40,7 +34,7 @@ def contexts(self: T) -> List[str]: return self.execute(Command.CONTEXTS)['value'] @property - def current_context(self: T) -> str: + def current_context(self) -> str: """Returns the current context of the current session. Usage: @@ -52,7 +46,7 @@ def current_context(self: T) -> str: return self.execute(Command.GET_CURRENT_CONTEXT)['value'] @property - def context(self: T) -> str: + def context(self) -> str: """Returns the current context of the current session. Usage: @@ -63,9 +57,9 @@ def context(self: T) -> str: """ return self.current_context - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.CONTEXTS] = ('GET', '/session/$sessionId/contexts') - self.command_executor._commands[Command.GET_CURRENT_CONTEXT] = ('GET', '/session/$sessionId/context') - self.command_executor._commands[Command.SWITCH_TO_CONTEXT] = ('POST', '/session/$sessionId/context') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.CONTEXTS] = ('GET', '/session/$sessionId/contexts') + commands[Command.GET_CURRENT_CONTEXT] = ('GET', '/session/$sessionId/context') + commands[Command.SWITCH_TO_CONTEXT] = ('POST', '/session/$sessionId/context') diff --git a/appium/webdriver/extensions/device_time.py b/appium/webdriver/extensions/device_time.py index 241c56dcf..3bf0d44e2 100644 --- a/appium/webdriver/extensions/device_time.py +++ b/appium/webdriver/extensions/device_time.py @@ -12,22 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Optional, TypeVar, Union +from typing import Optional -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'DeviceTime']) - - -class DeviceTime(webdriver.Remote): +class DeviceTime(CanExecuteCommands): @property - def device_time(self: T) -> str: + def device_time(self) -> str: """Returns the date and time from the device. Return: @@ -35,7 +29,7 @@ def device_time(self: T) -> str: """ return self.execute(Command.GET_DEVICE_TIME_GET, {})['value'] - def get_device_time(self: T, format: Optional[str] = None) -> str: + def get_device_time(self, format: Optional[str] = None) -> str: """Returns the date and time from the device. Args: @@ -55,14 +49,14 @@ def get_device_time(self: T, format: Optional[str] = None) -> str: return self.device_time return self.execute(Command.GET_DEVICE_TIME_POST, {'format': format})['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_DEVICE_TIME_GET] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_DEVICE_TIME_GET] = ( 'GET', '/session/$sessionId/appium/device/system_time', ) - self.command_executor._commands[Command.GET_DEVICE_TIME_POST] = ( + commands[Command.GET_DEVICE_TIME_POST] = ( 'POST', '/session/$sessionId/appium/device/system_time', ) diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index 74b1097ff..010c297d0 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -12,23 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, Optional, TypeVar, Union +from typing import Any, Dict, Optional, Union -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'ExecuteDriver']) - - -class ExecuteDriver(webdriver.Remote): +class ExecuteDriver(CanExecuteCommands): # TODO Inner class case - def execute_driver(self: T, script: str, script_type: str = 'webdriverio', timeout_ms: Optional[int] = None) -> Any: + 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. @@ -63,7 +57,7 @@ def __init__(self, res: Dict): response = self.execute(Command.EXECUTE_DRIVER, option)['value'] return Result(response) - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.EXECUTE_DRIVER] = ('POST', '/session/$sessionId/appium/execute_driver') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + 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 50467cb6f..4152e471b 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,19 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict -from selenium import webdriver +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'ExecuteMobileCommand']) - -class ExecuteMobileCommand(webdriver.Remote): - def press_button(self: T, button_name: str) -> T: +class ExecuteMobileCommand(CanExecuteScripts): + def press_button(self, button_name: str) -> 'WebDriver': """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -40,10 +37,10 @@ def press_button(self: T, button_name: str) -> T: """ data = {'name': button_name} self.execute_script('mobile: pressButton', data) - return self + return self # type: ignore @property - def battery_info(self: T) -> Dict[str, Any]: + 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 6a6357da5..6cfb68888 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,21 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, Optional -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'HardwareActions']) - -class HardwareActions(webdriver.Remote): - def lock(self: T, seconds: Optional[int] = None) -> T: +class HardwareActions(CanExecuteCommands): + def lock(self, seconds: Optional[int] = None) -> 'WebDriver': """Lock the device. No changes are made if the device is already unlocked. Args: @@ -42,19 +39,18 @@ def lock(self: T, seconds: Optional[int] = None) -> T: self.execute(Command.LOCK) else: self.execute(Command.LOCK, {'seconds': seconds}) + return self # type: ignore - return self - - def unlock(self: T) -> T: + def unlock(self) -> 'WebDriver': """Unlock the device. No changes are made if the device is already locked. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.UNLOCK) - return self + return self # type: ignore - def is_locked(self: T) -> bool: + def is_locked(self) -> bool: """Checks whether the device is locked. Returns: @@ -62,16 +58,16 @@ def is_locked(self: T) -> bool: """ return self.execute(Command.IS_LOCKED)['value'] - def shake(self: T) -> T: + def shake(self) -> 'WebDriver': """Shake the device. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.SHAKE) - return self + return self # type: ignore - def touch_id(self: T, match: bool) -> T: + def touch_id(self, match: bool) -> 'WebDriver': """Simulate touchId on iOS Simulator Args: @@ -82,18 +78,18 @@ def touch_id(self: T, match: bool) -> T: """ data = {'match': match} self.execute(Command.TOUCH_ID, data) - return self + return self # type: ignore - def toggle_touch_id_enrollment(self: T) -> T: + def toggle_touch_id_enrollment(self) -> 'WebDriver': """Toggle enroll touchId on iOS Simulator Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.TOGGLE_TOUCH_ID_ENROLLMENT) - return self + return self # type: ignore - def finger_print(self: T, finger_id: int) -> Any: + def finger_print(self, finger_id: int) -> Any: """Authenticate users by using their finger print scans on supported Android emulators. Args: @@ -104,19 +100,19 @@ def finger_print(self: T, finger_id: int) -> Any: """ return self.execute(Command.FINGER_PRINT, {'fingerprintId': finger_id})['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.LOCK] = ('POST', '/session/$sessionId/appium/device/lock') - self.command_executor._commands[Command.UNLOCK] = ('POST', '/session/$sessionId/appium/device/unlock') - self.command_executor._commands[Command.IS_LOCKED] = ('POST', '/session/$sessionId/appium/device/is_locked') - self.command_executor._commands[Command.SHAKE] = ('POST', '/session/$sessionId/appium/device/shake') - self.command_executor._commands[Command.TOUCH_ID] = ('POST', '/session/$sessionId/appium/simulator/touch_id') - self.command_executor._commands[Command.TOGGLE_TOUCH_ID_ENROLLMENT] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.LOCK] = ('POST', '/session/$sessionId/appium/device/lock') + commands[Command.UNLOCK] = ('POST', '/session/$sessionId/appium/device/unlock') + commands[Command.IS_LOCKED] = ('POST', '/session/$sessionId/appium/device/is_locked') + commands[Command.SHAKE] = ('POST', '/session/$sessionId/appium/device/shake') + commands[Command.TOUCH_ID] = ('POST', '/session/$sessionId/appium/simulator/touch_id') + commands[Command.TOGGLE_TOUCH_ID_ENROLLMENT] = ( 'POST', '/session/$sessionId/appium/simulator/toggle_touch_id_enrollment', ) - self.command_executor._commands[Command.FINGER_PRINT] = ( + commands[Command.FINGER_PRINT] = ( 'POST', '/session/$sessionId/appium/device/finger_print', ) diff --git a/appium/webdriver/extensions/images_comparison.py b/appium/webdriver/extensions/images_comparison.py index b5f552490..5d4778af7 100644 --- a/appium/webdriver/extensions/images_comparison.py +++ b/appium/webdriver/extensions/images_comparison.py @@ -12,21 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, TypeVar, Union +from typing import Any, Dict, Union -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'ImagesComparison']) - - -class ImagesComparison(webdriver.Remote): - def match_images_features(self: T, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Dict[str, Any]: +class ImagesComparison(CanExecuteCommands): + def match_images_features(self, base64_image1: bytes, base64_image2: bytes, **opts: Any) -> Dict[str, Any]: """Performs images matching by features. Read @@ -76,7 +70,7 @@ def match_images_features(self: T, base64_image1: bytes, base64_image2: bytes, * return self.execute(Command.COMPARE_IMAGES, options)['value'] def find_image_occurrence( - self: T, base64_full_image: bytes, base64_partial_image: bytes, **opts: Any + 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. @@ -110,7 +104,7 @@ def find_image_occurrence( return self.execute(Command.COMPARE_IMAGES, options)['value'] def get_images_similarity( - self: T, base64_image1: bytes, base64_image2: bytes, **opts: Any + self, base64_image1: bytes, base64_image2: bytes, **opts: Any ) -> Dict[str, Union[bytes, Dict]]: """Performs images matching to calculate the similarity score between them. @@ -136,7 +130,7 @@ def get_images_similarity( options = {'mode': 'getSimilarity', 'firstImage': base64_image1, 'secondImage': base64_image2, 'options': opts} return self.execute(Command.COMPARE_IMAGES, options)['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.COMPARE_IMAGES] = ('POST', '/session/$sessionId/appium/compare_images') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + 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 460b5e5e5..bf7ebf1c3 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import TYPE_CHECKING, List -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command @@ -22,12 +22,10 @@ # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'IME']) - -class IME(webdriver.Remote): +class IME(CanExecuteCommands): @property - def available_ime_engines(self: T) -> List[str]: + 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']) @@ -38,7 +36,7 @@ def available_ime_engines(self: T) -> List[str]: """ return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] - def is_ime_active(self: T) -> bool: + def is_ime_active(self) -> bool: """Checks whether the device has IME service active. Android only. @@ -47,7 +45,7 @@ def is_ime_active(self: T) -> bool: """ return self.execute(Command.IS_IME_ACTIVE, {})['value'] - def activate_ime_engine(self: T, engine: str) -> T: + def activate_ime_engine(self, engine: str) -> 'WebDriver': """Activates the given IME engine on the device. Android only. @@ -61,9 +59,9 @@ def activate_ime_engine(self: T, engine: str) -> T: """ data = {'engine': engine} self.execute(Command.ACTIVATE_IME_ENGINE, data) - return self + return self # type: ignore - def deactivate_ime_engine(self: T) -> T: + def deactivate_ime_engine(self) -> 'WebDriver': """Deactivates the currently active IME engine on the device. Android only. @@ -72,10 +70,10 @@ def deactivate_ime_engine(self: T) -> T: Union['WebDriver', 'IME']: Self instance """ self.execute(Command.DEACTIVATE_IME_ENGINE, {}) - return self + return self # type: ignore @property - def active_ime_engine(self: T) -> str: + def active_ime_engine(self) -> str: """Returns the activity and package of the currently active IME engine (e.g., 'com.android.inputmethod.latin/.LatinIME'). @@ -86,17 +84,17 @@ def active_ime_engine(self: T) -> str: """ return self.execute(Command.GET_ACTIVE_IME_ENGINE, {})['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_AVAILABLE_IME_ENGINES] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_AVAILABLE_IME_ENGINES] = ( 'GET', '/session/$sessionId/ime/available_engines', ) - self.command_executor._commands[Command.IS_IME_ACTIVE] = ('GET', '/session/$sessionId/ime/activated') - self.command_executor._commands[Command.ACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/activate') - self.command_executor._commands[Command.DEACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/deactivate') - self.command_executor._commands[Command.GET_ACTIVE_IME_ENGINE] = ( + commands[Command.IS_IME_ACTIVE] = ('GET', '/session/$sessionId/ime/activated') + commands[Command.ACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/activate') + commands[Command.DEACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/deactivate') + commands[Command.GET_ACTIVE_IME_ENGINE] = ( 'GET', '/session/$sessionId/ime/active_engine', ) diff --git a/appium/webdriver/extensions/keyboard.py b/appium/webdriver/extensions/keyboard.py index 0cce8c66e..07a323ba2 100644 --- a/appium/webdriver/extensions/keyboard.py +++ b/appium/webdriver/extensions/keyboard.py @@ -12,23 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Dict, Optional -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Keyboard']) - -class Keyboard(webdriver.Remote): +class Keyboard(CanExecuteCommands): def hide_keyboard( - self: T, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None - ) -> T: + self, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None + ) -> 'WebDriver': """Hides the software keyboard on the device. In iOS, use `key_name` to press @@ -51,9 +48,9 @@ def hide_keyboard( strategy = 'tapOutside' data['strategy'] = strategy self.execute(Command.HIDE_KEYBOARD, data) - return self + return self # type: ignore - def is_keyboard_shown(self: T) -> bool: + def is_keyboard_shown(self) -> bool: """Attempts to detect whether a software keyboard is present Returns: @@ -61,7 +58,7 @@ def is_keyboard_shown(self: T) -> bool: """ return self.execute(Command.IS_KEYBOARD_SHOWN)['value'] - def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: + def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver': """Sends a keycode to the device. Android only. @@ -80,9 +77,9 @@ def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: if metastate is not None: data['metastate'] = metastate self.execute(Command.KEY_EVENT, data) - return self + return self # type: ignore - def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: + def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> 'WebDriver': """Sends a keycode to the device. Android only. Possible keycodes can be found @@ -104,9 +101,11 @@ def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: if flags is not None: data['flags'] = flags self.execute(Command.PRESS_KEYCODE, data) - return self + return self # type: ignore - def long_press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: + def long_press_keycode( + self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None + ) -> 'WebDriver': """Sends a long press of keycode to the device. Android only. Possible keycodes can be found in @@ -126,25 +125,25 @@ def long_press_keycode(self: T, keycode: int, metastate: Optional[int] = None, f if flags is not None: data['flags'] = flags self.execute(Command.LONG_PRESS_KEYCODE, data) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.HIDE_KEYBOARD] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.HIDE_KEYBOARD] = ( 'POST', '/session/$sessionId/appium/device/hide_keyboard', ) - self.command_executor._commands[Command.IS_KEYBOARD_SHOWN] = ( + commands[Command.IS_KEYBOARD_SHOWN] = ( 'GET', '/session/$sessionId/appium/device/is_keyboard_shown', ) - self.command_executor._commands[Command.KEY_EVENT] = ('POST', '/session/$sessionId/appium/device/keyevent') - self.command_executor._commands[Command.PRESS_KEYCODE] = ( + commands[Command.KEY_EVENT] = ('POST', '/session/$sessionId/appium/device/keyevent') + commands[Command.PRESS_KEYCODE] = ( 'POST', '/session/$sessionId/appium/device/press_keycode', ) - self.command_executor._commands[Command.LONG_PRESS_KEYCODE] = ( + commands[Command.LONG_PRESS_KEYCODE] = ( 'POST', '/session/$sessionId/appium/device/long_press_keycode', ) diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 68d451a61..2f7e312e5 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, TypeVar, Union +from typing import TYPE_CHECKING, Dict, Union -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command @@ -22,11 +22,9 @@ # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Location']) - -class Location(webdriver.Remote): - def toggle_location_services(self: T) -> T: +class Location(CanExecuteCommands): + def toggle_location_services(self) -> 'WebDriver': """Toggle the location services on the device. Android only. @@ -35,16 +33,16 @@ def toggle_location_services(self: T) -> T: Union['WebDriver', 'Location']: Self instance """ self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) - return self + return self # type: ignore def set_location( - self: T, + self, latitude: Union[float, str], longitude: Union[float, str], altitude: Union[float, str] = None, speed: Union[float, str] = None, satellites: Union[float, str] = None, - ) -> T: + ) -> 'WebDriver': """Set the location of the device Args: @@ -70,10 +68,10 @@ def set_location( if satellites is not None: data['location']['satellites'] = satellites self.execute(Command.SET_LOCATION, data) - return self + return self # type: ignore @property - def location(self: T) -> Dict[str, float]: + def location(self) -> Dict[str, float]: """Retrieves the current location Returns: @@ -84,12 +82,12 @@ def location(self: T) -> Dict[str, float]: """ return self.execute(Command.GET_LOCATION)['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.TOGGLE_LOCATION_SERVICES] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.TOGGLE_LOCATION_SERVICES] = ( 'POST', '/session/$sessionId/appium/device/toggle_location_services', ) - self.command_executor._commands[Command.GET_LOCATION] = ('GET', '/session/$sessionId/location') - self.command_executor._commands[Command.SET_LOCATION] = ('POST', '/session/$sessionId/location') + commands[Command.GET_LOCATION] = ('GET', '/session/$sessionId/location') + commands[Command.SET_LOCATION] = ('POST', '/session/$sessionId/location') diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index b9f766924..50cdcb32d 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -12,21 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, List, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Union -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'LogEvent']) - -class LogEvent(webdriver.Remote): - def get_events(self: T, type: List[str] = None) -> Dict[str, Union[str, int]]: +class LogEvent(CanExecuteCommands): + 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) @@ -49,7 +46,7 @@ def get_events(self: T, type: List[str] = None) -> Dict[str, Union[str, int]]: data['type'] = type return self.execute(Command.GET_EVENTS, data)['value'] - def log_event(self: T, vendor: str, event: str) -> T: + def log_event(self, vendor: str, event: str) -> 'WebDriver': """Log a custom event on the Appium server. (Since Appium 1.16.0) @@ -65,10 +62,10 @@ def log_event(self: T, vendor: str, event: str) -> T: """ data = {'vendor': vendor, 'event': event} self.execute(Command.LOG_EVENT, data) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_EVENTS] = ('POST', '/session/$sessionId/appium/events') - self.command_executor._commands[Command.LOG_EVENT] = ('POST', '/session/$sessionId/appium/log_event') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_EVENTS] = ('POST', '/session/$sessionId/appium/events') + commands[Command.LOG_EVENT] = ('POST', '/session/$sessionId/appium/log_event') diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index fd5be3f3d..4d273c67f 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,22 +13,20 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Optional -from selenium import webdriver from selenium.common.exceptions import InvalidArgumentException +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands + from ..mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'RemoteFS']) - -class RemoteFS(webdriver.Remote): - def pull_file(self: T, path: str) -> str: +class RemoteFS(CanExecuteCommands): + def pull_file(self, path: str) -> str: """Retrieves the file at `path`. Args: @@ -42,7 +40,7 @@ def pull_file(self: T, path: str) -> str: } return self.execute(Command.PULL_FILE, data)['value'] - def pull_folder(self: T, path: str) -> str: + def pull_folder(self, path: str) -> str: """Retrieves a folder at `path`. Args: @@ -57,8 +55,8 @@ def pull_folder(self: T, path: str) -> str: return self.execute(Command.PULL_FOLDER, data)['value'] def push_file( - self: T, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None - ) -> T: + self, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None + ) -> 'WebDriver': """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` @@ -89,11 +87,11 @@ def push_file( 'data': base64data, } self.execute(Command.PUSH_FILE, data) - return self - - # pylint: disable=protected-access - # noinspection PyProtectedMember - 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] = ('POST', '/session/$sessionId/appium/device/pull_folder') - self.command_executor._commands[Command.PUSH_FILE] = ('POST', '/session/$sessionId/appium/device/push_file') + return self # type: ignore + + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.PULL_FILE] = ('POST', '/session/$sessionId/appium/device/pull_file') + commands[Command.PULL_FOLDER] = ('POST', '/session/$sessionId/appium/device/pull_folder') + commands[Command.PUSH_FILE] = ('POST', '/session/$sessionId/appium/device/push_file') diff --git a/appium/webdriver/extensions/screen_record.py b/appium/webdriver/extensions/screen_record.py index 3ab5e4c39..53c97811f 100644 --- a/appium/webdriver/extensions/screen_record.py +++ b/appium/webdriver/extensions/screen_record.py @@ -12,21 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, TypeVar, Union +from typing import Any, Union -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'ScreenRecord']) - - -class ScreenRecord(webdriver.Remote): - def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: +class ScreenRecord(CanExecuteCommands): + def start_recording_screen(self, **options: Any) -> Union[bytes, str]: """Start asynchronous screen recording process. +--------------+-----+---------+-----+-------+ @@ -167,7 +161,7 @@ def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: del options['password'] return self.execute(Command.START_RECORDING_SCREEN, {'options': options})['value'] - def stop_recording_screen(self: T, **options: Any) -> bytes: + def stop_recording_screen(self, **options: Any) -> bytes: """Gather the output from the previously started screen recording to a media file. Keyword Args: @@ -200,14 +194,14 @@ def stop_recording_screen(self: T, **options: Any) -> bytes: del options['password'] return self.execute(Command.STOP_RECORDING_SCREEN, {'options': options})['value'] - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.START_RECORDING_SCREEN] = ( + def _add_commands(self) -> None: + # noinspection PyProtectedMember + commands = self.command_executor._commands + commands[Command.START_RECORDING_SCREEN] = ( 'POST', '/session/$sessionId/appium/start_recording_screen', ) - self.command_executor._commands[Command.STOP_RECORDING_SCREEN] = ( + commands[Command.STOP_RECORDING_SCREEN] = ( 'POST', '/session/$sessionId/appium/stop_recording_screen', ) diff --git a/appium/webdriver/extensions/search_context/__init__.py b/appium/webdriver/extensions/search_context/__init__.py index 2279aae62..ed9ad3ece 100644 --- a/appium/webdriver/extensions/search_context/__init__.py +++ b/appium/webdriver/extensions/search_context/__init__.py @@ -12,11 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - -from selenium import webdriver -from selenium.webdriver.remote.webelement import WebElement as SeleniumWebElement - from .android import AndroidSearchContext from .custom import CustomSearchContext from .ios import iOSSearchContext @@ -25,18 +20,16 @@ class AppiumSearchContext( - webdriver.Remote, AndroidSearchContext, CustomSearchContext, iOSSearchContext, MobileSearchContext, WindowsSearchContext, ): - """Returns appium driver search conext""" + """Returns appium driver search context""" class AppiumWebElementSearchContext( - SeleniumWebElement, AndroidSearchContext, CustomSearchContext, iOSSearchContext, diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py index 94192ea0b..7aea8219a 100644 --- a/appium/webdriver/extensions/search_context/android.py +++ b/appium/webdriver/extensions/search_context/android.py @@ -12,27 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - import json -from typing import TYPE_CHECKING, Any, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, List, Optional from appium.common.logger import logger +from appium.protocols.webdriver.can_find_elements import CanFindElements from appium.webdriver.common.appiumby import AppiumBy -from .base_search_context import BaseSearchContext - if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound='AndroidSearchContext') - -class AndroidSearchContext(BaseSearchContext): +# noinspection PyPep8Naming +class AndroidSearchContext(CanFindElements): """Define search context for Android""" def find_element_by_android_view_matcher( - self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None + self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> 'WebElement': """ deprecated:: 2.1.0 @@ -59,7 +55,9 @@ def find_element_by_android_view_matcher( 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') + driver.find_element_by_android_view_matcher( + name='withText', args=['Accessibility'], className='ViewMatchers' + ) """ logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEW_MATCHER' instead.") @@ -69,7 +67,7 @@ def find_element_by_android_view_matcher( ) def find_element_by_android_data_matcher( - self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None + self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> 'WebElement': """ deprecated:: 2.1.0 @@ -107,7 +105,7 @@ def find_element_by_android_data_matcher( ) def find_elements_by_android_data_matcher( - self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None + self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> List['WebElement']: """ deprecated:: 2.1.0 @@ -141,7 +139,7 @@ def find_elements_by_android_data_matcher( ) def _build_data_matcher( - self: T, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None + self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None ) -> str: result = {} @@ -151,7 +149,7 @@ def _build_data_matcher( return json.dumps(result) - def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement': + def find_element_by_android_uiautomator(self, uia_string: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. @@ -172,7 +170,7 @@ def find_element_by_android_uiautomator(self: T, uia_string: str) -> 'WebElement return self.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebElement']: + def find_elements_by_android_uiautomator(self, uia_string: str) -> List['WebElement']: """ [Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. @@ -192,7 +190,7 @@ def find_elements_by_android_uiautomator(self: T, uia_string: str) -> List['WebE return self.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) - def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': + def find_element_by_android_viewtag(self, tag: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead. @@ -215,7 +213,7 @@ def find_element_by_android_viewtag(self: T, tag: str) -> 'WebElement': return self.find_element(by=AppiumBy.ANDROID_VIEWTAG, value=tag) - def find_elements_by_android_viewtag(self: T, tag: str) -> List['WebElement']: + def find_elements_by_android_viewtag(self, tag: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.ANDROID_VIEWTAG' instead. diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py index 623769588..93ba82d37 100644 --- a/appium/webdriver/extensions/search_context/custom.py +++ b/appium/webdriver/extensions/search_context/custom.py @@ -12,25 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import TYPE_CHECKING, List from appium.common.logger import logger +from appium.protocols.webdriver.can_find_elements import CanFindElements from appium.webdriver.common.appiumby import AppiumBy -from .base_search_context import BaseSearchContext - if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=Union[BaseSearchContext, 'CustomSearchContext']) - -class CustomSearchContext(BaseSearchContext): +class CustomSearchContext(CanFindElements): """Define search context for custom plugin""" - def find_element_by_custom(self: T, selector: str) -> 'WebElement': + def find_element_by_custom(self, selector: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.CUSTOM' instead. @@ -55,7 +50,7 @@ def find_element_by_custom(self: T, selector: str) -> 'WebElement': return self.find_element(by=AppiumBy.CUSTOM, value=selector) - def find_elements_by_custom(self: T, selector: str) -> List['WebElement']: + def find_elements_by_custom(self, selector: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.CUSTOM' instead. diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py index fddfff876..eef6c4e2b 100644 --- a/appium/webdriver/extensions/search_context/ios.py +++ b/appium/webdriver/extensions/search_context/ios.py @@ -12,25 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import TYPE_CHECKING, List from appium.common.logger import logger +from appium.protocols.webdriver.can_find_elements import CanFindElements from appium.webdriver.common.appiumby import AppiumBy -from .base_search_context import BaseSearchContext - if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=Union[BaseSearchContext, 'iOSSearchContext']) - -class iOSSearchContext(BaseSearchContext): +class iOSSearchContext(CanFindElements): """Define search context for iOS""" - def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': + def find_element_by_ios_uiautomation(self, uia_string: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.IOS_UIAUTOMATION' instead. @@ -52,7 +47,7 @@ def find_element_by_ios_uiautomation(self: T, uia_string: str) -> 'WebElement': return self.find_element(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) - def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElement']: + def find_elements_by_ios_uiautomation(self, uia_string: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.IOS_UIAUTOMATION' instead. @@ -74,7 +69,7 @@ def find_elements_by_ios_uiautomation(self: T, uia_string: str) -> List['WebElem return self.find_elements(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) - def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement': + def find_element_by_ios_predicate(self, predicate_string: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.IOS_PREDICATE' instead. @@ -96,7 +91,7 @@ def find_element_by_ios_predicate(self: T, predicate_string: str) -> 'WebElement return self.find_element(by=AppiumBy.IOS_PREDICATE, value=predicate_string) - def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebElement']: + def find_elements_by_ios_predicate(self, predicate_string: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.IOS_PREDICATE' instead. @@ -117,7 +112,7 @@ def find_elements_by_ios_predicate(self: T, predicate_string: str) -> List['WebE return self.find_elements(by=AppiumBy.IOS_PREDICATE, value=predicate_string) - def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebElement': + def find_element_by_ios_class_chain(self, class_chain_string: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.IOS_CLASS_CHAIN' instead. @@ -138,7 +133,7 @@ def find_element_by_ios_class_chain(self: T, class_chain_string: str) -> 'WebEle return self.find_element(by=AppiumBy.IOS_CLASS_CHAIN, value=class_chain_string) - def find_elements_by_ios_class_chain(self: T, class_chain_string: str) -> List['WebElement']: + def find_elements_by_ios_class_chain(self, class_chain_string: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.IOS_CLASS_CHAIN' instead. diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py index a3070f555..3518df2f6 100644 --- a/appium/webdriver/extensions/search_context/mobile.py +++ b/appium/webdriver/extensions/search_context/mobile.py @@ -12,26 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - import base64 -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import TYPE_CHECKING, List from appium.common.logger import logger +from appium.protocols.webdriver.can_find_elements import CanFindElements from appium.webdriver.common.appiumby import AppiumBy -from .base_search_context import BaseSearchContext - if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=Union[BaseSearchContext, 'MobileSearchContext']) - -class MobileSearchContext(BaseSearchContext): +class MobileSearchContext(CanFindElements): """Define search context for Mobile(Android, iOS)""" - def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElement': + def find_element_by_accessibility_id(self, accessibility_id: str) -> 'WebElement': """ deprecated:: 2.1.0 Please use 'find_element' with 'AppiumBy.ACCESSIBILITY_ID' instead. @@ -54,7 +49,7 @@ def find_element_by_accessibility_id(self: T, accessibility_id: str) -> 'WebElem return self.find_element(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['WebElement']: + def find_elements_by_accessibility_id(self, accessibility_id: str) -> List['WebElement']: """ deprecated:: 2.1.0 Please use 'find_elements' with 'AppiumBy.ACCESSIBILITY_ID' instead. @@ -77,7 +72,7 @@ def find_elements_by_accessibility_id(self: T, accessibility_id: str) -> List['W return self.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) - def find_element_by_image(self: T, img_path: str) -> 'WebElement': + def find_element_by_image(self, img_path: str) -> 'WebElement': """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. @@ -94,7 +89,7 @@ def find_element_by_image(self: T, img_path: str) -> 'WebElement': return self.find_element(by=AppiumBy.IMAGE, value=b64_data) - def find_elements_by_image(self: T, img_path: str) -> List['WebElement']: + def find_elements_by_image(self, img_path: str) -> List['WebElement']: """Finds a portion of a screenshot by an image. Uses driver.find_image_occurrence under the hood. Note that this will diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py index 7608c026b..016f28c29 100644 --- a/appium/webdriver/extensions/search_context/windows.py +++ b/appium/webdriver/extensions/search_context/windows.py @@ -12,25 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=abstract-method - -from typing import TYPE_CHECKING, List, TypeVar, Union +from typing import TYPE_CHECKING, List from appium.common.logger import logger +from appium.protocols.webdriver.can_find_elements import CanFindElements from appium.webdriver.common.appiumby import AppiumBy -from .base_search_context import BaseSearchContext - if TYPE_CHECKING: from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=Union[BaseSearchContext, 'WindowsSearchContext']) - -class WindowsSearchContext(BaseSearchContext): +class WindowsSearchContext(CanFindElements): """Define search context for Windows""" - def find_element_by_windows_uiautomation(self: T, win_uiautomation: str) -> 'WebElement': + def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> 'WebElement': """[Deprecated] Finds an element by windows uiautomation Args: @@ -48,7 +43,7 @@ def find_element_by_windows_uiautomation(self: T, win_uiautomation: str) -> 'Web ) return self.find_element(by=AppiumBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) - def find_elements_by_windows_uiautomation(self: T, win_uiautomation: str) -> List['WebElement']: + def find_elements_by_windows_uiautomation(self, win_uiautomation: str) -> List['WebElement']: """[Deprecated] Finds elements by windows uiautomation Args: diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index 48d5a3c86..e632f47ac 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -12,24 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, List, TypeVar, Union - -from selenium import webdriver +from typing import Any, Dict, List from appium.common.logger import logger +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver - -T = TypeVar('T', bound=Union['WebDriver', 'Session']) - -class Session(webdriver.Remote): +class Session(CanExecuteCommands): @property - def session(self: T) -> Dict[str, Any]: + def session(self) -> Dict[str, Any]: """Retrieves session information from the current session Usage: @@ -41,7 +34,7 @@ def session(self: T) -> Dict[str, Any]: return self.execute(Command.GET_SESSION)['value'] @property - def all_sessions(self: T) -> List[Dict[str, Any]]: + def all_sessions(self) -> List[Dict[str, Any]]: """Retrieves all sessions that are open Usage: @@ -53,7 +46,7 @@ def all_sessions(self: T) -> List[Dict[str, Any]]: return self.execute(Command.GET_ALL_SESSIONS)['value'] @property - def events(self: T) -> Dict: + def events(self) -> Dict: """Retrieves events information from the current session Usage: @@ -69,8 +62,8 @@ def events(self: T) -> Dict: logger.warning('Could not find events information in the session. Error: %s', e) return {} - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_SESSION] = ('GET', '/session/$sessionId') - self.command_executor._commands[Command.GET_ALL_SESSIONS] = ('GET', '/sessions') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_SESSION] = ('GET', '/session/$sessionId') + commands[Command.GET_ALL_SESSIONS] = ('GET', '/sessions') diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index 2e2ea0301..5815ab7a8 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,21 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict -from selenium import webdriver +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command if TYPE_CHECKING: - # noinspection PyUnresolvedReferences from appium.webdriver.webdriver import WebDriver -T = TypeVar('T', bound=Union['WebDriver', 'Settings']) - -class Settings(webdriver.Remote): - def get_settings(self: T) -> Dict[str, Any]: +class Settings(CanExecuteCommands): + 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 @@ -37,7 +34,7 @@ def get_settings(self: T) -> Dict[str, Any]: """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self: T, settings: Dict[str, Any]) -> T: + def update_settings(self, settings: Dict[str, Any]) -> 'WebDriver': """Set settings for the current session. For more on settings, see: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md @@ -48,10 +45,10 @@ def update_settings(self: T, settings: Dict[str, Any]) -> T: data = {"settings": settings} self.execute(Command.UPDATE_SETTINGS, data) - return self + return self # type: ignore - # pylint: disable=protected-access - # noinspection PyProtectedMember - def _addCommands(self) -> None: - self.command_executor._commands[Command.GET_SETTINGS] = ('GET', '/session/$sessionId/appium/settings') - self.command_executor._commands[Command.UPDATE_SETTINGS] = ('POST', '/session/$sessionId/appium/settings') + def _add_commands(self) -> None: + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + commands[Command.GET_SETTINGS] = ('GET', '/session/$sessionId/appium/settings') + commands[Command.UPDATE_SETTINGS] = ('POST', '/session/$sessionId/appium/settings') diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index 1502c3080..64b3b40c7 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,17 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, TypeVar +from typing import TYPE_CHECKING, Optional from selenium.webdriver.remote.switch_to import SwitchTo +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands + from .mobilecommand import MobileCommand -T = TypeVar('T', bound='MobileSwitchTo') +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class MobileSwitchTo(SwitchTo): - def context(self, context_name: Optional[str]) -> T: + _driver: CanExecuteCommands + + def context(self, context_name: Optional[str]) -> 'WebDriver': """Sets the context for the current session. Passing `None` is equal to switching to native context. @@ -33,4 +38,5 @@ def context(self, context_name: Optional[str]) -> T: driver.switch_to.context('WEBVIEW_1') """ self._driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {'name': context_name}) + # noinspection PyTypeChecker return self diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 9890ce622..0e137f3e7 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -15,8 +15,9 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use import copy -from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from selenium import webdriver from selenium.common.exceptions import InvalidArgumentException from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand @@ -109,9 +110,6 @@ def _make_w3c_caps(caps: Dict) -> Dict[str, Union[Dict[str, Any], List[Dict[str, return {'alwaysMatch': always_match, 'firstMatch': [{}]} -T = TypeVar('T', bound='WebDriver') - - class ExtensionBase: """ Used to define an extension command as driver's methods. @@ -223,6 +221,7 @@ def add_command(self) -> Tuple[str, str]: class WebDriver( + webdriver.Remote, AppiumSearchContext, ActionHelpers, Activities, @@ -259,14 +258,16 @@ def __init__( proxy: str = None, keep_alive: bool = True, direct_connection: bool = True, - extensions: List[T] = [], + extensions: Optional[List['WebDriver']] = None, strict_ssl: bool = True, ): if strict_ssl is False: # pylint: disable=E1101 + # noinspection PyPackageRequirements import urllib3 + # noinspection PyUnresolvedReferences AppiumConnection.set_certificate_bundle_path(None) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) @@ -275,7 +276,7 @@ def __init__( ) if hasattr(self, 'command_executor'): - self._addCommands() + self._add_commands() self.error_handler = MobileErrorHandler() @@ -293,7 +294,7 @@ def __init__( By.IMAGE = AppiumBy.IMAGE By.CUSTOM = AppiumBy.CUSTOM - self._extensions = extensions + self._extensions = extensions or [] for extension in self._extensions: instance = extension(self.execute) method_name = instance.method_name() @@ -303,6 +304,7 @@ def __init__( # add a new method named 'instance.method_name()' and call it setattr(WebDriver, method_name, getattr(instance, method_name)) method, url_cmd = instance.add_command() + # noinspection PyProtectedMember self.command_executor._commands[method_name] = (method.upper(), url_cmd) # type: ignore def delete_extensions(self) -> None: @@ -320,10 +322,11 @@ def _update_command_executor(self, keep_alive: bool) -> None: direct_port = 'directConnectPort' direct_path = 'directConnectPath' + assert self.caps, 'Driver capabilities must be defined' if not {direct_protocol, direct_host, direct_port, direct_path}.issubset(set(self.caps)): message = 'Direct connect capabilities from server were:\n' for key in [direct_protocol, direct_host, direct_port, direct_path]: - message += '{}: \'{}\' '.format(key, self.caps.get(key, '')) + message += f'{key}: \'{self.caps.get(key, "")}\' ' logger.debug(message) return @@ -336,17 +339,19 @@ def _update_command_executor(self, keep_alive: bool) -> None: logger.debug('Updated request endpoint to %s', executor) # Override command executor self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) - self._addCommands() + self._add_commands() # https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277 + # noinspection PyAttributeOutsideInit def start_session(self, capabilities: Dict, browser_profile: Optional[str] = None) -> None: """Creates a new session with the desired capabilities. Override for Appium Args: - capabilities: Capabilities which have following keys like 'automation_name', 'platform_name', 'platform_version', 'app'. - Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md for more details. + capabilities: Capabilities which have following keys like 'automation_name', 'platform_name', + 'platform_version', 'app'. + Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md for more details. browser_profile: Browser profile """ if not isinstance(capabilities, dict): @@ -372,6 +377,7 @@ def start_session(self, capabilities: Dict, browser_profile: Optional[str] = Non if self.caps is None: self.caps = response.get('capabilities') + # noinspection PyMethodMayBeStatic def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: """Manage capabilities whether W3C format or MJSONWP format""" w3c_caps = _make_w3c_caps(capabilities) @@ -455,7 +461,7 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """ return MobileWebElement(self, element_id) - def set_value(self, element: MobileWebElement, value: str) -> T: + def set_value(self, element: MobileWebElement, value: str) -> 'WebDriver': """Set the value on an element in the application. Args: @@ -485,37 +491,38 @@ def switch_to(self) -> MobileSwitchTo: return MobileSwitchTo(self) - # pylint: disable=protected-access - - def _addCommands(self) -> None: + def _add_commands(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 for mixin_class in filter(lambda x: not issubclass(x, WebDriver), self.__class__.__mro__): - if hasattr(mixin_class, self._addCommands.__name__): - get_atter = getattr(mixin_class, self._addCommands.__name__, None) + if hasattr(mixin_class, self._add_commands.__name__): + get_atter = getattr(mixin_class, self._add_commands.__name__, None) if get_atter: get_atter(self) - self.command_executor._commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') - self.command_executor._commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') - self.command_executor._commands[Command.SET_IMMEDIATE_VALUE] = ( + # noinspection PyProtectedMember,PyUnresolvedReferences + commands = self.command_executor._commands + + commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') + commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') + commands[Command.SET_IMMEDIATE_VALUE] = ( 'POST', '/session/$sessionId/appium/element/$id/value', ) # TODO Move commands for element to webelement - self.command_executor._commands[Command.REPLACE_KEYS] = ( + commands[Command.REPLACE_KEYS] = ( 'POST', '/session/$sessionId/appium/element/$id/replace_value', ) - self.command_executor._commands[Command.CLEAR] = ('POST', '/session/$sessionId/element/$id/clear') - self.command_executor._commands[Command.LOCATION_IN_VIEW] = ( + commands[Command.CLEAR] = ('POST', '/session/$sessionId/element/$id/clear') + commands[Command.LOCATION_IN_VIEW] = ( 'GET', '/session/$sessionId/element/$id/location_in_view', ) # override for Appium 1.x # Appium 2.0 and Appium 1.22 work with `/se/log` and `/se/log/types` - self.command_executor._commands[Command.GET_LOG] = ('POST', '/session/$sessionId/log') - self.command_executor._commands[Command.GET_AVAILABLE_LOG_TYPES] = ('GET', '/session/$sessionId/log/types') + commands[Command.GET_LOG] = ('POST', '/session/$sessionId/log') + commands[Command.GET_AVAILABLE_LOG_TYPES] = ('GET', '/session/$sessionId/log/types') diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 3f5f3a7c1..e8fc9073d 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -12,20 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, Optional, TypeVar, Union +from typing import Callable, Dict, List, Optional, Union from selenium.webdriver.common.utils import keys_to_typing from selenium.webdriver.remote.command import Command as RemoteCommand +from selenium.webdriver.remote.webelement import WebElement as SeleniumWebElement from appium.webdriver.common.appiumby import AppiumBy from .extensions.search_context import AppiumWebElementSearchContext from .mobilecommand import MobileCommand as Command -T = TypeVar('T', bound='WebElement') +class WebElement(SeleniumWebElement, AppiumWebElementSearchContext): + _execute: Callable + _id: str -class WebElement(AppiumWebElementSearchContext): def get_attribute(self, name: str) -> Optional[Union[str, Dict]]: """Gets the given attribute or property of the element. @@ -54,22 +56,22 @@ def get_attribute(self, name: str) -> Optional[Union[str, Dict]]: """ resp = self._execute(RemoteCommand.GET_ELEMENT_ATTRIBUTE, {'name': name}) - attributeValue = resp.get('value') + attribute_value = resp.get('value') - if attributeValue is None: + if attribute_value is None: return None - if isinstance(attributeValue, dict): - return attributeValue + if isinstance(attribute_value, dict): + return attribute_value # Convert to str along to the spec - if not isinstance(attributeValue, str): - attributeValue = str(attributeValue) + if not isinstance(attribute_value, str): + attribute_value = str(attribute_value) - if name != 'value' and attributeValue.lower() in ('true', 'false'): - return attributeValue.lower() + if name != 'value' and attribute_value.lower() in ('true', 'false'): + return attribute_value.lower() - return attributeValue + return attribute_value def is_displayed(self) -> bool: """Whether the element is visible to a user. @@ -78,7 +80,7 @@ def is_displayed(self) -> bool: """ return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> T: + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> 'WebElement': """Find an element given a AppiumBy strategy and locator Override for Appium @@ -110,7 +112,7 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> List[T]: + def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> List['WebElement']: """Find elements given a AppiumBy strategy and locator Args: @@ -138,7 +140,7 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) - return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] - def clear(self) -> T: + def clear(self) -> 'WebElement': """Clears text. Override for Appium @@ -150,7 +152,7 @@ def clear(self) -> T: self._execute(Command.CLEAR, data) return self - def set_text(self, keys: str = '') -> T: + def set_text(self, keys: str = '') -> 'WebElement': """Sends text to the element. Previous text is removed. @@ -183,7 +185,7 @@ def location_in_view(self) -> Dict[str, int]: """ return self._execute(Command.LOCATION_IN_VIEW)['value'] - def set_value(self, value: str) -> T: + def set_value(self, value: str) -> 'WebElement': """Set the value on this element in the application Args: @@ -200,7 +202,7 @@ def set_value(self, value: str) -> T: return self # Override - def send_keys(self, *value: str) -> T: + def send_keys(self, *value: str) -> 'WebElement': """Simulates typing into the element. Args: diff --git a/test/functional/ios/keyboard_tests.py b/test/functional/ios/keyboard_tests.py index ba0bd05d4..5397e2cec 100644 --- a/test/functional/ios/keyboard_tests.py +++ b/test/functional/ios/keyboard_tests.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from time import sleep from typing import TYPE_CHECKING import pytest diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index a62cf2852..ffc910189 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -19,7 +19,7 @@ from .helper.desired_capabilities import get_desired_capabilities -class TestSafari(object): +class TestSafari: def setup_method(self) -> None: desired_caps = get_desired_capabilities() desired_caps.update({'browserName': 'safari', 'nativeWebTap': True, 'safariIgnoreFraudWarning': True}) From 71410bab56522ae3ca36ee7fcb6dee349d6bc65a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 10:23:21 -0800 Subject: [PATCH 049/548] chore(deps-dev): update pre-commit requirement from ~=2.16 to ~=2.17 (#678) 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/v2.16.0...v2.17.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 335f4bf2d..5b194d3c8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.16" +pre-commit = "~=2.17" [packages] selenium = "~=4.0" From 588f83f28007d58e03c17759a5a5b807f4a08ae8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 09:41:36 -0800 Subject: [PATCH 050/548] chore(deps): update pytest requirement from ~=6.2 to ~=7.0 (#682) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...7.0.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5b194d3c8..04d0e793c 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ selenium = "~=4.0" black = "==21.12b0" -pytest = "~=6.2" +pytest = "~=7.0" pytest-cov = "~=3.0" tox = "~=3.24" From 7a7be33827f8a92c3efbd9003c537ae259ad59cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:51:23 -0800 Subject: [PATCH 051/548] chore(deps): bump black from 21.12b0 to 22.1.0 (#681) Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/22.1.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 04d0e793c..d396ddb21 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.17" [packages] selenium = "~=4.0" -black = "==21.12b0" +black = "==22.1.0" pytest = "~=7.0" pytest-cov = "~=3.0" From 2c04e4ce59ed04b91088ca55d1d3698653de6ebc Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 26 Feb 2022 15:40:57 +0900 Subject: [PATCH 052/548] chore: restrict selenium client version (#686) --- Pipfile | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index d396ddb21..c9880276d 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.17" [packages] -selenium = "~=4.0" +selenium = "~=4.0.0" black = "==22.1.0" diff --git a/setup.py b/setup.py index c822c44f5..e215ef5fc 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium ~= 4.0'], + install_requires=['selenium ~= 4.0.0'], ) From 50408753532de6d085ed08c0dbd3bf8dc9185ef0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 25 Feb 2022 22:41:42 -0800 Subject: [PATCH 053/548] Bump 2.1.3 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index c07bdebc4..7b8301ffa 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.1.2' +version = '2.1.3' From 9597581e3d85616400be36f212287ff655b3b730 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 25 Feb 2022 22:41:46 -0800 Subject: [PATCH 054/548] Update changelog for 2.1.3 --- CHANGELOG.rst | 2860 ------------------------------------------------- 1 file changed, 2860 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4fe6ab2c3..e69de29bb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,2860 +0,0 @@ -Changelog -========= - - -v2.1.2 (2021-12-30) -------------------- - -Fix -~~~ -- Default duration in tap (#673) [Kazuaki Matsuo] - -Other -~~~~~ -- Bump 2.1.2. [Kazuaki Matsuo] -- Update changelog for 2.1.1. [Kazuaki Matsuo] - - -v2.1.1 (2021-12-24) -------------------- - -New -~~~ -- Feat: use 'touch' pointer action (#670) [Kazuaki Matsuo] - - * chore: specify touch - - * comment out touch in drag_and_drop - - * fix mypy - - * add desctiption of touch action - -Test -~~~~ -- Ci: remove ==2021.5.29 (#653) [Kazuaki Matsuo] - - * ci: remove ==2021.5.29 - - * bump black - -Other -~~~~~ -- Bump 2.1.1. [Kazuaki Matsuo] -- Chore(deps): bump black from 21.11b1 to 21.12b0 (#664) - [dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/commits) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 - (#663) [dependabot[bot]] - - 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/v2.15.0...v2.16.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) - - --- - updated-dependencies: - - dependency-name: astroid - dependency-type: direct:production - ... -- Update changelog for 2.1.0. [Kazuaki Matsuo] - - -v2.1.0 (2021-11-27) -------------------- - -New -~~~ -- Feat: add AppiumBy instead of MobileBy (#659) [Kazuaki Matsuo] - - * feat: add AppiumBy instead of MobileBy - - * add class description - - * use deprecated:: - -Other -~~~~~ -- Bump 2.1.0. [Kazuaki Matsuo] -- Chore: add deprecated mark for find_element_by* (#657) [Kazuaki - Matsuo] -- Chore: relax selenium version control (#656) [Kazuaki Matsuo] -- Chore: tweak keyword in metadata. [Kazuaki Matsuo] -- Update changelog for 2.0.0. [Kazuaki Matsuo] - - -v2.0.0 (2021-11-09) -------------------- - -New -~~~ -- Feat: Change base selenium client version to selenium 4 (#636) - [Kazuaki Matsuo] - - - Changed base selenium client version to v4 - - No longer forceMjsonwp works - - Add strict_ssl option in webdriver.Remote - -Test -~~~~ -- Ci: set pipenv==2021.5.29 to prevent dependencies error (#651) - [Kazuaki Matsuo] - - * ci: add --pre - - * specify pipenv as same as the previous ok case - - * set 2021.5.29 in tox as well - -Other -~~~~~ -- Bump 2.0.0. [Kazuaki Matsuo] -- Docs: update readme. [Kazuaki Matsuo] -- Chore: add Python 3.9 as metadata. [Kazuaki Matsuo] -- Chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) - [dependabot[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) - - --- - updated-dependencies: - - dependency-name: isort - dependency-type: direct:production - ... -- Update changelog for 2.0.0.rc6. [Kazuaki Matsuo] -- Bump 2.0.0.rc6. [Kazuaki Matsuo] -- Docs: update readme. [Kazuaki Matsuo] -- Chore: adding deprecation mark in touch actions and multi touch (#648) - [Kazuaki Matsuo] - - * chore: add deprecation mark in touch actions and multi touch - - * chore: add deprecated mark in MultiAction class - - * docs: update readme -- Chore: deprecate -windows uiautomation (#649) [Kazuaki Matsuo] - - * chore: add Deprecated for -windows uiautomation - - * chore: add logger -- Update changelog for 2.0.0.rc5. [Kazuaki Matsuo] -- Bump 2.0.0.rc5. [Kazuaki Matsuo] -- Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 - (#603) [Kazuaki Matsuo, dependabot[bot]] - - Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) -- Update gitchangelog once. [Kazuaki Matsuo] -- Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 - (#637) [Kazuaki Matsuo, dependabot[bot]] - - Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. - - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) - - --- - updated-dependencies: - - dependency-name: sphinx-rtd-theme - dependency-type: direct:production - ... -- Chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) - [Kazuaki Matsuo] - - chore: cleanup no longer needed code in w3c, bump dev Pipfile -- Chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) - [dependabot[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.12.0...v3.0.0) - - --- - updated-dependencies: - - dependency-name: pytest-cov - dependency-type: direct:production - ... -- Update changelog for 1.3.0. [Kazuaki Matsuo] - - -v1.3.0 (2021-09-27) -------------------- - -New -~~~ -- Feat: do not raise an error in case method is already defined (#632) - [Kazuaki Matsuo] -- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] - - * feat: add satellites in set_location - - * fix review -- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] - - * chore: add placeholder - - * move to extention way - - * revert pytest - - * add todo - - * call method_name instead of wrapper - - * remove types - - * rename a method - - * add examples - - * add types-python-dateutil as error message - - * add example more - - * tweak naming - - * Explicit Dict - -Other -~~~~~ -- Bump 1.3.0. [Kazuaki Matsuo] -- Chore(deps): update types-python-dateutil requirement (#633) - [dependabot[bot]] - - Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. - - [Release notes](https://github.com/python/typeshed/releases) - - [Commits](https://github.com/python/typeshed/commits) - - --- - updated-dependencies: - - dependency-name: types-python-dateutil - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 - (#634) [dependabot[bot]] - - 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/v2.13.0...v2.15.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) - - --- - updated-dependencies: - - dependency-name: astroid - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) - [dependabot[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.23.0...3.24.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:production - ... -- Update changelog for 1.2.0. [Kazuaki Matsuo] - - -v1.2.0 (2021-06-07) -------------------- - -New -~~~ -- Feat: allow to add a command dynamically (#608) [Kazuaki Matsuo] - - * add add_commmand in python - - * add test - - * add exceptions, tweak method - - * append docstring - - * add $id example - - * use pytest.raises - - * add examples as docstring - -Other -~~~~~ -- Bump 1.2.0. [Kazuaki Matsuo] -- Chore(deps-dev): update pre-commit requirement from ~=2.12 to ~=2.13 - (#607) [dependabot[bot]] - - 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/v2.12.0...v2.13.0) -- Chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 - (#606) [Kazuaki Matsuo, dependabot[bot]] - - * chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 - - 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.11.0...v2.12.0) -- Chore(deps): update pylint requirement from ~=2.7 to ~=2.8 (#600) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.0...pylint-2.8.1) -- Chore(deps-dev): update pre-commit requirement from ~=2.11 to ~=2.12 - (#599) [dependabot[bot]] - - 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/v2.11.0...v2.12.0) -- Chore(deps): update isort requirement from ~=5.7 to ~=5.8 (#596) - [dependabot[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.7.0...5.8.0) - - -v1.1.0 (2021-03-10) -------------------- - -New -~~~ -- Feat: Add optional location speed attribute for android devices (#594) - [salabogdan] -- Feat: Added docstring for macOS screenrecord option (#580) [Mori - Atsushi] - - * Added docstring for macOS screenrecord option - - * tweak - - * review comment -- Feat: add warning to drop forceMjsonwp for W3C (#567) [Kazuaki Matsuo] - - * tweak - - * fix test - - * print warning - - * revert test - - * Update webdriver.py - - * fix autopep8 -- Feat: Added descriptions for newly added screenrecord opts (#540) - [Mori Atsushi] - - * Add description for newly added opts for screen record - - * Updates - -Test -~~~~ -- Ci: Use node v12 (#585) [Mori Atsushi] - - * Use node 12 on ci - - * Update copyright - - * Update README for doc - - * tweak - - * fix copyright - - * try py310 - - * remove py310 -- Ci: remove travis (#581) [Mori Atsushi] - - * Removed travis and run unit test on azure - - * review comment - - * run tox on azure pipelines - - * removed tox-travis from pipfile -- Ci: move azure project to Appium CI, update readme (#564) [Kazuaki - Matsuo] -- Ci: Added py39-dev for travis (#557) [Mori Atsushi] - - * ci: Added py39-dev - - * Add xv option for debug - - * [debug] pip list - - * Avoid error in py39 - - * Updated modules in pre-commit -- Ci: upgrade xcode and macos (#556) [Mori Atsushi] - - * ci: upgrade xcode ver and macos - - * Upgrade iOS ver for functional tests - - * Changed xcode to 11.6 - -Other -~~~~~ -- Chore(deps-dev): update pre-commit requirement from ~=2.10 to ~=2.11 - (#595) [dependabot[bot]] - - 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/v2.10.0...v2.11.0) -- Chore(deps): update tox requirement from ~=3.22 to ~=3.23 (#593) - [dependabot[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/3.23.0/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/3.22.0...3.23.0) -- Chore(deps): update pylint requirement from ~=2.6 to ~=2.7 (#588) - [Mori Atsushi, dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.7.0) -- Chore(deps): update astroid requirement from ~=2.4 to ~=2.5 (#587) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.4.0...astroid-2.5) -- Chore(deps): update mypy requirement from ~=0.800 to ~=0.812 (#589) - [Mori Atsushi, dependabot[bot]] - - * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) - - Signed-off-by: dependabot[bot] - - * Fix mypy error with mypy v0.812 (#590) - - * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) -- Chore(deps): update tox requirement from ~=3.21 to ~=3.22 (#586) - [dependabot[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.21.0...3.22.0) -- Chore: Add table for screen_record kwarg (#582) [Mori Atsushi] - - * Add table for kwarg - - * update - - * Add missing doc to stop_recording - - * Push auto-generated changes by sphinx - - * delete duplicated entry [skip ci] -- Chore(deps): update isort requirement from ~=5.0 to ~=5.7 (#578) - [dependabot-preview[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.0.0...5.7.0) -- Create Dependabot config file (#579) [dependabot-preview[bot], - dependabot-preview[bot]] -- Chore: Update pipfile to respect isort v5 (#577) [Mori Atsushi] -- Chore: Fix iOS app management functional tests (#575) [Mori Atsushi] - - * Added sleep to wait the app has gone - - * Upgrade AndroidSDK to 30 from 27 - - * Added sleep to ios tc - - * Fix android activities test - - * Revert android sdk ver - - * Used timer instead of fixed wait time - - * Created wait_for - - * Update test/functional/test_helper.py - - * review comments - - * review comments - - * Extend callable type - - * fix - - * review comment - - * review comment - - * review comment - - * fix comment -- Chore: Fix functional keyboard tests with appium v1.21.0-beta.0 (#574) - [Mori Atsushi] - - * Fix function keyboard tests - - * Updated class name for keyboard -- Chore: Apply Black code formatter (#571) [Mori Atsushi] - - * Applied black (length: 120, String skipped) - - * Updated related to ci - - * Update README -- Chore: address selenium-4 branch in readme (#566) [Kazuaki Matsuo] -- Docs: fix wrong code example in README.md (#555) [sanlengjingvv] -- Update changelog for 1.0.2. [Kazuaki Matsuo] - - -v1.0.2 (2020-07-15) -------------------- -- Bump 1.0.2. [Kazuaki Matsuo] -- Chore: Add the workaround to avoid service freezes on Windows (#552) - [Mykola Mokhnach] -- Chore: add checking package file count comparison in release script - (#547) [Kazuaki Matsuo] - - * chore: Add file count in release script - - * use f string for Python 3 :P - - * handle exit in method -- Update changelog for 1.0.1. [Kazuaki Matsuo] - - -v1.0.1 (2020-05-18) -------------------- - -Fix -~~~ -- Broken package (#545) [Kazuaki Matsuo] - - * add appium/webdriver/py.typed in find_packages - - * fix - -Other -~~~~~ -- Bump 1.0.1. [Kazuaki Matsuo] -- Update changelog for 1.0.0. [Kazuaki Matsuo] - - -v1.0.0 (2020-05-16) -------------------- - -New -~~~ -- Feat: Added Makefile (#530) [Mori Atsushi] - - * Created setup.cfg - - * Updated lib ver for pre-commit - - * Fix ci.sh to set failure even when one command failed - - * Fix pylint error - - * Add help to Makefile - - * Update README - - * Add check-all command -- Feat: Merge python3 branch to master (#526) [Hannes Hauer, Hannes - Hauer * 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 * Revert some unexpected changes * review - comments * Changed bound for TypeVar * Fix crashing ci * - Remove beta Co-authored-by: dependabot-preview[bot] - <27856297+dependabot-preview[bot]@users.noreply.github.com>, Kazuaki - Matsuo, Kazuaki Matsuo, Mori Atsushi, Mykola Mokhnach, Mykola - Mokhnach, Nrupesh Patel, Nrupesh Patel, Venkatesh, Venkatesh] - - * Drop py2 support (#478) - - * Drop py2 support - - * Support 3.7+ - - * 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 - - * Add mypy to pre-commit (#485) - - * chore: Applied some py3 formats (#486) - - * Removed unused import - - * Removed unnecessary codes - - * Applied f'' format instead ''.format() - - * Fixes - - * tweak - - * 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 - - * 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 - - * 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] - - * Add 'from' to except (#503) - - * 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] - - * 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 - - * 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: - -Fix -~~~ -- Tune mixin types, so linters could recognize them better (#536) - [Mykola Mokhnach] - -Test -~~~~ -- Test: Add appium_service functional test (#531) [Mori Atsushi] - - * Add appium_service functional test - - * Fix expressions - -Other -~~~~~ -- Bump 1.0.0. [Kazuaki Matsuo] -- Chore: Updates docstring (#533) [Mori Atsushi] - - * Updates docstring - - * Add description to Returns field - - * Remove type from docstring - - Since type hint already added to args - - * Set default lang to en - - * Change usage style in docstring - - * Updates - - * Remove rtype - - unnecessary anymore since type hint works for auto completion - - * tweak - - * Update return type - - * Restore types for keyword args - - * Remove types from Return field - - Except for property and TypeVar -- Chore: Remove saucetestcase from the client (#539) [Mykola Mokhnach] -- Chore: add py.typed in package, add maintainers (#538) [Kazuaki - Matsuo] -- Docs: Update documentation (#527) [Kazuaki Matsuo] - - * Chore: correct license, update readme - - * cleanup - - * docs: update the url of documentation -- Chore: Update readme and gitchangelog section role (#524) [Kazuaki - Matsuo] - - * chore: tweak changelog filter - - * address stoping Python 2 support - - * 2 instead of 2.0... - - * tweak readme -- Update changelog for 0.52. [Kazuaki Matsuo] - - -v0.52 (2020-04-23) ------------------- - -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] -- Chore: Fix int - str comparison error in ios desired capabilities - (#517) [Venkatesh] - - if number >= PytestXdistWorker.COUNT: -- Update changelog for 0.51. [Kazuaki Matsuo] - - -v0.51 (2020-04-12) ------------------- - -New -~~~ -- 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 -~~~ -- 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 - -Other -~~~~~ -- Bump 0.51. [Kazuaki Matsuo] -- Update changelog for 0.50. [Kazuaki Matsuo] - - -v0.50 (2020-02-10) ------------------- - -New -~~~ -- 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 - -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 - - * 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 -- Update changelog for 0.49. [Kazuaki Matsuo] - - -v0.49 (2019-12-24) ------------------- - -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] - - * Fix get_all_sessions - - * Revert changes - - * Move execute_mobile_command codes to mixin class - - * Update docstring - - It's same to webdriver.py - - * Use /sessions as endpoint for all_sessions - - https://github.com/appium/appium-base-driver/blob/master/docs/mjsonwp/protocol-methods.md - - * Delete unnecessary codes -- Replace apk for functional test (#470) [Mori Atsushi] - - * Replace apk for functional test - - https://github.com/appium/android-apidemos/releases/tag/v3.1.0 - - * Use sdkVer 27 - - * Update app package name - - * Fix: can't find android device - - * review comments - - * tweak -- Support for log_event and get_events command (#469) [Mori Atsushi] - - * Use appium/events as endpoint to get events - - * Removed unnecessary codes - - * Update unittest along to changes - - * Update docstring - - * Created LogEvents class - - * Support log_event - - * Add unittest for log_event - - * Add functional test for log_event and get_event - - * review comments - - * Restore events API - - * Add type as arg to get_events - - * tweak - - * Removed type arg from get_events - - It isn't implemented yet for now - - * Add type arg to get_event - - The value isn't passed to the server for now. - - * Updated along to type -- Cleaned up test codes (#466) [Mori Atsushi] - - * Deleted unnecessary codes - - * Move functional tests to correct class - - * Move some tests - - * Created search_context/windows_test - - * [functional] Created search_context package - - * Remove class method decolator - - * Fix import error - - * Add BaseTestCase for ios functional testcases - - * Add test_helper for android functional test - - * Add __init__.py - - * Deleted unused imports -- Move search context methods from webdriver and webelement to - search_context (#461) [Mori Atsushi] - - * Move ios search context methods to search_context file - - * Move android search text methods - - * Move windows search context - - * Move mobile search context - - * Divided search_context into each class - - * Move custom and image methods - - * Move contents in search_context.py to __init__.py - - * Add rtype to each docstring for auto completion in IDE - - * Add comments -- [CI] Run functional tests nightly (#463) [Mori Atsushi] - - * [CI] Run functional tests nightly - - * Extend timeout to wait for 2nd session created - - * Skip flaky test_all_sessions -- Revert some changes to fix broken codes (#462) [Mori Atsushi] - - * Revert some changes - - * Fix typo -- Move commands from webdriver as mixins class (#459) [Manoj Kumar] - - * move to mixins class - - * Create common class with its tests - - * incorporating PR comments -- 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] -- Add unit tests for keyboard API (#452) [Manoj Kumar] - - * Add Unit tests for Keyboard API - - * incorporating review comments - - * change per review comment -- Feat: Adding getAllSessions (#446) [Manoj Kumar] - - * Adding getAllSessions - - * adjust per lint - - * fix comments -- Add downloads badge (#441) [Mori Atsushi] - -Fix -~~~ -- 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 -- Fix CI fails (Updated iOS ver) (#440) [Mori Atsushi] - - * Updated iOS ver to fix CI fails - - * Update capability for safari test on ios - - * Fix travis CI fails -- Fix CI fails (#436) [Mori Atsushi] - - * Skip taking the screenshot not in CI - - * Skip py38 on travis -- Fix isort behavior for mock (#432) [Mori Atsushi] - - * Fix isort behavior for mock - - * Add guide to add 3rd party modules to isort conf - - * Add guide for docstrings - - * Delete unnecessary codes -- Fix android flaky tests (#413) [Mori Atsushi] - - * Fix android flaky tests - - * Use androidSdkVer 27 for emulator - - * Skip find_by_accessibility_id, find_by_uiautomator - - * Changed from https://github.com/ki4070ma/python-client/pull/5 - - * Add save_appium_log.yml - - * 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] - - -v0.47 (2019-08-22) ------------------- - -New -~~~ -- Add events property (#429) [Dan Graham] - - * add GET_SESSION - - * add events property, this property will get the current information of the session and get the events timings - - * add method for getting session_capabilities - - * update docstring - - * apply isort -- Add screenrecord unittest (#426) [Mori Atsushi] - - * Fix wrong docstring - - * Add screen_record unittest - - * Rename class names - - * Move test files - - * Fix docstring -- Add videoFilters option documentation (#419) [Mykola Mokhnach] -- Add remote_fs unittest (#410) [Mori Atsushi] - - * Add test_push_file unittest - - * 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 - - And update docstring - - * Update docstring - - * Update docstring - - * Fix import error - - * fix - - * fix import order - - * tweak -- Update changelog for 0.46. [Kazuaki Matsuo] - - -v0.46 (2019-06-27) ------------------- -- Bump 0.46. [Kazuaki Matsuo] -- Bug fix joining path in _get_main_script (#408) [Nicholas Frederick] -- Update changelog for 0.45. [Kazuaki Matsuo] - - -v0.45 (2019-06-26) ------------------- - -New -~~~ -- Add execute driver (#406) [Kazuaki Matsuo] - - * add execute driver - - * append docstring -- Add how to solve pipenv error to readme (#403) [Mori Atsushi] - - * Add how to solve pipenv error to readme - - * review comments - - * tweak - - * review comments -- Add autocompletion for pycharm (#404) [Mori Atsushi] - - * Add autocompletion for pycharm - - * Removed flaky tests from running -- 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 - - * Add missing param - - * Add run_test template - - * Fixed: test running failed - - * Fixed - - * Fixed - - * fixed - - * Add run_android_test - - * Changed emulator to Nexus6 - - * Run all android tests - - * fixed - - * Resolve python-dateutil dependency - - * Run on 3 workers - - * Add chromedriver installation - - * Skip failed test cases on ci - - * fixed - - * Extend adbExecTimeout - - * Add script source to comment - - * Run 5 workers for android - - * Use Node11 - - * Extend wait time - - * Reduced running android functional tests - - * Revert some changes -- Use the same format for docstring (#395) [Mori Atsushi] - - * Update docstring - - * Update docstring - - * Update docstring - - * tweak - - * tweak - - * tweak - - * tweak - - * tweak - - * Update docstring - - * Update docstring - - * Update docstring - - * Update docstring - - * tweak - - * Update -- Publish functional test report (#394) [Mori Atsushi] - - * Move functional tests to template - - * Add publish_test_result - - * Fix typo -- Divide functional appium tests into each module(iOS) (#391) [Mori - Atsushi] - - * Divide ios appium_tests to each module - - * Fix test file name - - * Add CI status badge -- Run iOS functional tests on azure pipelines (#390) [Mori Atsushi] - - * Set up CI with Azure Pipelines - - * review comments - - * update README -- Update changelog for 0.44. [Kazuaki Matsuo] - - -v0.44 (2019-05-24) ------------------- - -Fix -~~~ -- Installed selenium4 when 'setup.py install' (#389) [Mori Atsushi] - - * Fix: installed selenium4 when setup.py install - - * Keep existing comparison operator -- Fix ios functional tests failed (#385) [Mori Atsushi] - - * Fix safari test(iOS) - - * Fix: find_by_ios_predicate - - * Delete find_by_uiautomation_tests - - since uiautomation is deprecated - - * Move non test files - - * Replace test app with the latest - - * 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 - - * Add set_network_speed unittest - - * Add api doc - - * revert unexpected change - - * revert change -- Update changelog for 0.43. [Kazuaki Matsuo] - - -v0.43 (2019-05-18) ------------------- - -New -~~~ -- Add assertions for w3c (#384) [Kazuaki Matsuo] -- Add isort to pre-commit (#379) [Mori Atsushi] - - * Add isort to pre-commit - - * Add isort.conf - - * Applied isort for test/unit - - * Add current dir to isrot arg - - * Add check to ci.sh - - * Use exit code for condition check in ci.sh - -Fix -~~~ -- 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 - - * Replace double quote with single quote under android dir - - * Remove creating tmp file - - * tweak -- Fix functional tests failed (android, ime/multi_action) (#372) [Mori - Atsushi] - - * Fix test failed: ime_tests, multi_action_tests - - * revert change and add impl for python3 - - * Remove py3 dependency - - * Change deepcopy to copy - - * Update ime_tests -- Fix functional tests failed (android, touch_action) (#374) [Mori - Atsushi] - - * Fix: test_drag_and_drop - - * Fix: test_long_press - - * Fix: long_press_x_y, swipe - - * Fix: press_and_wait - - * Fix: driver_drag_and_drop - - * Tweak - - * 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 - - * Move android commands to android package - - * Update setup.py to include added packages - - * Changed find_packages to whitelist style -- Update changelog for 0.42. [Kazuaki Matsuo] - - -v0.42 (2019-05-10) ------------------- - -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] - - * Fix test failed: test_send_keys, test_screen_record - - * Fix test failed: test_update_settings - - * Fix test failed: test_start_activity_other_app - - * Move and rename helper package - - * 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] - - * Support get_performance_data, get_performance_data_types - - * Add api doc - - * Add performance unittest - - * Tweak - - * Update api doc -- Support set_gsm_voice (#367) [Mori Atsushi] - - * Support set_gsm_voice - - * Add set_gsm_voice unittest - - * Fix typo -- Support get_system_bars (#363) [Mori Atsushi] - - * Support get_system_bars - - * Add api doc - - * Add get_system_bars unittest - - * Remove FIXME -- Support make_gsm_call (#360) [Mori Atsushi] - - * Move const to gsm_signal_strength - - * Support make_gsm_call - - * Add make_gsm_call unittest - - * Move const to gsm class - - * Move get_dict_const to common.helper - - * Rename func - - * Use OrderedDict to keep defined order -- Support set_gsm_signal (#357) [Mori Atsushi] - - * Support set_gsm_signal - - * Fix: NONE_OR_UNKNOWN doesn't work - - * Add set_gsm_signal unittest - - * Use int for signal strength const - - * Raise exception when signal strength is out of range - - * Fix: wrong class name - - * Removed args validation - - Since arg validation already done by server side - - * Show warning log when arg is out of range - - * Some changes for less maintenance -- Mobile:pinchOpen and mobile:pinchClose no longer implemented in appium - drivers (#358) [Jonah] -- Remove unnecessary codes. [Atsushi Mori] -- Replace 'on' with AC_ON. [Atsushi Mori] -- Update api doc. [Atsushi Mori] -- Define AC_OFF, AC_ON as const. [Atsushi Mori] -- Skip pylint warnings. [Atsushi Mori] -- Update api doc. [Atsushi Mori] -- Support set_power_ac. [Atsushi Mori] -- Support set_power_capacity. [Atsushi Mori] -- Update changelog for 0.41. [Kazuaki Matsuo] -- Bump 0.41. [Kazuaki Matsuo] - - -v0.41 (2019-04-23) ------------------- - -New -~~~ -- Add send sms support (#351) [Mori Atsushi] - - * Support sendSms function - - * Added api doc - - * Add sms unittest - - * Revert unexpected changes - - * Update api doc -- 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) ------------------- - -Fix -~~~ -- Fix RuntimeError: maximum recursion depth exceeded in cmp happened - (#343) [Kazuaki Matsuo] - - * fix maximum recursion depth exceeded in sub classes - - * add docstring - - * add comparison of a number of commands - - * 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] - - * add direct connect feature - - * rmeove todo - - * update readme, extract _update_command_executor - - * add logger - - * make log level info - - * show warning if no directConnectXxxxx in dict - - * tweak error message - - * tweak message format -- Add datamatcher (#335) [Kazuaki Matsuo] - - * add datamatcher - - * add zero case - - * defines search context for driver and element - -Other -~~~~~ -- Update changelog for 0.38. [Kazuaki Matsuo] -- Bump 0.38. [Kazuaki Matsuo] - - -v0.38 (2019-02-11) ------------------- -- Bump 0.38. [Kazuaki Matsuo] -- Remove io.open from getting version code (#334) [Kazuaki Matsuo] - - * remove io.open - - * remove appium module from release script - - -v0.37 (2019-02-10) ------------------- - -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 - (#324) [Mykola Mokhnach] -- Add pressure option (#322) [Kazuaki Matsuo] - - * add pressure option - - * add a test, tweak comment and the method - - * 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] - - -v0.36 (2019-01-18) ------------------- -- Bump 0.36. [Kazuaki MATSUO] -- Import keyboard, add tests (#319) [Kazuaki Matsuo] -- Update changelog for 0.35. [Kazuaki MATSUO] - - -v0.35 (2019-01-17) ------------------- - -New -~~~ -- Add location unittest (#317) [Mori Atsushi] - - * Add test_location - - * Add test_set_location - - * Add test_toggle_location_services -- Add settings unittest (#315) [Mori Atsushi] - - * Add settings unittest - - * Remove unused import -- Added format to device_time as argument (#312) [Mori Atsushi] -- Add devicetime unittest (#309) [Mori Atsushi] - - * Add device time test - - * Removed unnecessary check from device time test - - * Changed assertion for device time test - - Along to review comments - - * Changed quote for string from double to single -- Add activities unittest (#310) [Tadashi Nemoto] - - * Add test_start_activity - - * Add current_activity and wait_activity - - * Fix pytest 4.0.2 - - * Add test_start_activity_with_opts - - * Added options -- Add network unittest (#308) [Mori Atsushi] - - * Add network connection test - - * Added set network connection test - - * Add toggle wifi test - - * Removed unnecessary codes from toggle wifi test - - * Changed assertion for set network connection test -- Add touch action unittest (#306) [Tadashi Nemoto] - - * Add press test - - * Add test_long_press - - * Add test_wait - - * Add remaining tests - - * Add tap - - * 10 -> 9 - - * Modify based on comment -- 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] -- Update changelog for 0.34. [Kazuaki MATSUO] - - -v0.34 (2018-12-18) ------------------- - -Fix -~~~ -- Fix missing package, missing commands and a test (#296) [Kazuaki - Matsuo] - - * add extensions into package - - * 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) ------------------- - -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) ------------------- - -New -~~~ -- Add unit tests for isLocked Library (#288) [Venkatesh Singh] - - * Add unit tests for isLocked Lib - - * moved isLocked library tests in lock.py -- 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] - - * extract bytes and add a test for set clipboard -- Introduce httpretty for unittest to mock Appium server (#281) [Kazuaki - Matsuo] - - * add httpretty - - * add clipboard tests as an example - - * add test for forceMjsonwp -- Update setup elements (#280) [Kazuaki Matsuo] - - * update setup elements - - * remove docgen since we can use markdown format in pypi -- Release automation (#276) [Kazuaki Matsuo] -- Updated requirements.txt file with version (#275) - [RajeshkumarAyyadurai] - - * updated required dependecies with version number as a best practice - - * updated required dependencies with version - - * updated pylint library version to support for python 2.7 -- Append document for recording screen (#271) [Kazuaki Matsuo] - - * append document for recording screen - - * add since appium 1.10.0 - - * remove Only works for real devices since the feature can work on both -- Update changelog for 0.31. [Kazuaki MATSUO] - - -v0.31 (2018-11-21) ------------------- -- V0.31. [Kazuaki MATSUO] -- Driver.push_file(destination_path, source_path) feature (#270) [Javon - Davis] - - * used base64 library for conversion - - * remove unnecessary library use - - * changed text in test file - - * * Using context when reading file - * changed docstring format - * Catch error thrown if file not present and present user with a better message - - * fixed incorrect file path in test - - * removed change in pul_file that broke backwards compat and updated docstring description for `destination_path` - - -v0.30 (2018-10-31) ------------------- - -New -~~~ -- Add release section in readme. [Kazuaki MATSUO] - -Fix -~~~ -- Fix python3 set_clipboard error (#267) [Kazuaki Matsuo] - - * fix python3 set_clipboard error - - * apply formatter - -Other -~~~~~ -- V0.30. [Kazuaki MATSUO] - - -v0.29 (2018-10-30) ------------------- - -New -~~~ -- Add an endpoint for pressing buttons (#262) [Alex] -- Add custom locator strategy (#260) [Jonathan Lipps] -- Add a duration for scroll for ios (#256) [Kazuaki Matsuo] - - * add a duration for scroll for ios - - * tweak default duration - - * apply autoformat - - * set 600 duration by default if it's w3c spec - - * skip wait if duration is none - - * add comment -- Add finger print (#252) [Kazuaki Matsuo] - - * add fingre print - - * apply auto format -- Add find_elements w3c for webelement (#251) [Kazuaki Matsuo] - - * add find_elements w3c for webelement - - * add tests for child elements - - * add todo for future work -- Add a github issue template (#250) [Kazuaki Matsuo] -- Add xdist port handling (#248) [Kazuaki Matsuo] - - * add handling port number to run ios tests in parallel - - * define PytestXdistWorker - - * use gw0 if the number of worker is over the count of workers -- Add autopep8 (#243) [Kazuaki Matsuo] - - * apply autopep8 - - * add development section as the first draft - - * relax max-line-length - - * add global-config -- Add toggle wifi command (#241) [joshuazhusince1986] - - * add toggle_wifi command - - * update comment to indicate toggle_wifi is only for Android -- Add selenium into ci-requirements (#240) [Kazuaki Matsuo] - - fix pylint - - add --py3k -- Add travis to run pylint and unit tests (#239) [Kazuaki Matsuo] - - * add pylint - - * add rcfile - - * tweak pylint - - * fix lint - - * add running pytest - - * tweak indentations -- Add tag view for android (#238) [Kazuaki Matsuo] - - * add tag view for android - - * fix typo... and tweak names of arguments - - * 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) ------------------- - -Fix -~~~ -- Fix base64 encoded string (#231) [Kazuaki Matsuo] - -Other -~~~~~ -- V0.28. [Isaac Murchie] - - -v0.27 (2018-07-10) ------------------- - -New -~~~ -- Add support for is keyboard shown command. [Jonathan Lipps] -- Add find by image commands and tests (#224) [Jonathan Lipps] - - * add find by image commands and tests - - * remove and ignore pytest cache files - - * address review comments - - * fix docstrings -- Add flags argument to press_keycode (#222) [Mykola Mokhnach] - - * Add flags argument to press_keycode - - * Add flags to long press as well -- Add an endpoint for getting battery info (#217) [Mykola Mokhnach] -- Add wrappers for OpenCV-based image comparison (#216) [Mykola - Mokhnach] - - * Add wrappers for OpenCV-based image comparison - - * Tune some docs -- Add clipboard handlers (#209) [Mykola Mokhnach] - - * Add clipboard handlers - - * Fix documentation - - * fix options notation -- Add applications management endpoint handlers (#204) [Mykola Mokhnach] -- Add methods for start/stop screen record API endpoints (#201) [Mykola - Mokhnach] - - * Add methods for start/stop screen record API endpoints - - * Fix typo - - * Add a separate test for Android and get rid of redundant stuff - - * Tune documentation - - * Add videoSize arg description - - * Fix arg name -- Add appium prefix in create session and fix find_elements for W3C - (#196) [Kazuaki Matsuo] - - * add appium prefix in create session - - * fix find_elements by w3c for Appium - - * introduce forceMjsonwp - - * refine a bit - - * fix some tests - - * 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) ------------------- -- V0.26. [Isaac Murchie] - - -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] -- Correct a wording. [Kazuaki MATSUO] -- Create README.md. [Kazuaki Matsuo] -- Append class chain related descriptions. [Kazuaki MATSUO] -- Update README to include instructions for using iOS predicates. [Emil - Petersen] -- Update docs for UIAutomation selector to include version requirement. - [Emil Petersen] - - -v0.24 (2016-12-20) ------------------- - -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) ------------------- - -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) ------------------- -- V0.22. [Isaac Murchie] -- Use id instead of elementId. [Isaac Murchie] - - -v0.21 (2016-01-20) ------------------- - -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 - - -v0.20 (2015-10-12) ------------------- -- V0.20. [Isaac Murchie] -- Revert actions change. [Isaac Murchie] - - -v0.19 (2015-10-09) ------------------- -- V0.19. [Isaac Murchie] -- Change 'actions' to 'gestures' in single action. [Isaac Murchie] - - -v0.18 (2015-10-07) ------------------- - -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] -- Use WebDriverWait to implement wait_activity. [zhaoqifa] -- Make tap duration be handled as ms, not s. [Isaac Murchie] -- Bump version. [Isaac Murchie] -- Bump version. [Isaac Murchie] -- Move monkeypatched set_value into WebElement. [Isaac Murchie] - - -v0.14 (2015-03-06) ------------------- - -Fix -~~~ -- Fix issue with single tap. [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) ------------------- - -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. -- Update webdriver.py. [urtow] - - -v0.11 (2014-11-14) ------------------- - -New -~~~ -- Add toggle_location_services. [Isaac Murchie] - -Other -~~~~~ -- Bump version. [Isaac Murchie] -- Update webdriver.py. [urtow] - - Start_y - y-coordinate for start, not end - - -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] -- Make long_press works with 'duration' parameter. [ianxiaohanxu] - - Add a new parameter 'duration = None' to _get_opts -- Typo fix! [Cass] -- Update README.md. [Johan Lundstroem] - - Verison -> Version -- Revert "Fix for #23: Re-add 'keyevent' temporarily." [Payman Delshad] - - This reverts commit ccbcaf809704bf1ac752d1b4446d1175b7434c36. - - -v0.9 (2014-07-07) ------------------ - -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] -- 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] -- Renamed keyevent to press_keycode and added long_press_keycode. - [Payman Delshad] -- Bump version. [Isaac Murchie] -- Numerous fixes. [Alexander Bayandin] - - 1. fix comparation with None - 2. remove unused imports - 3. fix imports order (according to pep8) - 4. style fixes (according to pep8) - 5. another minor fixes -- Update zoom/pinch signatures. [Isaac Murchie] -- Remove tag name, use class. [Isaac Murchie] -- Don't send multitouch for single finger tap. [Isaac Murchie] -- Miscellaneous fixes. [Isaac Murchie] -- Update desired caps. [Isaac Murchie] -- Basic module structure. [Isaac Murchie] - - From 2f79b7200e668d9b2b9a1d548687f70de69e23b5 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 27 Feb 2022 21:51:20 -0800 Subject: [PATCH 055/548] Bump 2.1.4 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 7b8301ffa..70f6a08b4 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.1.3' +version = '2.1.4' From c97238216e828266819f82538c77a993fdf39cf2 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 27 Feb 2022 21:56:04 -0800 Subject: [PATCH 056/548] docs: update missing changelog --- CHANGELOG.rst | 2928 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2928 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e69de29bb..ce9fafeb9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -0,0 +1,2928 @@ +Changelog +========= + + +v2.1.4 (2022-02-28) +------------------- +- Bump 2.1.4. [Kazuaki Matsuo] +- Update changelog for 2.1.3. [Kazuaki Matsuo] + + +v2.1.3 (2022-02-26) +------------------- + +Test +~~~~ +- Test: update tests to use find_element(by...) (#674) [Kazuaki Matsuo] + + * test: update find element/s methods + + * fix arguments + + * fix default value + +Other +~~~~~ +- Bump 2.1.3. [Kazuaki Matsuo] +- Chore: restrict selenium client version (#686) [Kazuaki Matsuo] +- Chore(deps): bump black from 21.12b0 to 22.1.0 (#681) + [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/commits/22.1.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + ... +- Chore(deps): update pytest requirement from ~=6.2 to ~=7.0 (#682) + [dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...7.0.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.16 to ~=2.17 + (#678) [dependabot[bot]] + + 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/v2.16.0...v2.17.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Refactor: Update types descriptions for mixin classes (#677) [Mykola + Mokhnach] +- Chore: bump mypy (#675) [Kazuaki Matsuo] +- Update changelog for 2.1.2. [Kazuaki Matsuo] + + +v2.1.2 (2021-12-30) +------------------- + +Fix +~~~ +- Default duration in tap (#673) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.1.2. [Kazuaki Matsuo] +- Update changelog for 2.1.1. [Kazuaki Matsuo] + + +v2.1.1 (2021-12-24) +------------------- + +New +~~~ +- Feat: use 'touch' pointer action (#670) [Kazuaki Matsuo] + + * chore: specify touch + + * comment out touch in drag_and_drop + + * fix mypy + + * add desctiption of touch action + +Test +~~~~ +- Ci: remove ==2021.5.29 (#653) [Kazuaki Matsuo] + + * ci: remove ==2021.5.29 + + * bump black + +Other +~~~~~ +- Bump 2.1.1. [Kazuaki Matsuo] +- Chore(deps): bump black from 21.11b1 to 21.12b0 (#664) + [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/commits) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 + (#663) [dependabot[bot]] + + 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/v2.15.0...v2.16.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Update changelog for 2.1.0. [Kazuaki Matsuo] + + +v2.1.0 (2021-11-27) +------------------- + +New +~~~ +- Feat: add AppiumBy instead of MobileBy (#659) [Kazuaki Matsuo] + + * feat: add AppiumBy instead of MobileBy + + * add class description + + * use deprecated:: + +Other +~~~~~ +- Bump 2.1.0. [Kazuaki Matsuo] +- Chore: add deprecated mark for find_element_by* (#657) [Kazuaki + Matsuo] +- Chore: relax selenium version control (#656) [Kazuaki Matsuo] +- Chore: tweak keyword in metadata. [Kazuaki Matsuo] +- Update changelog for 2.0.0. [Kazuaki Matsuo] + + +v2.0.0 (2021-11-09) +------------------- + +New +~~~ +- Feat: Change base selenium client version to selenium 4 (#636) + [Kazuaki Matsuo] + + - Changed base selenium client version to v4 + - No longer forceMjsonwp works + - Add strict_ssl option in webdriver.Remote + +Test +~~~~ +- Ci: set pipenv==2021.5.29 to prevent dependencies error (#651) + [Kazuaki Matsuo] + + * ci: add --pre + + * specify pipenv as same as the previous ok case + + * set 2021.5.29 in tox as well + +Other +~~~~~ +- Bump 2.0.0. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: add Python 3.9 as metadata. [Kazuaki Matsuo] +- Chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) + [dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) + + --- + updated-dependencies: + - dependency-name: isort + dependency-type: direct:production + ... +- Update changelog for 2.0.0.rc6. [Kazuaki Matsuo] +- Bump 2.0.0.rc6. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: adding deprecation mark in touch actions and multi touch (#648) + [Kazuaki Matsuo] + + * chore: add deprecation mark in touch actions and multi touch + + * chore: add deprecated mark in MultiAction class + + * docs: update readme +- Chore: deprecate -windows uiautomation (#649) [Kazuaki Matsuo] + + * chore: add Deprecated for -windows uiautomation + + * chore: add logger +- Update changelog for 2.0.0.rc5. [Kazuaki Matsuo] +- Bump 2.0.0.rc5. [Kazuaki Matsuo] +- Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 + (#603) [Kazuaki Matsuo, dependabot[bot]] + + Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. + - [Release notes](https://github.com/sphinx-doc/sphinx/releases) + - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) + - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) +- Update gitchangelog once. [Kazuaki Matsuo] +- Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 + (#637) [Kazuaki Matsuo, dependabot[bot]] + + Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. + - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) + - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) + - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) + + --- + updated-dependencies: + - dependency-name: sphinx-rtd-theme + dependency-type: direct:production + ... +- Chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) + [Kazuaki Matsuo] + + chore: cleanup no longer needed code in w3c, bump dev Pipfile +- Chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) + [dependabot[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.12.0...v3.0.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:production + ... +- Update changelog for 1.3.0. [Kazuaki Matsuo] + + +v1.3.0 (2021-09-27) +------------------- + +New +~~~ +- Feat: do not raise an error in case method is already defined (#632) + [Kazuaki Matsuo] +- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] + + * feat: add satellites in set_location + + * fix review +- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] + + * chore: add placeholder + + * move to extention way + + * revert pytest + + * add todo + + * call method_name instead of wrapper + + * remove types + + * rename a method + + * add examples + + * add types-python-dateutil as error message + + * add example more + + * tweak naming + + * Explicit Dict + +Other +~~~~~ +- Bump 1.3.0. [Kazuaki Matsuo] +- Chore(deps): update types-python-dateutil requirement (#633) + [dependabot[bot]] + + Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. + - [Release notes](https://github.com/python/typeshed/releases) + - [Commits](https://github.com/python/typeshed/commits) + + --- + updated-dependencies: + - dependency-name: types-python-dateutil + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 + (#634) [dependabot[bot]] + + 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/v2.13.0...v2.15.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) + [dependabot[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.23.0...3.24.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Update changelog for 1.2.0. [Kazuaki Matsuo] + + +v1.2.0 (2021-06-07) +------------------- + +New +~~~ +- Feat: allow to add a command dynamically (#608) [Kazuaki Matsuo] + + * add add_commmand in python + + * add test + + * add exceptions, tweak method + + * append docstring + + * add $id example + + * use pytest.raises + + * add examples as docstring + +Other +~~~~~ +- Bump 1.2.0. [Kazuaki Matsuo] +- Chore(deps-dev): update pre-commit requirement from ~=2.12 to ~=2.13 + (#607) [dependabot[bot]] + + 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/v2.12.0...v2.13.0) +- Chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 + (#606) [Kazuaki Matsuo, dependabot[bot]] + + * chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 + + 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.11.0...v2.12.0) +- Chore(deps): update pylint requirement from ~=2.7 to ~=2.8 (#600) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.0...pylint-2.8.1) +- Chore(deps-dev): update pre-commit requirement from ~=2.11 to ~=2.12 + (#599) [dependabot[bot]] + + 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/v2.11.0...v2.12.0) +- Chore(deps): update isort requirement from ~=5.7 to ~=5.8 (#596) + [dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.7.0...5.8.0) + + +v1.1.0 (2021-03-10) +------------------- + +New +~~~ +- Feat: Add optional location speed attribute for android devices (#594) + [salabogdan] +- Feat: Added docstring for macOS screenrecord option (#580) [Mori + Atsushi] + + * Added docstring for macOS screenrecord option + + * tweak + + * review comment +- Feat: add warning to drop forceMjsonwp for W3C (#567) [Kazuaki Matsuo] + + * tweak + + * fix test + + * print warning + + * revert test + + * Update webdriver.py + + * fix autopep8 +- Feat: Added descriptions for newly added screenrecord opts (#540) + [Mori Atsushi] + + * Add description for newly added opts for screen record + + * Updates + +Test +~~~~ +- Ci: Use node v12 (#585) [Mori Atsushi] + + * Use node 12 on ci + + * Update copyright + + * Update README for doc + + * tweak + + * fix copyright + + * try py310 + + * remove py310 +- Ci: remove travis (#581) [Mori Atsushi] + + * Removed travis and run unit test on azure + + * review comment + + * run tox on azure pipelines + + * removed tox-travis from pipfile +- Ci: move azure project to Appium CI, update readme (#564) [Kazuaki + Matsuo] +- Ci: Added py39-dev for travis (#557) [Mori Atsushi] + + * ci: Added py39-dev + + * Add xv option for debug + + * [debug] pip list + + * Avoid error in py39 + + * Updated modules in pre-commit +- Ci: upgrade xcode and macos (#556) [Mori Atsushi] + + * ci: upgrade xcode ver and macos + + * Upgrade iOS ver for functional tests + + * Changed xcode to 11.6 + +Other +~~~~~ +- Chore(deps-dev): update pre-commit requirement from ~=2.10 to ~=2.11 + (#595) [dependabot[bot]] + + 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/v2.10.0...v2.11.0) +- Chore(deps): update tox requirement from ~=3.22 to ~=3.23 (#593) + [dependabot[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/3.23.0/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/3.22.0...3.23.0) +- Chore(deps): update pylint requirement from ~=2.6 to ~=2.7 (#588) + [Mori Atsushi, dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.7.0) +- Chore(deps): update astroid requirement from ~=2.4 to ~=2.5 (#587) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.4.0...astroid-2.5) +- Chore(deps): update mypy requirement from ~=0.800 to ~=0.812 (#589) + [Mori Atsushi, dependabot[bot]] + + * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) + + Signed-off-by: dependabot[bot] + + * Fix mypy error with mypy v0.812 (#590) + + * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) +- Chore(deps): update tox requirement from ~=3.21 to ~=3.22 (#586) + [dependabot[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.21.0...3.22.0) +- Chore: Add table for screen_record kwarg (#582) [Mori Atsushi] + + * Add table for kwarg + + * update + + * Add missing doc to stop_recording + + * Push auto-generated changes by sphinx + + * delete duplicated entry [skip ci] +- Chore(deps): update isort requirement from ~=5.0 to ~=5.7 (#578) + [dependabot-preview[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.0.0...5.7.0) +- Create Dependabot config file (#579) [dependabot-preview[bot], + dependabot-preview[bot]] +- Chore: Update pipfile to respect isort v5 (#577) [Mori Atsushi] +- Chore: Fix iOS app management functional tests (#575) [Mori Atsushi] + + * Added sleep to wait the app has gone + + * Upgrade AndroidSDK to 30 from 27 + + * Added sleep to ios tc + + * Fix android activities test + + * Revert android sdk ver + + * Used timer instead of fixed wait time + + * Created wait_for + + * Update test/functional/test_helper.py + + * review comments + + * review comments + + * Extend callable type + + * fix + + * review comment + + * review comment + + * review comment + + * fix comment +- Chore: Fix functional keyboard tests with appium v1.21.0-beta.0 (#574) + [Mori Atsushi] + + * Fix function keyboard tests + + * Updated class name for keyboard +- Chore: Apply Black code formatter (#571) [Mori Atsushi] + + * Applied black (length: 120, String skipped) + + * Updated related to ci + + * Update README +- Chore: address selenium-4 branch in readme (#566) [Kazuaki Matsuo] +- Docs: fix wrong code example in README.md (#555) [sanlengjingvv] +- Update changelog for 1.0.2. [Kazuaki Matsuo] + + +v1.0.2 (2020-07-15) +------------------- +- Bump 1.0.2. [Kazuaki Matsuo] +- Chore: Add the workaround to avoid service freezes on Windows (#552) + [Mykola Mokhnach] +- Chore: add checking package file count comparison in release script + (#547) [Kazuaki Matsuo] + + * chore: Add file count in release script + + * use f string for Python 3 :P + + * handle exit in method +- Update changelog for 1.0.1. [Kazuaki Matsuo] + + +v1.0.1 (2020-05-18) +------------------- + +Fix +~~~ +- Broken package (#545) [Kazuaki Matsuo] + + * add appium/webdriver/py.typed in find_packages + + * fix + +Other +~~~~~ +- Bump 1.0.1. [Kazuaki Matsuo] +- Update changelog for 1.0.0. [Kazuaki Matsuo] + + +v1.0.0 (2020-05-16) +------------------- + +New +~~~ +- Feat: Added Makefile (#530) [Mori Atsushi] + + * Created setup.cfg + + * Updated lib ver for pre-commit + + * Fix ci.sh to set failure even when one command failed + + * Fix pylint error + + * Add help to Makefile + + * Update README + + * Add check-all command +- Feat: Merge python3 branch to master (#526) [Hannes Hauer, Hannes + Hauer * 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 * Revert some unexpected changes * review + comments * Changed bound for TypeVar * Fix crashing ci * + Remove beta Co-authored-by: dependabot-preview[bot] + <27856297+dependabot-preview[bot]@users.noreply.github.com>, Kazuaki + Matsuo, Kazuaki Matsuo, Mori Atsushi, Mykola Mokhnach, Mykola + Mokhnach, Nrupesh Patel, Nrupesh Patel, Venkatesh, Venkatesh] + + * Drop py2 support (#478) + + * Drop py2 support + + * Support 3.7+ + + * 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 + + * Add mypy to pre-commit (#485) + + * chore: Applied some py3 formats (#486) + + * Removed unused import + + * Removed unnecessary codes + + * Applied f'' format instead ''.format() + + * Fixes + + * tweak + + * 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 + + * 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 + + * 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] + + * Add 'from' to except (#503) + + * 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] + + * 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 + + * 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: + +Fix +~~~ +- Tune mixin types, so linters could recognize them better (#536) + [Mykola Mokhnach] + +Test +~~~~ +- Test: Add appium_service functional test (#531) [Mori Atsushi] + + * Add appium_service functional test + + * Fix expressions + +Other +~~~~~ +- Bump 1.0.0. [Kazuaki Matsuo] +- Chore: Updates docstring (#533) [Mori Atsushi] + + * Updates docstring + + * Add description to Returns field + + * Remove type from docstring + + Since type hint already added to args + + * Set default lang to en + + * Change usage style in docstring + + * Updates + + * Remove rtype + + unnecessary anymore since type hint works for auto completion + + * tweak + + * Update return type + + * Restore types for keyword args + + * Remove types from Return field + + Except for property and TypeVar +- Chore: Remove saucetestcase from the client (#539) [Mykola Mokhnach] +- Chore: add py.typed in package, add maintainers (#538) [Kazuaki + Matsuo] +- Docs: Update documentation (#527) [Kazuaki Matsuo] + + * Chore: correct license, update readme + + * cleanup + + * docs: update the url of documentation +- Chore: Update readme and gitchangelog section role (#524) [Kazuaki + Matsuo] + + * chore: tweak changelog filter + + * address stoping Python 2 support + + * 2 instead of 2.0... + + * tweak readme +- Update changelog for 0.52. [Kazuaki Matsuo] + + +v0.52 (2020-04-23) +------------------ + +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] +- Chore: Fix int - str comparison error in ios desired capabilities + (#517) [Venkatesh] + + if number >= PytestXdistWorker.COUNT: +- Update changelog for 0.51. [Kazuaki Matsuo] + + +v0.51 (2020-04-12) +------------------ + +New +~~~ +- 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 +~~~ +- 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 + +Other +~~~~~ +- Bump 0.51. [Kazuaki Matsuo] +- Update changelog for 0.50. [Kazuaki Matsuo] + + +v0.50 (2020-02-10) +------------------ + +New +~~~ +- 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 + +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 + + * 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 +- Update changelog for 0.49. [Kazuaki Matsuo] + + +v0.49 (2019-12-24) +------------------ + +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] + + * Fix get_all_sessions + + * Revert changes + + * Move execute_mobile_command codes to mixin class + + * Update docstring + + It's same to webdriver.py + + * Use /sessions as endpoint for all_sessions + + https://github.com/appium/appium-base-driver/blob/master/docs/mjsonwp/protocol-methods.md + + * Delete unnecessary codes +- Replace apk for functional test (#470) [Mori Atsushi] + + * Replace apk for functional test + + https://github.com/appium/android-apidemos/releases/tag/v3.1.0 + + * Use sdkVer 27 + + * Update app package name + + * Fix: can't find android device + + * review comments + + * tweak +- Support for log_event and get_events command (#469) [Mori Atsushi] + + * Use appium/events as endpoint to get events + + * Removed unnecessary codes + + * Update unittest along to changes + + * Update docstring + + * Created LogEvents class + + * Support log_event + + * Add unittest for log_event + + * Add functional test for log_event and get_event + + * review comments + + * Restore events API + + * Add type as arg to get_events + + * tweak + + * Removed type arg from get_events + + It isn't implemented yet for now + + * Add type arg to get_event + + The value isn't passed to the server for now. + + * Updated along to type +- Cleaned up test codes (#466) [Mori Atsushi] + + * Deleted unnecessary codes + + * Move functional tests to correct class + + * Move some tests + + * Created search_context/windows_test + + * [functional] Created search_context package + + * Remove class method decolator + + * Fix import error + + * Add BaseTestCase for ios functional testcases + + * Add test_helper for android functional test + + * Add __init__.py + + * Deleted unused imports +- Move search context methods from webdriver and webelement to + search_context (#461) [Mori Atsushi] + + * Move ios search context methods to search_context file + + * Move android search text methods + + * Move windows search context + + * Move mobile search context + + * Divided search_context into each class + + * Move custom and image methods + + * Move contents in search_context.py to __init__.py + + * Add rtype to each docstring for auto completion in IDE + + * Add comments +- [CI] Run functional tests nightly (#463) [Mori Atsushi] + + * [CI] Run functional tests nightly + + * Extend timeout to wait for 2nd session created + + * Skip flaky test_all_sessions +- Revert some changes to fix broken codes (#462) [Mori Atsushi] + + * Revert some changes + + * Fix typo +- Move commands from webdriver as mixins class (#459) [Manoj Kumar] + + * move to mixins class + + * Create common class with its tests + + * incorporating PR comments +- 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] +- Add unit tests for keyboard API (#452) [Manoj Kumar] + + * Add Unit tests for Keyboard API + + * incorporating review comments + + * change per review comment +- Feat: Adding getAllSessions (#446) [Manoj Kumar] + + * Adding getAllSessions + + * adjust per lint + + * fix comments +- Add downloads badge (#441) [Mori Atsushi] + +Fix +~~~ +- 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 +- Fix CI fails (Updated iOS ver) (#440) [Mori Atsushi] + + * Updated iOS ver to fix CI fails + + * Update capability for safari test on ios + + * Fix travis CI fails +- Fix CI fails (#436) [Mori Atsushi] + + * Skip taking the screenshot not in CI + + * Skip py38 on travis +- Fix isort behavior for mock (#432) [Mori Atsushi] + + * Fix isort behavior for mock + + * Add guide to add 3rd party modules to isort conf + + * Add guide for docstrings + + * Delete unnecessary codes +- Fix android flaky tests (#413) [Mori Atsushi] + + * Fix android flaky tests + + * Use androidSdkVer 27 for emulator + + * Skip find_by_accessibility_id, find_by_uiautomator + + * Changed from https://github.com/ki4070ma/python-client/pull/5 + + * Add save_appium_log.yml + + * 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] + + +v0.47 (2019-08-22) +------------------ + +New +~~~ +- Add events property (#429) [Dan Graham] + + * add GET_SESSION + + * add events property, this property will get the current information of the session and get the events timings + + * add method for getting session_capabilities + + * update docstring + + * apply isort +- Add screenrecord unittest (#426) [Mori Atsushi] + + * Fix wrong docstring + + * Add screen_record unittest + + * Rename class names + + * Move test files + + * Fix docstring +- Add videoFilters option documentation (#419) [Mykola Mokhnach] +- Add remote_fs unittest (#410) [Mori Atsushi] + + * Add test_push_file unittest + + * 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 + + And update docstring + + * Update docstring + + * Update docstring + + * Fix import error + + * fix + + * fix import order + + * tweak +- Update changelog for 0.46. [Kazuaki Matsuo] + + +v0.46 (2019-06-27) +------------------ +- Bump 0.46. [Kazuaki Matsuo] +- Bug fix joining path in _get_main_script (#408) [Nicholas Frederick] +- Update changelog for 0.45. [Kazuaki Matsuo] + + +v0.45 (2019-06-26) +------------------ + +New +~~~ +- Add execute driver (#406) [Kazuaki Matsuo] + + * add execute driver + + * append docstring +- Add how to solve pipenv error to readme (#403) [Mori Atsushi] + + * Add how to solve pipenv error to readme + + * review comments + + * tweak + + * review comments +- Add autocompletion for pycharm (#404) [Mori Atsushi] + + * Add autocompletion for pycharm + + * Removed flaky tests from running +- 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 + + * Add missing param + + * Add run_test template + + * Fixed: test running failed + + * Fixed + + * Fixed + + * fixed + + * Add run_android_test + + * Changed emulator to Nexus6 + + * Run all android tests + + * fixed + + * Resolve python-dateutil dependency + + * Run on 3 workers + + * Add chromedriver installation + + * Skip failed test cases on ci + + * fixed + + * Extend adbExecTimeout + + * Add script source to comment + + * Run 5 workers for android + + * Use Node11 + + * Extend wait time + + * Reduced running android functional tests + + * Revert some changes +- Use the same format for docstring (#395) [Mori Atsushi] + + * Update docstring + + * Update docstring + + * Update docstring + + * tweak + + * tweak + + * tweak + + * tweak + + * tweak + + * Update docstring + + * Update docstring + + * Update docstring + + * Update docstring + + * tweak + + * Update +- Publish functional test report (#394) [Mori Atsushi] + + * Move functional tests to template + + * Add publish_test_result + + * Fix typo +- Divide functional appium tests into each module(iOS) (#391) [Mori + Atsushi] + + * Divide ios appium_tests to each module + + * Fix test file name + + * Add CI status badge +- Run iOS functional tests on azure pipelines (#390) [Mori Atsushi] + + * Set up CI with Azure Pipelines + + * review comments + + * update README +- Update changelog for 0.44. [Kazuaki Matsuo] + + +v0.44 (2019-05-24) +------------------ + +Fix +~~~ +- Installed selenium4 when 'setup.py install' (#389) [Mori Atsushi] + + * Fix: installed selenium4 when setup.py install + + * Keep existing comparison operator +- Fix ios functional tests failed (#385) [Mori Atsushi] + + * Fix safari test(iOS) + + * Fix: find_by_ios_predicate + + * Delete find_by_uiautomation_tests + + since uiautomation is deprecated + + * Move non test files + + * Replace test app with the latest + + * 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 + + * Add set_network_speed unittest + + * Add api doc + + * revert unexpected change + + * revert change +- Update changelog for 0.43. [Kazuaki Matsuo] + + +v0.43 (2019-05-18) +------------------ + +New +~~~ +- Add assertions for w3c (#384) [Kazuaki Matsuo] +- Add isort to pre-commit (#379) [Mori Atsushi] + + * Add isort to pre-commit + + * Add isort.conf + + * Applied isort for test/unit + + * Add current dir to isrot arg + + * Add check to ci.sh + + * Use exit code for condition check in ci.sh + +Fix +~~~ +- 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 + + * Replace double quote with single quote under android dir + + * Remove creating tmp file + + * tweak +- Fix functional tests failed (android, ime/multi_action) (#372) [Mori + Atsushi] + + * Fix test failed: ime_tests, multi_action_tests + + * revert change and add impl for python3 + + * Remove py3 dependency + + * Change deepcopy to copy + + * Update ime_tests +- Fix functional tests failed (android, touch_action) (#374) [Mori + Atsushi] + + * Fix: test_drag_and_drop + + * Fix: test_long_press + + * Fix: long_press_x_y, swipe + + * Fix: press_and_wait + + * Fix: driver_drag_and_drop + + * Tweak + + * 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 + + * Move android commands to android package + + * Update setup.py to include added packages + + * Changed find_packages to whitelist style +- Update changelog for 0.42. [Kazuaki Matsuo] + + +v0.42 (2019-05-10) +------------------ + +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] + + * Fix test failed: test_send_keys, test_screen_record + + * Fix test failed: test_update_settings + + * Fix test failed: test_start_activity_other_app + + * Move and rename helper package + + * 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] + + * Support get_performance_data, get_performance_data_types + + * Add api doc + + * Add performance unittest + + * Tweak + + * Update api doc +- Support set_gsm_voice (#367) [Mori Atsushi] + + * Support set_gsm_voice + + * Add set_gsm_voice unittest + + * Fix typo +- Support get_system_bars (#363) [Mori Atsushi] + + * Support get_system_bars + + * Add api doc + + * Add get_system_bars unittest + + * Remove FIXME +- Support make_gsm_call (#360) [Mori Atsushi] + + * Move const to gsm_signal_strength + + * Support make_gsm_call + + * Add make_gsm_call unittest + + * Move const to gsm class + + * Move get_dict_const to common.helper + + * Rename func + + * Use OrderedDict to keep defined order +- Support set_gsm_signal (#357) [Mori Atsushi] + + * Support set_gsm_signal + + * Fix: NONE_OR_UNKNOWN doesn't work + + * Add set_gsm_signal unittest + + * Use int for signal strength const + + * Raise exception when signal strength is out of range + + * Fix: wrong class name + + * Removed args validation + + Since arg validation already done by server side + + * Show warning log when arg is out of range + + * Some changes for less maintenance +- Mobile:pinchOpen and mobile:pinchClose no longer implemented in appium + drivers (#358) [Jonah] +- Remove unnecessary codes. [Atsushi Mori] +- Replace 'on' with AC_ON. [Atsushi Mori] +- Update api doc. [Atsushi Mori] +- Define AC_OFF, AC_ON as const. [Atsushi Mori] +- Skip pylint warnings. [Atsushi Mori] +- Update api doc. [Atsushi Mori] +- Support set_power_ac. [Atsushi Mori] +- Support set_power_capacity. [Atsushi Mori] +- Update changelog for 0.41. [Kazuaki Matsuo] +- Bump 0.41. [Kazuaki Matsuo] + + +v0.41 (2019-04-23) +------------------ + +New +~~~ +- Add send sms support (#351) [Mori Atsushi] + + * Support sendSms function + + * Added api doc + + * Add sms unittest + + * Revert unexpected changes + + * Update api doc +- 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) +------------------ + +Fix +~~~ +- Fix RuntimeError: maximum recursion depth exceeded in cmp happened + (#343) [Kazuaki Matsuo] + + * fix maximum recursion depth exceeded in sub classes + + * add docstring + + * add comparison of a number of commands + + * 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] + + * add direct connect feature + + * rmeove todo + + * update readme, extract _update_command_executor + + * add logger + + * make log level info + + * show warning if no directConnectXxxxx in dict + + * tweak error message + + * tweak message format +- Add datamatcher (#335) [Kazuaki Matsuo] + + * add datamatcher + + * add zero case + + * defines search context for driver and element + +Other +~~~~~ +- Update changelog for 0.38. [Kazuaki Matsuo] +- Bump 0.38. [Kazuaki Matsuo] + + +v0.38 (2019-02-11) +------------------ +- Bump 0.38. [Kazuaki Matsuo] +- Remove io.open from getting version code (#334) [Kazuaki Matsuo] + + * remove io.open + + * remove appium module from release script + + +v0.37 (2019-02-10) +------------------ + +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 + (#324) [Mykola Mokhnach] +- Add pressure option (#322) [Kazuaki Matsuo] + + * add pressure option + + * add a test, tweak comment and the method + + * 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] + + +v0.36 (2019-01-18) +------------------ +- Bump 0.36. [Kazuaki MATSUO] +- Import keyboard, add tests (#319) [Kazuaki Matsuo] +- Update changelog for 0.35. [Kazuaki MATSUO] + + +v0.35 (2019-01-17) +------------------ + +New +~~~ +- Add location unittest (#317) [Mori Atsushi] + + * Add test_location + + * Add test_set_location + + * Add test_toggle_location_services +- Add settings unittest (#315) [Mori Atsushi] + + * Add settings unittest + + * Remove unused import +- Added format to device_time as argument (#312) [Mori Atsushi] +- Add devicetime unittest (#309) [Mori Atsushi] + + * Add device time test + + * Removed unnecessary check from device time test + + * Changed assertion for device time test + + Along to review comments + + * Changed quote for string from double to single +- Add activities unittest (#310) [Tadashi Nemoto] + + * Add test_start_activity + + * Add current_activity and wait_activity + + * Fix pytest 4.0.2 + + * Add test_start_activity_with_opts + + * Added options +- Add network unittest (#308) [Mori Atsushi] + + * Add network connection test + + * Added set network connection test + + * Add toggle wifi test + + * Removed unnecessary codes from toggle wifi test + + * Changed assertion for set network connection test +- Add touch action unittest (#306) [Tadashi Nemoto] + + * Add press test + + * Add test_long_press + + * Add test_wait + + * Add remaining tests + + * Add tap + + * 10 -> 9 + + * Modify based on comment +- 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] +- Update changelog for 0.34. [Kazuaki MATSUO] + + +v0.34 (2018-12-18) +------------------ + +Fix +~~~ +- Fix missing package, missing commands and a test (#296) [Kazuaki + Matsuo] + + * add extensions into package + + * 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) +------------------ + +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) +------------------ + +New +~~~ +- Add unit tests for isLocked Library (#288) [Venkatesh Singh] + + * Add unit tests for isLocked Lib + + * moved isLocked library tests in lock.py +- 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] + + * extract bytes and add a test for set clipboard +- Introduce httpretty for unittest to mock Appium server (#281) [Kazuaki + Matsuo] + + * add httpretty + + * add clipboard tests as an example + + * add test for forceMjsonwp +- Update setup elements (#280) [Kazuaki Matsuo] + + * update setup elements + + * remove docgen since we can use markdown format in pypi +- Release automation (#276) [Kazuaki Matsuo] +- Updated requirements.txt file with version (#275) + [RajeshkumarAyyadurai] + + * updated required dependecies with version number as a best practice + + * updated required dependencies with version + + * updated pylint library version to support for python 2.7 +- Append document for recording screen (#271) [Kazuaki Matsuo] + + * append document for recording screen + + * add since appium 1.10.0 + + * remove Only works for real devices since the feature can work on both +- Update changelog for 0.31. [Kazuaki MATSUO] + + +v0.31 (2018-11-21) +------------------ +- V0.31. [Kazuaki MATSUO] +- Driver.push_file(destination_path, source_path) feature (#270) [Javon + Davis] + + * used base64 library for conversion + + * remove unnecessary library use + + * changed text in test file + + * * Using context when reading file + * changed docstring format + * Catch error thrown if file not present and present user with a better message + + * fixed incorrect file path in test + + * removed change in pul_file that broke backwards compat and updated docstring description for `destination_path` + + +v0.30 (2018-10-31) +------------------ + +New +~~~ +- Add release section in readme. [Kazuaki MATSUO] + +Fix +~~~ +- Fix python3 set_clipboard error (#267) [Kazuaki Matsuo] + + * fix python3 set_clipboard error + + * apply formatter + +Other +~~~~~ +- V0.30. [Kazuaki MATSUO] + + +v0.29 (2018-10-30) +------------------ + +New +~~~ +- Add an endpoint for pressing buttons (#262) [Alex] +- Add custom locator strategy (#260) [Jonathan Lipps] +- Add a duration for scroll for ios (#256) [Kazuaki Matsuo] + + * add a duration for scroll for ios + + * tweak default duration + + * apply autoformat + + * set 600 duration by default if it's w3c spec + + * skip wait if duration is none + + * add comment +- Add finger print (#252) [Kazuaki Matsuo] + + * add fingre print + + * apply auto format +- Add find_elements w3c for webelement (#251) [Kazuaki Matsuo] + + * add find_elements w3c for webelement + + * add tests for child elements + + * add todo for future work +- Add a github issue template (#250) [Kazuaki Matsuo] +- Add xdist port handling (#248) [Kazuaki Matsuo] + + * add handling port number to run ios tests in parallel + + * define PytestXdistWorker + + * use gw0 if the number of worker is over the count of workers +- Add autopep8 (#243) [Kazuaki Matsuo] + + * apply autopep8 + + * add development section as the first draft + + * relax max-line-length + + * add global-config +- Add toggle wifi command (#241) [joshuazhusince1986] + + * add toggle_wifi command + + * update comment to indicate toggle_wifi is only for Android +- Add selenium into ci-requirements (#240) [Kazuaki Matsuo] + + fix pylint + + add --py3k +- Add travis to run pylint and unit tests (#239) [Kazuaki Matsuo] + + * add pylint + + * add rcfile + + * tweak pylint + + * fix lint + + * add running pytest + + * tweak indentations +- Add tag view for android (#238) [Kazuaki Matsuo] + + * add tag view for android + + * fix typo... and tweak names of arguments + + * 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) +------------------ + +Fix +~~~ +- Fix base64 encoded string (#231) [Kazuaki Matsuo] + +Other +~~~~~ +- V0.28. [Isaac Murchie] + + +v0.27 (2018-07-10) +------------------ + +New +~~~ +- Add support for is keyboard shown command. [Jonathan Lipps] +- Add find by image commands and tests (#224) [Jonathan Lipps] + + * add find by image commands and tests + + * remove and ignore pytest cache files + + * address review comments + + * fix docstrings +- Add flags argument to press_keycode (#222) [Mykola Mokhnach] + + * Add flags argument to press_keycode + + * Add flags to long press as well +- Add an endpoint for getting battery info (#217) [Mykola Mokhnach] +- Add wrappers for OpenCV-based image comparison (#216) [Mykola + Mokhnach] + + * Add wrappers for OpenCV-based image comparison + + * Tune some docs +- Add clipboard handlers (#209) [Mykola Mokhnach] + + * Add clipboard handlers + + * Fix documentation + + * fix options notation +- Add applications management endpoint handlers (#204) [Mykola Mokhnach] +- Add methods for start/stop screen record API endpoints (#201) [Mykola + Mokhnach] + + * Add methods for start/stop screen record API endpoints + + * Fix typo + + * Add a separate test for Android and get rid of redundant stuff + + * Tune documentation + + * Add videoSize arg description + + * Fix arg name +- Add appium prefix in create session and fix find_elements for W3C + (#196) [Kazuaki Matsuo] + + * add appium prefix in create session + + * fix find_elements by w3c for Appium + + * introduce forceMjsonwp + + * refine a bit + + * fix some tests + + * 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) +------------------ +- V0.26. [Isaac Murchie] + + +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] +- Correct a wording. [Kazuaki MATSUO] +- Create README.md. [Kazuaki Matsuo] +- Append class chain related descriptions. [Kazuaki MATSUO] +- Update README to include instructions for using iOS predicates. [Emil + Petersen] +- Update docs for UIAutomation selector to include version requirement. + [Emil Petersen] + + +v0.24 (2016-12-20) +------------------ + +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) +------------------ + +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) +------------------ +- V0.22. [Isaac Murchie] +- Use id instead of elementId. [Isaac Murchie] + + +v0.21 (2016-01-20) +------------------ + +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 + + +v0.20 (2015-10-12) +------------------ +- V0.20. [Isaac Murchie] +- Revert actions change. [Isaac Murchie] + + +v0.19 (2015-10-09) +------------------ +- V0.19. [Isaac Murchie] +- Change 'actions' to 'gestures' in single action. [Isaac Murchie] + + +v0.18 (2015-10-07) +------------------ + +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] +- Use WebDriverWait to implement wait_activity. [zhaoqifa] +- Make tap duration be handled as ms, not s. [Isaac Murchie] +- Bump version. [Isaac Murchie] +- Bump version. [Isaac Murchie] +- Move monkeypatched set_value into WebElement. [Isaac Murchie] + + +v0.14 (2015-03-06) +------------------ + +Fix +~~~ +- Fix issue with single tap. [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) +------------------ + +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. +- Update webdriver.py. [urtow] + + +v0.11 (2014-11-14) +------------------ + +New +~~~ +- Add toggle_location_services. [Isaac Murchie] + +Other +~~~~~ +- Bump version. [Isaac Murchie] +- Update webdriver.py. [urtow] + + Start_y - y-coordinate for start, not end + + +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] +- Make long_press works with 'duration' parameter. [ianxiaohanxu] + + Add a new parameter 'duration = None' to _get_opts +- Typo fix! [Cass] +- Update README.md. [Johan Lundstroem] + + Verison -> Version +- Revert "Fix for #23: Re-add 'keyevent' temporarily." [Payman Delshad] + + This reverts commit ccbcaf809704bf1ac752d1b4446d1175b7434c36. + + +v0.9 (2014-07-07) +----------------- + +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] +- 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] +- Renamed keyevent to press_keycode and added long_press_keycode. + [Payman Delshad] +- Bump version. [Isaac Murchie] +- Numerous fixes. [Alexander Bayandin] + + 1. fix comparation with None + 2. remove unused imports + 3. fix imports order (according to pep8) + 4. style fixes (according to pep8) + 5. another minor fixes +- Update zoom/pinch signatures. [Isaac Murchie] +- Remove tag name, use class. [Isaac Murchie] +- Don't send multitouch for single finger tap. [Isaac Murchie] +- Miscellaneous fixes. [Isaac Murchie] +- Update desired caps. [Isaac Murchie] +- Basic module structure. [Isaac Murchie] + + From 7dbf3237bff5b503049c47d5815250acb8d6180a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:50:29 +0900 Subject: [PATCH 057/548] chore(deps): update pytest requirement from ~=7.0 to ~=7.1 (#694) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...7.1.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c9880276d..1d0605751 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ selenium = "~=4.0.0" black = "==22.1.0" -pytest = "~=7.0" +pytest = "~=7.1" pytest-cov = "~=3.0" tox = "~=3.24" From 23553dfc7cb1a87e2fa79a81bb0f36b00bdc5169 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:50:48 +0900 Subject: [PATCH 058/548] chore(deps): update typing-extensions requirement from ~=4.0 to ~=4.1 (#684) Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. - [Release notes](https://github.com/python/typing/releases) - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) - [Commits](https://github.com/python/typing/compare/4.0.0...4.1.1) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1d0605751..1ea45b602 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.1" pytest-cov = "~=3.0" tox = "~=3.24" -typing-extensions = "~=4.0" +typing-extensions = "~=4.1" httpretty = "~=1.1" python-dateutil = "~=2.8" From ce526e6f587adcf9c37690dd1a427ec71871de3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:51:09 +0900 Subject: [PATCH 059/548] chore(deps): update mypy requirement from ~=0.930 to ~=0.941 (#696) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.930...v0.941) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1ea45b602..9f4e48f6f 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.12" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.930" +mypy = "~=0.941" From 8826bb35ea529a480226361b87b3b345206a6493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:48:50 -0700 Subject: [PATCH 060/548] chore(deps): update pylint requirement from ~=2.12 to ~=2.13 (#702) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.12.0...v2.13.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 9f4e48f6f..8c4fca3be 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.12" +pylint = "~=2.13" astroid = "~=2.9" isort = "~=5.10" From 56ce5b029a2e878f0fe0c97042f58372327d7a64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:48:58 -0700 Subject: [PATCH 061/548] chore(deps): update mypy requirement from ~=0.941 to ~=0.942 (#703) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.941...v0.942) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8c4fca3be..5679b4412 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.13" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.941" +mypy = "~=0.942" From 09a0cd0c72cc9e63c26c516f8bd8a4ac7b211808 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 29 Mar 2022 14:48:11 +0900 Subject: [PATCH 062/548] feat: add non-w3c but still need commands (#701) * add non-w3c but still need commands * fix id as $ --- Pipfile | 2 +- appium/webdriver/extensions/action_helpers.py | 4 +- appium/webdriver/extensions/ime.py | 7 ++-- appium/webdriver/extensions/location.py | 9 ++-- appium/webdriver/mobilecommand.py | 16 +++++-- appium/webdriver/webdriver.py | 42 ++++++++++++++++++- appium/webdriver/webelement.py | 2 +- setup.py | 2 +- 8 files changed, 66 insertions(+), 18 deletions(-) diff --git a/Pipfile b/Pipfile index 5679b4412..2015bacf6 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.17" [packages] -selenium = "~=4.0.0" +selenium = "~=4.1.3" black = "==22.1.0" diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index fd079e025..64faa2055 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -126,10 +126,10 @@ def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) x = position[0] y = position[1] - # https://github.com/SeleniumHQ/selenium/blob/64447d4b03f6986337d1ca8d8b6476653570bcc1/py/selenium/webdriver/common/actions/pointer_input.py#L24 + # https://github.com/SeleniumHQ/selenium/blob/trunk/py/selenium/webdriver/common/actions/pointer_input.py new_input = actions.w3c_actions.add_pointer_input('touch', f'finger{finger}') new_input.create_pointer_move(x=x, y=y) - new_input.create_pointer_down(MouseButton.LEFT) + new_input.create_pointer_down(button=MouseButton.LEFT) if duration: new_input.create_pause(duration / 1000) else: diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index bf7ebf1c3..253a3ac7a 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -34,7 +34,7 @@ def available_ime_engines(self) -> List[str]: Returns: :obj:`list` of :obj:`str`: The available input methods for an Android device """ - return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] + return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {}).get('value', []) def is_ime_active(self) -> bool: """Checks whether the device has IME service active. @@ -43,7 +43,7 @@ def is_ime_active(self) -> bool: Returns: `True` if IME service is active """ - return self.execute(Command.IS_IME_ACTIVE, {})['value'] + return self.execute(Command.IS_IME_ACTIVE, {}).get('value', False) def activate_ime_engine(self, engine: str) -> 'WebDriver': """Activates the given IME engine on the device. @@ -82,9 +82,10 @@ def active_ime_engine(self) -> str: Returns: str: The activity and package of the currently active IME engine """ - return self.execute(Command.GET_ACTIVE_IME_ENGINE, {})['value'] + return self.execute(Command.GET_ACTIVE_IME_ENGINE, {}).get('value', '') def _add_commands(self) -> None: + """Add IME commands. They are not in W3C spec.""" # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands commands[Command.GET_AVAILABLE_IME_ENGINES] = ( diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 2f7e312e5..c0096e9c7 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -39,9 +39,9 @@ def set_location( self, latitude: Union[float, str], longitude: Union[float, str], - altitude: Union[float, str] = None, - speed: Union[float, str] = None, - satellites: Union[float, str] = None, + altitude: Union[float, str, None] = None, + speed: Union[float, str, None] = None, + satellites: Union[float, str, None] = None, ) -> 'WebDriver': """Set the location of the device @@ -80,9 +80,10 @@ def location(self) -> Dict[str, float]: - longitude (float) - altitude (float) """ - return self.execute(Command.GET_LOCATION)['value'] + return self.execute(Command.GET_LOCATION).get('value', {}) def _add_commands(self) -> None: + """Add location endpoints. They are not int w3c spec.""" # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands commands[Command.TOGGLE_LOCATION_SERVICES] = ( diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index 966d7e4c1..334f4710c 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -18,9 +18,11 @@ class MobileCommand: GET_SESSION = 'getSession' GET_ALL_SESSIONS = 'getAllSessions' + ## MJSONWP for Selenium v4 GET_LOCATION = 'getLocation' SET_LOCATION = 'setLocation' + ## MJSONWP for Selenium v4 GET_AVAILABLE_IME_ENGINES = 'getAvailableIMEEngines' IS_IME_ACTIVE = 'isIMEActive' ACTIVATE_IME_ENGINE = 'activateIMEEngine' @@ -80,6 +82,16 @@ class MobileCommand: GET_EVENTS = 'getLogEvents' LOG_EVENT = 'logCustomEvent' + ## MJSONWP for Selenium v4 + IS_ELEMENT_DISPLAYED = 'isElementDisplayed' + GET_CAPABILITIES = 'getCapabilities' + GET_SCREEN_ORIENTATION = 'getScreenOrientation' + SET_SCREEN_ORIENTATION = 'setScreenOrientation' + + # To override selenium commands + GET_LOG = 'getLog' + GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes' + # Android OPEN_NOTIFICATIONS = 'openNotifications' START_ACTIVITY = 'startActivity' @@ -107,7 +119,3 @@ class MobileCommand: # iOS TOUCH_ID = 'touchId' TOGGLE_TOUCH_ID_ENROLLMENT = 'toggleTouchIdEnrollment' - - # To override selenium commands - GET_LOG = 'getLog' - GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 0e137f3e7..7694365e6 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -18,7 +18,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union from selenium import webdriver -from selenium.common.exceptions import InvalidArgumentException +from selenium.common.exceptions import InvalidArgumentException, WebDriverException from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -80,7 +80,7 @@ _EXTENSION_CAPABILITY = ':' # override -# Add appium prefix for the non-W3C capabilities +# Add appium prefix for the MJSONWP capabilities def _make_w3c_caps(caps: Dict) -> Dict[str, Union[Dict[str, Any], List[Dict[str, Any]]]]: @@ -491,6 +491,35 @@ def switch_to(self) -> MobileSwitchTo: return MobileSwitchTo(self) + # MJSONWP for Selenium v4 + @property + def orientation(self) -> str: + """ + Gets the current orientation of the device + :Usage: + :: + orientation = driver.orientation + """ + return self.execute(Command.GET_SCREEN_ORIENTATION)['value'] + + # MJSONWP for Selenium v4 + @orientation.setter + def orientation(self, value: str) -> None: + """ + Sets the current orientation of the device + :Args: + - value: orientation to set it to. + :Usage: + :: + driver.orientation = 'landscape' + """ + allowed_values = ['LANDSCAPE', 'PORTRAIT'] + if value.upper() in allowed_values: + self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value}) + else: + + raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + def _add_commands(self) -> None: # call the overridden command binders from all mixin classes except for # appium.webdriver.webdriver.WebDriver and its sub-classes @@ -504,6 +533,7 @@ def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands + # FIXME: remove after a while as MJSONWP commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') commands[Command.SET_IMMEDIATE_VALUE] = ( @@ -522,7 +552,15 @@ def _add_commands(self) -> None: '/session/$sessionId/element/$id/location_in_view', ) + ## MJSONWP for Selenium v4 + commands[Command.IS_ELEMENT_DISPLAYED] = ('GET', '/session/$sessionId/element/$id/displayed') + commands[Command.GET_CAPABILITIES] = ('GET', '/session/$sessionId') + + commands[Command.GET_SCREEN_ORIENTATION] = ('GET', '/session/$sessionId/orientation') + commands[Command.SET_SCREEN_ORIENTATION] = ('POST', '/session/$sessionId/orientation') + # override for Appium 1.x # Appium 2.0 and Appium 1.22 work with `/se/log` and `/se/log/types` + # FIXME: remove after a while commands[Command.GET_LOG] = ('POST', '/session/$sessionId/log') commands[Command.GET_AVAILABLE_LOG_TYPES] = ('GET', '/session/$sessionId/log/types') diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index e8fc9073d..922bded43 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -78,7 +78,7 @@ def is_displayed(self) -> bool: Override for Appium """ - return self._execute(RemoteCommand.IS_ELEMENT_DISPLAYED)['value'] + return self._execute(Command.IS_ELEMENT_DISPLAYED)['value'] def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> 'WebElement': """Find an element given a AppiumBy strategy and locator diff --git a/setup.py b/setup.py index e215ef5fc..b4416af40 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium ~= 4.0.0'], + install_requires=['selenium ~= 4.1.3'], ) From 07256fd66055d18162fd83f0d4e765bc1f6da3d5 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 29 Mar 2022 16:54:13 +0900 Subject: [PATCH 063/548] revert: pylint (#706) --- appium/webdriver/extensions/ime.py | 6 +++--- appium/webdriver/extensions/location.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index 253a3ac7a..c18376e71 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -34,7 +34,7 @@ def available_ime_engines(self) -> List[str]: Returns: :obj:`list` of :obj:`str`: The available input methods for an Android device """ - return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {}).get('value', []) + return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] # pylint: disable=unsubscriptable-object def is_ime_active(self) -> bool: """Checks whether the device has IME service active. @@ -43,7 +43,7 @@ def is_ime_active(self) -> bool: Returns: `True` if IME service is active """ - return self.execute(Command.IS_IME_ACTIVE, {}).get('value', False) + return self.execute(Command.IS_IME_ACTIVE, {})['value'] # pylint: disable=unsubscriptable-object def activate_ime_engine(self, engine: str) -> 'WebDriver': """Activates the given IME engine on the device. @@ -82,7 +82,7 @@ def active_ime_engine(self) -> str: Returns: str: The activity and package of the currently active IME engine """ - return self.execute(Command.GET_ACTIVE_IME_ENGINE, {}).get('value', '') + return self.execute(Command.GET_ACTIVE_IME_ENGINE, {})['value'] # pylint: disable=unsubscriptable-object def _add_commands(self) -> None: """Add IME commands. They are not in W3C spec.""" diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index c0096e9c7..0db48300c 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -80,7 +80,7 @@ def location(self) -> Dict[str, float]: - longitude (float) - altitude (float) """ - return self.execute(Command.GET_LOCATION).get('value', {}) + return self.execute(Command.GET_LOCATION)['value'] # pylint: disable=unsubscriptable-object def _add_commands(self) -> None: """Add location endpoints. They are not int w3c spec.""" From 96681e924b4310bcaa23c839746b4923925a2d96 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 29 Mar 2022 00:54:32 -0700 Subject: [PATCH 064/548] chore: relax selenium version as same as before --- Pipfile | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 2015bacf6..87169c59c 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.17" [packages] -selenium = "~=4.1.3" +selenium = "~=4.1" black = "==22.1.0" diff --git a/setup.py b/setup.py index b4416af40..dba676df2 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium ~= 4.1.3'], + install_requires=['selenium ~= 4.1'], ) From 79406bc044f564261e83d6a5ae689b0adcfc7632 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 17:38:19 +0900 Subject: [PATCH 065/548] chore(deps): bump black from 22.1.0 to 22.3.0 (#705) Bumps [black](https://github.com/psf/black) from 22.1.0 to 22.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.1.0...22.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 87169c59c..b9c87567e 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.17" [packages] selenium = "~=4.1" -black = "==22.1.0" +black = "==22.3.0" pytest = "~=7.1" pytest-cov = "~=3.0" From 2d0c4daba8656ed6f3e840aeeeb247f71bb9746d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 30 Mar 2022 02:12:35 -0700 Subject: [PATCH 066/548] Bump 2.2.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 70f6a08b4..d4b750f9f 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.1.4' +version = '2.2.0' From 512ab9beb660927a10c70e8a6db9912e47821003 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 30 Mar 2022 02:12:48 -0700 Subject: [PATCH 067/548] Update changelog for 2.2.0 --- CHANGELOG.rst | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ce9fafeb9..c65096245 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,101 @@ Changelog ========= +v2.2.0 (2022-03-30) +------------------- + +New +~~~ +- Feat: add non-w3c but still need commands (#701) [Kazuaki Matsuo] + + * add non-w3c but still need commands + + * fix id as $ + +Other +~~~~~ +- Bump 2.2.0. [Kazuaki Matsuo] +- Chore(deps): bump black from 22.1.0 to 22.3.0 (#705) [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.1.0 to 22.3.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.1.0...22.3.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Revert: pylint (#706) [Kazuaki Matsuo] +- Chore: relax selenium version as same as before. [Kazuaki Matsuo] +- Chore(deps): update mypy requirement from ~=0.941 to ~=0.942 (#703) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.941...v0.942) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.12 to ~=2.13 (#702) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.12.0...v2.13.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.930 to ~=0.941 (#696) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.930...v0.941) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update typing-extensions requirement from ~=4.0 to ~=4.1 + (#684) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. + - [Release notes](https://github.com/python/typing/releases) + - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) + - [Commits](https://github.com/python/typing/compare/4.0.0...4.1.1) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update pytest requirement from ~=7.0 to ~=7.1 (#694) + [dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...7.1.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:production + ... +- Docs: update missing changelog. [Kazuaki Matsuo] + + v2.1.4 (2022-02-28) ------------------- - Bump 2.1.4. [Kazuaki Matsuo] From 4f8064fff2d6d7f197112f2e83be0eacbbec4265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:36:26 +0900 Subject: [PATCH 068/548] chore(deps-dev): update pre-commit requirement from ~=2.17 to ~=2.18 (#708) 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/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b9c87567e..bf51eeafe 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.17" +pre-commit = "~=2.18" [packages] selenium = "~=4.1" From f5b0526f903eee98f443b47d6fec7a4f1d0a4838 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 03:03:13 +0900 Subject: [PATCH 069/548] chore(deps): update tox requirement from ~=3.24 to ~=3.25 (#709) 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.24.0...3.25.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index bf51eeafe..7a65720c6 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ black = "==22.3.0" pytest = "~=7.1" pytest-cov = "~=3.0" -tox = "~=3.24" +tox = "~=3.25" typing-extensions = "~=4.1" httpretty = "~=1.1" From cb1a4eaed0628a064deb70ba04fe5a5cd53312a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 02:58:30 +0900 Subject: [PATCH 070/548] chore(deps): update typing-extensions requirement from ~=4.1 to ~=4.2 (#711) Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. - [Release notes](https://github.com/python/typing/releases) - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) - [Commits](https://github.com/python/typing/compare/4.1.0...4.2.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 7a65720c6..735cb01d8 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.1" pytest-cov = "~=3.0" tox = "~=3.25" -typing-extensions = "~=4.1" +typing-extensions = "~=4.2" httpretty = "~=1.1" python-dateutil = "~=2.8" From 336a76257e809f5562e287d68322d9f256d880c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 11:27:17 -0700 Subject: [PATCH 071/548] chore(deps): update mypy requirement from ~=0.942 to ~=0.950 (#712) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.942...v0.950) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 735cb01d8..adb42b030 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.13" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.942" +mypy = "~=0.950" From df33c85371d933ed0678f759ccf660343c66662a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 May 2022 22:11:00 -0700 Subject: [PATCH 072/548] chore(deps-dev): update pre-commit requirement from ~=2.18 to ~=2.19 (#713) 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/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.18.0...v2.19.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index adb42b030..e34592f36 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.18" +pre-commit = "~=2.19" [packages] selenium = "~=4.1" From 956417a55de28123bcf2404ebbbc95b0d9fcd072 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 May 2022 15:28:52 +0900 Subject: [PATCH 073/548] chore(deps): update mypy requirement from ~=0.950 to ~=0.960 (#714) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.950...v0.960) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e34592f36..df9d84247 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.13" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.950" +mypy = "~=0.960" From 1385efc80d87cb50c600548229ff74d9106c402b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 May 2022 15:26:49 -0700 Subject: [PATCH 074/548] chore(deps): update sphinx requirement from <5.0,>=4.0 to >=4.0,<6.0 (#716) Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.0...v5.0.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 426e81603..536f4d5e1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx >= 4.0, < 5.0 +sphinx >= 4.0, < 6.0 sphinx_rtd_theme < 2.0 From 697bb64db15378dfbcd6b4bc3f4931da520259a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 16:13:12 +0200 Subject: [PATCH 075/548] chore(deps): update selenium requirement from ~=4.1 to ~=4.2 (#715) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.1.0...selenium-4.2.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index df9d84247..1928dd370 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.19" [packages] -selenium = "~=4.1" +selenium = "~=4.2" black = "==22.3.0" From a3942815a7b60548785db0ea2f8506b25b0693e2 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 9 Jun 2022 18:20:21 +0200 Subject: [PATCH 076/548] chore: Disable pylint checks fail CI (#719) --- Pipfile | 2 +- ci.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 1928dd370..fe6dc7927 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.13" +pylint = "~=2.14.1" astroid = "~=2.9" isort = "~=5.10" diff --git a/ci.sh b/ci.sh index 8e0e8a132..2aff764de 100755 --- a/ci.sh +++ b/ci.sh @@ -11,9 +11,10 @@ if ! make isort ARGS=--check-only ; then EXIT_STATUS=1 fi -if ! make pylint ARGS=--errors-only ; then +if ! make pylint ; then echo "Please run command 'make pylint' on your local and fix errors" - EXIT_STATUS=1 + # TODO: pylint erroneously complains about many things it should not complain about + # EXIT_STATUS=1 fi if ! make unittest ARGS=--junitxml=./test/unit/junit.xml ; then From c27138c0505a6595f9c5f48f3e4d3ccb996301cd Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 10 Jun 2022 21:06:58 +0200 Subject: [PATCH 077/548] feat: Add support for w3c options (#720) --- appium/options/__init__.py | 0 appium/options/android/__init__.py | 1 + .../options/android/uiautomator2/__init__.py | 0 appium/options/android/uiautomator2/base.py | 31 +++++ appium/options/common/__init__.py | 1 + .../options/common/automation_name_option.py | 42 +++++++ appium/options/common/base.py | 110 ++++++++++++++++++ .../options/common/supports_capabilities.py | 30 +++++ appium/options/ios/__init__.py | 1 + appium/options/ios/xcuitest/__init__.py | 0 appium/options/ios/xcuitest/base.py | 31 +++++ appium/options/mac/__init__.py | 1 + appium/options/mac/mac2/__init__.py | 0 appium/options/mac/mac2/base.py | 31 +++++ appium/webdriver/webdriver.py | 103 ++++------------ test/functional/android/helper/test_helper.py | 13 ++- test/functional/ios/helper/test_helper.py | 9 +- test/functional/mac/helper/test_helper.py | 8 +- test/unit/helper/test_helper.py | 10 +- test/unit/webdriver/webdriver_test.py | 49 +++++--- 20 files changed, 360 insertions(+), 111 deletions(-) create mode 100644 appium/options/__init__.py create mode 100644 appium/options/android/__init__.py create mode 100644 appium/options/android/uiautomator2/__init__.py create mode 100644 appium/options/android/uiautomator2/base.py create mode 100644 appium/options/common/__init__.py create mode 100644 appium/options/common/automation_name_option.py create mode 100644 appium/options/common/base.py create mode 100644 appium/options/common/supports_capabilities.py create mode 100644 appium/options/ios/__init__.py create mode 100644 appium/options/ios/xcuitest/__init__.py create mode 100644 appium/options/ios/xcuitest/base.py create mode 100644 appium/options/mac/__init__.py create mode 100644 appium/options/mac/mac2/__init__.py create mode 100644 appium/options/mac/mac2/base.py diff --git a/appium/options/__init__.py b/appium/options/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/__init__.py b/appium/options/android/__init__.py new file mode 100644 index 000000000..f03a4e7a0 --- /dev/null +++ b/appium/options/android/__init__.py @@ -0,0 +1 @@ +from .uiautomator2.base import UiAutomator2Options diff --git a/appium/options/android/uiautomator2/__init__.py b/appium/options/android/uiautomator2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/uiautomator2/base.py b/appium/options/android/uiautomator2/base.py new file mode 100644 index 000000000..d067d78f1 --- /dev/null +++ b/appium/options/android/uiautomator2/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class UiAutomator2Options( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'UIAutomator2', + AppiumOptions.PLATFORM_NAME: 'Android', + } diff --git a/appium/options/common/__init__.py b/appium/options/common/__init__.py new file mode 100644 index 000000000..a7a37ea6f --- /dev/null +++ b/appium/options/common/__init__.py @@ -0,0 +1 @@ +from .base import AppiumOptions diff --git a/appium/options/common/automation_name_option.py b/appium/options/common/automation_name_option.py new file mode 100644 index 000000000..b52250608 --- /dev/null +++ b/appium/options/common/automation_name_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + + +class AutomationNameOption(SupportsCapabilities): + AUTOMATION_NAME = 'automationName' + + @property + def automation_name(self) -> Optional[str]: + """ + :Returns: String representing the name of the automation engine + """ + return self.get_capability(self.AUTOMATION_NAME) + + @automation_name.setter + def automation_name(self, value: str) -> None: + """ + Set the automation driver to use. + + :Args: + - value: One of supported automation names + + """ + self.set_capability(self.AUTOMATION_NAME, value) diff --git a/appium/options/common/base.py b/appium/options/common/base.py new file mode 100644 index 000000000..f7245d257 --- /dev/null +++ b/appium/options/common/base.py @@ -0,0 +1,110 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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 copy +from typing import Any, Dict, TypeVar + +from selenium.webdriver.common.options import BaseOptions + +from .automation_name_option import AutomationNameOption + +APPIUM_PREFIX = 'appium:' +T = TypeVar('T', bound='AppiumOptions') + + +class AppiumOptions( + BaseOptions, + AutomationNameOption, +): + _caps: Dict + PLATFORM_NAME = 'platformName' + W3C_CAPABILITY_NAMES = frozenset( + [ + 'acceptInsecureCerts', + 'browserName', + 'browserVersion', + PLATFORM_NAME, + 'pageLoadStrategy', + 'proxy', + 'setWindowRect', + 'timeouts', + 'unhandledPromptBehavior', + ] + ) + _OSS_W3C_CONVERSION = { + 'acceptSslCerts': 'acceptInsecureCerts', + 'version': 'browserVersion', + 'platform': PLATFORM_NAME, + } + + # noinspection PyMissingConstructor + def __init__(self) -> None: + self._caps = self.default_capabilities + # FIXME: https://github.com/SeleniumHQ/selenium/issues/10755 + self._ignore_local_proxy = False + + def set_capability(self, name: str, value: Any) -> T: + w3c_name = name if name in self.W3C_CAPABILITY_NAMES or ':' in name else f'{APPIUM_PREFIX}{name}' + if value is None: + if w3c_name in self._caps: + del self._caps[w3c_name] + else: + self._caps[w3c_name] = value + return self + + def get_capability(self, name: str) -> Any: + """Fetches capability value or None if the capability is not set""" + return self._caps[name] if name in self._caps else self._caps.get(f'{APPIUM_PREFIX}{name}') + + def load_capabilities(self, caps: Dict[str, Any]) -> T: + """Sets multiple capabilities""" + for name, value in caps.items(): + self.set_capability(name, value) + return self + + @staticmethod + def as_w3c(capabilities: Dict) -> Dict: + """ + Formats given capabilities to a valid W3C session request object + + :param capabilities: Capabilities mapping + :return: W3C session request object + """ + + def process_key(k: str) -> str: + key = AppiumOptions._OSS_W3C_CONVERSION.get(k, k) + if key in AppiumOptions.W3C_CAPABILITY_NAMES: + return key + return key if ':' in key else f'{APPIUM_PREFIX}{key}' + + processed_caps = {process_key(k): v for k, v in copy.deepcopy(capabilities).items()} + return {'capabilities': {'firstMatch': [{}], 'alwaysMatch': processed_caps}} + + def to_w3c(self) -> Dict: + """ + Formats the instance to a valid W3C session request object + + :return: W3C session request object + """ + return self.as_w3c(self.to_capabilities()) + + def to_capabilities(self) -> Dict: + return copy.copy(self._caps) + + @property + def default_capabilities(self) -> Dict: + return {} diff --git a/appium/options/common/supports_capabilities.py b/appium/options/common/supports_capabilities.py new file mode 100644 index 000000000..b56f153ca --- /dev/null +++ b/appium/options/common/supports_capabilities.py @@ -0,0 +1,30 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, TypeVar + +from appium.protocols.protocol import Protocol + +T = TypeVar('T') + + +class SupportsCapabilities(Protocol): + def set_capability(self: T, name: str, value: Any) -> T: + ... + + def get_capability(self: T, name: str) -> Any: + ... diff --git a/appium/options/ios/__init__.py b/appium/options/ios/__init__.py new file mode 100644 index 000000000..cc2b162d6 --- /dev/null +++ b/appium/options/ios/__init__.py @@ -0,0 +1 @@ +from .xcuitest.base import XCUITestOptions diff --git a/appium/options/ios/xcuitest/__init__.py b/appium/options/ios/xcuitest/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/base.py b/appium/options/ios/xcuitest/base.py new file mode 100644 index 000000000..9718dfec3 --- /dev/null +++ b/appium/options/ios/xcuitest/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class XCUITestOptions( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'XCUITest', + AppiumOptions.PLATFORM_NAME: 'iOS', + } diff --git a/appium/options/mac/__init__.py b/appium/options/mac/__init__.py new file mode 100644 index 000000000..41a091df5 --- /dev/null +++ b/appium/options/mac/__init__.py @@ -0,0 +1 @@ +from .mac2.base import Mac2Options diff --git a/appium/options/mac/mac2/__init__.py b/appium/options/mac/mac2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py new file mode 100644 index 000000000..7c1ebc700 --- /dev/null +++ b/appium/options/mac/mac2/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class Mac2Options( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'Mac2', + AppiumOptions.PLATFORM_NAME: 'Mac', + } diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 7694365e6..16257df4b 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -14,7 +14,6 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use -import copy from typing import Any, Callable, Dict, List, Optional, Tuple, Union from selenium import webdriver @@ -24,6 +23,7 @@ from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.logger import logger +from appium.options.common.base import AppiumOptions from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection @@ -59,56 +59,6 @@ from .switch_to import MobileSwitchTo from .webelement import WebElement as MobileWebElement -# From remote/webdriver.py -_W3C_CAPABILITY_NAMES = frozenset( - [ - 'acceptInsecureCerts', - 'browserName', - 'browserVersion', - 'platformName', - 'pageLoadStrategy', - 'proxy', - 'setWindowRect', - 'timeouts', - 'unhandledPromptBehavior', - ] -) - -# From remote/webdriver.py -_OSS_W3C_CONVERSION = {'acceptSslCerts': 'acceptInsecureCerts', 'version': 'browserVersion', 'platform': 'platformName'} - -_EXTENSION_CAPABILITY = ':' - -# override -# Add appium prefix for the MJSONWP capabilities - - -def _make_w3c_caps(caps: Dict) -> Dict[str, Union[Dict[str, Any], List[Dict[str, Any]]]]: - appium_prefix = 'appium:' - - caps = copy.deepcopy(caps) - profile = caps.get('firefox_profile') - always_match = {} - if caps.get('proxy') and caps['proxy'].get('proxyType'): - caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower() - for k, v in caps.items(): - if v and k in _OSS_W3C_CONVERSION: - always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v - if k in _W3C_CAPABILITY_NAMES or _EXTENSION_CAPABILITY in k: - always_match[k] = v - else: - if not k.startswith(appium_prefix): - always_match[appium_prefix + k] = v - if profile: - moz_opts = always_match.get('moz:firefoxOptions', {}) - # If it's already present, assume the caller did that intentionally. - if 'profile' not in moz_opts: - # Don't mutate the original capabilities. - new_opts = copy.deepcopy(moz_opts) - new_opts['profile'] = profile - always_match['moz:firefoxOptions'] = new_opts - return {'alwaysMatch': always_match, 'firstMatch': [{}]} - class ExtensionBase: """ @@ -260,6 +210,7 @@ def __init__( direct_connection: bool = True, extensions: Optional[List['WebDriver']] = None, strict_ssl: bool = True, + options: Union[AppiumOptions, List[AppiumOptions]] = None, ): if strict_ssl is False: @@ -267,12 +218,20 @@ def __init__( # noinspection PyPackageRequirements import urllib3 + # pylint: disable=E1101 + # noinspection PyPackageRequirements + import urllib3.exceptions + # noinspection PyUnresolvedReferences AppiumConnection.set_certificate_bundle_path(None) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) super().__init__( - AppiumConnection(command_executor, keep_alive=keep_alive), desired_capabilities, browser_profile, proxy + command_executor=AppiumConnection(command_executor, keep_alive=keep_alive), + desired_capabilities=desired_capabilities, + browser_profile=browser_profile, + proxy=proxy, + options=options, ) if hasattr(self, 'command_executor'): @@ -343,45 +302,25 @@ def _update_command_executor(self, keep_alive: bool) -> None: # https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277 # noinspection PyAttributeOutsideInit - def start_session(self, capabilities: Dict, browser_profile: Optional[str] = None) -> None: + def start_session(self, capabilities: Union[Dict, AppiumOptions], browser_profile: Optional[str] = None) -> None: """Creates a new session with the desired capabilities. Override for Appium Args: - capabilities: Capabilities which have following keys like 'automation_name', 'platform_name', - 'platform_version', 'app'. - Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md for more details. + capabilities: Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md + for more details. browser_profile: Browser profile """ - if not isinstance(capabilities, dict): - raise InvalidArgumentException('Capabilities must be a dictionary') - if browser_profile: - if 'moz:firefoxOptions' in capabilities: - # encoded is defined in selenium's original codes - capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded # type: ignore - else: - # encoded is defined in selenium's original codes - capabilities.update({'firefox_profile': browser_profile.encoded}) # type: ignore - - parameters = self._merge_capabilities(capabilities) - - response = self.execute(RemoteCommand.NEW_SESSION, parameters) + if not isinstance(capabilities, (dict, AppiumOptions)): + raise InvalidArgumentException('Capabilities must be a dictionary or AppiumOptions instance') + + w3c_caps = AppiumOptions.as_w3c(capabilities) if isinstance(capabilities, dict) else capabilities.to_w3c() + response = self.execute(RemoteCommand.NEW_SESSION, w3c_caps) if 'sessionId' not in response: response = response['value'] self.session_id = response['sessionId'] - self.caps = response.get('value') - - # if capabilities is none we are probably speaking to - # a W3C endpoint - if self.caps is None: - self.caps = response.get('capabilities') - - # noinspection PyMethodMayBeStatic - def _merge_capabilities(self, capabilities: Dict) -> Dict[str, Any]: - """Manage capabilities whether W3C format or MJSONWP format""" - w3c_caps = _make_w3c_caps(capabilities) - return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities} + self.caps = response.get('value') or response.get('capabilities') def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: """ @@ -552,7 +491,7 @@ def _add_commands(self) -> None: '/session/$sessionId/element/$id/location_in_view', ) - ## MJSONWP for Selenium v4 + # MJSONWP for Selenium v4 commands[Command.IS_ELEMENT_DISPLAYED] = ('GET', '/session/$sessionId/element/$id/displayed') commands[Command.GET_CAPABILITIES] = ('GET', '/session/$sessionId') diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index 1f726f55e..1e94d34db 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -15,17 +15,13 @@ import base64 import os -from typing import TYPE_CHECKING from appium import webdriver +from appium.options.android import UiAutomator2Options from test.functional.test_helper import is_ci from . import desired_capabilities -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - from appium.webdriver.webelement import WebElement - # the emulator is sometimes slow and needs time to think SLEEPY_TIME = 10 @@ -36,11 +32,16 @@ class BaseTestCase: 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) + self.driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', options=UiAutomator2Options().load_capabilities(desired_caps) + ) if is_ci(): self.driver.start_recording_screen() def teardown_method(self, method) -> None: # type: ignore + if not hasattr(self, 'driver'): + return + if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') diff --git a/test/functional/ios/helper/test_helper.py b/test/functional/ios/helper/test_helper.py index c9a4da868..3491aa25f 100644 --- a/test/functional/ios/helper/test_helper.py +++ b/test/functional/ios/helper/test_helper.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. - import base64 import os from appium import webdriver +from appium.options.ios import XCUITestOptions from test.functional.test_helper import is_ci from . import desired_capabilities @@ -25,11 +25,16 @@ class BaseTestCase(object): 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) + self.driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', options=XCUITestOptions().load_capabilities(desired_caps) + ) if is_ci(): self.driver.start_recording_screen() def teardown_method(self, method) -> None: # type: ignore + if not hasattr(self, 'driver'): + return + if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') diff --git a/test/functional/mac/helper/test_helper.py b/test/functional/mac/helper/test_helper.py index f52bc9aa9..5232d0641 100644 --- a/test/functional/mac/helper/test_helper.py +++ b/test/functional/mac/helper/test_helper.py @@ -14,13 +14,19 @@ from appium import webdriver +from appium.options.mac import Mac2Options from .desired_capabilities import get_desired_capabilities class BaseTestCase(object): def setup_method(self) -> None: - self.driver = webdriver.Remote('http://localhost:4723/wd/hub', get_desired_capabilities()) + self.driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', options=Mac2Options().load_capabilities(get_desired_capabilities()) + ) def teardown_method(self, method) -> None: # type: ignore + if not hasattr(self, 'driver'): + return + self.driver.quit() diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py index 9766e1b76..b0a818e9b 100644 --- a/test/unit/helper/test_helper.py +++ b/test/unit/helper/test_helper.py @@ -18,6 +18,8 @@ import httpretty from appium import webdriver +from appium.options.android import UiAutomator2Options +from appium.options.ios import XCUITestOptions # :return: A string of test URL SERVER_URL_BASE = 'http://localhost:4723/wd/hub' @@ -81,7 +83,7 @@ def android_w3c_driver() -> 'WebDriver': 'automationName': 'UIAutomator2', } - driver = webdriver.Remote(SERVER_URL_BASE, desired_caps) + driver = webdriver.Remote(SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(desired_caps)) return driver @@ -115,7 +117,7 @@ def ios_w3c_driver() -> 'WebDriver': 'automationName': 'XCUITest', } - driver = webdriver.Remote(SERVER_URL_BASE, desired_caps) + driver = webdriver.Remote(SERVER_URL_BASE, options=XCUITestOptions().load_capabilities(desired_caps)) return driver @@ -149,7 +151,9 @@ def ios_w3c_driver_with_extensions(extensions) -> 'WebDriver': 'automationName': 'XCUITest', } - driver = webdriver.Remote(SERVER_URL_BASE, desired_caps, extensions=extensions) + driver = webdriver.Remote( + SERVER_URL_BASE, options=XCUITestOptions().load_capabilities(desired_caps), extensions=extensions + ) return driver diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 6565277aa..00be6cbdf 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -15,11 +15,11 @@ import json import httpretty -import pytest from mock import patch from appium import version as appium_version from appium import webdriver +from appium.options.android import UiAutomator2Options from appium.webdriver.webdriver import ExtensionBase, WebDriver from test.unit.helper.test_helper import ( android_w3c_driver, @@ -57,7 +57,13 @@ def test_create_session(self): request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) assert request_json.get('capabilities') is not None - assert request_json.get('desiredCapabilities') is not None + assert request_json['capabilities']['alwaysMatch'] == { + 'platformName': 'Android', + 'appium:deviceName': 'Android Emulator', + 'appium:app': 'path/to/app', + 'appium:automationName': 'UIAutomator2', + } + assert request_json.get('desiredCapabilities') is None assert driver.session_id == 'session-id' @@ -75,13 +81,10 @@ def test_create_session_change_session_id(self): body='{ "value": "title on another session id"}', ) - desired_caps = { - 'platformName': 'Android', - 'deviceName': 'Android Emulator', - 'app': 'path/to/app', - 'automationName': 'UIAutomator2', - } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + options = ( + UiAutomator2Options().set_capability('deviceName', 'Android Emulator').set_capability('app', 'path/to/app') + ) + driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options) # current session assert driver.session_id == 'session-id' @@ -124,7 +127,11 @@ def test_create_session_register_uridirect(self): 'app': 'path/to/app', 'automationName': 'UIAutomator2', } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, direct_connection=True) + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', + options=UiAutomator2Options().load_capabilities(desired_caps), + direct_connection=True, + ) assert 'http://localhost2:4800/special/path/wd/hub' == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts @@ -161,7 +168,11 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): 'app': 'path/to/app', 'automationName': 'UIAutomator2', } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, direct_connection=True) + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', + options=UiAutomator2Options().load_capabilities(desired_caps), + direct_connection=True, + ) assert 'http://localhost:4723/wd/hub' == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts @@ -231,7 +242,7 @@ def test_command(self): return self.execute()['value'] def add_command(self): - return ('get', 'session/$sessionId/path/to/custom/url') + return 'get', 'session/$sessionId/path/to/custom/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( @@ -254,7 +265,7 @@ def test_command(self, argument): return self.execute(argument)['value'] def add_command(self): - return ('post', 'session/$sessionId/path/to/custom/url') + return 'post', 'session/$sessionId/path/to/custom/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( @@ -280,7 +291,7 @@ def test_command(self, element_id): return self.execute({'id': element_id})['value'] def add_command(self): - return ('GET', 'session/$sessionId/path/to/custom/$id/url') + return 'GET', 'session/$sessionId/path/to/custom/$id/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( @@ -294,20 +305,22 @@ def add_command(self): class SubWebDriver(WebDriver): - def __init__(self, command_executor, desired_capabilities, direct_connection=False): + def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None): super().__init__( command_executor=command_executor, desired_capabilities=desired_capabilities, direct_connection=direct_connection, + options=options, ) class SubSubWebDriver(SubWebDriver): - def __init__(self, command_executor, desired_capabilities, direct_connection=False): + def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None): super().__init__( command_executor=command_executor, desired_capabilities=desired_capabilities, direct_connection=direct_connection, + options=options, ) @@ -350,7 +363,9 @@ def android_w3c_driver(self, driver_class): 'automationName': 'UIAutomator2', } - driver = driver_class('http://localhost:4723/wd/hub', desired_caps) + driver = driver_class( + 'http://localhost:4723/wd/hub', options=UiAutomator2Options().load_capabilities(desired_caps) + ) return driver @httpretty.activate From 0ff83df100a4bae2d5547e0ba501b501890f6374 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jun 2022 22:34:27 +0200 Subject: [PATCH 078/548] chore(deps): update mypy requirement from ~=0.960 to ~=0.961 (#718) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.960...v0.961) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index fe6dc7927..bec7ec70b 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.14.1" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.960" +mypy = "~=0.961" From d4c44b4b68c611be88007ee666ee8f59c79ce9f1 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 12 Jun 2022 17:24:57 +0200 Subject: [PATCH 079/548] feat: Add base options for all supported automation names (#721) --- appium/options/android/__init__.py | 1 + appium/options/android/espresso/__init__.py | 0 appium/options/android/espresso/base.py | 31 +++++++++++++++++++++ appium/options/gecko/__init__.py | 1 + appium/options/gecko/base.py | 30 ++++++++++++++++++++ appium/options/ios/__init__.py | 1 + appium/options/ios/safari/__init__.py | 0 appium/options/ios/safari/base.py | 31 +++++++++++++++++++++ appium/options/windows/__init__.py | 1 + appium/options/windows/windows/__init__.py | 0 appium/options/windows/windows/base.py | 31 +++++++++++++++++++++ test/unit/webdriver/webdriver_test.py | 6 ++-- 12 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 appium/options/android/espresso/__init__.py create mode 100644 appium/options/android/espresso/base.py create mode 100644 appium/options/gecko/__init__.py create mode 100644 appium/options/gecko/base.py create mode 100644 appium/options/ios/safari/__init__.py create mode 100644 appium/options/ios/safari/base.py create mode 100644 appium/options/windows/__init__.py create mode 100644 appium/options/windows/windows/__init__.py create mode 100644 appium/options/windows/windows/base.py diff --git a/appium/options/android/__init__.py b/appium/options/android/__init__.py index f03a4e7a0..8926c5a87 100644 --- a/appium/options/android/__init__.py +++ b/appium/options/android/__init__.py @@ -1 +1,2 @@ +from .espresso.base import EspressoOptions from .uiautomator2.base import UiAutomator2Options diff --git a/appium/options/android/espresso/__init__.py b/appium/options/android/espresso/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/espresso/base.py b/appium/options/android/espresso/base.py new file mode 100644 index 000000000..b38640c38 --- /dev/null +++ b/appium/options/android/espresso/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class EspressoOptions( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'Espresso', + AppiumOptions.PLATFORM_NAME: 'Android', + } diff --git a/appium/options/gecko/__init__.py b/appium/options/gecko/__init__.py new file mode 100644 index 000000000..1dfd32877 --- /dev/null +++ b/appium/options/gecko/__init__.py @@ -0,0 +1 @@ +from .base import GeckoOptions diff --git a/appium/options/gecko/base.py b/appium/options/gecko/base.py new file mode 100644 index 000000000..483314167 --- /dev/null +++ b/appium/options/gecko/base.py @@ -0,0 +1,30 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class GeckoOptions( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'Gecko', + } diff --git a/appium/options/ios/__init__.py b/appium/options/ios/__init__.py index cc2b162d6..21e82f751 100644 --- a/appium/options/ios/__init__.py +++ b/appium/options/ios/__init__.py @@ -1 +1,2 @@ +from .safari.base import SafariOptions from .xcuitest.base import XCUITestOptions diff --git a/appium/options/ios/safari/__init__.py b/appium/options/ios/safari/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/safari/base.py b/appium/options/ios/safari/base.py new file mode 100644 index 000000000..bbee54a0e --- /dev/null +++ b/appium/options/ios/safari/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class SafariOptions( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.PLATFORM_NAME: 'iOS', + AppiumOptions.AUTOMATION_NAME: 'Safari', + } diff --git a/appium/options/windows/__init__.py b/appium/options/windows/__init__.py new file mode 100644 index 000000000..12d31fe34 --- /dev/null +++ b/appium/options/windows/__init__.py @@ -0,0 +1 @@ +from .windows.base import WindowsOptions diff --git a/appium/options/windows/windows/__init__.py b/appium/options/windows/windows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/windows/windows/base.py b/appium/options/windows/windows/base.py new file mode 100644 index 000000000..f1c34aa43 --- /dev/null +++ b/appium/options/windows/windows/base.py @@ -0,0 +1,31 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict + +from appium.options.common.base import AppiumOptions + + +class WindowsOptions( + AppiumOptions, +): + @property + def default_capabilities(self) -> Dict: + return { + AppiumOptions.AUTOMATION_NAME: 'Windows', + AppiumOptions.PLATFORM_NAME: 'Windows', + } diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 00be6cbdf..add6c7dec 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -40,12 +40,12 @@ def test_create_session(self): ) desired_caps = { - 'platformName': 'Android', 'deviceName': 'Android Emulator', 'app': 'path/to/app', - 'automationName': 'UIAutomator2', } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', options=UiAutomator2Options().load_capabilities(desired_caps) + ) # This tests counts the same request twice on Azure only for now (around 20th May, 2021). Local running works. # Should investigate the cause. From b26766583830ae83e20416629c8bdd24b58e5658 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 13 Jun 2022 18:33:11 +0200 Subject: [PATCH 080/548] test: Use Appium2 to run functional tests (#723) --- .pre-commit-config.yaml | 23 ++++----- README.md | 2 +- azure-pipelines.yml | 9 ---- ci-jobs/functional/run_android_test.yml | 14 ++++-- ci-jobs/functional/run_ios_test.yml | 10 +++- .../{run_appium.yml => setup_appium.yml} | 12 ++--- ci-jobs/functional/start-emulator.sh | 48 +++++++++++++++---- ci-jobs/functional_test.yml | 7 ++- test/functional/android/chrome_tests.py | 6 ++- .../android/context_switching_tests.py | 6 ++- test/functional/android/helper/test_helper.py | 7 ++- .../search_context/find_by_image_tests.py | 6 ++- .../find_by_view_matcher_tests.py | 9 ++-- .../ios/helper/desired_capabilities.py | 11 +++-- test/functional/ios/helper/test_helper.py | 5 +- test/functional/ios/safari_tests.py | 9 ++-- test/functional/ios/webdriver_tests.py | 10 ++-- test/functional/mac/helper/test_helper.py | 4 +- test/helpers/__init__.py | 0 test/helpers/constants.py | 1 + test/unit/helper/test_helper.py | 10 ++-- test/unit/webdriver/webdriver_test.py | 41 ++++++++-------- 22 files changed, 144 insertions(+), 106 deletions(-) rename ci-jobs/functional/{run_appium.yml => setup_appium.yml} (61%) create mode 100644 test/helpers/__init__.py create mode 100644 test/helpers/constants.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 416676dba..094d3bb1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,17 @@ -- repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 +repos: + - repo: https://github.com/pre-commit/mirrors-isort + rev: v5.10 hooks: - - id: isort - args: ["."] -- repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.800' + - id: isort + args: [ "." ] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.961 hooks: - - id: mypy + - id: mypy entry: mypy appium/ test/functional pass_filenames: false -- repo: https://github.com/psf/black - rev: 20.8b1 + - repo: https://github.com/psf/black + rev: 22.3.0 hooks: - - id: black - args: [".", "-l", "120", "-S"] \ No newline at end of file + - id: black + args: [ ".", "-l", "120", "-S" ] diff --git a/README.md b/README.md index 89e64e6cc..907de970b 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ $ pytest test/functional/ios/search_context/find_by_ios_class_chain_tests.py #### In parallel for iOS -1. Create simulators named 'iPhone 8 - 8100' and 'iPhone 8 - 8101' +1. Create simulators named 'iPhone X - 8100' and 'iPhone X - 8101' 2. Install test libraries via pip, `pip install pytest pytest-xdist` 3. Run tests diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 063f712dd..a4c355e51 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,12 +33,3 @@ jobs: inputs: testResultsFiles: '**/junit.xml' testRunTitle: 'Publish test results for Python $(python.version)' - -# Runs tests nightly to make sure they works against appium@beta -schedules: - - cron: "0 0 * * *" - displayName: Daily Nightly build (UTC) - branches: - include: - - master - always: true diff --git a/ci-jobs/functional/run_android_test.yml b/ci-jobs/functional/run_android_test.yml index 4c7df3660..7fa99a198 100644 --- a/ci-jobs/functional/run_android_test.yml +++ b/ci-jobs/functional/run_android_test.yml @@ -4,17 +4,23 @@ jobs: vmImage: ${{ parameters.vmImage }} variables: ANDROID_SDK_VERSION: ${{ parameters.sdkVer }} + ANDROID_AVD: testemulator CI: ${{ parameters.ci }} steps: - - template: ./run_appium.yml - parameters: - OPENCV: ${{ parameters.opencv }} - DONT_RUN_APPIUM: ${{ parameters.dont_run_appium }} + - template: ./setup_appium.yml + - script: appium driver install uiautomator2 + displayName: Install UIAutomator2 driver - script: bash ci-jobs/functional/start-emulator.sh displayName: Create and run Emulator - script: | + if [[ '${{ parameters.dontRunAppium }}' != true ]]; then + appium --relaxed-security > appium_log.txt 2>&1 & + sleep 2 + fi + pushd "$(pwd)" cd test/functional/android python -m pytest ${{ parameters.testFiles}} ${{ parameters.pytestOpt }} + popd displayName: Run Android functional tests - template: ./publish_test_result.yml - template: ./save_appium_log.yml diff --git a/ci-jobs/functional/run_ios_test.yml b/ci-jobs/functional/run_ios_test.yml index 5fa792d0e..122dca1d8 100644 --- a/ci-jobs/functional/run_ios_test.yml +++ b/ci-jobs/functional/run_ios_test.yml @@ -5,13 +5,21 @@ jobs: variables: CI: ${{ parameters.ci }} steps: - - template: ./run_appium.yml + - template: ./setup_appium.yml - template: ./ios_setup.yml parameters: xcodeVersion: ${{ parameters.xcodeForIOS }} + - script: appium driver install xcuitest + displayName: Install XCUITest driver - script: | + if [[ '${{ parameters.dontRunAppium }}' != true ]]; then + appium --relaxed-security > appium_log.txt 2>&1 & + sleep 2 + fi + pushd "$(pwd)" cd test/functional/ios python -m pytest ${{ parameters.testFiles}} ${{ parameters.pytestOpt }} + popd displayName: Run iOS functional tests - template: ./publish_test_result.yml - template: ./save_appium_log.yml diff --git a/ci-jobs/functional/run_appium.yml b/ci-jobs/functional/setup_appium.yml similarity index 61% rename from ci-jobs/functional/run_appium.yml rename to ci-jobs/functional/setup_appium.yml index a866957c4..a6a7e6ace 100644 --- a/ci-jobs/functional/run_appium.yml +++ b/ci-jobs/functional/setup_appium.yml @@ -1,13 +1,10 @@ steps: - task: NodeTool@0 inputs: - versionSpec: '12.x' - displayName: Install Node 12.x -- script: npm install -g appium@beta --chromedriver_version='2.44' + versionSpec: '16.x' + displayName: Install Node 16.x +- script: npm install -g appium@next --chromedriver_version='2.44' displayName: Install appium -- script: npm install -g opencv4nodejs - condition: eq('${{ parameters.opencv }}', true) - displayName: Install opencv4nodejs - task: UsePythonVersion@0 inputs: versionSpec: '3.x' @@ -29,6 +26,3 @@ steps: appium --version node --version displayName: Check versions -- script: nohup appium --relaxed-security > appium_log.txt & - condition: ne('${{ parameters.dont_run_appium }}', true) - displayName: Run Appium in background diff --git a/ci-jobs/functional/start-emulator.sh b/ci-jobs/functional/start-emulator.sh index 6228a9883..ea538d42c 100644 --- a/ci-jobs/functional/start-emulator.sh +++ b/ci-jobs/functional/start-emulator.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# This file comes from https://github.com/appium/ruby_lib_core - # This script was copy-pasted from https://docs.microsoft.com/en-us/azure/devops/pipelines/languages/android?view=azure-devops#test-on-the-android-emulator # with some changes @@ -13,16 +11,48 @@ echo "y" | ${ANDROID_HOME}/tools/bin/sdkmanager --install "$emulator" ${ANDROID_HOME}/tools/bin/avdmanager list # Create emulator -echo "no" | ${ANDROID_HOME}/tools/bin/avdmanager create avd -d "Nexus 6" -n testemulator -k "${emulator}" --force +echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -d "Nexus 6" -n $ANDROID_AVD -k "$emulator" -c 1500M --force echo ${ANDROID_HOME}/emulator/emulator -list-avds echo "Starting emulator" # Start emulator in background -nohup ${ANDROID_HOME}/emulator/emulator -avd testemulator -no-boot-anim -no-snapshot > /dev/null 2>&1 & -${ANDROID_HOME}/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82' - -${ANDROID_HOME}/platform-tools/adb devices - -echo "Emulator started" +nohup $ANDROID_HOME/emulator/emulator -avd $ANDROID_AVD -no-snapshot > /dev/null 2>&1 & + +adb wait-for-device get-serialno +secondsStarted=`date +%s` +TIMEOUT=360 +while [[ $(( `date +%s` - $secondsStarted )) -lt $TIMEOUT ]]; do + # Fail fast if Emulator process crashed + pgrep -nf avd || exit 1 + + pstat=$(adb shell ps) + if ! [[ $pstat =~ "root " ]]; then + # In recent APIs running `ps` without `-A` only returns + # processes belonging to the current user (in this case `shell`) + pstat=$(adb shell ps -A) + fi + + if [[ $pstat =~ "com.android.systemui" ]]; then + echo "System UI process is running. Checking services availability" + if adb shell "ime list && pm get-install-location && echo PASS" | grep -q "PASS"; then + break + fi + fi + + sleep 5 + secondsElapsed=$(( `date +%s` - $secondsStarted )) + secondsLeft=$(( $TIMEOUT - $secondsElapsed )) + echo "Waiting until emulator finishes services startup; ${secondsElapsed}s elapsed; ${secondsLeft}s left" +done + +bootDuration=$(( `date +%s` - $secondsStarted )) +if [[ $bootDuration -ge $TIMEOUT ]]; then + echo "Emulator has failed to fully start within ${TIMEOUT}s" + exit 1 +fi +echo "Emulator booting took ${bootDuration}s" +adb shell input keyevent 82 + +adb devices -l diff --git a/ci-jobs/functional_test.yml b/ci-jobs/functional_test.yml index 2553d34ca..7efab21dd 100644 --- a/ci-jobs/functional_test.yml +++ b/ci-jobs/functional_test.yml @@ -1,8 +1,8 @@ parameters: - vmImage: 'macOS-10.15' + vmImage: 'macOS-12' pytestOpt: '--doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html' androidSdkVer: 27 - xcodeForIOS: 12.3 + xcodeForIOS: 13.4 CI: true jobs: @@ -30,7 +30,6 @@ 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' @@ -47,7 +46,7 @@ jobs: testFiles: 'appium_service_tests.py' sdkVer: ${{ parameters.androidSdkVer }} CI: ${{ parameters.ci }} - DONT_RUN_APPIUM: true + dontRunAppium: true - template: ./functional/run_android_test.yml parameters: name: 'func_test_android4' diff --git a/test/functional/android/chrome_tests.py b/test/functional/android/chrome_tests.py index f7e0ae913..cf0ccae55 100644 --- a/test/functional/android/chrome_tests.py +++ b/test/functional/android/chrome_tests.py @@ -13,7 +13,9 @@ # limitations under the License. from appium import webdriver +from appium.options.common import AppiumOptions from appium.webdriver.common.appiumby import AppiumBy +from test.helpers.constants import SERVER_URL_BASE from .helper.desired_capabilities import get_desired_capabilities @@ -22,13 +24,13 @@ 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) + self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) 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.get(f'{SERVER_URL_BASE}/test/guinea-pig') self.driver.find_element(by=AppiumBy.LINK_TEXT, value='i am a link').click() assert 'I am some other page content' in self.driver.page_source diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index e5ae1e8a0..a8320d846 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -16,7 +16,9 @@ from appium import webdriver from appium.common.exceptions import NoSuchContextException +from appium.options.common import AppiumOptions from appium.webdriver.common.appiumby import AppiumBy +from test.helpers.constants import SERVER_URL_BASE from .helper import desired_capabilities @@ -24,8 +26,8 @@ @pytest.mark.skip(reason="Need to fix broken test") 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) + caps = desired_capabilities.get_desired_capabilities('selendroid-test-app.apk') + self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) def teardown_method(self) -> None: self.driver.quit() diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index 1e94d34db..ddc784e7a 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -19,6 +19,7 @@ from appium import webdriver from appium.options.android import UiAutomator2Options from test.functional.test_helper import is_ci +from test.helpers.constants import SERVER_URL_BASE from . import desired_capabilities @@ -31,10 +32,8 @@ class BaseTestCase: 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', options=UiAutomator2Options().load_capabilities(desired_caps) - ) + caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') + self.driver = webdriver.Remote(SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(caps)) if is_ci(): self.driver.start_recording_screen() 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 907dceaad..ab12e8969 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -18,15 +18,17 @@ from selenium.common.exceptions import NoSuchElementException, TimeoutException from appium import webdriver +from appium.options.common import AppiumOptions from appium.webdriver.common.appiumby import AppiumBy from test.functional.android.helper import desired_capabilities from test.functional.test_helper import wait_for_element +from test.helpers.constants import SERVER_URL_BASE class TestFindByImage(object): def setup_method(self) -> None: - desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') - self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') + self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) # relax template matching self.driver.update_settings( 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 index d71fda4bd..ccb7a2ae3 100644 --- a/test/functional/android/search_context/find_by_view_matcher_tests.py +++ b/test/functional/android/search_context/find_by_view_matcher_tests.py @@ -18,17 +18,18 @@ from selenium.common.exceptions import WebDriverException from appium import webdriver +from appium.options.common import AppiumOptions from appium.webdriver.common.appiumby import AppiumBy -from appium.webdriver.extensions.search_context.android import AndroidSearchContext from test.functional.android.helper.test_helper import BaseTestCase, desired_capabilities, is_ci +from test.helpers.constants import SERVER_URL_BASE 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) + caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') + caps['automationName'] = 'Espresso' + self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) if is_ci(): self.driver.start_recording_screen() diff --git a/test/functional/ios/helper/desired_capabilities.py b/test/functional/ios/helper/desired_capabilities.py index ff0c00571..fc9aa0b75 100644 --- a/test/functional/ios/helper/desired_capabilities.py +++ b/test/functional/ios/helper/desired_capabilities.py @@ -28,7 +28,7 @@ def get_desired_capabilities(app: Optional[str] = None) -> Dict[str, Any]: desired_caps: Dict[str, Any] = { 'deviceName': iphone_device_name(), 'platformName': 'iOS', - 'platformVersion': '14.3', + 'platformVersion': '15.5', 'automationName': 'XCUITest', 'allowTouchIdEnroll': True, 'wdaLocalPort': wda_port(), @@ -66,13 +66,14 @@ def wda_port() -> int: return 8100 -# Before running tests, you must have iOS simulators named 'iPhone 8 - 8100' and 'iPhone 8 - 8101' +# Before running tests, you must have iOS simulators named 'iPhone 12 - 8100' and 'iPhone 12 - 8101' def iphone_device_name() -> str: + prefix = 'iPhone 12' if PytestXdistWorker.NUMBER == PytestXdistWorker.gw(0): - return 'iPhone 8 - 8100' + return f'{prefix} - 8100' elif PytestXdistWorker.NUMBER == PytestXdistWorker.gw(1): - return 'iPhone 8 - 8101' + return f'{prefix} - 8101' - return 'iPhone 8' + return prefix diff --git a/test/functional/ios/helper/test_helper.py b/test/functional/ios/helper/test_helper.py index 3491aa25f..2a975aa58 100644 --- a/test/functional/ios/helper/test_helper.py +++ b/test/functional/ios/helper/test_helper.py @@ -18,6 +18,7 @@ from appium import webdriver from appium.options.ios import XCUITestOptions from test.functional.test_helper import is_ci +from test.helpers.constants import SERVER_URL_BASE from . import desired_capabilities @@ -25,9 +26,7 @@ class BaseTestCase(object): def setup_method(self) -> None: desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') - self.driver = webdriver.Remote( - 'http://localhost:4723/wd/hub', options=XCUITestOptions().load_capabilities(desired_caps) - ) + self.driver = webdriver.Remote(SERVER_URL_BASE, options=XCUITestOptions().load_capabilities(desired_caps)) if is_ci(): self.driver.start_recording_screen() diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index ffc910189..312f6ceb4 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -15,16 +15,17 @@ import time from appium import webdriver +from appium.options.common import AppiumOptions +from test.helpers.constants import SERVER_URL_BASE from .helper.desired_capabilities import get_desired_capabilities class TestSafari: def setup_method(self) -> None: - desired_caps = get_desired_capabilities() - desired_caps.update({'browserName': 'safari', 'nativeWebTap': True, 'safariIgnoreFraudWarning': True}) - - self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + caps = get_desired_capabilities() + caps.update({'browserName': 'safari', 'nativeWebTap': True, 'safariIgnoreFraudWarning': True}) + self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) def teardown_method(self) -> None: self.driver.quit() diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index b132a2810..597c42f0a 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -18,10 +18,12 @@ from selenium.webdriver.support.ui import WebDriverWait from appium import webdriver +from appium.options.common import AppiumOptions from appium.webdriver.applicationstate import ApplicationState from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase from test.functional.test_helper import get_available_from_port_range, wait_for_condition +from test.helpers.constants import SERVER_URL_BASE from ..test_helper import is_ci from .helper import desired_capabilities @@ -36,9 +38,9 @@ class TestWebDriver(BaseTestCase): @pytest.mark.skipif(condition=is_ci(), reason='Need to fix flaky test during running on CI.') def test_all_sessions(self) -> None: port = get_available_from_port_range(8200, 8300) - desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') - desired_caps['deviceName'] = 'iPhone Xs Max' - desired_caps['wdaLocalPort'] = port + caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') + caps['deviceName'] = 'iPhone Xs Max' + caps['wdaLocalPort'] = port class session_counts_is_two: TIMEOUT = 10 @@ -48,7 +50,7 @@ def __call__(self, driver: 'WebDriver') -> bool: driver2 = None try: - driver2 = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + driver2 = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) WebDriverWait(driver2, session_counts_is_two.TIMEOUT).until(session_counts_is_two()) assert len(self.driver.all_sessions) == 2 finally: diff --git a/test/functional/mac/helper/test_helper.py b/test/functional/mac/helper/test_helper.py index 5232d0641..1e5e4c3bd 100644 --- a/test/functional/mac/helper/test_helper.py +++ b/test/functional/mac/helper/test_helper.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. - from appium import webdriver from appium.options.mac import Mac2Options +from test.helpers.constants import SERVER_URL_BASE from .desired_capabilities import get_desired_capabilities @@ -22,7 +22,7 @@ class BaseTestCase(object): def setup_method(self) -> None: self.driver = webdriver.Remote( - 'http://localhost:4723/wd/hub', options=Mac2Options().load_capabilities(get_desired_capabilities()) + SERVER_URL_BASE, options=Mac2Options().load_capabilities(get_desired_capabilities()) ) def teardown_method(self, method) -> None: # type: ignore diff --git a/test/helpers/__init__.py b/test/helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/helpers/constants.py b/test/helpers/constants.py new file mode 100644 index 000000000..9f453add8 --- /dev/null +++ b/test/helpers/constants.py @@ -0,0 +1 @@ +SERVER_URL_BASE = 'http://127.0.0.1:4723' diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py index b0a818e9b..6c85eb2e2 100644 --- a/test/unit/helper/test_helper.py +++ b/test/unit/helper/test_helper.py @@ -20,9 +20,7 @@ from appium import webdriver from appium.options.android import UiAutomator2Options from appium.options.ios import XCUITestOptions - -# :return: A string of test URL -SERVER_URL_BASE = 'http://localhost:4723/wd/hub' +from test.helpers.constants import SERVER_URL_BASE if TYPE_CHECKING: from httpretty.core import HTTPrettyRequestEmpty @@ -36,7 +34,11 @@ def appium_command(command: str) -> str: Returns: str: A string of command URL """ - return f'{SERVER_URL_BASE}{command}' + return ( + f'{SERVER_URL_BASE}{command}' + if SERVER_URL_BASE.endswith('/') or command.startswith('/') + else f'{SERVER_URL_BASE}/{command}' + ) def android_w3c_driver() -> 'WebDriver': diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index add6c7dec..13fbfab6d 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -21,6 +21,7 @@ from appium import webdriver from appium.options.android import UiAutomator2Options from appium.webdriver.webdriver import ExtensionBase, WebDriver +from test.helpers.constants import SERVER_URL_BASE from test.unit.helper.test_helper import ( android_w3c_driver, appium_command, @@ -35,7 +36,7 @@ class TestWebDriverWebDriver(object): def test_create_session(self): httpretty.register_uri( httpretty.POST, - 'http://localhost:4723/wd/hub/session', + f'{SERVER_URL_BASE}/session', body='{ "value": { "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}}}', ) @@ -43,9 +44,7 @@ def test_create_session(self): 'deviceName': 'Android Emulator', 'app': 'path/to/app', } - driver = webdriver.Remote( - 'http://localhost:4723/wd/hub', options=UiAutomator2Options().load_capabilities(desired_caps) - ) + driver = webdriver.Remote(SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(desired_caps)) # This tests counts the same request twice on Azure only for now (around 20th May, 2021). Local running works. # Should investigate the cause. @@ -71,20 +70,20 @@ def test_create_session(self): def test_create_session_change_session_id(self): httpretty.register_uri( httpretty.POST, - 'http://localhost:4723/wd/hub/session', + f'{SERVER_URL_BASE}/session', body='{ "value": { "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}}}', ) httpretty.register_uri( httpretty.GET, - 'http://localhost:4723/wd/hub/session/another-session-id/title', + f'{SERVER_URL_BASE}/session/another-session-id/title', body='{ "value": "title on another session id"}', ) options = ( UiAutomator2Options().set_capability('deviceName', 'Android Emulator').set_capability('app', 'path/to/app') ) - driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options) + driver = webdriver.Remote(SERVER_URL_BASE, options=options) # current session assert driver.session_id == 'session-id' @@ -98,7 +97,7 @@ def test_create_session_change_session_id(self): def test_create_session_register_uridirect(self): httpretty.register_uri( httpretty.POST, - 'http://localhost:4723/wd/hub/session', + f'{SERVER_URL_BASE}/session', body=json.dumps( { 'value': { @@ -128,7 +127,7 @@ def test_create_session_register_uridirect(self): 'automationName': 'UIAutomator2', } driver = webdriver.Remote( - 'http://localhost:4723/wd/hub', + SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(desired_caps), direct_connection=True, ) @@ -140,7 +139,7 @@ def test_create_session_register_uridirect(self): def test_create_session_register_uridirect_no_direct_connect_path(self): httpretty.register_uri( httpretty.POST, - 'http://localhost:4723/wd/hub/session', + f'{SERVER_URL_BASE}/session', body=json.dumps( { 'value': { @@ -158,7 +157,7 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): httpretty.register_uri( httpretty.GET, - 'http://localhost:4723/wd/hub/session/session-id/contexts', + f'{SERVER_URL_BASE}/session/session-id/contexts', body=json.dumps({'value': ['NATIVE_APP', 'CHROMIUM']}), ) @@ -169,12 +168,12 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): 'automationName': 'UIAutomator2', } driver = webdriver.Remote( - 'http://localhost:4723/wd/hub', + SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(desired_caps), direct_connection=True, ) - assert 'http://localhost:4723/wd/hub' == driver.command_executor._url + assert SERVER_URL_BASE == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts @httpretty.activate @@ -242,12 +241,12 @@ def test_command(self): return self.execute()['value'] def add_command(self): - return 'get', 'session/$sessionId/path/to/custom/url' + return 'get', '/session/$sessionId/path/to/custom/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( httpretty.GET, - appium_command('session/1234567890/path/to/custom/url'), + appium_command('/session/1234567890/path/to/custom/url'), body=json.dumps({'value': {}}), ) result = driver.test_command() @@ -265,12 +264,12 @@ def test_command(self, argument): return self.execute(argument)['value'] def add_command(self): - return 'post', 'session/$sessionId/path/to/custom/url' + return 'post', '/session/$sessionId/path/to/custom/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( httpretty.POST, - appium_command('session/1234567890/path/to/custom/url'), + appium_command('/session/1234567890/path/to/custom/url'), body=json.dumps({'value': {}}), ) result = driver.test_command({'dummy': 'test argument'}) @@ -291,12 +290,12 @@ def test_command(self, element_id): return self.execute({'id': element_id})['value'] def add_command(self): - return 'GET', 'session/$sessionId/path/to/custom/$id/url' + return 'GET', '/session/$sessionId/path/to/custom/$id/url' driver = ios_w3c_driver_with_extensions([CustomURLCommand]) httpretty.register_uri( httpretty.GET, - appium_command('session/1234567890/path/to/custom/element_id/url'), + appium_command('/session/1234567890/path/to/custom/element_id/url'), body=json.dumps({'value': {}}), ) result = driver.test_command('element_id') @@ -363,9 +362,7 @@ def android_w3c_driver(self, driver_class): 'automationName': 'UIAutomator2', } - driver = driver_class( - 'http://localhost:4723/wd/hub', options=UiAutomator2Options().load_capabilities(desired_caps) - ) + driver = driver_class(SERVER_URL_BASE, options=UiAutomator2Options().load_capabilities(desired_caps)) return driver @httpretty.activate From a2bba19360a6205fc6f0679b595ae560181e70c3 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 13 Jun 2022 18:33:27 +0200 Subject: [PATCH 081/548] docs: Update README with the new options format (#722) --- README.md | 88 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 907de970b..e5e0201b5 100644 --- a/README.md +++ b/README.md @@ -104,37 +104,38 @@ environment: ```python # Android environment -import unittest from appium import webdriver +# Options are only available since client version 2.3.0 +# If you use an older client then switch to desired_capabilities +# instead: https://github.com/appium/python-client/pull/720 +from appium.options.android import UiAutomator2Options from appium.webdriver.common.appiumby import AppiumBy -desired_caps = dict( - platformName='Android', - platformVersion='10', - automationName='uiautomator2', - deviceName='Android Emulator', - app=PATH('../../../apps/selendroid-test-app.apk') -) -self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) +options = UiAutomator2Options().\ + set_capability('platformVersion', '10').\ + set_capability('deviceName', 'Android Emulator').\ + set_capability('app', PATH('../../../apps/selendroid-test-app.apk')) +# Appium1 points to http://127.0.0.1:4723/wd/hub by default +self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() ``` ```python # iOS environment -import unittest from appium import webdriver +# Options are only available since client version 2.3.0 +# If you use an older client then switch to desired_capabilities +# instead: https://github.com/appium/python-client/pull/720 +from appium.options.ios import XCUITestOptions from appium.webdriver.common.appiumby import AppiumBy -desired_caps = dict( - platformName='iOS', - platformVersion='13.4', - automationName='xcuitest', - deviceName='iPhone Simulator', - app=PATH('../../apps/UICatalog.app.zip') -) - -self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) +options = XCUITestOptions().\ + set_capability('platformVersion', '13.4').\ + set_capability('deviceName', 'iPhone Simulator').\ + set_capability('app', PATH('../../apps/UICatalog.app.zip')) +# Appium1 points to http://127.0.0.1:4723/wd/hub by default +self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() ``` @@ -151,18 +152,24 @@ If your Selenium/Appium server decorates the new session capabilities response w Then python client will switch its endpoint to the one specified by the values of those keys. ```python -import unittest from appium import webdriver - -desired_caps = dict( - platformName='iOS', - platformVersion='13.4', - automationName='xcuitest', - deviceName='iPhone Simulator', - app=PATH('../../apps/UICatalog.app.zip') +# Options are only available since client version 2.3.0 +# If you use an older client then switch to desired_capabilities +# instead: https://github.com/appium/python-client/pull/720 +from appium.options.ios import XCUITestOptions + +options = XCUITestOptions().load_capabilities({ + 'platformVersion': '13.4', + 'deviceName': 'iPhone Simulator', + 'app': PATH('../../apps/UICatalog.app.zip'), +}) + +self.driver = webdriver.Remote( + # Appium1 points to http://127.0.0.1:4723/wd/hub by default + 'http://127.0.0.1:4723', + options=options, + direct_connection=True ) - -self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, direct_connection=True) ``` ## Relax SSL validation @@ -170,18 +177,19 @@ self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, dir `strict_ssl` option allows you to send commands to an invalid certificate host like a self-signed one. ```python -import unittest from appium import webdriver - -desired_caps = dict( - platformName='iOS', - platformVersion='13.4', - automationName='xcuitest', - deviceName='iPhone Simulator', - app=PATH('../../apps/UICatalog.app.zip') -) - -self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, strict_ssl=False) +# Options are only available since client version 2.3.0 +# If you use an older client then switch to desired_capabilities +# instead: https://github.com/appium/python-client/pull/720 +from appium.options.common import AppiumOptions + +options = AppiumOptions() +options.platform_name = 'mac' +options.automation_name = 'safari' +options.set_capability('browser_name', 'safari') + +# Appium1 points to http://127.0.0.1:4723/wd/hub by default +self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=False) ``` ## Documentation From 0fcea82c9bcaf9137bc7b1591c37b092927751a2 Mon Sep 17 00:00:00 2001 From: VladimirPodolyan <36446855+VladimirPodolyan@users.noreply.github.com> Date: Wed, 15 Jun 2022 04:28:32 +0300 Subject: [PATCH 082/548] chore: Update comments to locator patches (#724) * Update webelement.py * update comment section * CR fixes --- appium/webdriver/webdriver.py | 8 ++++++-- appium/webdriver/webelement.py | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 16257df4b..19da31b12 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -337,7 +337,9 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> `appium.webdriver.webelement.WebElement`: The found element """ - # TODO: If we need, we should enable below converter for Web context + # We prefer to patch locators in the client code + # Checking current context every time a locator is accessed could significantly slow down tests + # Check https://github.com/appium/python-client/pull/724 before submitting any issue # if by == By.ID: # by = By.CSS_SELECTOR # value = '[id="%s"]' % value @@ -368,7 +370,9 @@ def find_elements( Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements """ - # TODO: If we need, we should enable below converter for Web context + # We prefer to patch locators in the client code + # Checking current context every time a locator is accessed could significantly slow down tests + # Check https://github.com/appium/python-client/pull/724 before submitting any issue # if by == By.ID: # by = By.CSS_SELECTOR # value = '[id="%s"]' % value diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 922bded43..2e4fca90c 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -97,7 +97,9 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> Returns: `appium.webdriver.webelement.WebElement` """ - # TODO: If we need, we should enable below converter for Web context + # We prefer to patch locators in the client code + # Checking current context every time a locator is accessed could significantly slow down tests + # Check https://github.com/appium/python-client/pull/724 before submitting any issue # if by == By.ID: # by = By.CSS_SELECTOR # value = '[id="%s"]' % value @@ -125,7 +127,9 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) - Returns: :obj:`list` of :obj:`appium.webdriver.webelement.WebElement` """ - # TODO: If we need, we should enable below converter for Web context + # We prefer to patch locators in the client code + # Checking current context every time a locator is accessed could significantly slow down tests + # Check https://github.com/appium/python-client/pull/724 before submitting any issue # if by == By.ID: # by = By.CSS_SELECTOR # value = '[id="%s"]' % value @@ -135,8 +139,8 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) - # by = By.CSS_SELECTOR # value = ".%s" % value # elif by == By.NAME: - # by = By.CSS_SELECTOR - # value = '[name="%s"]' % value + # by = By.CSS_SELECTOR + # value = '[name="%s"]' % value return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] From 4cfd355f57989b890a35c4ff6ca08cd5e07db715 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 15 Jun 2022 22:21:37 -0700 Subject: [PATCH 083/548] Bump 2.3.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index d4b750f9f..a4b8895b3 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.2.0' +version = '2.3.0' From 8321b9c5122b1f848c34d0dc230e38d522e09fa9 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 15 Jun 2022 22:21:58 -0700 Subject: [PATCH 084/548] docs: Update changelog for 2.3.0 --- CHANGELOG.rst | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c65096245..8f48c12d9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,140 @@ Changelog ========= +v2.3.0 (2022-06-13) +------------------- + +New +~~~ +- Feat: Add base options for all supported automation names (#721) + [Mykola Mokhnach] +- Feat: Add support for w3c options (#720) [Mykola Mokhnach] + +Test +~~~~ +- Test: Use Appium2 to run functional tests (#723) [Mykola Mokhnach] + +Other +~~~~~ +- Docs: Update README with the new options format (#722) [Mykola + Mokhnach] +- Chore(deps): update mypy requirement from ~=0.960 to ~=0.961 (#718) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.960...v0.961) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore: Disable pylint checks fail CI (#719) [Mykola Mokhnach] +- Chore(deps): update selenium requirement from ~=4.1 to ~=4.2 (#715) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.1.0...selenium-4.2.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update sphinx requirement from <5.0,>=4.0 to >=4.0,<6.0 + (#716) [dependabot[bot]] + + Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. + - [Release notes](https://github.com/sphinx-doc/sphinx/releases) + - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) + - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.0...v5.0.0) + + --- + updated-dependencies: + - dependency-name: sphinx + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.950 to ~=0.960 (#714) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.950...v0.960) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.18 to ~=2.19 + (#713) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.18.0...v2.19.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update mypy requirement from ~=0.942 to ~=0.950 (#712) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.942...v0.950) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update typing-extensions requirement from ~=4.1 to ~=4.2 + (#711) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. + - [Release notes](https://github.com/python/typing/releases) + - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) + - [Commits](https://github.com/python/typing/compare/4.1.0...4.2.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.24 to ~=3.25 (#709) + [dependabot[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.24.0...3.25.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.17 to ~=2.18 + (#708) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Update changelog for 2.2.0. [Kazuaki Matsuo] + + v2.2.0 (2022-03-30) ------------------- From 60ec7ce69a9224eab28af3e89bf9ea1acb920ac4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 16 Jun 2022 10:24:48 +0200 Subject: [PATCH 085/548] feat: Add common options (#728) --- appium/options/android/espresso/base.py | 25 +++++++++-- appium/options/android/uiautomator2/base.py | 31 ++++++++++++-- appium/options/common/app_option.py | 41 +++++++++++++++++++ appium/options/common/auto_web_view_option.py | 40 ++++++++++++++++++ .../options/common/automation_name_option.py | 16 +++----- appium/options/common/base.py | 12 +++++- .../common/clear_system_files_option.py | 38 +++++++++++++++++ appium/options/common/device_name_option.py | 38 +++++++++++++++++ .../enable_performance_logging_option.py | 38 +++++++++++++++++ appium/options/common/event_timings_option.py | 40 ++++++++++++++++++ appium/options/common/full_reset_option.py | 38 +++++++++++++++++ appium/options/common/is_headless_option.py | 39 ++++++++++++++++++ appium/options/common/language_option.py | 38 +++++++++++++++++ appium/options/common/locale_option.py | 38 +++++++++++++++++ .../common/new_command_timeout_option.py | 41 +++++++++++++++++++ appium/options/common/no_reset_option.py | 38 +++++++++++++++++ appium/options/common/orientation_option.py | 40 ++++++++++++++++++ appium/options/common/other_apps_option.py | 39 ++++++++++++++++++ appium/options/common/postrun_option.py | 39 ++++++++++++++++++ appium/options/common/prerun_option.py | 40 ++++++++++++++++++ ...rint_page_source_on_find_failure_option.py | 40 ++++++++++++++++++ .../options/common/skip_log_capture_option.py | 38 +++++++++++++++++ appium/options/common/udid_option.py | 38 +++++++++++++++++ appium/options/gecko/base.py | 3 +- appium/options/ios/safari/base.py | 3 +- appium/options/ios/xcuitest/base.py | 31 ++++++++++++-- appium/options/mac/mac2/base.py | 11 +++-- appium/options/windows/windows/base.py | 11 +++-- 28 files changed, 856 insertions(+), 28 deletions(-) create mode 100644 appium/options/common/app_option.py create mode 100644 appium/options/common/auto_web_view_option.py create mode 100644 appium/options/common/clear_system_files_option.py create mode 100644 appium/options/common/device_name_option.py create mode 100644 appium/options/common/enable_performance_logging_option.py create mode 100644 appium/options/common/event_timings_option.py create mode 100644 appium/options/common/full_reset_option.py create mode 100644 appium/options/common/is_headless_option.py create mode 100644 appium/options/common/language_option.py create mode 100644 appium/options/common/locale_option.py create mode 100644 appium/options/common/new_command_timeout_option.py create mode 100644 appium/options/common/no_reset_option.py create mode 100644 appium/options/common/orientation_option.py create mode 100644 appium/options/common/other_apps_option.py create mode 100644 appium/options/common/postrun_option.py create mode 100644 appium/options/common/prerun_option.py create mode 100644 appium/options/common/print_page_source_on_find_failure_option.py create mode 100644 appium/options/common/skip_log_capture_option.py create mode 100644 appium/options/common/udid_option.py diff --git a/appium/options/android/espresso/base.py b/appium/options/android/espresso/base.py index b38640c38..34ce50c22 100644 --- a/appium/options/android/espresso/base.py +++ b/appium/options/android/espresso/base.py @@ -17,15 +17,34 @@ from typing import Dict -from appium.options.common.base import AppiumOptions +from appium.options.common.app_option import AppOption +from appium.options.common.auto_web_view_option import AutoWebViewOption +from appium.options.common.automation_name_option import AUTOMATION_NAME +from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.device_name_option import DeviceNameOption +from appium.options.common.is_headless_option import IsHeadlessOption +from appium.options.common.language_option import LanguageOption +from appium.options.common.locale_option import LocaleOption +from appium.options.common.orientation_option import OrientationOption +from appium.options.common.skip_log_capture_option import SkipLogCaptureOption +from appium.options.common.udid_option import UdidOption class EspressoOptions( AppiumOptions, + AppOption, + OrientationOption, + UdidOption, + LanguageOption, + LocaleOption, + IsHeadlessOption, + SkipLogCaptureOption, + AutoWebViewOption, + DeviceNameOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'Espresso', - AppiumOptions.PLATFORM_NAME: 'Android', + AUTOMATION_NAME: 'Espresso', + PLATFORM_NAME: 'Android', } diff --git a/appium/options/android/uiautomator2/base.py b/appium/options/android/uiautomator2/base.py index d067d78f1..91727876e 100644 --- a/appium/options/android/uiautomator2/base.py +++ b/appium/options/android/uiautomator2/base.py @@ -17,15 +17,40 @@ from typing import Dict -from appium.options.common.base import AppiumOptions +from appium.options.common.app_option import AppOption +from appium.options.common.auto_web_view_option import AutoWebViewOption +from appium.options.common.automation_name_option import AUTOMATION_NAME +from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.clear_system_files_option import ClearSystemFilesOption +from appium.options.common.device_name_option import DeviceNameOption +from appium.options.common.enable_performance_logging_option import EnablePerformanceLoggingOption +from appium.options.common.is_headless_option import IsHeadlessOption +from appium.options.common.language_option import LanguageOption +from appium.options.common.locale_option import LocaleOption +from appium.options.common.orientation_option import OrientationOption +from appium.options.common.other_apps_option import OtherAppsOption +from appium.options.common.skip_log_capture_option import SkipLogCaptureOption +from appium.options.common.udid_option import UdidOption class UiAutomator2Options( AppiumOptions, + AppOption, + ClearSystemFilesOption, + OrientationOption, + UdidOption, + LanguageOption, + LocaleOption, + IsHeadlessOption, + SkipLogCaptureOption, + AutoWebViewOption, + EnablePerformanceLoggingOption, + OtherAppsOption, + DeviceNameOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'UIAutomator2', - AppiumOptions.PLATFORM_NAME: 'Android', + AUTOMATION_NAME: 'UIAutomator2', + PLATFORM_NAME: 'Android', } diff --git a/appium/options/common/app_option.py b/appium/options/common/app_option.py new file mode 100644 index 000000000..55fd2abba --- /dev/null +++ b/appium/options/common/app_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +APP = 'app' + + +class AppOption(SupportsCapabilities): + @property + def app(self) -> Optional[str]: + """ + :Returns: String representing app location. + """ + return self.get_capability(APP) + + @app.setter + def app(self, value: str) -> None: + """ + Set the absolute local path for the location of the App. + The app must be located on the same machine where Appium + server is running. + Could also be a valid URL. + """ + self.set_capability(APP, value) diff --git a/appium/options/common/auto_web_view_option.py b/appium/options/common/auto_web_view_option.py new file mode 100644 index 000000000..3ba7965c6 --- /dev/null +++ b/appium/options/common/auto_web_view_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +AUTO_WEB_VIEW = 'autoWebView' + + +class AutoWebViewOption(SupportsCapabilities): + @property + def auto_web_view(self) -> Optional[bool]: + """ + :Returns: Whether the driver should try to automatically switch + to a web view context after the session is started. + """ + return self.get_capability(AUTO_WEB_VIEW) + + @auto_web_view.setter + def auto_web_view(self, value: bool) -> None: + """ + Set whether the driver should try to automatically switch + a web view context after the session is started. + """ + self.set_capability(AUTO_WEB_VIEW, value) diff --git a/appium/options/common/automation_name_option.py b/appium/options/common/automation_name_option.py index b52250608..3250b5700 100644 --- a/appium/options/common/automation_name_option.py +++ b/appium/options/common/automation_name_option.py @@ -19,24 +19,20 @@ from .supports_capabilities import SupportsCapabilities +AUTOMATION_NAME = 'automationName' -class AutomationNameOption(SupportsCapabilities): - AUTOMATION_NAME = 'automationName' +class AutomationNameOption(SupportsCapabilities): @property def automation_name(self) -> Optional[str]: """ - :Returns: String representing the name of the automation engine + :Returns: String representing the name of the automation engine name. """ - return self.get_capability(self.AUTOMATION_NAME) + return self.get_capability(AUTOMATION_NAME) @automation_name.setter def automation_name(self, value: str) -> None: """ - Set the automation driver to use. - - :Args: - - value: One of supported automation names - + Set the automation driver name to use for the given platform. """ - self.set_capability(self.AUTOMATION_NAME, value) + self.set_capability(AUTOMATION_NAME, value) diff --git a/appium/options/common/base.py b/appium/options/common/base.py index f7245d257..41d36d06d 100644 --- a/appium/options/common/base.py +++ b/appium/options/common/base.py @@ -21,17 +21,27 @@ from selenium.webdriver.common.options import BaseOptions from .automation_name_option import AutomationNameOption +from .event_timings_option import EventTimingsOption +from .full_reset_option import FullResetOption +from .new_command_timeout_option import NewCommandTimeoutOption +from .no_reset_option import NoResetOption +from .print_page_source_on_find_failure_option import PrintPageSourceOnFindFailureOption APPIUM_PREFIX = 'appium:' T = TypeVar('T', bound='AppiumOptions') +PLATFORM_NAME = 'platformName' class AppiumOptions( BaseOptions, AutomationNameOption, + EventTimingsOption, + PrintPageSourceOnFindFailureOption, + NoResetOption, + FullResetOption, + NewCommandTimeoutOption, ): _caps: Dict - PLATFORM_NAME = 'platformName' W3C_CAPABILITY_NAMES = frozenset( [ 'acceptInsecureCerts', diff --git a/appium/options/common/clear_system_files_option.py b/appium/options/common/clear_system_files_option.py new file mode 100644 index 000000000..9eb6fceaa --- /dev/null +++ b/appium/options/common/clear_system_files_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +CLEAR_SYSTEM_FILES = 'clearSystemFiles' + + +class ClearSystemFilesOption(SupportsCapabilities): + @property + def clear_system_files(self) -> Optional[bool]: + """ + :Returns: Whether the driver should delete generated files at the end of a session. + """ + return self.get_capability(CLEAR_SYSTEM_FILES) + + @clear_system_files.setter + def clear_system_files(self, value: bool) -> None: + """ + Set whether the driver should delete generated files at the end of a session. + """ + self.set_capability(CLEAR_SYSTEM_FILES, value) diff --git a/appium/options/common/device_name_option.py b/appium/options/common/device_name_option.py new file mode 100644 index 000000000..34d8f989c --- /dev/null +++ b/appium/options/common/device_name_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +DEVICE_NAME = 'deviceName' + + +class DeviceNameOption(SupportsCapabilities): + @property + def device_name(self) -> Optional[str]: + """ + :Returns: The name of the device. + """ + return self.get_capability(DEVICE_NAME) + + @device_name.setter + def device_name(self, value: str) -> None: + """ + Set the name of the device to be used in the test. + """ + self.set_capability(DEVICE_NAME, value) diff --git a/appium/options/common/enable_performance_logging_option.py b/appium/options/common/enable_performance_logging_option.py new file mode 100644 index 000000000..d6ea1d682 --- /dev/null +++ b/appium/options/common/enable_performance_logging_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +ENABLE_PERFORMANCE_LOGGING = 'enablePerformanceLogging' + + +class EnablePerformanceLoggingOption(SupportsCapabilities): + @property + def enable_performance_logging(self) -> Optional[bool]: + """ + :Returns: Whether to enable additional performance logging. + """ + return self.get_capability(ENABLE_PERFORMANCE_LOGGING) + + @enable_performance_logging.setter + def enable_performance_logging(self, value: bool) -> None: + """ + Set whether to enable additional performance logging. + """ + self.set_capability(ENABLE_PERFORMANCE_LOGGING, value) diff --git a/appium/options/common/event_timings_option.py b/appium/options/common/event_timings_option.py new file mode 100644 index 000000000..d9d8b3e2f --- /dev/null +++ b/appium/options/common/event_timings_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +EVENT_TIMINGS = 'eventTimings' + + +class EventTimingsOption(SupportsCapabilities): + @property + def event_timings(self) -> Optional[bool]: + """ + :Returns: Whether the driver should to report the timings + for various Appium-internal events. + """ + return self.get_capability(EVENT_TIMINGS) + + @event_timings.setter + def event_timings(self, value: bool) -> None: + """ + Set whether the driver should to report the timings + for various Appium-internal events. + """ + self.set_capability(EVENT_TIMINGS, value) diff --git a/appium/options/common/full_reset_option.py b/appium/options/common/full_reset_option.py new file mode 100644 index 000000000..fc8f53040 --- /dev/null +++ b/appium/options/common/full_reset_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +FULL_RESET = 'fullReset' + + +class FullResetOption(SupportsCapabilities): + @property + def full_reset(self) -> Optional[bool]: + """ + :Returns: Whether the driver should perform a full reset. + """ + return self.get_capability(FULL_RESET) + + @full_reset.setter + def full_reset(self, value: bool) -> None: + """ + Set whether the driver should perform a full reset. + """ + self.set_capability(FULL_RESET, value) diff --git a/appium/options/common/is_headless_option.py b/appium/options/common/is_headless_option.py new file mode 100644 index 000000000..3ab3fc98f --- /dev/null +++ b/appium/options/common/is_headless_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +IS_HEADLESS = 'isHeadless' + + +class IsHeadlessOption(SupportsCapabilities): + @property + def is_headless(self) -> Optional[bool]: + """ + :Returns: Whether the driver should start emulator/simulator in headless mode. + """ + return self.get_capability(IS_HEADLESS) + + @is_headless.setter + def is_headless(self, value: bool) -> None: + """ + Set emulator/simulator to start in headless mode (e.g. no UI is shown). + It is only applied if the emulator is not running before the test starts. + """ + self.set_capability(IS_HEADLESS, value) diff --git a/appium/options/common/language_option.py b/appium/options/common/language_option.py new file mode 100644 index 000000000..c12ebd37b --- /dev/null +++ b/appium/options/common/language_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +LANGUAGE = 'language' + + +class LanguageOption(SupportsCapabilities): + @property + def language(self) -> Optional[str]: + """ + :Returns: Language abbreviation to use in a test session. + """ + return self.get_capability(LANGUAGE) + + @language.setter + def language(self, value: str) -> None: + """ + Set language abbreviation to use in a test session. + """ + self.set_capability(LANGUAGE, value) diff --git a/appium/options/common/locale_option.py b/appium/options/common/locale_option.py new file mode 100644 index 000000000..2c9d24e38 --- /dev/null +++ b/appium/options/common/locale_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +LOCALE = 'locale' + + +class LocaleOption(SupportsCapabilities): + @property + def locale(self) -> Optional[str]: + """ + :Returns: Locale abbreviation to use in a test session. + """ + return self.get_capability(LOCALE) + + @locale.setter + def locale(self, value: str) -> None: + """ + Set locale abbreviation to use in a test session. + """ + self.set_capability(LOCALE, value) diff --git a/appium/options/common/new_command_timeout_option.py b/appium/options/common/new_command_timeout_option.py new file mode 100644 index 000000000..a91f2ee53 --- /dev/null +++ b/appium/options/common/new_command_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from .supports_capabilities import SupportsCapabilities + +NEW_COMMAND_TIMEOUT = 'newCommandTimeout' + + +class NewCommandTimeoutOption(SupportsCapabilities): + @property + def new_command_timeout(self) -> Optional[timedelta]: + """ + :Returns: The allowed time before seeing a new server command. + """ + value = self.get_capability(NEW_COMMAND_TIMEOUT) + return None if value is None else timedelta(seconds=value) + + @new_command_timeout.setter + def new_command_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the allowed time before seeing a new server command. + The value could either be provided as timedelta instance or an integer number of seconds. + """ + self.set_capability(NEW_COMMAND_TIMEOUT, value.seconds if isinstance(value, timedelta) else value) diff --git a/appium/options/common/no_reset_option.py b/appium/options/common/no_reset_option.py new file mode 100644 index 000000000..01ca5e7b5 --- /dev/null +++ b/appium/options/common/no_reset_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +NO_RESET = 'noReset' + + +class NoResetOption(SupportsCapabilities): + @property + def full_reset(self) -> Optional[bool]: + """ + :Returns: Whether the driver should not perform a reset. + """ + return self.get_capability(NO_RESET) + + @full_reset.setter + def full_reset(self, value: bool) -> None: + """ + Set whether the driver should not perform a reset. + """ + self.set_capability(NO_RESET, value) diff --git a/appium/options/common/orientation_option.py b/appium/options/common/orientation_option.py new file mode 100644 index 000000000..6060fe8a5 --- /dev/null +++ b/appium/options/common/orientation_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +ORIENTATION = 'orientation' + + +class OrientationOption(SupportsCapabilities): + @property + def orientation(self) -> Optional[str]: + """ + :Returns: The orientation of the device's screen. + Usually this is either 'PORTRAIT' or 'LANDSCAPE'. + """ + return self.get_capability(ORIENTATION) + + @orientation.setter + def orientation(self, value: str) -> None: + """ + Set the orientation of the device's screen. + Usually this is either 'PORTRAIT' or 'LANDSCAPE'. + """ + self.set_capability(ORIENTATION, value) diff --git a/appium/options/common/other_apps_option.py b/appium/options/common/other_apps_option.py new file mode 100644 index 000000000..1751968de --- /dev/null +++ b/appium/options/common/other_apps_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +OTHER_APPS = 'otherApps' + + +class OtherAppsOption(SupportsCapabilities): + @property + def other_apps(self) -> Optional[str]: + """ + :Returns: Locations of apps to install before running a test. + """ + return self.get_capability(OTHER_APPS) + + @other_apps.setter + def other_apps(self, value: str) -> None: + """ + Set locations of apps to install before running a test. + Each item could be separated with a single comma. + """ + self.set_capability(OTHER_APPS, value) diff --git a/appium/options/common/postrun_option.py b/appium/options/common/postrun_option.py new file mode 100644 index 000000000..076c222a7 --- /dev/null +++ b/appium/options/common/postrun_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +POSTRUN = 'postrun' + + +class PostrunOption(SupportsCapabilities): + @property + def postrun(self) -> Optional[str]: + """ + :return: System script which is supposed to be executed upon + driver session quit. + """ + return self.get_capability(POSTRUN) + + @postrun.setter + def postrun(self, value: str) -> None: + """ + Set a system script to execute upon driver session quit. + """ + self.set_capability(POSTRUN, value) diff --git a/appium/options/common/prerun_option.py b/appium/options/common/prerun_option.py new file mode 100644 index 000000000..8f703cbb8 --- /dev/null +++ b/appium/options/common/prerun_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +PRERUN = 'prerun' + + +class PrerunOption(SupportsCapabilities): + @property + def prerun(self) -> Optional[str]: + """ + :return: System script which is supposed to be executed before + a driver session is initialised. + """ + return self.get_capability(PRERUN) + + @prerun.setter + def prerun(self, value: str) -> None: + """ + Set a system script which is supposed to be executed before + a driver session is initialised. + """ + self.set_capability(PRERUN, value) diff --git a/appium/options/common/print_page_source_on_find_failure_option.py b/appium/options/common/print_page_source_on_find_failure_option.py new file mode 100644 index 000000000..935bc2769 --- /dev/null +++ b/appium/options/common/print_page_source_on_find_failure_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +PRINT_PAGE_SOURCE_ON_FIND_FAILURE = 'printPageSourceOnFindFailure' + + +class PrintPageSourceOnFindFailureOption(SupportsCapabilities): + @property + def print_page_source_on_find_failure(self) -> Optional[bool]: + """ + :Returns: Whether the driver should print the page source to the log + if a find failure occurs. + """ + return self.get_capability(PRINT_PAGE_SOURCE_ON_FIND_FAILURE) + + @print_page_source_on_find_failure.setter + def print_page_source_on_find_failure(self, value: bool) -> None: + """ + Set whether the driver should print the page source to the log + if a find failure occurs. + """ + self.set_capability(PRINT_PAGE_SOURCE_ON_FIND_FAILURE, value) diff --git a/appium/options/common/skip_log_capture_option.py b/appium/options/common/skip_log_capture_option.py new file mode 100644 index 000000000..0489df775 --- /dev/null +++ b/appium/options/common/skip_log_capture_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +SKIP_LOG_CAPTURE = 'skipLogCapture' + + +class SkipLogCaptureOption(SupportsCapabilities): + @property + def skip_log_capture(self) -> Optional[bool]: + """ + :Returns: Whether the driver should not record device logs. + """ + return self.get_capability(SKIP_LOG_CAPTURE) + + @skip_log_capture.setter + def skip_log_capture(self, value: bool) -> None: + """ + Set whether the driver should not record device logs. + """ + self.set_capability(SKIP_LOG_CAPTURE, value) diff --git a/appium/options/common/udid_option.py b/appium/options/common/udid_option.py new file mode 100644 index 000000000..1e73b58cb --- /dev/null +++ b/appium/options/common/udid_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +UDID = 'udid' + + +class UdidOption(SupportsCapabilities): + @property + def udid(self) -> Optional[str]: + """ + :Returns: The unique identifier of the device under test. + """ + return self.get_capability(UDID) + + @udid.setter + def udid(self, value: str) -> None: + """ + Set the unique identifier of the device under test. + """ + self.set_capability(UDID, value) diff --git a/appium/options/gecko/base.py b/appium/options/gecko/base.py index 483314167..d11b2511c 100644 --- a/appium/options/gecko/base.py +++ b/appium/options/gecko/base.py @@ -17,6 +17,7 @@ from typing import Dict +from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import AppiumOptions @@ -26,5 +27,5 @@ class GeckoOptions( @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'Gecko', + AUTOMATION_NAME: 'Gecko', } diff --git a/appium/options/ios/safari/base.py b/appium/options/ios/safari/base.py index bbee54a0e..80db4328b 100644 --- a/appium/options/ios/safari/base.py +++ b/appium/options/ios/safari/base.py @@ -17,6 +17,7 @@ from typing import Dict +from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import AppiumOptions @@ -27,5 +28,5 @@ class SafariOptions( def default_capabilities(self) -> Dict: return { AppiumOptions.PLATFORM_NAME: 'iOS', - AppiumOptions.AUTOMATION_NAME: 'Safari', + AUTOMATION_NAME: 'Safari', } diff --git a/appium/options/ios/xcuitest/base.py b/appium/options/ios/xcuitest/base.py index 9718dfec3..12f609d86 100644 --- a/appium/options/ios/xcuitest/base.py +++ b/appium/options/ios/xcuitest/base.py @@ -17,15 +17,40 @@ from typing import Dict -from appium.options.common.base import AppiumOptions +from appium.options.common.app_option import AppOption +from appium.options.common.auto_web_view_option import AutoWebViewOption +from appium.options.common.automation_name_option import AUTOMATION_NAME +from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.clear_system_files_option import ClearSystemFilesOption +from appium.options.common.device_name_option import DeviceNameOption +from appium.options.common.enable_performance_logging_option import EnablePerformanceLoggingOption +from appium.options.common.is_headless_option import IsHeadlessOption +from appium.options.common.language_option import LanguageOption +from appium.options.common.locale_option import LocaleOption +from appium.options.common.orientation_option import OrientationOption +from appium.options.common.other_apps_option import OtherAppsOption +from appium.options.common.skip_log_capture_option import SkipLogCaptureOption +from appium.options.common.udid_option import UdidOption class XCUITestOptions( AppiumOptions, + AppOption, + ClearSystemFilesOption, + OrientationOption, + UdidOption, + LanguageOption, + LocaleOption, + IsHeadlessOption, + SkipLogCaptureOption, + AutoWebViewOption, + EnablePerformanceLoggingOption, + OtherAppsOption, + DeviceNameOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'XCUITest', - AppiumOptions.PLATFORM_NAME: 'iOS', + AUTOMATION_NAME: 'XCUITest', + PLATFORM_NAME: 'iOS', } diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py index 7c1ebc700..fc37cdddc 100644 --- a/appium/options/mac/mac2/base.py +++ b/appium/options/mac/mac2/base.py @@ -17,15 +17,20 @@ from typing import Dict -from appium.options.common.base import AppiumOptions +from appium.options.common.automation_name_option import AUTOMATION_NAME +from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.postrun_option import PostrunOption +from appium.options.common.prerun_option import PrerunOption class Mac2Options( AppiumOptions, + PrerunOption, + PostrunOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'Mac2', - AppiumOptions.PLATFORM_NAME: 'Mac', + AUTOMATION_NAME: 'Mac2', + PLATFORM_NAME: 'Mac', } diff --git a/appium/options/windows/windows/base.py b/appium/options/windows/windows/base.py index f1c34aa43..a1f4b2f7a 100644 --- a/appium/options/windows/windows/base.py +++ b/appium/options/windows/windows/base.py @@ -17,15 +17,20 @@ from typing import Dict -from appium.options.common.base import AppiumOptions +from appium.options.common.automation_name_option import AUTOMATION_NAME +from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.postrun_option import PostrunOption +from appium.options.common.prerun_option import PrerunOption class WindowsOptions( AppiumOptions, + PrerunOption, + PostrunOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.AUTOMATION_NAME: 'Windows', - AppiumOptions.PLATFORM_NAME: 'Windows', + AUTOMATION_NAME: 'Windows', + PLATFORM_NAME: 'Windows', } From 22dfeca560f8f0d5527bfdc0e0200055ee3e30f5 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 17 Jun 2022 09:21:50 +0200 Subject: [PATCH 086/548] chore: Add better error handling for session creation responses (#727) --- appium/webdriver/appium_service.py | 25 +++++-- appium/webdriver/webdriver.py | 25 +++++-- ci-jobs/functional/setup_appium.yml | 7 +- .../android/appium_service_tests.py | 48 +++++++------ test/unit/helper/test_helper.py | 67 ++++++++--------- test/unit/webdriver/webdriver_test.py | 72 +++++++++---------- 6 files changed, 133 insertions(+), 111 deletions(-) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index e3e817a10..e99c1ad0b 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import re import subprocess as sp import sys import time @@ -24,7 +25,8 @@ DEFAULT_PORT = 4723 STARTUP_TIMEOUT_MS = 60000 MAIN_SCRIPT_PATH = 'appium/build/lib/main.js' -STATUS_URL = '/wd/hub/status' +STATUS_URL = '/status' +DEFAULT_BASE_PATH = '/' def find_executable(executable: str) -> Optional[str]: @@ -47,9 +49,9 @@ def find_executable(executable: str) -> Optional[str]: def poll_url(host: str, port: int, path: str, timeout_ms: int) -> bool: time_started_sec = time.time() + conn = urllib3.PoolManager(timeout=1.0) while time.time() < time_started_sec + timeout_ms / 1000.0: try: - conn = urllib3.PoolManager(timeout=1.0) resp = conn.request('HEAD', f'http://{host}:{port}{path}') if resp.status < 400: return True @@ -112,6 +114,13 @@ def _parse_port(args: List[str]) -> int: return int(args[idx + 1]) return DEFAULT_PORT + @staticmethod + def _parse_base_path(args: List[str]) -> str: + for idx, arg in enumerate(args or []): + if arg in ('--base-path', '-pa') and idx < len(args) - 1: + return args[idx + 1] + return DEFAULT_BASE_PATH + @staticmethod def _parse_host(args: List[str]) -> str: for idx, arg in enumerate(args or []): @@ -166,7 +175,11 @@ def start(self, **kwargs: Any) -> sp.Popen: host = self._parse_host(args) 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)): + base_path = self._parse_base_path(args) + status_url_path = ( + STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"[/]+$", "", base_path)}{STATUS_URL}' + ) + if not self.is_running or (timeout_ms > 0 and not poll_url(host, port, status_url_path, 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 and self._process.stderr is not None: @@ -218,7 +231,11 @@ def is_listening(self) -> bool: return False host = self._parse_host(self._cmd) port = self._parse_port(self._cmd) - return self.is_running and poll_url(host, port, STATUS_URL, 1000) + base_path = self._parse_base_path(self._cmd) + status_url_path = ( + STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"[/]+$", "", base_path)}{STATUS_URL}' + ) + return self.is_running and poll_url(host, port, status_url_path, 1000) if __name__ == '__main__': diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 19da31b12..e26d00770 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -17,7 +17,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union from selenium import webdriver -from selenium.common.exceptions import InvalidArgumentException, WebDriverException +from selenium.common.exceptions import InvalidArgumentException, SessionNotCreatedException, WebDriverException from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -317,10 +317,25 @@ def start_session(self, capabilities: Union[Dict, AppiumOptions], browser_profil w3c_caps = AppiumOptions.as_w3c(capabilities) if isinstance(capabilities, dict) else capabilities.to_w3c() response = self.execute(RemoteCommand.NEW_SESSION, w3c_caps) - if 'sessionId' not in response: - response = response['value'] - self.session_id = response['sessionId'] - self.caps = response.get('value') or response.get('capabilities') + # https://w3c.github.io/webdriver/#new-session + if not isinstance(response, dict): + raise SessionNotCreatedException( + f'A valid W3C session creation response must be a dictionary. Got "{response}" instead' + ) + # Due to a W3C spec parsing misconception some servers + # pack the createSession response stuff into 'value' dictionary and + # some other put it to the top level of the response JSON nesting hierarchy + get_response_value: Callable[[str], Optional[Any]] = lambda key: response.get(key) or ( + response['value'].get(key) if isinstance(response.get('value'), dict) else None + ) + session_id = get_response_value('sessionId') + if not session_id: + raise SessionNotCreatedException( + f'A valid W3C session creation response must contain a non-empty "sessionId" entry. ' + f'Got "{response}" instead' + ) + self.session_id = session_id + self.caps = get_response_value('capabilities') or {} def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: """ diff --git a/ci-jobs/functional/setup_appium.yml b/ci-jobs/functional/setup_appium.yml index a6a7e6ace..6a220ef5a 100644 --- a/ci-jobs/functional/setup_appium.yml +++ b/ci-jobs/functional/setup_appium.yml @@ -10,15 +10,14 @@ steps: versionSpec: '3.x' - script: brew install ffmpeg displayName: Resolve dependencies (Appium server) -# - script: pip install trio==0.17.0 -# displayName: Install trio -- script: python setup.py install - displayName: Install python language bindings for Appium - script: | + pip install --upgrade pip pip install pipenv pipenv lock --clear pipenv install --system displayName: Resolve dependencies (Python) +- script: python setup.py install + displayName: Install python language bindings for Appium - script: | git --no-pager log -n1 python --version diff --git a/test/functional/android/appium_service_tests.py b/test/functional/android/appium_service_tests.py index 806bdb290..a400dfb56 100644 --- a/test/functional/android/appium_service_tests.py +++ b/test/functional/android/appium_service_tests.py @@ -13,29 +13,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -from appium.webdriver.appium_service import AppiumService -from appium.webdriver.common.appiumby import AppiumBy -from test.functional.android.helper.test_helper import BaseTestCase -from test.functional.test_helper import wait_for_element - -DEFAULT_PORT = 4723 - +from typing import Generator -class TestAppiumService(BaseTestCase): +import pytest - service: AppiumService - - @classmethod - def setup_class(cls) -> None: - cls.service = AppiumService() - cls.service.start(args=['--address', '127.0.0.1', '-p', str(DEFAULT_PORT)]) +from appium.webdriver.appium_service import AppiumService - def test_appium_service(self) -> None: - assert self.service.is_running - assert self.service.is_listening - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Accessibility') - assert el is not None - @classmethod - def teardown_class(cls) -> None: - cls.service.stop() +@pytest.fixture +def appium_service() -> Generator[AppiumService, None, None]: + service = AppiumService() + service.start( + args=[ + '--address', + '127.0.0.1', + '-p', + '4773', + '--base-path', + '/wd/hub', + ] + ) + try: + yield service + finally: + service.stop() + + +@pytest.skip('Unstable in CI env') +def test_appium_service(appium_service: AppiumService) -> None: + assert appium_service.is_running + assert appium_service.is_listening diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py index 6c85eb2e2..7037757e1 100644 --- a/test/unit/helper/test_helper.py +++ b/test/unit/helper/test_helper.py @@ -50,29 +50,27 @@ def android_w3c_driver() -> 'WebDriver': response_body_json = json.dumps( { - 'value': { - 'sessionId': '1234567890', - 'capabilities': { - 'platform': 'LINUX', - 'desired': { - 'platformName': 'Android', - 'automationName': 'uiautomator2', - 'platformVersion': '7.1.1', - 'deviceName': 'Android Emulator', - 'app': '/test/apps/ApiDemos-debug.apk', - }, + 'sessionId': '1234567890', + 'capabilities': { + 'platform': 'LINUX', + 'desired': { 'platformName': 'Android', 'automationName': 'uiautomator2', 'platformVersion': '7.1.1', - 'deviceName': 'emulator-5554', + 'deviceName': 'Android Emulator', 'app': '/test/apps/ApiDemos-debug.apk', - 'deviceUDID': 'emulator-5554', - 'appPackage': 'io.appium.android.apis', - 'appWaitPackage': 'io.appium.android.apis', - 'appActivity': 'io.appium.android.apis.ApiDemos', - 'appWaitActivity': 'io.appium.android.apis.ApiDemos', }, - } + 'platformName': 'Android', + 'automationName': 'uiautomator2', + 'platformVersion': '7.1.1', + 'deviceName': 'emulator-5554', + 'app': '/test/apps/ApiDemos-debug.apk', + 'deviceUDID': 'emulator-5554', + 'appPackage': 'io.appium.android.apis', + 'appWaitPackage': 'io.appium.android.apis', + 'appActivity': 'io.appium.android.apis.ApiDemos', + 'appWaitActivity': 'io.appium.android.apis.ApiDemos', + }, } ) @@ -95,18 +93,15 @@ def ios_w3c_driver() -> 'WebDriver': Returns: `webdriver.webdriver.WebDriver`: An instance of WebDriver """ - response_body_json = json.dumps( { - 'value': { - 'sessionId': '1234567890', - 'capabilities': { - 'device': 'iphone', - 'browserName': 'UICatalog', - 'sdkVersion': '11.4', - 'CFBundleIdentifier': 'com.example.apple-samplecode.UICatalog', - }, - } + 'sessionId': '1234567890', + 'capabilities': { + 'device': 'iphone', + 'browserName': 'UICatalog', + 'sdkVersion': '11.4', + 'CFBundleIdentifier': 'com.example.apple-samplecode.UICatalog', + }, } ) @@ -132,15 +127,13 @@ def ios_w3c_driver_with_extensions(extensions) -> 'WebDriver': response_body_json = json.dumps( { - 'value': { - 'sessionId': '1234567890', - 'capabilities': { - 'device': 'iphone', - 'browserName': 'UICatalog', - 'sdkVersion': '11.4', - 'CFBundleIdentifier': 'com.example.apple-samplecode.UICatalog', - }, - } + 'sessionId': '1234567890', + 'capabilities': { + 'device': 'iphone', + 'browserName': 'UICatalog', + 'sdkVersion': '11.4', + 'CFBundleIdentifier': 'com.example.apple-samplecode.UICatalog', + }, } ) diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 13fbfab6d..51ea40d93 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -37,7 +37,7 @@ def test_create_session(self): httpretty.register_uri( httpretty.POST, f'{SERVER_URL_BASE}/session', - body='{ "value": { "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}}}', + body='{ "value": {"sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}} }', ) desired_caps = { @@ -71,7 +71,7 @@ def test_create_session_change_session_id(self): httpretty.register_uri( httpretty.POST, f'{SERVER_URL_BASE}/session', - body='{ "value": { "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}}}', + body='{ "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"} }', ) httpretty.register_uri( @@ -100,16 +100,14 @@ def test_create_session_register_uridirect(self): f'{SERVER_URL_BASE}/session', body=json.dumps( { - 'value': { - 'sessionId': 'session-id', - 'capabilities': { - 'deviceName': 'Android Emulator', - 'directConnectProtocol': 'http', - 'directConnectHost': 'localhost2', - 'directConnectPort': 4800, - 'directConnectPath': '/special/path/wd/hub', - }, - } + 'sessionId': 'session-id', + 'capabilities': { + 'deviceName': 'Android Emulator', + 'directConnectProtocol': 'http', + 'directConnectHost': 'localhost2', + 'directConnectPort': 4800, + 'directConnectPath': '/special/path/wd/hub', + }, } ), ) @@ -142,15 +140,13 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): f'{SERVER_URL_BASE}/session', body=json.dumps( { - 'value': { - 'sessionId': 'session-id', - 'capabilities': { - 'deviceName': 'Android Emulator', - 'directConnectProtocol': 'http', - 'directConnectHost': 'localhost2', - 'directConnectPort': 4800, - }, - } + 'sessionId': 'session-id', + 'capabilities': { + 'deviceName': 'Android Emulator', + 'directConnectProtocol': 'http', + 'directConnectHost': 'localhost2', + 'directConnectPort': 4800, + }, } ), ) @@ -327,29 +323,27 @@ class TestSubModuleWebDriver(object): def android_w3c_driver(self, driver_class): response_body_json = json.dumps( { - 'value': { - 'sessionId': '1234567890', - 'capabilities': { - 'platform': 'LINUX', - 'desired': { - 'platformName': 'Android', - 'automationName': 'uiautomator2', - 'platformVersion': '7.1.1', - 'deviceName': 'Android Emulator', - 'app': '/test/apps/ApiDemos-debug.apk', - }, + 'sessionId': '1234567890', + 'capabilities': { + 'platform': 'LINUX', + 'desired': { 'platformName': 'Android', 'automationName': 'uiautomator2', 'platformVersion': '7.1.1', - 'deviceName': 'emulator-5554', + 'deviceName': 'Android Emulator', 'app': '/test/apps/ApiDemos-debug.apk', - 'deviceUDID': 'emulator-5554', - 'appPackage': 'io.appium.android.apis', - 'appWaitPackage': 'io.appium.android.apis', - 'appActivity': 'io.appium.android.apis.ApiDemos', - 'appWaitActivity': 'io.appium.android.apis.ApiDemos', }, - } + 'platformName': 'Android', + 'automationName': 'uiautomator2', + 'platformVersion': '7.1.1', + 'deviceName': 'emulator-5554', + 'app': '/test/apps/ApiDemos-debug.apk', + 'deviceUDID': 'emulator-5554', + 'appPackage': 'io.appium.android.apis', + 'appWaitPackage': 'io.appium.android.apis', + 'appActivity': 'io.appium.android.apis.ApiDemos', + 'appWaitActivity': 'io.appium.android.apis.ApiDemos', + }, } ) From cc999cea4a34510b42af22663c9c8d5037ab9bb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Jun 2022 15:17:12 +0900 Subject: [PATCH 087/548] chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 (#725) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index bec7ec70b..86f0c1ed5 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.1" +pylint = "~=2.14.2" astroid = "~=2.9" isort = "~=5.10" From 15b106c5769fc7b5307f8daecdcfbc7f3cc7dea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Jun 2022 15:17:12 +0900 Subject: [PATCH 088/548] chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 (#725) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index bec7ec70b..86f0c1ed5 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.1" +pylint = "~=2.14.2" astroid = "~=2.9" isort = "~=5.10" From c400357ca0028e7a83124a91331cf69c31be91c4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 18 Jun 2022 23:28:31 -0700 Subject: [PATCH 089/548] chore: bump version to 2.4.0 --- CHANGELOG.rst | 22 ++++++++++++++++++++++ appium/version.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f48c12d9..55805c7fc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,28 @@ Changelog ========= +v2.4.0 (2022-06-17) +------------------- + +New +~~~ +- Feat: Add common options (#728) [Mykola Mokhnach] + +Other +~~~~~ +- Chore: Add better error handling for session creation responses (#727) + [Mykola Mokhnach] +- Docs: Update changelog for 2.3.0. [Kazuaki Matsuo] +- Bump 2.3.0. [Kazuaki Matsuo] +- Chore: Update comments to locator patches (#724) [VladimirPodolyan] + + * Update webelement.py + + * update comment section + + * CR fixes + + v2.3.0 (2022-06-13) ------------------- diff --git a/appium/version.py b/appium/version.py index a4b8895b3..ecc158079 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.3.0' +version = '2.4.0' From 312c229fa89b6387236553c6f036085a835d3ed8 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 19 Jun 2022 22:09:19 +0200 Subject: [PATCH 090/548] feat: Add Mac2Driver options (#730) --- appium/options/mac/mac2/arguments_option.py | 39 ++++++++++++++++ appium/options/mac/mac2/base.py | 21 +++++++++ .../options/mac/mac2/bootstrap_root_option.py | 41 +++++++++++++++++ appium/options/mac/mac2/bundle_id_option.py | 44 +++++++++++++++++++ appium/options/mac/mac2/environment_option.py | 41 +++++++++++++++++ .../mac/mac2/server_startup_timeout_option.py | 44 +++++++++++++++++++ .../mac/mac2/show_server_logs_option.py | 39 ++++++++++++++++ .../options/mac/mac2/skip_app_kill_option.py | 40 +++++++++++++++++ appium/options/mac/mac2/system_host_option.py | 42 ++++++++++++++++++ appium/options/mac/mac2/system_port_option.py | 39 ++++++++++++++++ .../mac2/web_driver_agent_mac_url_option.py | 39 ++++++++++++++++ 11 files changed, 429 insertions(+) create mode 100644 appium/options/mac/mac2/arguments_option.py create mode 100644 appium/options/mac/mac2/bootstrap_root_option.py create mode 100644 appium/options/mac/mac2/bundle_id_option.py create mode 100644 appium/options/mac/mac2/environment_option.py create mode 100644 appium/options/mac/mac2/server_startup_timeout_option.py create mode 100644 appium/options/mac/mac2/show_server_logs_option.py create mode 100644 appium/options/mac/mac2/skip_app_kill_option.py create mode 100644 appium/options/mac/mac2/system_host_option.py create mode 100644 appium/options/mac/mac2/system_port_option.py create mode 100644 appium/options/mac/mac2/web_driver_agent_mac_url_option.py diff --git a/appium/options/mac/mac2/arguments_option.py b/appium/options/mac/mac2/arguments_option.py new file mode 100644 index 000000000..77f790f09 --- /dev/null +++ b/appium/options/mac/mac2/arguments_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import List, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ARGUMENTS = 'arguments' + + +class ArgumentsOption(SupportsCapabilities): + @property + def arguments(self) -> Optional[List[str]]: + """ + :Returns: Array of application command line arguments. + """ + return self.get_capability(ARGUMENTS) + + @arguments.setter + def arguments(self, value: List[str]) -> None: + """ + Set the array of application command line arguments. This capability is + only going to be applied if the application is not running on session startup. + """ + self.set_capability(ARGUMENTS, value) diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py index fc37cdddc..84641449a 100644 --- a/appium/options/mac/mac2/base.py +++ b/appium/options/mac/mac2/base.py @@ -22,11 +22,32 @@ from appium.options.common.postrun_option import PostrunOption from appium.options.common.prerun_option import PrerunOption +from .arguments_option import ArgumentsOption +from .bootstrap_root_option import BootstrapRootOption +from .bundle_id_option import BundleIdOption +from .environment_option import EnvironmentOption +from .server_startup_timeout_option import ServerStartupTimeoutOption +from .show_server_logs_option import ShowServerLogsOption +from .skip_app_kill_option import SkipAppKillOption +from .system_host_option import SystemHostOption +from .system_port_option import SystemPortOption +from .web_driver_agent_mac_url_option import WebDriverAgentMacUrlOption + class Mac2Options( AppiumOptions, PrerunOption, PostrunOption, + ArgumentsOption, + BootstrapRootOption, + BundleIdOption, + EnvironmentOption, + ServerStartupTimeoutOption, + ShowServerLogsOption, + SkipAppKillOption, + SystemHostOption, + SystemPortOption, + WebDriverAgentMacUrlOption, ): @property def default_capabilities(self) -> Dict: diff --git a/appium/options/mac/mac2/bootstrap_root_option.py b/appium/options/mac/mac2/bootstrap_root_option.py new file mode 100644 index 000000000..1be1d9e98 --- /dev/null +++ b/appium/options/mac/mac2/bootstrap_root_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +BOOTSTRAP_ROOT = 'bootstrapRoot' + + +class BootstrapRootOption(SupportsCapabilities): + @property + def bootstrap_root(self) -> Optional[str]: + """ + :Returns: The full path to WebDriverAgentMac root folder where Xcode project + of the server sources lives. + """ + return self.get_capability(BOOTSTRAP_ROOT) + + @bootstrap_root.setter + def bootstrap_root(self, value: str) -> None: + """ + Set the full path to WebDriverAgentMac root folder where Xcode project + of the server sources lives. By default, this project is located in + the same folder where the corresponding driver Node.js module lives. + """ + self.set_capability(BOOTSTRAP_ROOT, value) diff --git a/appium/options/mac/mac2/bundle_id_option.py b/appium/options/mac/mac2/bundle_id_option.py new file mode 100644 index 000000000..e02f54f6e --- /dev/null +++ b/appium/options/mac/mac2/bundle_id_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +BUNDLE_ID = 'bundleId' + + +class BundleIdOption(SupportsCapabilities): + @property + def bundle_id(self) -> Optional[str]: + """ + :Returns: The bundle identifier of the application to automate. + """ + return self.get_capability(BUNDLE_ID) + + @bundle_id.setter + def bundle_id(self, value: str) -> None: + """ + Set the bundle identifier of the application to automate, for example + com.apple.TextEdit. This is an optional capability. If it is not provided + then the session will be started without an application under test + (actually, it will be Finder). If the application with the given + identifier is not installed then an error will be thrown on session + startup. If the application is already running then it will be moved to + the foreground. + """ + self.set_capability(BUNDLE_ID, value) diff --git a/appium/options/mac/mac2/environment_option.py b/appium/options/mac/mac2/environment_option.py new file mode 100644 index 000000000..f67ca0fe3 --- /dev/null +++ b/appium/options/mac/mac2/environment_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ENVIRONMENT = 'environment' + + +class EnvironmentOption(SupportsCapabilities): + @property + def environment(self) -> Optional[Dict[str, str]]: + """ + :Returns: Application environment variables mapping. + """ + return self.get_capability(ENVIRONMENT) + + @environment.setter + def environment(self, value: Dict[str, str]) -> None: + """ + Set the dictionary of environment variables (name->value) that are going to be passed + to the application under test on top of environment variables inherited from + the parent process. This option is only going to be applied if the application + is not running on session startup. + """ + self.set_capability(ENVIRONMENT, value) diff --git a/appium/options/mac/mac2/server_startup_timeout_option.py b/appium/options/mac/mac2/server_startup_timeout_option.py new file mode 100644 index 000000000..390e1ed29 --- /dev/null +++ b/appium/options/mac/mac2/server_startup_timeout_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SERVER_STARTUP_TIMEOUT = 'serverStartupTimeout' + + +class ServerStartupTimeoutOption(SupportsCapabilities): + @property + def server_startup_timeout(self) -> Optional[timedelta]: + """ + :Returns: Get the timeout to wait util the WebDriverAgentMac + project is built and started. + """ + value_ms = self.get_capability(SERVER_STARTUP_TIMEOUT) + return None if value_ms is None else timedelta(milliseconds=value_ms) + + @server_startup_timeout.setter + def server_startup_timeout(self, value: Union[int, timedelta]) -> None: + """ + Set the timeout to wait util the WebDriverAgentMac + project is built and started. + """ + self.set_capability( + SERVER_STARTUP_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/mac/mac2/show_server_logs_option.py b/appium/options/mac/mac2/show_server_logs_option.py new file mode 100644 index 000000000..edee52c2d --- /dev/null +++ b/appium/options/mac/mac2/show_server_logs_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOW_SERVER_LOGS = 'showServerLogs' + + +class ShowServerLogsOption(SupportsCapabilities): + @property + def show_server_logs(self) -> Optional[bool]: + """ + :Returns: Whether to show WDA server logs in the Appium log. + """ + return self.get_capability(SHOW_SERVER_LOGS) + + @show_server_logs.setter + def show_server_logs(self, value: bool) -> None: + """ + Set it to true in order to include xcodebuild output to the Appium + server log. false by default. + """ + self.set_capability(SHOW_SERVER_LOGS, value) diff --git a/appium/options/mac/mac2/skip_app_kill_option.py b/appium/options/mac/mac2/skip_app_kill_option.py new file mode 100644 index 000000000..370a13006 --- /dev/null +++ b/appium/options/mac/mac2/skip_app_kill_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SKIP_APP_KILL = 'skipAppKill' + + +class SkipAppKillOption(SupportsCapabilities): + @property + def skip_app_kill(self) -> Optional[bool]: + """ + :Returns: Whether to skip the termination of the application under test. + """ + return self.get_capability(SKIP_APP_KILL) + + @skip_app_kill.setter + def skip_app_kill(self, value: bool) -> None: + """ + Set whether to skip the termination of the application under test + when the testing session quits. false by default. This capability + is only going to be applied if bundleId is set. + """ + self.set_capability(SKIP_APP_KILL, value) diff --git a/appium/options/mac/mac2/system_host_option.py b/appium/options/mac/mac2/system_host_option.py new file mode 100644 index 000000000..14bee716b --- /dev/null +++ b/appium/options/mac/mac2/system_host_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SYSTEM_HOST = 'systemHost' + + +class SystemHostOption(SupportsCapabilities): + @property + def system_host(self) -> Optional[str]: + """ + :Returns: The name of the host for the internal server to listen on. + """ + return self.get_capability(SYSTEM_HOST) + + @system_host.setter + def system_host(self, value: str) -> None: + """ + Set the name of the host for the internal server to listen on. + If not provided then Mac2Driver will use the default host + address 127.0.0.1. You could set it to 0.0.0.0 to make the + server listening on all available network interfaces. + It is also possible to set the particular interface name, for example en1. + """ + self.set_capability(SYSTEM_HOST, value) diff --git a/appium/options/mac/mac2/system_port_option.py b/appium/options/mac/mac2/system_port_option.py new file mode 100644 index 000000000..f4fb60e6b --- /dev/null +++ b/appium/options/mac/mac2/system_port_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SYSTEM_PORT = 'systemPort' + + +class SystemPortOption(SupportsCapabilities): + @property + def system_port(self) -> Optional[int]: + """ + :Returns: The number of the port for the internal server to listen on. + """ + return self.get_capability(SYSTEM_PORT) + + @system_port.setter + def system_port(self, value: int) -> None: + """ + Set the number of the port for the internal server to listen on. + If not provided then Mac2Driver will use the default port 10100. + """ + self.set_capability(SYSTEM_PORT, value) diff --git a/appium/options/mac/mac2/web_driver_agent_mac_url_option.py b/appium/options/mac/mac2/web_driver_agent_mac_url_option.py new file mode 100644 index 000000000..5de658f06 --- /dev/null +++ b/appium/options/mac/mac2/web_driver_agent_mac_url_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEB_DRIVER_ARGENT_MAC_URL = 'webDriverAgentMacUrl' + + +class WebDriverAgentMacUrlOption(SupportsCapabilities): + @property + def web_driver_agent_mac_url(self) -> Optional[str]: + """ + :Returns: The URL Appium will connect to an existing WebDriverAgentMac instance. + """ + return self.get_capability(WEB_DRIVER_ARGENT_MAC_URL) + + @web_driver_agent_mac_url.setter + def web_driver_agent_mac_url(self, value: str) -> None: + """ + Set the URL Appium will connect to an existing WebDriverAgentMac + instance at this URL instead of starting a new one. + """ + self.set_capability(WEB_DRIVER_ARGENT_MAC_URL, value) From 2201e90eec53cd3df29ed9cfcb5ba94d300fb7a0 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 20 Jun 2022 21:36:54 +0200 Subject: [PATCH 091/548] feat: Add Safari driver options (#731) --- .../ios/safari/automatic_inspection_option.py | 41 +++++++++++++++ .../ios/safari/automatic_profiling_option.py | 41 +++++++++++++++ appium/options/ios/safari/base.py | 23 +++++++- .../options/ios/safari/device_name_option.py | 43 +++++++++++++++ .../options/ios/safari/device_type_option.py | 41 +++++++++++++++ .../options/ios/safari/device_udid_option.py | 43 +++++++++++++++ .../safari/platform_build_version_option.py | 41 +++++++++++++++ .../ios/safari/platform_version_option.py | 41 +++++++++++++++ .../ios/safari/use_simulator_option.py | 41 +++++++++++++++ .../ios/safari/webkit_webrtc_option.py | 52 +++++++++++++++++++ 10 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 appium/options/ios/safari/automatic_inspection_option.py create mode 100644 appium/options/ios/safari/automatic_profiling_option.py create mode 100644 appium/options/ios/safari/device_name_option.py create mode 100644 appium/options/ios/safari/device_type_option.py create mode 100644 appium/options/ios/safari/device_udid_option.py create mode 100644 appium/options/ios/safari/platform_build_version_option.py create mode 100644 appium/options/ios/safari/platform_version_option.py create mode 100644 appium/options/ios/safari/use_simulator_option.py create mode 100644 appium/options/ios/safari/webkit_webrtc_option.py diff --git a/appium/options/ios/safari/automatic_inspection_option.py b/appium/options/ios/safari/automatic_inspection_option.py new file mode 100644 index 000000000..ef95aa7b2 --- /dev/null +++ b/appium/options/ios/safari/automatic_inspection_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTOMATIC_INSPECTION = 'safari:automaticInspection' + + +class AutomaticInspectionOption(SupportsCapabilities): + @property + def automatic_inspection(self) -> Optional[bool]: + """ + :Returns: Whether to use automatic inspection. + """ + return self.get_capability(AUTOMATIC_INSPECTION) + + @automatic_inspection.setter + def automatic_inspection(self, value: bool) -> None: + """ + This capability instructs Safari to preload the Web Inspector and JavaScript + debugger in the background prior to returning a newly-created window. + To pause the test's execution in JavaScript and bring up Web Inspector's + Debugger tab, you can simply evaluate a debugger statement in the test page. + """ + self.set_capability(AUTOMATIC_INSPECTION, value) diff --git a/appium/options/ios/safari/automatic_profiling_option.py b/appium/options/ios/safari/automatic_profiling_option.py new file mode 100644 index 000000000..e45089d99 --- /dev/null +++ b/appium/options/ios/safari/automatic_profiling_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTOMATIC_PROFILING = 'safari:automaticProfiling' + + +class AutomaticProfilingOption(SupportsCapabilities): + @property + def automatic_profiling(self) -> Optional[bool]: + """ + :Returns: Whether to use automatic profiling. + """ + return self.get_capability(AUTOMATIC_PROFILING) + + @automatic_profiling.setter + def automatic_profiling(self, value: bool) -> None: + """ + This capability instructs Safari to preload the Web Inspector and start + a Timeline recording in the background prior to returning a newly-created + window. To view the recording, open the Web Inspector through Safari's + Develop menu. + """ + self.set_capability(AUTOMATIC_PROFILING, value) diff --git a/appium/options/ios/safari/base.py b/appium/options/ios/safari/base.py index 80db4328b..8c6cfa685 100644 --- a/appium/options/ios/safari/base.py +++ b/appium/options/ios/safari/base.py @@ -18,15 +18,34 @@ from typing import Dict from appium.options.common.automation_name_option import AUTOMATION_NAME -from appium.options.common.base import AppiumOptions +from appium.options.common.base import PLATFORM_NAME, AppiumOptions + +from .automatic_inspection_option import AutomaticInspectionOption +from .automatic_profiling_option import AutomaticProfilingOption +from .device_name_option import DeviceNameOption +from .device_type_option import DeviceTypeOption +from .device_udid_option import DeviceUdidOption +from .platform_build_version_option import PlatformBuildVersionOption +from .platform_version_option import PlatformVersionOption +from .use_simulator_option import UseSimulatorOption +from .webkit_webrtc_option import WebkitWebrtcOption class SafariOptions( AppiumOptions, + AutomaticInspectionOption, + AutomaticProfilingOption, + DeviceNameOption, + DeviceTypeOption, + DeviceUdidOption, + PlatformBuildVersionOption, + PlatformVersionOption, + UseSimulatorOption, + WebkitWebrtcOption, ): @property def default_capabilities(self) -> Dict: return { - AppiumOptions.PLATFORM_NAME: 'iOS', + PLATFORM_NAME: 'iOS', AUTOMATION_NAME: 'Safari', } diff --git a/appium/options/ios/safari/device_name_option.py b/appium/options/ios/safari/device_name_option.py new file mode 100644 index 000000000..b7697ed25 --- /dev/null +++ b/appium/options/ios/safari/device_name_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DEVICE_NAME = 'safari:deviceName' + + +class DeviceNameOption(SupportsCapabilities): + @property + def device_name(self) -> Optional[str]: + """ + :Returns: String representing the name of the device. + """ + return self.get_capability(DEVICE_NAME) + + @device_name.setter + def device_name(self, value: str) -> None: + """ + safaridriver will only create a session using hosts whose device name + matches the value of safari:deviceName. Device names are compared + case-insensitively. NOTE: Device names for connected devices are shown in + iTunes. If Xcode is installed, device names for connected devices are available + via the output of instruments(1) and in the Devices and Simulators window + (accessed in Xcode via "Window -> Devices and Simulators"). + """ + self.set_capability(DEVICE_NAME, value) diff --git a/appium/options/ios/safari/device_type_option.py b/appium/options/ios/safari/device_type_option.py new file mode 100644 index 000000000..d7951513e --- /dev/null +++ b/appium/options/ios/safari/device_type_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DEVICE_TYPE = 'safari:deviceType' + + +class DeviceTypeOption(SupportsCapabilities): + @property + def device_type(self) -> Optional[str]: + """ + :Returns: String representing the type of the device. + """ + return self.get_capability(DEVICE_TYPE) + + @device_type.setter + def device_type(self, value: str) -> None: + """ + If the value of safari:deviceType is 'iPhone', safaridriver will only create a session + using an iPhone device or iPhone simulator. If the value of safari:deviceType is 'iPad', + safaridriver will only create a session using an iPad device or iPad simulator. + Values of safari:deviceType are compared case-insensitively. + """ + self.set_capability(DEVICE_TYPE, value) diff --git a/appium/options/ios/safari/device_udid_option.py b/appium/options/ios/safari/device_udid_option.py new file mode 100644 index 000000000..7ef435fd2 --- /dev/null +++ b/appium/options/ios/safari/device_udid_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DEVICE_UDID = 'safari:deviceUDID' + + +class DeviceUdidOption(SupportsCapabilities): + @property + def device_udid(self) -> Optional[str]: + """ + :Returns: String representing the UDID of the device. + """ + return self.get_capability(DEVICE_UDID) + + @device_udid.setter + def device_udid(self, value: str) -> None: + """ + safaridriver will only create a session using hosts whose device UDID + matches the value of safari:deviceUDID. Device UDIDs are compared + case-insensitively. NOTE: If Xcode is installed, UDIDs for connected + devices are available via the output of instruments(1) and in the + Devices and Simulators window (accessed in Xcode via + "Window -> Devices and Simulators"). + """ + self.set_capability(DEVICE_UDID, value) diff --git a/appium/options/ios/safari/platform_build_version_option.py b/appium/options/ios/safari/platform_build_version_option.py new file mode 100644 index 000000000..4f6497c70 --- /dev/null +++ b/appium/options/ios/safari/platform_build_version_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +PLATFORM_BUILD_VERSION = 'safari:platformBuildVersion' + + +class PlatformBuildVersionOption(SupportsCapabilities): + @property + def platform_build_version(self) -> Optional[str]: + """ + :Returns: String representing the platform build version. + """ + return self.get_capability(PLATFORM_BUILD_VERSION) + + @platform_build_version.setter + def platform_build_version(self, value: str) -> None: + """ + safaridriver will only create a session using hosts whose OS build + version matches the value of safari:platformBuildVersion. Example + of a macOS build version is '18E193'. On macOS, the OS build version + can be determined by running the sw_vers(1) utility. + """ + self.set_capability(PLATFORM_BUILD_VERSION, value) diff --git a/appium/options/ios/safari/platform_version_option.py b/appium/options/ios/safari/platform_version_option.py new file mode 100644 index 000000000..d6a91fb79 --- /dev/null +++ b/appium/options/ios/safari/platform_version_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +PLATFORM_VERSION = 'safari:platformVersion' + + +class PlatformVersionOption(SupportsCapabilities): + @property + def platform_version(self) -> Optional[str]: + """ + :Returns: String representing the platform version. + """ + return self.get_capability(PLATFORM_VERSION) + + @platform_version.setter + def platform_version(self, value: str) -> None: + """ + safaridriver will only create a session using hosts whose OS + version matches the value of safari:platformVersion. OS version + numbers are prefix-matched. For example, if the value of safari:platformVersion + is '12', this will allow hosts with an OS version of '12.0' or '12.1' but not '10.12'. + """ + self.set_capability(PLATFORM_VERSION, value) diff --git a/appium/options/ios/safari/use_simulator_option.py b/appium/options/ios/safari/use_simulator_option.py new file mode 100644 index 000000000..8ac641d33 --- /dev/null +++ b/appium/options/ios/safari/use_simulator_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_SIMULATOR = 'safari:useSimulator' + + +class UseSimulatorOption(SupportsCapabilities): + @property + def use_simulator(self) -> Optional[bool]: + """ + :Returns: Whether to use iOS Simulator. + """ + return self.get_capability(USE_SIMULATOR) + + @use_simulator.setter + def use_simulator(self, value: bool) -> None: + """ + If the value of safari:useSimulator is true, safaridriver will only use + iOS Simulator hosts. If the value of safari:useSimulator is false, safaridriver + will not use iOS Simulator hosts. NOTE: An Xcode installation is required + in order to run WebDriver tests on iOS Simulator hosts. + """ + self.set_capability(USE_SIMULATOR, value) diff --git a/appium/options/ios/safari/webkit_webrtc_option.py b/appium/options/ios/safari/webkit_webrtc_option.py new file mode 100644 index 000000000..0890b6545 --- /dev/null +++ b/appium/options/ios/safari/webkit_webrtc_option.py @@ -0,0 +1,52 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEBKIT_WEBRTC = 'webkit:WebRTC' + + +class WebkitWebrtcOption(SupportsCapabilities): + @property + def webkit_webrtc(self) -> Optional[Dict[str, Any]]: + """ + :Returns: WebRTC policies. + """ + return self.get_capability(WEBKIT_WEBRTC) + + @webkit_webrtc.setter + def webkit_webrtc(self, value: Dict[str, Any]) -> None: + """ + This option allows a test to temporarily change Safari's policies + for WebRTC and Media Capture. + The following dictionary values are supported: + - DisableInsecureMediaCapture: Boolean value. + Normally, Safari refuses to allow media capture over insecure connections. + This restriction is relaxed by default for WebDriver sessions for testing + purposes (for example, a test web server not configured for HTTPS). When + this capability is specified, Safari will revert to the normal behavior of + preventing media capture over insecure connections. + - DisableICECandidateFiltering: Boolean value. + To protect a user's privacy, Safari normally filters out WebRTC + ICE candidates that correspond to internal network addresses when + capture devices are not in use. This capability suppresses ICE candidate + filtering so that both internal and external network addresses are + always sent as ICE candidates. + """ + self.set_capability(WEBKIT_WEBRTC, value) From d480ebaa1fcc52239d2a71d288e73e103f504429 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 21 Jun 2022 07:40:04 +0200 Subject: [PATCH 092/548] feat: Add Windows driver options (#732) --- appium/options/common/app_option.py | 2 +- appium/options/common/auto_web_view_option.py | 2 +- .../options/common/automation_name_option.py | 2 +- .../common/clear_system_files_option.py | 2 +- appium/options/common/device_name_option.py | 2 +- .../enable_performance_logging_option.py | 2 +- appium/options/common/event_timings_option.py | 2 +- appium/options/common/full_reset_option.py | 2 +- appium/options/common/is_headless_option.py | 2 +- appium/options/common/language_option.py | 2 +- appium/options/common/locale_option.py | 2 +- .../common/new_command_timeout_option.py | 2 +- appium/options/common/no_reset_option.py | 2 +- appium/options/common/orientation_option.py | 2 +- appium/options/common/other_apps_option.py | 2 +- appium/options/common/postrun_option.py | 8 +-- appium/options/common/prerun_option.py | 8 +-- ...rint_page_source_on_find_failure_option.py | 2 +- .../options/common/skip_log_capture_option.py | 2 +- appium/options/common/udid_option.py | 2 +- appium/options/mac/mac2/arguments_option.py | 2 +- .../options/mac/mac2/bootstrap_root_option.py | 2 +- appium/options/mac/mac2/bundle_id_option.py | 2 +- appium/options/mac/mac2/environment_option.py | 2 +- .../mac/mac2/server_startup_timeout_option.py | 2 +- .../mac/mac2/show_server_logs_option.py | 2 +- .../options/mac/mac2/skip_app_kill_option.py | 2 +- appium/options/mac/mac2/system_host_option.py | 2 +- appium/options/mac/mac2/system_port_option.py | 2 +- .../mac2/web_driver_agent_mac_url_option.py | 2 +- .../windows/app_top_level_window_option.py | 40 +++++++++++++++ .../windows/windows/app_working_dir_option.py | 40 +++++++++++++++ appium/options/windows/windows/base.py | 49 +++++++++++++++++++ .../windows/create_session_timeout_option.py | 45 +++++++++++++++++ .../windows/expreimental_web_driver_option.py | 39 +++++++++++++++ .../windows/windows/system_port_option.py | 41 ++++++++++++++++ .../windows/wait_for_app_launch_option.py | 43 ++++++++++++++++ 37 files changed, 333 insertions(+), 36 deletions(-) create mode 100644 appium/options/windows/windows/app_top_level_window_option.py create mode 100644 appium/options/windows/windows/app_working_dir_option.py create mode 100644 appium/options/windows/windows/create_session_timeout_option.py create mode 100644 appium/options/windows/windows/expreimental_web_driver_option.py create mode 100644 appium/options/windows/windows/system_port_option.py create mode 100644 appium/options/windows/windows/wait_for_app_launch_option.py diff --git a/appium/options/common/app_option.py b/appium/options/common/app_option.py index 55fd2abba..4a8fff1b1 100644 --- a/appium/options/common/app_option.py +++ b/appium/options/common/app_option.py @@ -26,7 +26,7 @@ class AppOption(SupportsCapabilities): @property def app(self) -> Optional[str]: """ - :Returns: String representing app location. + String representing app location. """ return self.get_capability(APP) diff --git a/appium/options/common/auto_web_view_option.py b/appium/options/common/auto_web_view_option.py index 3ba7965c6..6983aef47 100644 --- a/appium/options/common/auto_web_view_option.py +++ b/appium/options/common/auto_web_view_option.py @@ -26,7 +26,7 @@ class AutoWebViewOption(SupportsCapabilities): @property def auto_web_view(self) -> Optional[bool]: """ - :Returns: Whether the driver should try to automatically switch + Whether the driver should try to automatically switch to a web view context after the session is started. """ return self.get_capability(AUTO_WEB_VIEW) diff --git a/appium/options/common/automation_name_option.py b/appium/options/common/automation_name_option.py index 3250b5700..c18e0a15b 100644 --- a/appium/options/common/automation_name_option.py +++ b/appium/options/common/automation_name_option.py @@ -26,7 +26,7 @@ class AutomationNameOption(SupportsCapabilities): @property def automation_name(self) -> Optional[str]: """ - :Returns: String representing the name of the automation engine name. + String representing the name of the automation engine name. """ return self.get_capability(AUTOMATION_NAME) diff --git a/appium/options/common/clear_system_files_option.py b/appium/options/common/clear_system_files_option.py index 9eb6fceaa..208c4bb96 100644 --- a/appium/options/common/clear_system_files_option.py +++ b/appium/options/common/clear_system_files_option.py @@ -26,7 +26,7 @@ class ClearSystemFilesOption(SupportsCapabilities): @property def clear_system_files(self) -> Optional[bool]: """ - :Returns: Whether the driver should delete generated files at the end of a session. + Whether the driver should delete generated files at the end of a session. """ return self.get_capability(CLEAR_SYSTEM_FILES) diff --git a/appium/options/common/device_name_option.py b/appium/options/common/device_name_option.py index 34d8f989c..8e33cce39 100644 --- a/appium/options/common/device_name_option.py +++ b/appium/options/common/device_name_option.py @@ -26,7 +26,7 @@ class DeviceNameOption(SupportsCapabilities): @property def device_name(self) -> Optional[str]: """ - :Returns: The name of the device. + The name of the device. """ return self.get_capability(DEVICE_NAME) diff --git a/appium/options/common/enable_performance_logging_option.py b/appium/options/common/enable_performance_logging_option.py index d6ea1d682..22a4bdb30 100644 --- a/appium/options/common/enable_performance_logging_option.py +++ b/appium/options/common/enable_performance_logging_option.py @@ -26,7 +26,7 @@ class EnablePerformanceLoggingOption(SupportsCapabilities): @property def enable_performance_logging(self) -> Optional[bool]: """ - :Returns: Whether to enable additional performance logging. + Whether to enable additional performance logging. """ return self.get_capability(ENABLE_PERFORMANCE_LOGGING) diff --git a/appium/options/common/event_timings_option.py b/appium/options/common/event_timings_option.py index d9d8b3e2f..b9c0a1245 100644 --- a/appium/options/common/event_timings_option.py +++ b/appium/options/common/event_timings_option.py @@ -26,7 +26,7 @@ class EventTimingsOption(SupportsCapabilities): @property def event_timings(self) -> Optional[bool]: """ - :Returns: Whether the driver should to report the timings + Whether the driver should to report the timings for various Appium-internal events. """ return self.get_capability(EVENT_TIMINGS) diff --git a/appium/options/common/full_reset_option.py b/appium/options/common/full_reset_option.py index fc8f53040..aee86d7db 100644 --- a/appium/options/common/full_reset_option.py +++ b/appium/options/common/full_reset_option.py @@ -26,7 +26,7 @@ class FullResetOption(SupportsCapabilities): @property def full_reset(self) -> Optional[bool]: """ - :Returns: Whether the driver should perform a full reset. + Whether the driver should perform a full reset. """ return self.get_capability(FULL_RESET) diff --git a/appium/options/common/is_headless_option.py b/appium/options/common/is_headless_option.py index 3ab3fc98f..bc0c2b110 100644 --- a/appium/options/common/is_headless_option.py +++ b/appium/options/common/is_headless_option.py @@ -26,7 +26,7 @@ class IsHeadlessOption(SupportsCapabilities): @property def is_headless(self) -> Optional[bool]: """ - :Returns: Whether the driver should start emulator/simulator in headless mode. + Whether the driver should start emulator/simulator in headless mode. """ return self.get_capability(IS_HEADLESS) diff --git a/appium/options/common/language_option.py b/appium/options/common/language_option.py index c12ebd37b..f82de63d0 100644 --- a/appium/options/common/language_option.py +++ b/appium/options/common/language_option.py @@ -26,7 +26,7 @@ class LanguageOption(SupportsCapabilities): @property def language(self) -> Optional[str]: """ - :Returns: Language abbreviation to use in a test session. + Language abbreviation to use in a test session. """ return self.get_capability(LANGUAGE) diff --git a/appium/options/common/locale_option.py b/appium/options/common/locale_option.py index 2c9d24e38..58503a890 100644 --- a/appium/options/common/locale_option.py +++ b/appium/options/common/locale_option.py @@ -26,7 +26,7 @@ class LocaleOption(SupportsCapabilities): @property def locale(self) -> Optional[str]: """ - :Returns: Locale abbreviation to use in a test session. + Locale abbreviation to use in a test session. """ return self.get_capability(LOCALE) diff --git a/appium/options/common/new_command_timeout_option.py b/appium/options/common/new_command_timeout_option.py index a91f2ee53..ce71907c1 100644 --- a/appium/options/common/new_command_timeout_option.py +++ b/appium/options/common/new_command_timeout_option.py @@ -27,7 +27,7 @@ class NewCommandTimeoutOption(SupportsCapabilities): @property def new_command_timeout(self) -> Optional[timedelta]: """ - :Returns: The allowed time before seeing a new server command. + The allowed time before seeing a new server command. """ value = self.get_capability(NEW_COMMAND_TIMEOUT) return None if value is None else timedelta(seconds=value) diff --git a/appium/options/common/no_reset_option.py b/appium/options/common/no_reset_option.py index 01ca5e7b5..62b5e0d5f 100644 --- a/appium/options/common/no_reset_option.py +++ b/appium/options/common/no_reset_option.py @@ -26,7 +26,7 @@ class NoResetOption(SupportsCapabilities): @property def full_reset(self) -> Optional[bool]: """ - :Returns: Whether the driver should not perform a reset. + Whether the driver should not perform a reset. """ return self.get_capability(NO_RESET) diff --git a/appium/options/common/orientation_option.py b/appium/options/common/orientation_option.py index 6060fe8a5..4141a4d90 100644 --- a/appium/options/common/orientation_option.py +++ b/appium/options/common/orientation_option.py @@ -26,7 +26,7 @@ class OrientationOption(SupportsCapabilities): @property def orientation(self) -> Optional[str]: """ - :Returns: The orientation of the device's screen. + The orientation of the device's screen. Usually this is either 'PORTRAIT' or 'LANDSCAPE'. """ return self.get_capability(ORIENTATION) diff --git a/appium/options/common/other_apps_option.py b/appium/options/common/other_apps_option.py index 1751968de..6efbbbc4e 100644 --- a/appium/options/common/other_apps_option.py +++ b/appium/options/common/other_apps_option.py @@ -26,7 +26,7 @@ class OtherAppsOption(SupportsCapabilities): @property def other_apps(self) -> Optional[str]: """ - :Returns: Locations of apps to install before running a test. + Locations of apps to install before running a test. """ return self.get_capability(OTHER_APPS) diff --git a/appium/options/common/postrun_option.py b/appium/options/common/postrun_option.py index 076c222a7..f940cd6b6 100644 --- a/appium/options/common/postrun_option.py +++ b/appium/options/common/postrun_option.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Dict, Optional from .supports_capabilities import SupportsCapabilities @@ -24,15 +24,15 @@ class PostrunOption(SupportsCapabilities): @property - def postrun(self) -> Optional[str]: + def postrun(self) -> Optional[Dict[str, str]]: """ - :return: System script which is supposed to be executed upon + System script which is supposed to be executed upon driver session quit. """ return self.get_capability(POSTRUN) @postrun.setter - def postrun(self, value: str) -> None: + def postrun(self, value: Dict[str, str]) -> None: """ Set a system script to execute upon driver session quit. """ diff --git a/appium/options/common/prerun_option.py b/appium/options/common/prerun_option.py index 8f703cbb8..139874c76 100644 --- a/appium/options/common/prerun_option.py +++ b/appium/options/common/prerun_option.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Dict, Optional from .supports_capabilities import SupportsCapabilities @@ -24,15 +24,15 @@ class PrerunOption(SupportsCapabilities): @property - def prerun(self) -> Optional[str]: + def prerun(self) -> Optional[Dict[str, str]]: """ - :return: System script which is supposed to be executed before + System script which is supposed to be executed before a driver session is initialised. """ return self.get_capability(PRERUN) @prerun.setter - def prerun(self, value: str) -> None: + def prerun(self, value: Dict[str, str]) -> None: """ Set a system script which is supposed to be executed before a driver session is initialised. diff --git a/appium/options/common/print_page_source_on_find_failure_option.py b/appium/options/common/print_page_source_on_find_failure_option.py index 935bc2769..b93f07cf5 100644 --- a/appium/options/common/print_page_source_on_find_failure_option.py +++ b/appium/options/common/print_page_source_on_find_failure_option.py @@ -26,7 +26,7 @@ class PrintPageSourceOnFindFailureOption(SupportsCapabilities): @property def print_page_source_on_find_failure(self) -> Optional[bool]: """ - :Returns: Whether the driver should print the page source to the log + Whether the driver should print the page source to the log if a find failure occurs. """ return self.get_capability(PRINT_PAGE_SOURCE_ON_FIND_FAILURE) diff --git a/appium/options/common/skip_log_capture_option.py b/appium/options/common/skip_log_capture_option.py index 0489df775..668704e13 100644 --- a/appium/options/common/skip_log_capture_option.py +++ b/appium/options/common/skip_log_capture_option.py @@ -26,7 +26,7 @@ class SkipLogCaptureOption(SupportsCapabilities): @property def skip_log_capture(self) -> Optional[bool]: """ - :Returns: Whether the driver should not record device logs. + Whether the driver should not record device logs. """ return self.get_capability(SKIP_LOG_CAPTURE) diff --git a/appium/options/common/udid_option.py b/appium/options/common/udid_option.py index 1e73b58cb..f760a1d21 100644 --- a/appium/options/common/udid_option.py +++ b/appium/options/common/udid_option.py @@ -26,7 +26,7 @@ class UdidOption(SupportsCapabilities): @property def udid(self) -> Optional[str]: """ - :Returns: The unique identifier of the device under test. + The unique identifier of the device under test. """ return self.get_capability(UDID) diff --git a/appium/options/mac/mac2/arguments_option.py b/appium/options/mac/mac2/arguments_option.py index 77f790f09..3c9a5a70e 100644 --- a/appium/options/mac/mac2/arguments_option.py +++ b/appium/options/mac/mac2/arguments_option.py @@ -26,7 +26,7 @@ class ArgumentsOption(SupportsCapabilities): @property def arguments(self) -> Optional[List[str]]: """ - :Returns: Array of application command line arguments. + Array of application command line arguments. """ return self.get_capability(ARGUMENTS) diff --git a/appium/options/mac/mac2/bootstrap_root_option.py b/appium/options/mac/mac2/bootstrap_root_option.py index 1be1d9e98..25c53d1ba 100644 --- a/appium/options/mac/mac2/bootstrap_root_option.py +++ b/appium/options/mac/mac2/bootstrap_root_option.py @@ -26,7 +26,7 @@ class BootstrapRootOption(SupportsCapabilities): @property def bootstrap_root(self) -> Optional[str]: """ - :Returns: The full path to WebDriverAgentMac root folder where Xcode project + The full path to WebDriverAgentMac root folder where Xcode project of the server sources lives. """ return self.get_capability(BOOTSTRAP_ROOT) diff --git a/appium/options/mac/mac2/bundle_id_option.py b/appium/options/mac/mac2/bundle_id_option.py index e02f54f6e..06a3bdb89 100644 --- a/appium/options/mac/mac2/bundle_id_option.py +++ b/appium/options/mac/mac2/bundle_id_option.py @@ -26,7 +26,7 @@ class BundleIdOption(SupportsCapabilities): @property def bundle_id(self) -> Optional[str]: """ - :Returns: The bundle identifier of the application to automate. + The bundle identifier of the application to automate. """ return self.get_capability(BUNDLE_ID) diff --git a/appium/options/mac/mac2/environment_option.py b/appium/options/mac/mac2/environment_option.py index f67ca0fe3..b5d72a9fc 100644 --- a/appium/options/mac/mac2/environment_option.py +++ b/appium/options/mac/mac2/environment_option.py @@ -26,7 +26,7 @@ class EnvironmentOption(SupportsCapabilities): @property def environment(self) -> Optional[Dict[str, str]]: """ - :Returns: Application environment variables mapping. + Application environment variables mapping. """ return self.get_capability(ENVIRONMENT) diff --git a/appium/options/mac/mac2/server_startup_timeout_option.py b/appium/options/mac/mac2/server_startup_timeout_option.py index 390e1ed29..c5b224edf 100644 --- a/appium/options/mac/mac2/server_startup_timeout_option.py +++ b/appium/options/mac/mac2/server_startup_timeout_option.py @@ -27,7 +27,7 @@ class ServerStartupTimeoutOption(SupportsCapabilities): @property def server_startup_timeout(self) -> Optional[timedelta]: """ - :Returns: Get the timeout to wait util the WebDriverAgentMac + Get the timeout to wait util the WebDriverAgentMac project is built and started. """ value_ms = self.get_capability(SERVER_STARTUP_TIMEOUT) diff --git a/appium/options/mac/mac2/show_server_logs_option.py b/appium/options/mac/mac2/show_server_logs_option.py index edee52c2d..635304925 100644 --- a/appium/options/mac/mac2/show_server_logs_option.py +++ b/appium/options/mac/mac2/show_server_logs_option.py @@ -26,7 +26,7 @@ class ShowServerLogsOption(SupportsCapabilities): @property def show_server_logs(self) -> Optional[bool]: """ - :Returns: Whether to show WDA server logs in the Appium log. + Whether to show WDA server logs in the Appium log. """ return self.get_capability(SHOW_SERVER_LOGS) diff --git a/appium/options/mac/mac2/skip_app_kill_option.py b/appium/options/mac/mac2/skip_app_kill_option.py index 370a13006..adfcd19a3 100644 --- a/appium/options/mac/mac2/skip_app_kill_option.py +++ b/appium/options/mac/mac2/skip_app_kill_option.py @@ -26,7 +26,7 @@ class SkipAppKillOption(SupportsCapabilities): @property def skip_app_kill(self) -> Optional[bool]: """ - :Returns: Whether to skip the termination of the application under test. + Whether to skip the termination of the application under test. """ return self.get_capability(SKIP_APP_KILL) diff --git a/appium/options/mac/mac2/system_host_option.py b/appium/options/mac/mac2/system_host_option.py index 14bee716b..78e7ff4d9 100644 --- a/appium/options/mac/mac2/system_host_option.py +++ b/appium/options/mac/mac2/system_host_option.py @@ -26,7 +26,7 @@ class SystemHostOption(SupportsCapabilities): @property def system_host(self) -> Optional[str]: """ - :Returns: The name of the host for the internal server to listen on. + The name of the host for the internal server to listen on. """ return self.get_capability(SYSTEM_HOST) diff --git a/appium/options/mac/mac2/system_port_option.py b/appium/options/mac/mac2/system_port_option.py index f4fb60e6b..805856bff 100644 --- a/appium/options/mac/mac2/system_port_option.py +++ b/appium/options/mac/mac2/system_port_option.py @@ -26,7 +26,7 @@ class SystemPortOption(SupportsCapabilities): @property def system_port(self) -> Optional[int]: """ - :Returns: The number of the port for the internal server to listen on. + The number of the port for the internal server to listen on. """ return self.get_capability(SYSTEM_PORT) diff --git a/appium/options/mac/mac2/web_driver_agent_mac_url_option.py b/appium/options/mac/mac2/web_driver_agent_mac_url_option.py index 5de658f06..8ffab4391 100644 --- a/appium/options/mac/mac2/web_driver_agent_mac_url_option.py +++ b/appium/options/mac/mac2/web_driver_agent_mac_url_option.py @@ -26,7 +26,7 @@ class WebDriverAgentMacUrlOption(SupportsCapabilities): @property def web_driver_agent_mac_url(self) -> Optional[str]: """ - :Returns: The URL Appium will connect to an existing WebDriverAgentMac instance. + The URL Appium will connect to an existing WebDriverAgentMac instance. """ return self.get_capability(WEB_DRIVER_ARGENT_MAC_URL) diff --git a/appium/options/windows/windows/app_top_level_window_option.py b/appium/options/windows/windows/app_top_level_window_option.py new file mode 100644 index 000000000..f573fb48e --- /dev/null +++ b/appium/options/windows/windows/app_top_level_window_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_TOP_LEVEL_WINDOW = 'appTopLevelWindow' + + +class AppTopLevelWindowOption(SupportsCapabilities): + @property + def app_top_level_window(self) -> Optional[str]: + """ + Hexadecimal handle of an existing application top level window to attach to. + """ + return self.get_capability(APP_TOP_LEVEL_WINDOW) + + @app_top_level_window.setter + def app_top_level_window(self, value: str) -> None: + """ + Set the hexadecimal handle of an existing application top level + window to attach to, for example 0x12345 (should be of string type). + Either this capability or app one must be provided on session startup. + """ + self.set_capability(APP_TOP_LEVEL_WINDOW, value) diff --git a/appium/options/windows/windows/app_working_dir_option.py b/appium/options/windows/windows/app_working_dir_option.py new file mode 100644 index 000000000..0160f89aa --- /dev/null +++ b/appium/options/windows/windows/app_working_dir_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_WORKING_DIR = 'appWorkingDir' + + +class AppWorkingDirOption(SupportsCapabilities): + @property + def app_working_dir(self) -> Optional[str]: + """ + Full path to the folder, which is going to be set as the working + dir for the application under test. + """ + return self.get_capability(APP_WORKING_DIR) + + @app_working_dir.setter + def app_working_dir(self, value: str) -> None: + """ + Set the full path to the folder, which is going to be set as the working + dir for the application under test. This is only applicable for classic apps. + """ + self.set_capability(APP_WORKING_DIR, value) diff --git a/appium/options/windows/windows/base.py b/appium/options/windows/windows/base.py index a1f4b2f7a..f0f6ee3a4 100644 --- a/appium/options/windows/windows/base.py +++ b/appium/options/windows/windows/base.py @@ -17,17 +17,66 @@ from typing import Dict +from appium.options.common.app_option import AppOption from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import PLATFORM_NAME, AppiumOptions from appium.options.common.postrun_option import PostrunOption from appium.options.common.prerun_option import PrerunOption +from .app_top_level_window_option import AppTopLevelWindowOption +from .app_working_dir_option import AppWorkingDirOption +from .create_session_timeout_option import CreateSessionTimeoutOption +from .expreimental_web_driver_option import ExperimentalWebDriverOption +from .system_port_option import SystemPortOption +from .wait_for_app_launch_option import WaitForAppLaunchOption + class WindowsOptions( AppiumOptions, PrerunOption, PostrunOption, + AppOption, + AppTopLevelWindowOption, + AppWorkingDirOption, + CreateSessionTimeoutOption, + ExperimentalWebDriverOption, + SystemPortOption, + WaitForAppLaunchOption, ): + @AppOption.app.setter # type: ignore + def app(self, value: str) -> None: + """ + The name of the UWP application to test or full path to a classic app, + for example Microsoft.WindowsCalculator_8wekyb3d8bbwe!App or + C:\\Windows\\System32\\notepad.exe. It is also possible to set app to Root. + In such case the session will be invoked without any explicit target application + (actually, it will be Explorer). Either this capability or appTopLevelWindow must + be provided on session startup. + """ + AppOption.app.fset(self, value) # type: ignore + + @PrerunOption.prerun.setter # type: ignore + def prerun(self, value: Dict[str, str]) -> None: + """ + A mapping containing either 'script' or 'command' key. The value of + each key must be a valid PowerShell script or command to be + executed prior to the WinAppDriver session startup. + See https://github.com/appium/appium-windows-driver#power-shell-commands-execution + for more details. + """ + PrerunOption.prerun.fset(self, value) # type: ignore + + @PostrunOption.postrun.setter # type: ignore + def postrun(self, value: Dict[str, str]) -> None: + """ + A mapping containing either 'script' or 'command' key. The value of + each key must be a valid PowerShell script or command to be + executed after a WinAppDriver session is finished. + See https://github.com/appium/appium-windows-driver#power-shell-commands-execution + for more details. + """ + PostrunOption.postrun.fset(self, value) # type: ignore + @property def default_capabilities(self) -> Dict: return { diff --git a/appium/options/windows/windows/create_session_timeout_option.py b/appium/options/windows/windows/create_session_timeout_option.py new file mode 100644 index 000000000..161bd0e9c --- /dev/null +++ b/appium/options/windows/windows/create_session_timeout_option.py @@ -0,0 +1,45 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CREATE_SESSION_TIMEOUT = 'createSessionTimeout' + + +class CreateSessionTimeoutOption(SupportsCapabilities): + @property + def create_session_timeout(self) -> Optional[timedelta]: + """ + Timeout used to retry Appium Windows Driver session startup. + """ + value = self.get_capability(CREATE_SESSION_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @create_session_timeout.setter + def create_session_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the timeout used to retry Appium Windows Driver session startup. + This capability could be used as a workaround for the long startup times + of UWP applications (aka Failed to locate opened application window + with appId: TestCompany.my_app4!App, and processId: 8480). + """ + self.set_capability( + CREATE_SESSION_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/windows/windows/expreimental_web_driver_option.py b/appium/options/windows/windows/expreimental_web_driver_option.py new file mode 100644 index 000000000..ae065d717 --- /dev/null +++ b/appium/options/windows/windows/expreimental_web_driver_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +EXPERIMENTAL_WEB_DRIVER = 'ms:experimental-webdriver' + + +class ExperimentalWebDriverOption(SupportsCapabilities): + @property + def experimental_webdriver(self) -> Optional[bool]: + """ + Whether to enable experimental features and optimizations. + """ + return self.get_capability(EXPERIMENTAL_WEB_DRIVER) + + @experimental_webdriver.setter + def experimental_webdriver(self, value: bool) -> None: + """ + Enables experimental features and optimizations. See Appium Windows + Driver release notes for more details on this capability. + """ + self.set_capability(EXPERIMENTAL_WEB_DRIVER, value) diff --git a/appium/options/windows/windows/system_port_option.py b/appium/options/windows/windows/system_port_option.py new file mode 100644 index 000000000..30ac556af --- /dev/null +++ b/appium/options/windows/windows/system_port_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SYSTEM_PORT = 'systemPort' + + +class SystemPortOption(SupportsCapabilities): + @property + def system_port(self) -> Optional[int]: + """ + Port number to execute Appium Windows Driver server listener on. + """ + return self.get_capability(SYSTEM_PORT) + + @system_port.setter + def system_port(self, value: int) -> None: + """ + The port number to execute Appium Windows Driver server listener on, + for example 5556. The port must not be occupied. The default starting port + number for a new Appium Windows Driver session is 4724. If this port is + already busy then the next free port will be automatically selected. + """ + self.set_capability(SYSTEM_PORT, value) diff --git a/appium/options/windows/windows/wait_for_app_launch_option.py b/appium/options/windows/windows/wait_for_app_launch_option.py new file mode 100644 index 000000000..0b9cb0fa8 --- /dev/null +++ b/appium/options/windows/windows/wait_for_app_launch_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WAIT_FOR_APP_LAUNCH = 'ms:waitForAppLaunch' + + +class WaitForAppLaunchOption(SupportsCapabilities): + @property + def wait_for_app_launch(self) -> Optional[timedelta]: + """ + Timeout used to retry Appium Windows Driver session startup. + """ + value = self.get_capability(WAIT_FOR_APP_LAUNCH) + return None if value is None else timedelta(seconds=value) + + @wait_for_app_launch.setter + def wait_for_app_launch(self, value: Union[timedelta, int]) -> None: + """ + Similar to createSessionTimeout, but is + applied on the server side. Enables Appium Windows Driver to wait for + a defined amount of time after an app launch is initiated prior to + attaching to the application session. The limit for this is 50 seconds. + """ + self.set_capability(WAIT_FOR_APP_LAUNCH, value.seconds if isinstance(value, timedelta) else value) From 5b958b53f1df486314ca82cb35adce9161147aef Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 21 Jun 2022 22:17:58 +0200 Subject: [PATCH 093/548] refactor: Make system_port and system_host options common (#734) --- .../mac2 => common}/system_host_option.py | 4 -- .../mac2 => common}/system_port_option.py | 1 - appium/options/mac/mac2/base.py | 45 ++++++++++++++++++- appium/options/windows/windows/base.py | 12 ++++- .../windows/windows/system_port_option.py | 41 ----------------- ci-jobs/functional/run_android_test.yml | 6 +-- ci-jobs/functional/run_ios_test.yml | 6 +-- ci-jobs/functional/setup_appium.yml | 1 + ci-jobs/scripts/start_server.sh | 12 +++++ 9 files changed, 71 insertions(+), 57 deletions(-) rename appium/options/{mac/mac2 => common}/system_host_option.py (83%) rename appium/options/{mac/mac2 => common}/system_port_option.py (94%) delete mode 100644 appium/options/windows/windows/system_port_option.py create mode 100644 ci-jobs/scripts/start_server.sh diff --git a/appium/options/mac/mac2/system_host_option.py b/appium/options/common/system_host_option.py similarity index 83% rename from appium/options/mac/mac2/system_host_option.py rename to appium/options/common/system_host_option.py index 78e7ff4d9..0544de5ca 100644 --- a/appium/options/mac/mac2/system_host_option.py +++ b/appium/options/common/system_host_option.py @@ -34,9 +34,5 @@ def system_host(self) -> Optional[str]: def system_host(self, value: str) -> None: """ Set the name of the host for the internal server to listen on. - If not provided then Mac2Driver will use the default host - address 127.0.0.1. You could set it to 0.0.0.0 to make the - server listening on all available network interfaces. - It is also possible to set the particular interface name, for example en1. """ self.set_capability(SYSTEM_HOST, value) diff --git a/appium/options/mac/mac2/system_port_option.py b/appium/options/common/system_port_option.py similarity index 94% rename from appium/options/mac/mac2/system_port_option.py rename to appium/options/common/system_port_option.py index 805856bff..e11f3b59d 100644 --- a/appium/options/mac/mac2/system_port_option.py +++ b/appium/options/common/system_port_option.py @@ -34,6 +34,5 @@ def system_port(self) -> Optional[int]: def system_port(self, value: int) -> None: """ Set the number of the port for the internal server to listen on. - If not provided then Mac2Driver will use the default port 10100. """ self.set_capability(SYSTEM_PORT, value) diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py index 84641449a..3b44aa91b 100644 --- a/appium/options/mac/mac2/base.py +++ b/appium/options/mac/mac2/base.py @@ -21,6 +21,8 @@ from appium.options.common.base import PLATFORM_NAME, AppiumOptions from appium.options.common.postrun_option import PostrunOption from appium.options.common.prerun_option import PrerunOption +from appium.options.common.system_host_option import SystemHostOption +from appium.options.common.system_port_option import SystemPortOption from .arguments_option import ArgumentsOption from .bootstrap_root_option import BootstrapRootOption @@ -29,8 +31,6 @@ from .server_startup_timeout_option import ServerStartupTimeoutOption from .show_server_logs_option import ShowServerLogsOption from .skip_app_kill_option import SkipAppKillOption -from .system_host_option import SystemHostOption -from .system_port_option import SystemPortOption from .web_driver_agent_mac_url_option import WebDriverAgentMacUrlOption @@ -49,6 +49,47 @@ class Mac2Options( SystemPortOption, WebDriverAgentMacUrlOption, ): + @PrerunOption.prerun.setter # type: ignore + def prerun(self, value: Dict[str, str]) -> None: + """ + A mapping containing either 'script' or 'command' key. The value of + each key must be a valid AppleScript script or command to be + executed after before Mac2Driver session is started. See + https://github.com/appium/appium-mac2-driver#applescript-commands-execution + for more details. + """ + PrerunOption.prerun.fset(self, value) # type: ignore + + @PostrunOption.postrun.setter # type: ignore + def postrun(self, value: Dict[str, str]) -> None: + """ + A mapping containing either 'script' or 'command' key. The value of + each key must be a valid AppleScript script or command to be + executed after Mac2Driver session is stopped. See + https://github.com/appium/appium-mac2-driver#applescript-commands-execution + for more details. + """ + PostrunOption.postrun.fset(self, value) # type: ignore + + @SystemPortOption.system_port.setter # type: ignore + def system_port(self, value: int) -> None: + """ + Set the number of the port for the internal server to listen on. + If not provided then Mac2Driver will use the default port 10100. + """ + SystemPortOption.system_port.fset(self, value) # type: ignore + + @SystemHostOption.system_host.setter # type: ignore + def system_host(self, value: str) -> None: + """ + Set the number of the port for the internal server to listen on. + If not provided then Mac2Driver will use the default host + address 127.0.0.1. You could set it to 0.0.0.0 to make the + server listening on all available network interfaces. + It is also possible to set the particular interface name, for example en1. + """ + SystemHostOption.system_host.fset(self, value) # type: ignore + @property def default_capabilities(self) -> Dict: return { diff --git a/appium/options/windows/windows/base.py b/appium/options/windows/windows/base.py index f0f6ee3a4..21cf43f28 100644 --- a/appium/options/windows/windows/base.py +++ b/appium/options/windows/windows/base.py @@ -22,12 +22,12 @@ from appium.options.common.base import PLATFORM_NAME, AppiumOptions from appium.options.common.postrun_option import PostrunOption from appium.options.common.prerun_option import PrerunOption +from appium.options.common.system_port_option import SystemPortOption from .app_top_level_window_option import AppTopLevelWindowOption from .app_working_dir_option import AppWorkingDirOption from .create_session_timeout_option import CreateSessionTimeoutOption from .expreimental_web_driver_option import ExperimentalWebDriverOption -from .system_port_option import SystemPortOption from .wait_for_app_launch_option import WaitForAppLaunchOption @@ -77,6 +77,16 @@ def postrun(self, value: Dict[str, str]) -> None: """ PostrunOption.postrun.fset(self, value) # type: ignore + @SystemPortOption.system_port.setter # type: ignore + def system_port(self, value: int) -> None: + """ + The port number to execute Appium Windows Driver server listener on, + for example 5556. The port must not be occupied. The default starting port + number for a new Appium Windows Driver session is 4724. If this port is + already busy then the next free port will be automatically selected. + """ + SystemPortOption.system_port.fset(self, value) # type: ignore + @property def default_capabilities(self) -> Dict: return { diff --git a/appium/options/windows/windows/system_port_option.py b/appium/options/windows/windows/system_port_option.py deleted file mode 100644 index 30ac556af..000000000 --- a/appium/options/windows/windows/system_port_option.py +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed to the Software Freedom Conservancy (SFC) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The SFC licenses this file -# to you 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. - -from typing import Optional - -from appium.options.common.supports_capabilities import SupportsCapabilities - -SYSTEM_PORT = 'systemPort' - - -class SystemPortOption(SupportsCapabilities): - @property - def system_port(self) -> Optional[int]: - """ - Port number to execute Appium Windows Driver server listener on. - """ - return self.get_capability(SYSTEM_PORT) - - @system_port.setter - def system_port(self, value: int) -> None: - """ - The port number to execute Appium Windows Driver server listener on, - for example 5556. The port must not be occupied. The default starting port - number for a new Appium Windows Driver session is 4724. If this port is - already busy then the next free port will be automatically selected. - """ - self.set_capability(SYSTEM_PORT, value) diff --git a/ci-jobs/functional/run_android_test.yml b/ci-jobs/functional/run_android_test.yml index 7fa99a198..08b70a653 100644 --- a/ci-jobs/functional/run_android_test.yml +++ b/ci-jobs/functional/run_android_test.yml @@ -13,10 +13,8 @@ jobs: - script: bash ci-jobs/functional/start-emulator.sh displayName: Create and run Emulator - script: | - if [[ '${{ parameters.dontRunAppium }}' != true ]]; then - appium --relaxed-security > appium_log.txt 2>&1 & - sleep 2 - fi + bash ci-jobs/scripts/start_server.sh + pushd "$(pwd)" cd test/functional/android python -m pytest ${{ parameters.testFiles}} ${{ parameters.pytestOpt }} diff --git a/ci-jobs/functional/run_ios_test.yml b/ci-jobs/functional/run_ios_test.yml index 122dca1d8..79f046444 100644 --- a/ci-jobs/functional/run_ios_test.yml +++ b/ci-jobs/functional/run_ios_test.yml @@ -12,10 +12,8 @@ jobs: - script: appium driver install xcuitest displayName: Install XCUITest driver - script: | - if [[ '${{ parameters.dontRunAppium }}' != true ]]; then - appium --relaxed-security > appium_log.txt 2>&1 & - sleep 2 - fi + bash ci-jobs/scripts/start_server.sh + pushd "$(pwd)" cd test/functional/ios python -m pytest ${{ parameters.testFiles}} ${{ parameters.pytestOpt }} diff --git a/ci-jobs/functional/setup_appium.yml b/ci-jobs/functional/setup_appium.yml index 6a220ef5a..05931e40b 100644 --- a/ci-jobs/functional/setup_appium.yml +++ b/ci-jobs/functional/setup_appium.yml @@ -24,4 +24,5 @@ steps: ffmpeg -version appium --version node --version + npm --version displayName: Check versions diff --git a/ci-jobs/scripts/start_server.sh b/ci-jobs/scripts/start_server.sh new file mode 100644 index 000000000..f1b8918af --- /dev/null +++ b/ci-jobs/scripts/start_server.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +appium server --relaxed-security > appium_log.txt 2>&1 & +secondsStarted=$(date +%s) +while ! nc -z 127.0.0.1 4723; do + sleep 0.1 + secondsElapsed=$(( $(date +%s) - secondsStarted )) + if [[ $secondsElapsed -gt 30 ]]; then + echo "Appium server was unable to start within 30 seconds timeout" + exit 1 + fi +done From 0a0cff2e02ad19a2cb336c3e92fcc7378ed57fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 09:15:38 +0200 Subject: [PATCH 094/548] chore(deps): update pylint requirement from ~=2.14.2 to ~=2.14.3 (#733) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.2...v2.14.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 86f0c1ed5..2ad20aab3 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.2" +pylint = "~=2.14.3" astroid = "~=2.9" isort = "~=5.10" From b4e17a3a1efe33f08ac9ef883891b9fad4449a41 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Jun 2022 12:17:21 +0200 Subject: [PATCH 095/548] feat: Add Gecko driver options (#735) --- .../options/gecko/android_storage_option.py | 39 +++++++++++++++++ appium/options/gecko/base.py | 20 +++++++++ .../options/gecko/firefox_options_option.py | 38 ++++++++++++++++ .../options/gecko/marionette_port_option.py | 43 +++++++++++++++++++ appium/options/gecko/verbosity_option.py | 40 +++++++++++++++++ 5 files changed, 180 insertions(+) create mode 100644 appium/options/gecko/android_storage_option.py create mode 100644 appium/options/gecko/firefox_options_option.py create mode 100644 appium/options/gecko/marionette_port_option.py create mode 100644 appium/options/gecko/verbosity_option.py diff --git a/appium/options/gecko/android_storage_option.py b/appium/options/gecko/android_storage_option.py new file mode 100644 index 000000000..1209ac501 --- /dev/null +++ b/appium/options/gecko/android_storage_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ANDROID_STORAGE = 'androidStorage' + + +class AndroidStorageOption(SupportsCapabilities): + @property + def android_storage(self) -> Optional[str]: + """ + The currently set storage type. + """ + return self.get_capability(ANDROID_STORAGE) + + @android_storage.setter + def android_storage(self, value: str) -> None: + """ + See https://firefox-source-docs.mozilla.org/testing/geckodriver + /Flags.html#code-android-storage-var-android-storage-var-code + """ + self.set_capability(ANDROID_STORAGE, value) diff --git a/appium/options/gecko/base.py b/appium/options/gecko/base.py index d11b2511c..873e5646a 100644 --- a/appium/options/gecko/base.py +++ b/appium/options/gecko/base.py @@ -19,11 +19,31 @@ from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import AppiumOptions +from appium.options.common.system_port_option import SystemPortOption + +from .android_storage_option import AndroidStorageOption +from .firefox_options_option import FirefoxOptionsOption +from .marionette_port_option import MarionettePortOption +from .verbosity_option import VerbosityOption class GeckoOptions( AppiumOptions, + AndroidStorageOption, + FirefoxOptionsOption, + MarionettePortOption, + SystemPortOption, + VerbosityOption, ): + @SystemPortOption.system_port.setter # type: ignore + def system_port(self, value: int) -> None: + """ + The number of the port for the driver to listen on. Must be unique + for each session. If not provided then the driver will try to detect + it automatically. + """ + SystemPortOption.system_port.fset(self, value) # type: ignore + @property def default_capabilities(self) -> Dict: return { diff --git a/appium/options/gecko/firefox_options_option.py b/appium/options/gecko/firefox_options_option.py new file mode 100644 index 000000000..87906b4bb --- /dev/null +++ b/appium/options/gecko/firefox_options_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +FIREFOX_OPTIONS = 'moz:firefoxOptions' + + +class FirefoxOptionsOption(SupportsCapabilities): + @property + def firefox_options(self) -> Optional[Dict[str, Any]]: + """ + Firefox options mapping. + """ + return self.get_capability(FIREFOX_OPTIONS) + + @firefox_options.setter + def firefox_options(self, value: Dict[str, Any]) -> None: + """ + See https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions + """ + self.set_capability(FIREFOX_OPTIONS, value) diff --git a/appium/options/gecko/marionette_port_option.py b/appium/options/gecko/marionette_port_option.py new file mode 100644 index 000000000..24846fd76 --- /dev/null +++ b/appium/options/gecko/marionette_port_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MARIONETTE_PORT = 'marionettePort' + + +class MarionettePortOption(SupportsCapabilities): + @property + def marionette_port(self) -> Optional[int]: + """ + The number of the port for the Marionette server to listen on. + """ + return self.get_capability(MARIONETTE_PORT) + + @marionette_port.setter + def marionette_port(self, value: int) -> None: + """ + Selects the port for Geckodriver’s connection to the Marionette + remote protocol. The existing Firefox instance must have Marionette + enabled. To enable the remote protocol in Firefox, you can pass the + -marionette flag. Unless the marionette.port preference has been + user-set, Marionette will listen on port 2828, which is the default + value for this capability. + """ + self.set_capability(MARIONETTE_PORT, value) diff --git a/appium/options/gecko/verbosity_option.py b/appium/options/gecko/verbosity_option.py new file mode 100644 index 000000000..9888ddf2d --- /dev/null +++ b/appium/options/gecko/verbosity_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +VERBOSITY = 'verbosity' + + +class VerbosityOption(SupportsCapabilities): + @property + def verbosity(self) -> Optional[str]: + """ + The verbosity level of driver logging. + """ + return self.get_capability(VERBOSITY) + + @verbosity.setter + def verbosity(self, value: str) -> None: + """ + The verbosity level of driver logging. + By default, minimum verbosity is applied. + Either 'debug' or 'trace'. + """ + self.set_capability(VERBOSITY, value) From 6eca12427bcd8be6fd6760193b0321d5bc088a2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Jun 2022 07:44:16 +0200 Subject: [PATCH 096/548] chore(deps): update selenium requirement from ~=4.2 to ~=4.3 (#736) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.2.0...selenium-4.3.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 2ad20aab3..60967d29b 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.19" [packages] -selenium = "~=4.2" +selenium = "~=4.3" black = "==22.3.0" From 0264d81d60c30b187da7b2e58cc67f6aad0def5f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 25 Jun 2022 08:53:11 +0200 Subject: [PATCH 097/548] feat: Add xcuitest driver options (#737) --- .../{mac/mac2 => common}/bundle_id_option.py | 8 +- .../ios/safari/automatic_inspection_option.py | 2 +- .../ios/safari/automatic_profiling_option.py | 2 +- .../options/ios/safari/device_name_option.py | 2 +- .../options/ios/safari/device_type_option.py | 2 +- .../options/ios/safari/device_udid_option.py | 2 +- .../safari/platform_build_version_option.py | 2 +- .../ios/safari/platform_version_option.py | 2 +- .../ios/safari/use_simulator_option.py | 2 +- .../ios/safari/webkit_webrtc_option.py | 2 +- appium/options/ios/xcuitest/app/__init__.py | 0 .../app/app_install_strategy_option.py | 46 +++++ .../xcuitest/app/app_push_timeout_option.py | 42 +++++ .../app/localizable_strings_dir_option.py | 39 +++++ appium/options/ios/xcuitest/base.py | 163 ++++++++++++++++++ .../options/ios/xcuitest/general/__init__.py | 0 ...lude_device_caps_to_session_info_option.py | 41 +++++ .../general/reset_location_service_option.py | 39 +++++ appium/options/ios/xcuitest/other/__init__.py | 0 .../xcuitest/other/command_timeouts_option.py | 57 ++++++ .../xcuitest/other/launch_with_idb_option.py | 42 +++++ .../ios/xcuitest/other/show_ios_log_option.py | 39 +++++ .../xcuitest/other/use_json_source_option.py | 39 +++++ .../ios/xcuitest/simulator/__init__.py | 0 .../calendar_access_authorized_option.py | 41 +++++ .../simulator/calendar_format_option.py | 38 ++++ .../connect_hardware_keyboard_option.py | 43 +++++ .../simulator/custom_ssl_cert_option.py | 39 +++++ ...enforce_fresh_simulator_creation_option.py | 39 +++++ ...lator_software_keyboard_presence_option.py | 45 +++++ .../ios_simulator_logs_predicate_option.py | 38 ++++ .../simulator/keep_key_chains_option.py | 39 +++++ .../keychains_exclude_patterns_option.py | 44 +++++ .../xcuitest/simulator/permissions_option.py | 50 ++++++ .../simulator/reduce_motion_option.py | 40 +++++ .../reset_on_session_start_only_option.py | 41 +++++ .../xcuitest/simulator/scale_factor_option.py | 44 +++++ .../shutdown_other_simulators_option.py | 44 +++++ .../simulator_devices_set_path_option.py | 41 +++++ ...ulator_pasteboard_automatic_sync_option.py | 42 +++++ .../simulator_startup_timeout_option.py | 46 +++++ .../simulator_trace_pointer_option.py | 41 +++++ .../simulator_window_center_option.py | 41 +++++ appium/options/ios/xcuitest/wda/__init__.py | 0 ..._provisioning_device_regitration_option.py | 40 +++++ .../xcuitest/wda/auto_accept_alerts_option.py | 39 +++++ .../wda/auto_disimiss_alerts_option.py | 39 +++++ .../xcuitest/wda/derived_data_path_option.py | 41 +++++ .../disable_automatic_screenshots_option.py | 40 +++++ .../xcuitest/wda/force_app_launch_option.py | 42 +++++ .../xcuitest/wda/keychain_password_option.py | 39 +++++ .../ios/xcuitest/wda/keychain_path_option.py | 39 +++++ .../wda/max_typing_frequency_option.py | 40 +++++ .../xcuitest/wda/mjpeg_server_port_option.py | 42 +++++ .../xcuitest/wda/process_arguments_option.py | 42 +++++ .../xcuitest/wda/result_bundle_path_option.py | 42 +++++ .../xcuitest/wda/screenshot_quality_option.py | 42 +++++ .../wda/should_terminate_app_option.py | 42 +++++ ...hould_use_singleton_test_manager_option.py | 39 +++++ .../ios/xcuitest/wda/show_xcode_log_option.py | 40 +++++ .../wda/simple_is_visible_check_option.py | 42 +++++ .../wda/updated_wda_bundle_id_option.py | 39 +++++ .../wda/use_native_caching_strategy_option.py | 41 +++++ .../ios/xcuitest/wda/use_new_wda_option.py | 51 ++++++ .../xcuitest/wda/use_prebuilt_wda_option.py | 39 +++++ .../wda/use_simple_build_test_option.py | 40 +++++ .../xcuitest/wda/use_xctestrun_file_option.py | 49 ++++++ .../wda/wait_for_idle_timeout_option.py | 45 +++++ .../wda/wait_for_quiescence_option.py | 42 +++++ .../ios/xcuitest/wda/wda_base_url_option.py | 41 +++++ .../wda/wda_connection_timeout_option.py | 43 +++++ .../wda/wda_eventloop_idle_delay_option.py | 46 +++++ .../xcuitest/wda/wda_launch_timeout_option.py | 41 +++++ .../ios/xcuitest/wda/wda_local_port_option.py | 41 +++++ .../wda/wda_startup_retries_option.py | 39 +++++ .../wda/wda_startup_retry_interval_option.py | 43 +++++ .../wda/web_driver_agent_url_option.py | 39 +++++ .../ios/xcuitest/wda/xcode_org_id_option.py | 39 +++++ .../xcuitest/wda/xcode_signing_id_option.py | 39 +++++ .../options/ios/xcuitest/webview/__init__.py | 0 .../webview/absolute_web_locations_option.py | 42 +++++ .../additional_webview_bundle_ids_option.py | 40 +++++ .../enable_async_execute_from_https_option.py | 39 +++++ .../webview/full_context_list_option.py | 42 +++++ .../include_safari_in_webviews_option.py | 41 +++++ .../xcuitest/webview/native_web_tap_option.py | 40 +++++ .../webview/safari_garbage_collect_option.py | 39 +++++ .../safari_ignore_fraud_warning_option.py | 39 +++++ .../safari_ignore_web_hostnames_option.py | 42 +++++ .../webview/safari_initial_url_option.py | 38 ++++ ...i_log_all_communication_hex_dump_option.py | 43 +++++ .../safari_log_all_communication_option.py | 40 +++++ .../safari_open_links_in_background_option.py | 39 +++++ .../safari_socket_chunk_size_option.py | 41 +++++ ...i_web_inspector_max_frame_length_option.py | 41 +++++ .../webview/webkit_response_timeout_option.py | 43 +++++ .../webview/webview_connect_retries_option.py | 40 +++++ .../webview/webview_connect_timeout_option.py | 43 +++++ appium/options/mac/mac2/base.py | 16 +- setup.cfg | 2 +- 100 files changed, 3548 insertions(+), 18 deletions(-) rename appium/options/{mac/mac2 => common}/bundle_id_option.py (76%) create mode 100644 appium/options/ios/xcuitest/app/__init__.py create mode 100644 appium/options/ios/xcuitest/app/app_install_strategy_option.py create mode 100644 appium/options/ios/xcuitest/app/app_push_timeout_option.py create mode 100644 appium/options/ios/xcuitest/app/localizable_strings_dir_option.py create mode 100644 appium/options/ios/xcuitest/general/__init__.py create mode 100644 appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py create mode 100644 appium/options/ios/xcuitest/general/reset_location_service_option.py create mode 100644 appium/options/ios/xcuitest/other/__init__.py create mode 100644 appium/options/ios/xcuitest/other/command_timeouts_option.py create mode 100644 appium/options/ios/xcuitest/other/launch_with_idb_option.py create mode 100644 appium/options/ios/xcuitest/other/show_ios_log_option.py create mode 100644 appium/options/ios/xcuitest/other/use_json_source_option.py create mode 100644 appium/options/ios/xcuitest/simulator/__init__.py create mode 100644 appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py create mode 100644 appium/options/ios/xcuitest/simulator/calendar_format_option.py create mode 100644 appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py create mode 100644 appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py create mode 100644 appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py create mode 100644 appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py create mode 100644 appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py create mode 100644 appium/options/ios/xcuitest/simulator/keep_key_chains_option.py create mode 100644 appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py create mode 100644 appium/options/ios/xcuitest/simulator/permissions_option.py create mode 100644 appium/options/ios/xcuitest/simulator/reduce_motion_option.py create mode 100644 appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py create mode 100644 appium/options/ios/xcuitest/simulator/scale_factor_option.py create mode 100644 appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py create mode 100644 appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py create mode 100644 appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py create mode 100644 appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py create mode 100644 appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py create mode 100644 appium/options/ios/xcuitest/simulator/simulator_window_center_option.py create mode 100644 appium/options/ios/xcuitest/wda/__init__.py create mode 100644 appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py create mode 100644 appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py create mode 100644 appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py create mode 100644 appium/options/ios/xcuitest/wda/derived_data_path_option.py create mode 100644 appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py create mode 100644 appium/options/ios/xcuitest/wda/force_app_launch_option.py create mode 100644 appium/options/ios/xcuitest/wda/keychain_password_option.py create mode 100644 appium/options/ios/xcuitest/wda/keychain_path_option.py create mode 100644 appium/options/ios/xcuitest/wda/max_typing_frequency_option.py create mode 100644 appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py create mode 100644 appium/options/ios/xcuitest/wda/process_arguments_option.py create mode 100644 appium/options/ios/xcuitest/wda/result_bundle_path_option.py create mode 100644 appium/options/ios/xcuitest/wda/screenshot_quality_option.py create mode 100644 appium/options/ios/xcuitest/wda/should_terminate_app_option.py create mode 100644 appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py create mode 100644 appium/options/ios/xcuitest/wda/show_xcode_log_option.py create mode 100644 appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py create mode 100644 appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py create mode 100644 appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py create mode 100644 appium/options/ios/xcuitest/wda/use_new_wda_option.py create mode 100644 appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py create mode 100644 appium/options/ios/xcuitest/wda/use_simple_build_test_option.py create mode 100644 appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py create mode 100644 appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py create mode 100644 appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_base_url_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_local_port_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_startup_retries_option.py create mode 100644 appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py create mode 100644 appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py create mode 100644 appium/options/ios/xcuitest/wda/xcode_org_id_option.py create mode 100644 appium/options/ios/xcuitest/wda/xcode_signing_id_option.py create mode 100644 appium/options/ios/xcuitest/webview/__init__.py create mode 100644 appium/options/ios/xcuitest/webview/absolute_web_locations_option.py create mode 100644 appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py create mode 100644 appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py create mode 100644 appium/options/ios/xcuitest/webview/full_context_list_option.py create mode 100644 appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py create mode 100644 appium/options/ios/xcuitest/webview/native_web_tap_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_initial_url_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py create mode 100644 appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py create mode 100644 appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py create mode 100644 appium/options/ios/xcuitest/webview/webview_connect_retries_option.py create mode 100644 appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py diff --git a/appium/options/mac/mac2/bundle_id_option.py b/appium/options/common/bundle_id_option.py similarity index 76% rename from appium/options/mac/mac2/bundle_id_option.py rename to appium/options/common/bundle_id_option.py index 06a3bdb89..0546571a5 100644 --- a/appium/options/mac/mac2/bundle_id_option.py +++ b/appium/options/common/bundle_id_option.py @@ -33,12 +33,6 @@ def bundle_id(self) -> Optional[str]: @bundle_id.setter def bundle_id(self, value: str) -> None: """ - Set the bundle identifier of the application to automate, for example - com.apple.TextEdit. This is an optional capability. If it is not provided - then the session will be started without an application under test - (actually, it will be Finder). If the application with the given - identifier is not installed then an error will be thrown on session - startup. If the application is already running then it will be moved to - the foreground. + Set the bundle identifier of the application to automate. """ self.set_capability(BUNDLE_ID, value) diff --git a/appium/options/ios/safari/automatic_inspection_option.py b/appium/options/ios/safari/automatic_inspection_option.py index ef95aa7b2..3ab38e82d 100644 --- a/appium/options/ios/safari/automatic_inspection_option.py +++ b/appium/options/ios/safari/automatic_inspection_option.py @@ -26,7 +26,7 @@ class AutomaticInspectionOption(SupportsCapabilities): @property def automatic_inspection(self) -> Optional[bool]: """ - :Returns: Whether to use automatic inspection. + Whether to use automatic inspection. """ return self.get_capability(AUTOMATIC_INSPECTION) diff --git a/appium/options/ios/safari/automatic_profiling_option.py b/appium/options/ios/safari/automatic_profiling_option.py index e45089d99..76eae6206 100644 --- a/appium/options/ios/safari/automatic_profiling_option.py +++ b/appium/options/ios/safari/automatic_profiling_option.py @@ -26,7 +26,7 @@ class AutomaticProfilingOption(SupportsCapabilities): @property def automatic_profiling(self) -> Optional[bool]: """ - :Returns: Whether to use automatic profiling. + Whether to use automatic profiling. """ return self.get_capability(AUTOMATIC_PROFILING) diff --git a/appium/options/ios/safari/device_name_option.py b/appium/options/ios/safari/device_name_option.py index b7697ed25..ebc949ad8 100644 --- a/appium/options/ios/safari/device_name_option.py +++ b/appium/options/ios/safari/device_name_option.py @@ -26,7 +26,7 @@ class DeviceNameOption(SupportsCapabilities): @property def device_name(self) -> Optional[str]: """ - :Returns: String representing the name of the device. + String representing the name of the device. """ return self.get_capability(DEVICE_NAME) diff --git a/appium/options/ios/safari/device_type_option.py b/appium/options/ios/safari/device_type_option.py index d7951513e..329974e34 100644 --- a/appium/options/ios/safari/device_type_option.py +++ b/appium/options/ios/safari/device_type_option.py @@ -26,7 +26,7 @@ class DeviceTypeOption(SupportsCapabilities): @property def device_type(self) -> Optional[str]: """ - :Returns: String representing the type of the device. + String representing the type of the device. """ return self.get_capability(DEVICE_TYPE) diff --git a/appium/options/ios/safari/device_udid_option.py b/appium/options/ios/safari/device_udid_option.py index 7ef435fd2..12850acfc 100644 --- a/appium/options/ios/safari/device_udid_option.py +++ b/appium/options/ios/safari/device_udid_option.py @@ -26,7 +26,7 @@ class DeviceUdidOption(SupportsCapabilities): @property def device_udid(self) -> Optional[str]: """ - :Returns: String representing the UDID of the device. + String representing the UDID of the device. """ return self.get_capability(DEVICE_UDID) diff --git a/appium/options/ios/safari/platform_build_version_option.py b/appium/options/ios/safari/platform_build_version_option.py index 4f6497c70..f411ec22e 100644 --- a/appium/options/ios/safari/platform_build_version_option.py +++ b/appium/options/ios/safari/platform_build_version_option.py @@ -26,7 +26,7 @@ class PlatformBuildVersionOption(SupportsCapabilities): @property def platform_build_version(self) -> Optional[str]: """ - :Returns: String representing the platform build version. + String representing the platform build version. """ return self.get_capability(PLATFORM_BUILD_VERSION) diff --git a/appium/options/ios/safari/platform_version_option.py b/appium/options/ios/safari/platform_version_option.py index d6a91fb79..fd3d4b60b 100644 --- a/appium/options/ios/safari/platform_version_option.py +++ b/appium/options/ios/safari/platform_version_option.py @@ -26,7 +26,7 @@ class PlatformVersionOption(SupportsCapabilities): @property def platform_version(self) -> Optional[str]: """ - :Returns: String representing the platform version. + String representing the platform version. """ return self.get_capability(PLATFORM_VERSION) diff --git a/appium/options/ios/safari/use_simulator_option.py b/appium/options/ios/safari/use_simulator_option.py index 8ac641d33..25a09a6b0 100644 --- a/appium/options/ios/safari/use_simulator_option.py +++ b/appium/options/ios/safari/use_simulator_option.py @@ -26,7 +26,7 @@ class UseSimulatorOption(SupportsCapabilities): @property def use_simulator(self) -> Optional[bool]: """ - :Returns: Whether to use iOS Simulator. + Whether to use iOS Simulator. """ return self.get_capability(USE_SIMULATOR) diff --git a/appium/options/ios/safari/webkit_webrtc_option.py b/appium/options/ios/safari/webkit_webrtc_option.py index 0890b6545..7980df7ab 100644 --- a/appium/options/ios/safari/webkit_webrtc_option.py +++ b/appium/options/ios/safari/webkit_webrtc_option.py @@ -26,7 +26,7 @@ class WebkitWebrtcOption(SupportsCapabilities): @property def webkit_webrtc(self) -> Optional[Dict[str, Any]]: """ - :Returns: WebRTC policies. + WebRTC policies. """ return self.get_capability(WEBKIT_WEBRTC) diff --git a/appium/options/ios/xcuitest/app/__init__.py b/appium/options/ios/xcuitest/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/app/app_install_strategy_option.py b/appium/options/ios/xcuitest/app/app_install_strategy_option.py new file mode 100644 index 000000000..ff812fe86 --- /dev/null +++ b/appium/options/ios/xcuitest/app/app_install_strategy_option.py @@ -0,0 +1,46 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_INSTALL_STRATEGY = 'appInstallStrategy' + + +class AppInstallStrategyOption(SupportsCapabilities): + @property + def app_install_strategy(self) -> Optional[str]: + """ + App install strategy. + """ + return self.get_capability(APP_INSTALL_STRATEGY) + + @app_install_strategy.setter + def app_install_strategy(self, value: str) -> None: + """ + Select application installation strategy for real devices. The following + strategies are supported: + * serial (default) - pushes app files to the device in a sequential order; + this is the least performant strategy, although the most reliable; + * parallel - pushes app files simultaneously; this is usually the + most performant strategy, but sometimes could not be very stable; + * ios-deploy - tells the driver to use a third-party tool ios-deploy to + install the app; obviously the tool must be installed separately + first and must be present in PATH before it could be used. + """ + self.set_capability(APP_INSTALL_STRATEGY, value) diff --git a/appium/options/ios/xcuitest/app/app_push_timeout_option.py b/appium/options/ios/xcuitest/app/app_push_timeout_option.py new file mode 100644 index 000000000..15ae4c247 --- /dev/null +++ b/appium/options/ios/xcuitest/app/app_push_timeout_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_PUSH_TIMEOUT = 'appPushTimeout' + + +class AppPushTimeoutOption(SupportsCapabilities): + @property + def app_push_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout for application upload. + """ + value = self.get_capability(APP_PUSH_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @app_push_timeout.setter + def app_push_timeout(self, value: Union[timedelta, int]) -> None: + """ + The timeout for application upload. + Works for real devices only. + The default value is 30000ms. + """ + self.set_capability(APP_PUSH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/app/localizable_strings_dir_option.py b/appium/options/ios/xcuitest/app/localizable_strings_dir_option.py new file mode 100644 index 000000000..e78155a6c --- /dev/null +++ b/appium/options/ios/xcuitest/app/localizable_strings_dir_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +LOCALIZABLE_STRINGS_DIR = 'localizableStringsDir' + + +class LocalizableStringsDirOption(SupportsCapabilities): + @property + def localizable_strings_dir(self) -> Optional[str]: + """ + Resource folder name where the main locale strings are stored. + """ + return self.get_capability(LOCALIZABLE_STRINGS_DIR) + + @localizable_strings_dir.setter + def localizable_strings_dir(self, value: str) -> None: + """ + Where to look for localizable strings in the application bundle. + Defaults to en.lproj. + """ + self.set_capability(LOCALIZABLE_STRINGS_DIR, value) diff --git a/appium/options/ios/xcuitest/base.py b/appium/options/ios/xcuitest/base.py index 12f609d86..5ee96acaa 100644 --- a/appium/options/ios/xcuitest/base.py +++ b/appium/options/ios/xcuitest/base.py @@ -32,6 +32,88 @@ from appium.options.common.skip_log_capture_option import SkipLogCaptureOption from appium.options.common.udid_option import UdidOption +from .app.app_install_strategy_option import AppInstallStrategyOption +from .app.app_push_timeout_option import AppPushTimeoutOption +from .app.localizable_strings_dir_option import LocalizableStringsDirOption +from .general.include_device_caps_to_session_info_option import IncludeDeviceCapsToSessionInfoOption +from .general.reset_location_service_option import ResetLocationServiceOption +from .other.command_timeouts_option import CommandTimeoutsOption +from .other.launch_with_idb_option import LaunchWithIdbOption +from .other.show_ios_log_option import ShowIosLogOption +from .other.use_json_source_option import UseJsonSourceOption +from .simulator.calendar_access_authorized_option import CalendarAccessAuthorizedOption +from .simulator.calendar_format_option import CalendarFormatOption +from .simulator.connect_hardware_keyboard_option import ConnectHardwareKeyboardOption +from .simulator.custom_ssl_cert_option import CustomSslCertOption +from .simulator.enforce_fresh_simulator_creation_option import EnforceFreshSimulatorCreationOption +from .simulator.force_simulator_software_keyboard_presence_option import ForceSimulatorSoftwareKeyboardPresenceOption +from .simulator.ios_simulator_logs_predicate_option import IosSimulatorLogsPredicateOption +from .simulator.keep_key_chains_option import KeepKeyChainsOption +from .simulator.keychains_exclude_patterns_option import KeychainsExcludePatternsOption +from .simulator.permissions_option import PermissionsOption +from .simulator.reduce_motion_option import ReduceMotionOption +from .simulator.reset_on_session_start_only_option import ResetOnSessionStartOnlyOption +from .simulator.scale_factor_option import ScaleFactorOption +from .simulator.shutdown_other_simulators_option import ShutdownOtherSimulatorsOption +from .simulator.simulator_devices_set_path_option import SimulatorDevicesSetPathOption +from .simulator.simulator_pasteboard_automatic_sync_option import SimulatorPasteboardAutomaticSyncOption +from .simulator.simulator_startup_timeout_option import SimulatorStartupTimeoutOption +from .simulator.simulator_trace_pointer_option import SimulatorTracePointerOption +from .simulator.simulator_window_center_option import SimulatorWindowCenterOption +from .wda.allow_provisioning_device_regitration_option import AllowProvisioningDeviceRegistrationOption +from .wda.auto_accept_alerts_option import AutoAcceptAlertsOption +from .wda.auto_disimiss_alerts_option import AutoDismissAlertsOption +from .wda.derived_data_path_option import DerivedDataPathOption +from .wda.disable_automatic_screenshots_option import DisableAutomaticScreenshotsOption +from .wda.force_app_launch_option import ForceAppLaunchOption +from .wda.keychain_password_option import KeychainPasswordOption +from .wda.keychain_path_option import KeychainPathOption +from .wda.max_typing_frequency_option import MaxTypingFrequencyOption +from .wda.mjpeg_server_port_option import MjpegServerPortOption +from .wda.process_arguments_option import ProcessArgumentsOption +from .wda.result_bundle_path_option import ResultBundlePathOption +from .wda.screenshot_quality_option import ScreenshotQualityOption +from .wda.should_terminate_app_option import ShouldTerminateAppOption +from .wda.should_use_singleton_test_manager_option import ShouldUseSingletonTestManagerOption +from .wda.show_xcode_log_option import ShowXcodeLogOption +from .wda.simple_is_visible_check_option import SimpleIsVisibleCheckOption +from .wda.updated_wda_bundle_id_option import UpdatedWdaBundleIdOption +from .wda.use_native_caching_strategy_option import UseNativeCachingStrategyOption +from .wda.use_new_wda_option import UseNewWdaOption +from .wda.use_prebuilt_wda_option import UsePrebuiltWdaOption +from .wda.use_simple_build_test_option import UseSimpleBuildTestOption +from .wda.use_xctestrun_file_option import UseXctestrunFileOption +from .wda.wait_for_idle_timeout_option import WaitForIdleTimeoutOption +from .wda.wait_for_quiescence_option import WaitForQuiescenceOption +from .wda.wda_base_url_option import WdaBaseUrlOption +from .wda.wda_connection_timeout_option import WdaConnectionTimeoutOption +from .wda.wda_eventloop_idle_delay_option import WdaEventloopIdleDelayOption +from .wda.wda_launch_timeout_option import WdaLaunchTimeoutOption +from .wda.wda_local_port_option import WdaLocalPortOption +from .wda.wda_startup_retries_option import WdaStartupRetriesOption +from .wda.wda_startup_retry_interval_option import WdaStartupRetryIntervalOption +from .wda.web_driver_agent_url_option import WebDriverAgentUrlOption +from .wda.xcode_org_id_option import XcodeOrgIdOption +from .wda.xcode_signing_id_option import XcodeSigningIdOption +from .webview.absolute_web_locations_option import AbsoluteWebLocationsOption +from .webview.additional_webview_bundle_ids_option import AdditionalWebviewBundleIdsOption +from .webview.enable_async_execute_from_https_option import EnableAsyncExecuteFromHttpsOption +from .webview.full_context_list_option import FullContextListOption +from .webview.include_safari_in_webviews_option import IncludeSafariInWebviewsOption +from .webview.native_web_tap_option import NativeWebTapOption +from .webview.safari_garbage_collect_option import SafariGarbageCollectOption +from .webview.safari_ignore_fraud_warning_option import SafariIgnoreFraudWarningOption +from .webview.safari_ignore_web_hostnames_option import SafariIgnoreWebHostnamesOption +from .webview.safari_initial_url_option import SafariInitialUrlOption +from .webview.safari_log_all_communication_hex_dump_option import SafariLogAllCommunicationHexDumpOption +from .webview.safari_log_all_communication_option import SafariLogAllCommunicationOption +from .webview.safari_open_links_in_background_option import SafariOpenLinksInBackgroundOption +from .webview.safari_socket_chunk_size_option import SafariSocketChunkSizeOption +from .webview.safari_web_inspector_max_frame_length_option import SafariWebInspectorMaxFrameLengthOption +from .webview.webkit_response_timeout_option import WebkitResponseTimeoutOption +from .webview.webview_connect_retries_option import WebviewConnectRetriesOption +from .webview.webview_connect_timeout_option import WebviewConnectTimeoutOption + class XCUITestOptions( AppiumOptions, @@ -47,6 +129,87 @@ class XCUITestOptions( EnablePerformanceLoggingOption, OtherAppsOption, DeviceNameOption, + IncludeDeviceCapsToSessionInfoOption, + ResetLocationServiceOption, + AppInstallStrategyOption, + AppPushTimeoutOption, + LocalizableStringsDirOption, + CommandTimeoutsOption, + LaunchWithIdbOption, + ShowIosLogOption, + UseJsonSourceOption, + CalendarAccessAuthorizedOption, + CalendarFormatOption, + ConnectHardwareKeyboardOption, + CustomSslCertOption, + EnforceFreshSimulatorCreationOption, + ForceSimulatorSoftwareKeyboardPresenceOption, + IosSimulatorLogsPredicateOption, + KeepKeyChainsOption, + KeychainsExcludePatternsOption, + PermissionsOption, + ReduceMotionOption, + ResetOnSessionStartOnlyOption, + ScaleFactorOption, + ShutdownOtherSimulatorsOption, + SimulatorDevicesSetPathOption, + SimulatorPasteboardAutomaticSyncOption, + SimulatorStartupTimeoutOption, + SimulatorTracePointerOption, + SimulatorWindowCenterOption, + AllowProvisioningDeviceRegistrationOption, + AutoAcceptAlertsOption, + AutoDismissAlertsOption, + DerivedDataPathOption, + DisableAutomaticScreenshotsOption, + ForceAppLaunchOption, + KeychainPasswordOption, + KeychainPathOption, + MaxTypingFrequencyOption, + MjpegServerPortOption, + ProcessArgumentsOption, + ResultBundlePathOption, + ScreenshotQualityOption, + ShouldTerminateAppOption, + ShouldUseSingletonTestManagerOption, + ShowXcodeLogOption, + SimpleIsVisibleCheckOption, + UpdatedWdaBundleIdOption, + UseNativeCachingStrategyOption, + UseNewWdaOption, + UsePrebuiltWdaOption, + UseSimpleBuildTestOption, + UseXctestrunFileOption, + WaitForIdleTimeoutOption, + WaitForQuiescenceOption, + WdaBaseUrlOption, + WdaConnectionTimeoutOption, + WdaEventloopIdleDelayOption, + WdaLaunchTimeoutOption, + WdaLocalPortOption, + WdaStartupRetriesOption, + WdaStartupRetryIntervalOption, + WebDriverAgentUrlOption, + XcodeOrgIdOption, + XcodeSigningIdOption, + AbsoluteWebLocationsOption, + AdditionalWebviewBundleIdsOption, + EnableAsyncExecuteFromHttpsOption, + FullContextListOption, + IncludeSafariInWebviewsOption, + NativeWebTapOption, + SafariGarbageCollectOption, + SafariIgnoreFraudWarningOption, + SafariIgnoreWebHostnamesOption, + SafariInitialUrlOption, + SafariLogAllCommunicationHexDumpOption, + SafariLogAllCommunicationOption, + SafariOpenLinksInBackgroundOption, + SafariSocketChunkSizeOption, + SafariWebInspectorMaxFrameLengthOption, + WebkitResponseTimeoutOption, + WebviewConnectRetriesOption, + WebviewConnectTimeoutOption, ): @property def default_capabilities(self) -> Dict: diff --git a/appium/options/ios/xcuitest/general/__init__.py b/appium/options/ios/xcuitest/general/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py b/appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py new file mode 100644 index 000000000..b5df6d286 --- /dev/null +++ b/appium/options/ios/xcuitest/general/include_device_caps_to_session_info_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INCLUDE_DEVICE_CAPS_TO_SESSION_INFO = 'includeDeviceCapsToSessionInfo' + + +class IncludeDeviceCapsToSessionInfoOption(SupportsCapabilities): + @property + def include_device_caps_to_session_info(self) -> Optional[bool]: + """ + Whether to include screen information as the result of Get Session Capabilities. + """ + return self.get_capability(INCLUDE_DEVICE_CAPS_TO_SESSION_INFO) + + @include_device_caps_to_session_info.setter + def include_device_caps_to_session_info(self, value: bool) -> None: + """ + Whether to include screen information as the result of Get Session Capabilities. + It includes pixelRatio, statBarHeight and viewportRect, but + it causes an extra API call to WDA which may increase the response time. + Defaults to true. + """ + self.set_capability(INCLUDE_DEVICE_CAPS_TO_SESSION_INFO, value) diff --git a/appium/options/ios/xcuitest/general/reset_location_service_option.py b/appium/options/ios/xcuitest/general/reset_location_service_option.py new file mode 100644 index 000000000..c9e242cc5 --- /dev/null +++ b/appium/options/ios/xcuitest/general/reset_location_service_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +RESET_LOCATION_SERVICE = 'resetLocationService' + + +class ResetLocationServiceOption(SupportsCapabilities): + @property + def reset_location_service(self) -> Optional[bool]: + """ + Whether to reset the location service in the session deletion on real devices. + """ + return self.get_capability(RESET_LOCATION_SERVICE) + + @reset_location_service.setter + def reset_location_service(self, value: bool) -> None: + """ + Whether reset the location service in the session deletion on real devices. + Defaults to false. + """ + self.set_capability(RESET_LOCATION_SERVICE, value) diff --git a/appium/options/ios/xcuitest/other/__init__.py b/appium/options/ios/xcuitest/other/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/other/command_timeouts_option.py b/appium/options/ios/xcuitest/other/command_timeouts_option.py new file mode 100644 index 000000000..7c98d5cf0 --- /dev/null +++ b/appium/options/ios/xcuitest/other/command_timeouts_option.py @@ -0,0 +1,57 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Dict, Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +COMMAND_TIMEOUTS = 'commandTimeouts' + + +class CommandTimeoutsOption(SupportsCapabilities): + @property + def command_timeouts(self) -> Optional[Union[Dict[str, timedelta], timedelta]]: + """ + Custom timeout(s) for WDA backend commands execution. + """ + value = self.get_capability(COMMAND_TIMEOUTS) + if value is None: + return None + if isinstance(value, dict): + return {k: timedelta(milliseconds=v) for k, v in value.items()} + return timedelta(milliseconds=int(value)) + + @command_timeouts.setter + def command_timeouts(self, value: Union[Dict[str, timedelta], timedelta, int]) -> None: + """ + Custom timeout for all WDA backend commands execution. + This might be useful if WDA backend freezes unexpectedly or requires too + much time to fail and blocks automated test execution. + Dictionary keys are command names which you can find in server logs. Look for + "Executing command 'command_name'" records. + Timeout value is expected to contain max duration to wait for + the given WDA command to be executed before terminating the session forcefully. + The magic 'default' key allows to provide the timeout for all other commands that + were not explicitly mentioned as dictionary keys + """ + if isinstance(value, dict): + self.set_capability(COMMAND_TIMEOUTS, {k: v.microseconds // 1000 for k, v in value.items()}) + elif isinstance(value, timedelta): + self.set_capability(COMMAND_TIMEOUTS, f'{value.microseconds // 1000}') + else: + self.set_capability(COMMAND_TIMEOUTS, value) diff --git a/appium/options/ios/xcuitest/other/launch_with_idb_option.py b/appium/options/ios/xcuitest/other/launch_with_idb_option.py new file mode 100644 index 000000000..c8701c620 --- /dev/null +++ b/appium/options/ios/xcuitest/other/launch_with_idb_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +LAUNCH_WITH_IDB = 'launchWithIDB' + + +class LaunchWithIdbOption(SupportsCapabilities): + @property + def launch_with_idb(self) -> Optional[bool]: + """ + Whether to launch WebDriverAgentRunner with idb instead of xcodebuild. + """ + return self.get_capability(LAUNCH_WITH_IDB) + + @launch_with_idb.setter + def launch_with_idb(self, value: bool) -> None: + """ + Launch WebDriverAgentRunner with idb instead of xcodebuild. This could save + a significant amount of time by skipping the xcodebuild process, although the + idb might not be very reliable, especially with fresh Xcode SDKs. Check + the idb repository for more details on possible compatibility issues. + Defaults to false. + """ + self.set_capability(LAUNCH_WITH_IDB, value) diff --git a/appium/options/ios/xcuitest/other/show_ios_log_option.py b/appium/options/ios/xcuitest/other/show_ios_log_option.py new file mode 100644 index 000000000..b4bf75d3a --- /dev/null +++ b/appium/options/ios/xcuitest/other/show_ios_log_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOW_IOS_LOG = 'showIOSLog' + + +class ShowIosLogOption(SupportsCapabilities): + @property + def show_ios_log(self) -> Optional[bool]: + """ + Whether to show any logs captured from a device in the appium logs. + """ + return self.get_capability(SHOW_IOS_LOG) + + @show_ios_log.setter + def show_ios_log(self, value: bool) -> None: + """ + Whether to show any logs captured from a device in the appium logs. + Default false. + """ + self.set_capability(SHOW_IOS_LOG, value) diff --git a/appium/options/ios/xcuitest/other/use_json_source_option.py b/appium/options/ios/xcuitest/other/use_json_source_option.py new file mode 100644 index 000000000..1eac50bb8 --- /dev/null +++ b/appium/options/ios/xcuitest/other/use_json_source_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_JSON_SOURCE = 'useJSONSource' + + +class UseJsonSourceOption(SupportsCapabilities): + @property + def use_json_source(self) -> Optional[bool]: + """ + Whether to get JSON source from WDA and transform it to XML on the driver side. + """ + return self.get_capability(USE_JSON_SOURCE) + + @use_json_source.setter + def use_json_source(self, value: bool) -> None: + """ + Whether to get JSON source from WDA and transform it to XML on the driver side. + Defaults to false. + """ + self.set_capability(USE_JSON_SOURCE, value) diff --git a/appium/options/ios/xcuitest/simulator/__init__.py b/appium/options/ios/xcuitest/simulator/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py b/appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py new file mode 100644 index 000000000..a0e1afe9b --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/calendar_access_authorized_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CALENDAR_ACCESS_AUTHORIZED = 'calendarAccessAuthorized' + + +class CalendarAccessAuthorizedOption(SupportsCapabilities): + @property + def calendar_access_authorized(self) -> Optional[bool]: + """ + Whether to enable calendar access on IOS Simulator. + """ + return self.get_capability(CALENDAR_ACCESS_AUTHORIZED) + + @calendar_access_authorized.setter + def calendar_access_authorized(self, value: bool) -> None: + """ + Set this to true if you want to enable calendar access on IOS Simulator + with given bundleId. Set to false, if you want to disable calendar access + on IOS Simulator with given bundleId. If not set, the calendar + authorization status will not be set. + """ + self.set_capability(CALENDAR_ACCESS_AUTHORIZED, value) diff --git a/appium/options/ios/xcuitest/simulator/calendar_format_option.py b/appium/options/ios/xcuitest/simulator/calendar_format_option.py new file mode 100644 index 000000000..4e7565987 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/calendar_format_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CALENDAR_FORMAT = 'calendarFormat' + + +class CalendarFormatOption(SupportsCapabilities): + @property + def calendar_format(self) -> Optional[str]: + """ + Calendar format for the iOS Simulator. + """ + return self.get_capability(CALENDAR_FORMAT) + + @calendar_format.setter + def calendar_format(self, value: str) -> None: + """ + Set calendar format for the iOS Simulator. + """ + self.set_capability(CALENDAR_FORMAT, value) diff --git a/appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py b/appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py new file mode 100644 index 000000000..f7118d6de --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/connect_hardware_keyboard_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CONNECT_HARDWARE_KEYBOARD = 'connectHardwareKeyboard' + + +class ConnectHardwareKeyboardOption(SupportsCapabilities): + @property + def connect_hardware_keyboard(self) -> Optional[bool]: + """ + Whether to connect hardware keyboard to Simulator. + """ + return self.get_capability(CONNECT_HARDWARE_KEYBOARD) + + @connect_hardware_keyboard.setter + def connect_hardware_keyboard(self, value: bool) -> None: + """ + Set this option to true in order to enable hardware keyboard in Simulator. + The preference works only when Appium launches a simulator instance with + this value. It is set to false by default, because this helps to workaround + some XCTest bugs. connectHardwareKeyboard: true makes + forceSimulatorSoftwareKeyboardPresence: false if no explicit value is set + for forceSimulatorSoftwareKeyboardPresence capability since Appium 1.22.0. + """ + self.set_capability(CONNECT_HARDWARE_KEYBOARD, value) diff --git a/appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py b/appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py new file mode 100644 index 000000000..a3914b939 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/custom_ssl_cert_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CUSTOM_SSL_CERT = 'customSSLCert' + + +class CustomSslCertOption(SupportsCapabilities): + @property + def custom_ssl_cert(self) -> Optional[str]: + """ + SSL certificate content. + """ + return self.get_capability(CUSTOM_SSL_CERT) + + @custom_ssl_cert.setter + def custom_ssl_cert(self, value: str) -> None: + """ + Adds a root SSL certificate to IOS Simulator. + The certificate content must be provided in PEM format. + """ + self.set_capability(CUSTOM_SSL_CERT, value) diff --git a/appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py b/appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py new file mode 100644 index 000000000..cea87eb05 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/enforce_fresh_simulator_creation_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ENFORCE_FRESH_SIMULATOR_CREATION = 'enforceFreshSimulatorCreation' + + +class EnforceFreshSimulatorCreationOption(SupportsCapabilities): + @property + def enforce_fresh_simulator_creation(self) -> Optional[bool]: + """ + Whether to create a new simulator for each new test session. + """ + return self.get_capability(ENFORCE_FRESH_SIMULATOR_CREATION) + + @enforce_fresh_simulator_creation.setter + def enforce_fresh_simulator_creation(self, value: bool) -> None: + """ + Creates a new simulator in session creation and deletes it in session deletion. + Defaults to false. + """ + self.set_capability(ENFORCE_FRESH_SIMULATOR_CREATION, value) diff --git a/appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py b/appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py new file mode 100644 index 000000000..1e1a697aa --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/force_simulator_software_keyboard_presence_option.py @@ -0,0 +1,45 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE = 'forceSimulatorSoftwareKeyboardPresence' + + +class ForceSimulatorSoftwareKeyboardPresenceOption(SupportsCapabilities): + @property + def force_simulator_software_keyboard_presence(self) -> Optional[bool]: + """ + Whether to enforce software keyboard presence. + """ + return self.get_capability(FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE) + + @force_simulator_software_keyboard_presence.setter + def force_simulator_software_keyboard_presence(self, value: bool) -> None: + """ + Set this option to true in order to turn software keyboard on and turn + hardware keyboard off in Simulator since Appium 1.22.0. This option helps + to avoid Keyboard is not present error. It is set to true by default. + Appium respects preset simulator software/hardware keyboard preference + when this value is false, so connectHardwareKeyboard: false and + forceSimulatorSoftwareKeyboardPresence: false means for Appium to keep + the current Simulator keyboard preferences. This option has priority + over connectHardwareKeyboard. + """ + self.set_capability(FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE, value) diff --git a/appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py b/appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py new file mode 100644 index 000000000..c7d486477 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/ios_simulator_logs_predicate_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +IOS_SIMULATOR_LOGS_PREDICATE = 'iosSimulatorLogsPredicate' + + +class IosSimulatorLogsPredicateOption(SupportsCapabilities): + @property + def ios_simulator_logs_predicate(self) -> Optional[bool]: + """ + Get Simulator log filtering predicate. + """ + return self.get_capability(IOS_SIMULATOR_LOGS_PREDICATE) + + @ios_simulator_logs_predicate.setter + def ios_simulator_logs_predicate(self, value: bool) -> None: + """ + Set the --predicate flag in the ios simulator logs. + """ + self.set_capability(IOS_SIMULATOR_LOGS_PREDICATE, value) diff --git a/appium/options/ios/xcuitest/simulator/keep_key_chains_option.py b/appium/options/ios/xcuitest/simulator/keep_key_chains_option.py new file mode 100644 index 000000000..a304c935e --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/keep_key_chains_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEEP_KEY_CHAINS = 'keepKeyChains' + + +class KeepKeyChainsOption(SupportsCapabilities): + @property + def keep_key_chains(self) -> Optional[bool]: + """ + Whether to preserve Simulator keychains after full reset. + """ + return self.get_capability(KEEP_KEY_CHAINS) + + @keep_key_chains.setter + def keep_key_chains(self, value: bool) -> None: + """ + Set the capability to true in order to preserve Simulator keychains folder after + full reset. This feature has no effect on real devices. Defaults to false. + """ + self.set_capability(KEEP_KEY_CHAINS, value) diff --git a/appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py b/appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py new file mode 100644 index 000000000..d971f5c5c --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/keychains_exclude_patterns_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEYCHAINS_EXCLUDE_PATTERNS = 'keychainsExcludePatterns' + + +class KeychainsExcludePatternsOption(SupportsCapabilities): + @property + def keychains_exclude_patterns(self) -> Optional[str]: + """ + Keychains exclude patterns. + """ + return self.get_capability(KEYCHAINS_EXCLUDE_PATTERNS) + + @keychains_exclude_patterns.setter + def keychains_exclude_patterns(self, value: str) -> None: + """ + This capability accepts comma-separated path patterns, + which are going to be excluded from keychains restore while + full reset is being performed on Simulator. It might be + useful if you want to exclude only particular keychain types + from being restored, like the applications keychain. This + feature has no effect on real devices. E.g. "*keychain*.db*" + to exclude applications keychain from being restored + """ + self.set_capability(KEYCHAINS_EXCLUDE_PATTERNS, value) diff --git a/appium/options/ios/xcuitest/simulator/permissions_option.py b/appium/options/ios/xcuitest/simulator/permissions_option.py new file mode 100644 index 000000000..6146e382b --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/permissions_option.py @@ -0,0 +1,50 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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 +from typing import Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +PERMISSIONS = 'permissions' + + +class PermissionsOption(SupportsCapabilities): + @property + def permissions(self) -> Optional[Dict[str, Dict[str, str]]]: + """ + Get Simulator permissions. + """ + value = self.get_capability(PERMISSIONS) + return None if value is None else json.loads(value) + + @permissions.setter + def permissions(self, value: Dict[str, Dict[str, str]]) -> None: + """ + Allows setting of permissions for the specified application bundle on + Simulator only. + + Since Xcode SDK 11.4 Apple provides native APIs to interact with + application settings. Check the output of `xcrun simctl privacy booted` + command to get the list of available permission names. Use yes, no + and unset as values in order to grant, revoke or reset the corresponding + permission. Below Xcode SDK 11.4 it is required that applesimutils package + is installed and available in PATH. The list of available service names + and statuses can be found at https://github.com/wix/AppleSimulatorUtils. + For example: {"com.apple.mobilecal": {"calendar": "YES"}} + """ + self.set_capability(PERMISSIONS, json.dumps(value, ensure_ascii=False)) diff --git a/appium/options/ios/xcuitest/simulator/reduce_motion_option.py b/appium/options/ios/xcuitest/simulator/reduce_motion_option.py new file mode 100644 index 000000000..89dbe8779 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/reduce_motion_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +REDUCE_MOTION = 'reduceMotion' + + +class ReduceMotionOption(SupportsCapabilities): + @property + def reduce_motion(self) -> Optional[bool]: + """ + Whether to reduce motion accessibility preference. + """ + return self.get_capability(REDUCE_MOTION) + + @reduce_motion.setter + def reduce_motion(self, value: bool) -> None: + """ + Allows to turn on/off reduce motion accessibility preference. + Setting reduceMotion on helps to reduce flakiness during tests. + Only on simulators. + """ + self.set_capability(REDUCE_MOTION, value) diff --git a/appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py b/appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py new file mode 100644 index 000000000..294a72117 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/reset_on_session_start_only_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +RESET_ON_SESSION_START_ONLY = 'resetOnSessionStartOnly' + + +class ResetOnSessionStartOnlyOption(SupportsCapabilities): + @property + def reset_on_session_start_only(self) -> Optional[bool]: + """ + Whether to perform Simulator reset on test session finish (false) or not (true). + """ + return self.get_capability(RESET_ON_SESSION_START_ONLY) + + @reset_on_session_start_only.setter + def reset_on_session_start_only(self, value: bool) -> None: + """ + Whether to perform reset on test session finish (false) or not (true). + Keeping this variable set to true and Simulator running (the default + behaviour since version 1.6.4) may significantly shorten the duration of + test session initialization. + """ + self.set_capability(RESET_ON_SESSION_START_ONLY, value) diff --git a/appium/options/ios/xcuitest/simulator/scale_factor_option.py b/appium/options/ios/xcuitest/simulator/scale_factor_option.py new file mode 100644 index 000000000..8b7e6e037 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/scale_factor_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SCALE_FACTOR = 'scaleFactor' + + +class ScaleFactorOption(SupportsCapabilities): + @property + def scale_factor(self) -> Optional[str]: + """ + Simulator scale factor. + """ + return self.get_capability(SCALE_FACTOR) + + @scale_factor.setter + def scale_factor(self, value: str) -> None: + """ + Simulator scale factor. This is useful to have if the default resolution + of simulated device is greater than the actual display resolution. + So you can scale the simulator to see the whole device screen without scrolling. + Acceptable values for simulators running Xcode SDK 8 and older are: '1.0', + '0.75', '0.5', '0.33' and '0.25', where '1.0' means 100% scale. + For simulators running Xcode SDK 9 and above the value could be any valid + positive float number. + """ + self.set_capability(SCALE_FACTOR, value) diff --git a/appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py b/appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py new file mode 100644 index 000000000..a7c5edc51 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/shutdown_other_simulators_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHUTDOWN_OTHER_SIMULATORS = 'shutdownOtherSimulators' + + +class ShutdownOtherSimulatorsOption(SupportsCapabilities): + @property + def shutdown_other_simulators(self) -> Optional[bool]: + """ + Whether to shut down of other booted simulators except of the current one. + """ + return self.get_capability(SHUTDOWN_OTHER_SIMULATORS) + + @shutdown_other_simulators.setter + def shutdown_other_simulators(self, value: bool) -> None: + """ + If this capability set to true and the current device under test is an iOS + Simulator then Appium will try to shut down all the other running Simulators + before to start a new session. This might be useful while executing webview + tests on different devices, since only one device can be debugged remotely + at once due to an Apple bug. The capability only has an effect if + --relaxed-security command line argument is provided to the server. + Defaults to false. + """ + self.set_capability(SHUTDOWN_OTHER_SIMULATORS, value) diff --git a/appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py b/appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py new file mode 100644 index 000000000..845575355 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/simulator_devices_set_path_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMULATOR_DEVICES_SET_PATH = 'simulatorDevicesSetPath' + + +class SimulatorDevicesSetPathOption(SupportsCapabilities): + @property + def simulator_devices_set_path(self) -> Optional[str]: + """ + Alternative path to the simulator devices set. + """ + return self.get_capability(SIMULATOR_DEVICES_SET_PATH) + + @simulator_devices_set_path.setter + def simulator_devices_set_path(self, value: str) -> None: + """ + This capability allows to set an alternative path to the simulator devices + set in case you have multiple sets deployed on your local system. Such + feature could be useful if you, for example, would like to save disk space + on the main system volume. + """ + self.set_capability(SIMULATOR_DEVICES_SET_PATH, value) diff --git a/appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py b/appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py new file mode 100644 index 000000000..a1258df4e --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/simulator_pasteboard_automatic_sync_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMULATOR_PASTEBOARD_AUTOMATIC_SYNC = 'simulatorPasteboardAutomaticSync' + + +class SimulatorPasteboardAutomaticSyncOption(SupportsCapabilities): + @property + def simulator_pasteboard_automatic_sync(self) -> Optional[bool]: + """ + Pasteboard automation sync state. + """ + return self.get_capability(SIMULATOR_PASTEBOARD_AUTOMATIC_SYNC) + + @simulator_pasteboard_automatic_sync.setter + def simulator_pasteboard_automatic_sync(self, value: bool) -> None: + """ + Handle the -PasteboardAutomaticSync flag when simulator process launches. + It could improve launching simulator performance not to sync pasteboard with + the system when this value is off. on forces the flag enabled. system does + not provide the flag to the launching command. on, off, or system is available. + They are case-insensitive. Defaults to off. + """ + self.set_capability(SIMULATOR_PASTEBOARD_AUTOMATIC_SYNC, value) diff --git a/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py b/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py new file mode 100644 index 000000000..ad14462d4 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py @@ -0,0 +1,46 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMULATOR_STARTUP_TIMEOUT = 'simulatorStartupTimeout' + + +class SimulatorStartupTimeoutOption(SupportsCapabilities): + @property + def simulator_startup_timeout(self) -> Optional[timedelta]: + """ + Simulator startup timeout. + """ + value = self.get_capability(SIMULATOR_STARTUP_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @simulator_startup_timeout.setter + def simulator_startup_timeout(self, value: Union[timedelta, int]) -> None: + """ + Allows to change the default timeout for Simulator startup. + By default, this value is set to 120000ms (2 minutes), + although the startup could take longer on a weak hardware + or if other concurrent processes use much system resources + during the boot up procedure. + """ + self.set_capability( + SIMULATOR_STARTUP_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py b/appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py new file mode 100644 index 000000000..d5f5d1fd8 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/simulator_trace_pointer_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMULATOR_TRACE_POINTER = 'simulatorTracePointer' + + +class SimulatorTracePointerOption(SupportsCapabilities): + @property + def simulator_trace_pointer(self) -> Optional[bool]: + """ + Whether to highlight pointer moves in the Simulator window. + """ + return self.get_capability(SIMULATOR_TRACE_POINTER) + + @simulator_trace_pointer.setter + def simulator_trace_pointer(self, value: bool) -> None: + """ + Set whether to highlight pointer moves in the Simulator window. + The Simulator UI client must be shut down before the session + startup in order for this capability to be applied properly. + false by default. + """ + self.set_capability(SIMULATOR_TRACE_POINTER, value) diff --git a/appium/options/ios/xcuitest/simulator/simulator_window_center_option.py b/appium/options/ios/xcuitest/simulator/simulator_window_center_option.py new file mode 100644 index 000000000..1f8b2d8b8 --- /dev/null +++ b/appium/options/ios/xcuitest/simulator/simulator_window_center_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMULATOR_WINDOW_CENTER = 'simulatorWindowCenter' + + +class SimulatorWindowCenterOption(SupportsCapabilities): + @property + def simulator_window_center(self) -> Optional[str]: + """ + Simulator window center coordinates. + """ + return self.get_capability(SIMULATOR_WINDOW_CENTER) + + @simulator_window_center.setter + def simulator_window_center(self, value: str) -> None: + """ + Allows to explicitly set the coordinates of Simulator window center + for Xcode9+ SDK. This capability only has an effect if Simulator + window has not been opened yet for the current session before it started. + e.g. "{-100.0,100.0}" or "{500,500}", spaces are not allowed + """ + self.set_capability(SIMULATOR_WINDOW_CENTER, value) diff --git a/appium/options/ios/xcuitest/wda/__init__.py b/appium/options/ios/xcuitest/wda/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py b/appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py new file mode 100644 index 000000000..c88124b4c --- /dev/null +++ b/appium/options/ios/xcuitest/wda/allow_provisioning_device_regitration_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ALLOW_PROVISIONING_DEVICE_REGISTRATION = 'allowProvisioningDeviceRegistration' + + +class AllowProvisioningDeviceRegistrationOption(SupportsCapabilities): + @property + def allow_provisioning_device_registration(self) -> Optional[bool]: + """ + Whether to allow xcodebuild to register your destination device on the developer portal. + """ + return self.get_capability(ALLOW_PROVISIONING_DEVICE_REGISTRATION) + + @allow_provisioning_device_registration.setter + def allow_provisioning_device_registration(self, value: bool) -> None: + """ + Allow xcodebuild to register your destination device on the developer portal + if necessary. Requires a developer account to have been added in Xcode's Accounts + preference pane. Defaults to false. + """ + self.set_capability(ALLOW_PROVISIONING_DEVICE_REGISTRATION, value) diff --git a/appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py b/appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py new file mode 100644 index 000000000..05c55943e --- /dev/null +++ b/appium/options/ios/xcuitest/wda/auto_accept_alerts_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTO_ACCEPT_ALERTS = 'autoAcceptAlerts' + + +class AutoAcceptAlertsOption(SupportsCapabilities): + @property + def auto_accept_alerts(self) -> Optional[bool]: + """ + Whether to accept all alerts automatically. + """ + return self.get_capability(AUTO_ACCEPT_ALERTS) + + @auto_accept_alerts.setter + def auto_accept_alerts(self, value: bool) -> None: + """ + Accept all iOS alerts automatically if they pop up. This includes privacy + access permission alerts (e.g., location, contacts, photos). Default is false. + """ + self.set_capability(AUTO_ACCEPT_ALERTS, value) diff --git a/appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py b/appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py new file mode 100644 index 000000000..61a28414c --- /dev/null +++ b/appium/options/ios/xcuitest/wda/auto_disimiss_alerts_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTO_DISMISS_ALERTS = 'autoDismissAlerts' + + +class AutoDismissAlertsOption(SupportsCapabilities): + @property + def auto_dismiss_alerts(self) -> Optional[bool]: + """ + Whether to dismiss all alerts automatically. + """ + return self.get_capability(AUTO_DISMISS_ALERTS) + + @auto_dismiss_alerts.setter + def auto_dismiss_alerts(self, value: bool) -> None: + """ + Dismiss all iOS alerts automatically if they pop up. This includes privacy + access permission alerts (e.g., location, contacts, photos). Default is false. + """ + self.set_capability(AUTO_DISMISS_ALERTS, value) diff --git a/appium/options/ios/xcuitest/wda/derived_data_path_option.py b/appium/options/ios/xcuitest/wda/derived_data_path_option.py new file mode 100644 index 000000000..ffa40a682 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/derived_data_path_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DERIVED_DATA_PATH = 'derivedDataPath' + + +class DerivedDataPathOption(SupportsCapabilities): + @property + def derived_data_path(self) -> Optional[str]: + """ + Path to the derived data WDA folder. + """ + return self.get_capability(DERIVED_DATA_PATH) + + @derived_data_path.setter + def derived_data_path(self, value: str) -> None: + """ + Use along with usePrebuiltWDA capability and choose where to search for the existing WDA app. + If the capability is not set then Xcode will store the derived data in the default root + taken from preferences. + It also makes sense to choose different folders for parallel WDA sessions. + """ + self.set_capability(DERIVED_DATA_PATH, value) diff --git a/appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py b/appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py new file mode 100644 index 000000000..ac76af330 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/disable_automatic_screenshots_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DISABLE_AUTOMATIC_SCREENSHOTS = 'disableAutomaticScreenshots' + + +class DisableAutomaticScreenshotsOption(SupportsCapabilities): + @property + def disable_automatic_screenshots(self) -> Optional[bool]: + """ + Whether to disable automatic XCTest screenshots. + """ + return self.get_capability(DISABLE_AUTOMATIC_SCREENSHOTS) + + @disable_automatic_screenshots.setter + def disable_automatic_screenshots(self, value: bool) -> None: + """ + Disable automatic screenshots taken by XCTest at every interaction. + Default is up to WebDriverAgent's config to decide, which currently + defaults to true. + """ + self.set_capability(DISABLE_AUTOMATIC_SCREENSHOTS, value) diff --git a/appium/options/ios/xcuitest/wda/force_app_launch_option.py b/appium/options/ios/xcuitest/wda/force_app_launch_option.py new file mode 100644 index 000000000..364133d03 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/force_app_launch_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +FORCE_APP_LAUNCH = 'forceAppLaunch' + + +class ForceAppLaunchOption(SupportsCapabilities): + @property + def force_app_launch(self) -> Optional[bool]: + """ + Whether to enforce app restart on session startup. + """ + return self.get_capability(FORCE_APP_LAUNCH) + + @force_app_launch.setter + def force_app_launch(self, value: bool) -> None: + """ + Specify if the app should be forcefully restarted if it is already + running on session startup. This capability only has an effect if an + application identifier has been passed to the test session (either + explicitly, by setting bundleId, or implicitly, by providing app). + Default is true unless noReset capability is set to true. + """ + self.set_capability(FORCE_APP_LAUNCH, value) diff --git a/appium/options/ios/xcuitest/wda/keychain_password_option.py b/appium/options/ios/xcuitest/wda/keychain_password_option.py new file mode 100644 index 000000000..5514519f0 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/keychain_password_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEYCHAIN_PASSWORD = 'keychainPassword' + + +class KeychainPasswordOption(SupportsCapabilities): + @property + def keychain_password(self) -> Optional[str]: + """ + Custom keychain password. + """ + return self.get_capability(KEYCHAIN_PASSWORD) + + @keychain_password.setter + def keychain_password(self, value: str) -> None: + """ + Custom keychain password. The keychain is expected to + contain the private development key. + """ + self.set_capability(KEYCHAIN_PASSWORD, value) diff --git a/appium/options/ios/xcuitest/wda/keychain_path_option.py b/appium/options/ios/xcuitest/wda/keychain_path_option.py new file mode 100644 index 000000000..5cf397426 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/keychain_path_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEYCHAIN_PATH = 'keychainPath' + + +class KeychainPathOption(SupportsCapabilities): + @property + def keychain_path(self) -> Optional[str]: + """ + Path to a custom keychain. + """ + return self.get_capability(KEYCHAIN_PATH) + + @keychain_path.setter + def keychain_path(self, value: str) -> None: + """ + Path to a custom keychain, which + contains the private development key. + """ + self.set_capability(KEYCHAIN_PATH, value) diff --git a/appium/options/ios/xcuitest/wda/max_typing_frequency_option.py b/appium/options/ios/xcuitest/wda/max_typing_frequency_option.py new file mode 100644 index 000000000..d5ea8dea1 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/max_typing_frequency_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MAX_TYPING_FREQUENCY = 'maxTypingFrequency' + + +class MaxTypingFrequencyOption(SupportsCapabilities): + @property + def max_typing_frequency(self) -> Optional[int]: + """ + The number of keystrokes per minute. + """ + return self.get_capability(MAX_TYPING_FREQUENCY) + + @max_typing_frequency.setter + def max_typing_frequency(self, value: int) -> None: + """ + Maximum frequency of keystrokes for typing and clear. If your tests + are failing because of typing errors, you may want to adjust this. + Defaults to 60 keystrokes per minute. + """ + self.set_capability(MAX_TYPING_FREQUENCY, value) diff --git a/appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py b/appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py new file mode 100644 index 000000000..679c1af4a --- /dev/null +++ b/appium/options/ios/xcuitest/wda/mjpeg_server_port_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MJPEG_SERVER_PORT = 'mjpegServerPort' + + +class MjpegServerPortOption(SupportsCapabilities): + @property + def mjpeg_server_port(self) -> Optional[int]: + """ + Port number on which WDA broadcasts screenshots stream encoded into MJPEG + format from the device under test. + """ + return self.get_capability(MJPEG_SERVER_PORT) + + @mjpeg_server_port.setter + def mjpeg_server_port(self, value: int) -> None: + """ + Port number on which WDA broadcasts screenshots stream encoded into MJPEG + format from the device under test. It might be necessary to change this value + if the default port is busy because of other tests running in parallel. + Default value: 9100. + """ + self.set_capability(MJPEG_SERVER_PORT, value) diff --git a/appium/options/ios/xcuitest/wda/process_arguments_option.py b/appium/options/ios/xcuitest/wda/process_arguments_option.py new file mode 100644 index 000000000..5b169a945 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/process_arguments_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict, List, Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +PROCESS_ARGUMENTS = 'processArguments' + + +class ProcessArgumentsOption(SupportsCapabilities): + @property + def process_arguments(self) -> Optional[Dict[str, Union[List[str], Dict[str, str]]]]: + """ + Command line arguments and/or environment variables of the application under test. + """ + return self.get_capability(PROCESS_ARGUMENTS) + + @process_arguments.setter + def process_arguments(self, value: Dict[str, Union[List[str], Dict[str, str]]]) -> None: + """ + Provides process arguments and environment which will be sent + to the WebDriverAgent server. Acceptable dictionary keys are 'env' + and 'args'. The value of 'args' should be a list of app command line + arguments represented as strings and the value of 'env' is expected to + be a dictionary of environment variable names and their values (also strings). + """ + self.set_capability(PROCESS_ARGUMENTS, value) diff --git a/appium/options/ios/xcuitest/wda/result_bundle_path_option.py b/appium/options/ios/xcuitest/wda/result_bundle_path_option.py new file mode 100644 index 000000000..5cf7ec098 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/result_bundle_path_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +RESULT_BUNDLE_PATH = 'resultBundlePath' + + +class ResultBundlePathOption(SupportsCapabilities): + @property + def result_bundle_path(self) -> Optional[str]: + """ + Path where the resulting XCTest bundle should be stored. + """ + return self.get_capability(RESULT_BUNDLE_PATH) + + @result_bundle_path.setter + def result_bundle_path(self, value: str) -> None: + """ + Specify the path to the result bundle path as xcodebuild argument for + WebDriverAgent build under a security flag. WebDriverAgent process must + start/stop every time to pick up changed value of this property. + Specifying useNewWDA to true may help there. Please read 'man xcodebuild' + for more details. + """ + self.set_capability(RESULT_BUNDLE_PATH, value) diff --git a/appium/options/ios/xcuitest/wda/screenshot_quality_option.py b/appium/options/ios/xcuitest/wda/screenshot_quality_option.py new file mode 100644 index 000000000..d808db9d6 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/screenshot_quality_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SCREENSHOT_QUALITY = 'screenshotQuality' + + +class ScreenshotQualityOption(SupportsCapabilities): + @property + def screenshot_quality(self) -> Optional[int]: + """ + Screenshot quality value. + """ + return self.get_capability(SCREENSHOT_QUALITY) + + @screenshot_quality.setter + def screenshot_quality(self, value: int) -> None: + """ + Changes the quality of phone display screenshots following + xctest/xctimagequality Default value is 1. 0 is the highest and + 2 is the lowest quality. You can also change it via settings + command. 0 might cause OutOfMemory crash on high-resolution + devices like iPad Pro. + """ + self.set_capability(SCREENSHOT_QUALITY, value) diff --git a/appium/options/ios/xcuitest/wda/should_terminate_app_option.py b/appium/options/ios/xcuitest/wda/should_terminate_app_option.py new file mode 100644 index 000000000..8550627cf --- /dev/null +++ b/appium/options/ios/xcuitest/wda/should_terminate_app_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOULD_TERMINATE_APP = 'shouldTerminateApp' + + +class ShouldTerminateAppOption(SupportsCapabilities): + @property + def should_terminate_app(self) -> Optional[bool]: + """ + Whether to enforce app termination on session quit. + """ + return self.get_capability(SHOULD_TERMINATE_APP) + + @should_terminate_app.setter + def should_terminate_app(self, value: bool) -> None: + """ + Specify if the app should be terminated on session end. + This capability only has an effect if an application identifier + has been passed to the test session (either explicitly, + by setting bundleId, or implicitly, by providing app). + Default is true unless noReset capability is set to true. + """ + self.set_capability(SHOULD_TERMINATE_APP, value) diff --git a/appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py b/appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py new file mode 100644 index 000000000..569236810 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/should_use_singleton_test_manager_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOULD_USE_SINGLETON_TEST_MANAGER = 'shouldUseSingletonTestManager' + + +class ShouldUseSingletonTestManagerOption(SupportsCapabilities): + @property + def should_use_singleton_test_manager(self) -> Optional[bool]: + """ + Whether to use the default proxy for test management within WebDriverAgent. + """ + return self.get_capability(SHOULD_USE_SINGLETON_TEST_MANAGER) + + @should_use_singleton_test_manager.setter + def should_use_singleton_test_manager(self, value: bool) -> None: + """ + Use default proxy for test management within WebDriverAgent. Setting this to false + sometimes helps with socket hangup problems. Defaults to true. + """ + self.set_capability(SHOULD_USE_SINGLETON_TEST_MANAGER, value) diff --git a/appium/options/ios/xcuitest/wda/show_xcode_log_option.py b/appium/options/ios/xcuitest/wda/show_xcode_log_option.py new file mode 100644 index 000000000..0e435b71d --- /dev/null +++ b/appium/options/ios/xcuitest/wda/show_xcode_log_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOW_XCODE_LOG = 'showXcodeLog' + + +class ShowXcodeLogOption(SupportsCapabilities): + @property + def show_xcode_log(self) -> Optional[bool]: + """ + Whether to display the output of the Xcode command used to run the tests. + """ + return self.get_capability(SHOW_XCODE_LOG) + + @show_xcode_log.setter + def show_xcode_log(self, value: bool) -> None: + """ + Whether to display the output of the Xcode command used to run the tests in + server logs. If this is true, there will be lots of extra logging at startup. + Defaults to false. + """ + self.set_capability(SHOW_XCODE_LOG, value) diff --git a/appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py b/appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py new file mode 100644 index 000000000..694ed5743 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/simple_is_visible_check_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SIMPLE_IS_VISIBLE_CHECK = 'simpleIsVisibleCheck' + + +class SimpleIsVisibleCheckOption(SupportsCapabilities): + @property + def simple_is_visible_check(self) -> Optional[bool]: + """ + Whether to use native methods for determining visibility of elements. + """ + return self.get_capability(SIMPLE_IS_VISIBLE_CHECK) + + @simple_is_visible_check.setter + def simple_is_visible_check(self, value: bool) -> None: + """ + Use native methods for determining visibility of elements. + In some cases this takes a long time. Setting this capability to false will + cause the system to use the position and size of elements to make sure they + are visible on the screen. This can, however, lead to false results in some + situations. Defaults to false, except iOS 9.3, where it defaults to true. + """ + self.set_capability(SIMPLE_IS_VISIBLE_CHECK, value) diff --git a/appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py b/appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py new file mode 100644 index 000000000..b799c949b --- /dev/null +++ b/appium/options/ios/xcuitest/wda/updated_wda_bundle_id_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UPDATED_WDA_BUNDLE_ID = 'updatedWDABundleId' + + +class UpdatedWdaBundleIdOption(SupportsCapabilities): + @property + def updated_wda_bundle_id(self) -> Optional[str]: + """ + WDA bundle identifier. + """ + return self.get_capability(UPDATED_WDA_BUNDLE_ID) + + @updated_wda_bundle_id.setter + def updated_wda_bundle_id(self, value: str) -> None: + """ + Bundle id to update WDA to before building and launching on real devices. + This bundle id must be associated with a valid provisioning profile. + """ + self.set_capability(UPDATED_WDA_BUNDLE_ID, value) diff --git a/appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py b/appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py new file mode 100644 index 000000000..a3e30de17 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/use_native_caching_strategy_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_NATIVE_CACHING_STRATEGY = 'useNativeCachingStrategy' + + +class UseNativeCachingStrategyOption(SupportsCapabilities): + @property + def use_native_caching_strategy(self) -> Optional[bool]: + """ + Whether to use the native caching strategy. + """ + return self.get_capability(USE_NATIVE_CACHING_STRATEGY) + + @use_native_caching_strategy.setter + def use_native_caching_strategy(self, value: bool) -> None: + """ + Set this capability to false in order to use the custom elements caching + strategy. This might help to avoid stale element exception on property + change. By default, the native XCTest cache resolution is used (true) + for all native locators (e.g. all, but xpath). + """ + self.set_capability(USE_NATIVE_CACHING_STRATEGY, value) diff --git a/appium/options/ios/xcuitest/wda/use_new_wda_option.py b/appium/options/ios/xcuitest/wda/use_new_wda_option.py new file mode 100644 index 000000000..f52684a65 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/use_new_wda_option.py @@ -0,0 +1,51 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_NEW_WDA = 'useNewWDA' + + +class UseNewWdaOption(SupportsCapabilities): + @property + def use_new_wda(self) -> Optional[bool]: + """ + Whether whether to uninstall of any existing WebDriverAgent app + on the device under test. + """ + return self.get_capability(USE_NEW_WDA) + + @use_new_wda.setter + def use_new_wda(self, value: bool) -> None: + """ + If true, forces uninstall of any existing WebDriverAgent app on device. + Set it to true if you want to apply different startup options for WebDriverAgent + for each session. Although, it is only guaranteed to work stable on Simulator. + Real devices require WebDriverAgent client to run for as long as possible without + reinstall/restart to avoid issues like + https://github.com/facebook/WebDriverAgent/issues/507. The false value + (the default behaviour since driver version 2.35.0) will try to + detect currently running WDA listener executed by previous testing session(s) + and reuse it if possible, which is highly recommended for real device testing + and to speed up suites of multiple tests in general. A new WDA session will be + triggered at the default URL (http://localhost:8100) if WDA is not listening and + webDriverAgentUrl capability is not set. The negative/unset value of useNewWDA + capability has no effect prior to xcuitest driver version 2.35.0. + """ + self.set_capability(USE_NEW_WDA, value) diff --git a/appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py b/appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py new file mode 100644 index 000000000..76fd5163e --- /dev/null +++ b/appium/options/ios/xcuitest/wda/use_prebuilt_wda_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_PREBUILT_WDA = 'usePrebuiltWDA' + + +class UsePrebuiltWdaOption(SupportsCapabilities): + @property + def use_prebuilt_wda(self) -> Optional[bool]: + """ + Whether to skip the build phase of running the WDA app. + """ + return self.get_capability(USE_PREBUILT_WDA) + + @use_prebuilt_wda.setter + def use_prebuilt_wda(self, value: bool) -> None: + """ + Skips the build phase of running the WDA app. Building is then the responsibility + of the user. Only works for Xcode 8+. Defaults to false. + """ + self.set_capability(USE_PREBUILT_WDA, value) diff --git a/appium/options/ios/xcuitest/wda/use_simple_build_test_option.py b/appium/options/ios/xcuitest/wda/use_simple_build_test_option.py new file mode 100644 index 000000000..516e08cbe --- /dev/null +++ b/appium/options/ios/xcuitest/wda/use_simple_build_test_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_SIMPLE_BUILD_TEST = 'useSimpleBuildTest' + + +class UseSimpleBuildTestOption(SupportsCapabilities): + @property + def use_simple_build_test(self) -> Optional[bool]: + """ + Whether to enforce app termination on session quit. + """ + return self.get_capability(USE_SIMPLE_BUILD_TEST) + + @use_simple_build_test.setter + def use_simple_build_test(self, value: bool) -> None: + """ + Build with 'build' and run test with 'test' in xcodebuild for all Xcode versions if + this is true, or build with 'build-for-testing' and run tests with + 'test-without-building' for over Xcode 8 if this is false. Defaults to false. + """ + self.set_capability(USE_SIMPLE_BUILD_TEST, value) diff --git a/appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py b/appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py new file mode 100644 index 000000000..0d1a3c64b --- /dev/null +++ b/appium/options/ios/xcuitest/wda/use_xctestrun_file_option.py @@ -0,0 +1,49 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_XCTESTRUN_FILE = 'useXctestrunFile' + + +class UseXctestrunFileOption(SupportsCapabilities): + @property + def use_xctestrun_file(self) -> Optional[bool]: + """ + Whether to use of .xctestrun file to launch WDA. + """ + return self.get_capability(USE_XCTESTRUN_FILE) + + @use_xctestrun_file.setter + def use_xctestrun_file(self, value: bool) -> None: + """ + Use Xctestrun file to launch WDA. It will search for such file in bootstrapPath. + Expected name of file is WebDriverAgentRunner_iphoneos<sdkVersion>-arm64.xctestrun for + real device and WebDriverAgentRunner_iphonesimulator<sdkVersion>-x86_64.xctestrun for + simulator. One can do build-for-testing for WebDriverAgent project for simulator and + real device and then you will see Product Folder like this and you need to copy content + of this folder at bootstrapPath location. Since this capability expects that you have + already built WDA project, it neither checks whether you have necessary dependencies to + build WDA nor will it try to build project. Defaults to false. Tips: Xcodebuild builds for the + target platform version. We'd recommend you to build with minimal OS version which you'd + like to run as the original WDA module. e.g. If you build WDA for 12.2, the module cannot + run on iOS 11.4 because of loading some module error on simulator. A module built with 11.4 + can work on iOS 12.2. (This is xcodebuild's expected behaviour.) + """ + self.set_capability(USE_XCTESTRUN_FILE, value) diff --git a/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py b/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py new file mode 100644 index 000000000..3cf75ec42 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py @@ -0,0 +1,45 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WAIT_FOR_IDLE_TIMEOUT = 'waitForIdleTimeout' + + +class WaitForIdleTimeoutOption(SupportsCapabilities): + @property + def wait_for_idle_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until WDA responds to HTTP requests. + """ + value = self.get_capability(WAIT_FOR_IDLE_TIMEOUT) + return None if value is None else timedelta(seconds=value) + + @wait_for_idle_timeout.setter + def wait_for_idle_timeout(self, value: Union[timedelta, float]) -> None: + """ + The time to wait until the application under test is idling. + XCTest requires the app's main thread to be idling in order to execute any action on it, + so WDA might not even start/freeze if the app under test is constantly hogging the main + thread. The default value is 10 (seconds). Setting it to zero disables idling checks completely + (not recommended) and has the same effect as setting waitForQuiescence to false. + Available since Appium 1.20.0. + """ + self.set_capability(WAIT_FOR_IDLE_TIMEOUT, value.seconds if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py b/appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py new file mode 100644 index 000000000..b8be7f322 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wait_for_quiescence_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WAIT_FOR_QUIESCENCE = 'waitForQuiescence' + + +class WaitForQuiescenceOption(SupportsCapabilities): + @property + def wait_for_quiescence(self) -> Optional[bool]: + """ + Whether to wait for application quiescence. + """ + return self.get_capability(WAIT_FOR_QUIESCENCE) + + @wait_for_quiescence.setter + def wait_for_quiescence(self, value: Union[timedelta, float]) -> None: + """ + It allows to turn on/off waiting for application quiescence in WebDriverAgent, + while performing queries. The default value is true. You can avoid this kind + of issues if you turn it off. Consider using waitForIdleTimeout capability + instead for this purpose since Appium 1.20.0. + """ + self.set_capability(WAIT_FOR_QUIESCENCE, value) diff --git a/appium/options/ios/xcuitest/wda/wda_base_url_option.py b/appium/options/ios/xcuitest/wda/wda_base_url_option.py new file mode 100644 index 000000000..c2ca5cd81 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_base_url_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_BASE_URL = 'wdaBaseUrl' + + +class WdaBaseUrlOption(SupportsCapabilities): + @property + def wda_base_url(self) -> Optional[str]: + """ + Prefix to build a custom WebDriverAgent URL. + """ + return self.get_capability(WDA_BASE_URL) + + @wda_base_url.setter + def wda_base_url(self, value: str) -> None: + """ + This value, if specified, will be used as a prefix to build a custom + WebDriverAgent url. It is different from webDriverAgentUrl, because + if the latter is set then it expects WebDriverAgent to be already + listening and skips the building phase. Defaults to http://localhost. + """ + self.set_capability(WDA_BASE_URL, value) diff --git a/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py b/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py new file mode 100644 index 000000000..09364f58b --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_CONNECTION_TIMEOUT = 'wdaConnectionTimeout' + + +class WdaConnectionTimeoutOption(SupportsCapabilities): + @property + def wda_connection_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until WDA responds to HTTP requests. + """ + value = self.get_capability(WDA_CONNECTION_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @wda_connection_timeout.setter + def wda_connection_timeout(self, value: Union[timedelta, int]) -> None: + """ + Connection timeout to wait for a response from WebDriverAgent. + Defaults to 240000ms. + """ + self.set_capability( + WDA_CONNECTION_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py b/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py new file mode 100644 index 000000000..aa1a134df --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py @@ -0,0 +1,46 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_EVENTLOOP_IDLE_DELAY = 'wdaEventloopIdleDelay' + + +class WdaEventloopIdleDelayOption(SupportsCapabilities): + @property + def wda_eventloop_idle_delay(self) -> Optional[timedelta]: + """ + Event loop idle delay. + """ + value = self.get_capability(WDA_EVENTLOOP_IDLE_DELAY) + return None if value is None else timedelta(seconds=value) + + @wda_eventloop_idle_delay.setter + def wda_eventloop_idle_delay(self, value: Union[timedelta, float]) -> None: + """ + Delays the invocation of -[XCUIApplicationProcess setEventLoopHasIdled:] by the + duration specified with this capability. This can help quiescence apps + that fail to do so for no obvious reason (and creating a session fails for + that reason). This increases the time for session creation + because -[XCUIApplicationProcess setEventLoopHasIdled:] is called multiple times. + If you enable this capability start with at least 3 seconds and try increasing it, + if creating the session still fails. Defaults to 0. + """ + self.set_capability(WDA_EVENTLOOP_IDLE_DELAY, value.seconds if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py b/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py new file mode 100644 index 000000000..62a029ec7 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_LAUNCH_TIMEOUT = 'wdaLaunchTimeout' + + +class WdaLaunchTimeoutOption(SupportsCapabilities): + @property + def wda_launch_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until WDA is listening. + """ + value = self.get_capability(WDA_LAUNCH_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @wda_launch_timeout.setter + def wda_launch_timeout(self, value: Union[timedelta, int]) -> None: + """ + Timeout to wait for WebDriverAgent to be pingable, + after its building is finished. Defaults to 60000ms. + """ + self.set_capability(WDA_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/wda/wda_local_port_option.py b/appium/options/ios/xcuitest/wda/wda_local_port_option.py new file mode 100644 index 000000000..1af241a35 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_local_port_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_LOCAL_PORT = 'wdaLocalPort' + + +class WdaLocalPortOption(SupportsCapabilities): + @property + def wda_local_port(self) -> Optional[int]: + """ + Local port number where the WDA traffic is being forwarded. + """ + return self.get_capability(WDA_LOCAL_PORT) + + @wda_local_port.setter + def wda_local_port(self, value: int) -> None: + """ + This value, if specified, will be used to forward traffic from + Mac host to real ios devices over USB. + Default value is the same as the port number used by WDA on + the device under test (8100). + """ + self.set_capability(WDA_LOCAL_PORT, value) diff --git a/appium/options/ios/xcuitest/wda/wda_startup_retries_option.py b/appium/options/ios/xcuitest/wda/wda_startup_retries_option.py new file mode 100644 index 000000000..a70999621 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_startup_retries_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_STARTUP_RETRIES = 'wdaStartupRetries' + + +class WdaStartupRetriesOption(SupportsCapabilities): + @property + def wda_startup_retries(self) -> Optional[int]: + """ + Number of retries before to fail WDA deployment. + """ + return self.get_capability(WDA_STARTUP_RETRIES) + + @wda_startup_retries.setter + def wda_startup_retries(self, value: int) -> None: + """ + Number of times to try to build and launch WebDriverAgent onto the device. + Defaults to 2. + """ + self.set_capability(WDA_STARTUP_RETRIES, value) diff --git a/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py b/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py new file mode 100644 index 000000000..b033f878e --- /dev/null +++ b/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WDA_STARTUP_RETRY_INTERVAL = 'wdaStartupRetryInterval' + + +class WdaStartupRetryIntervalOption(SupportsCapabilities): + @property + def wda_startup_retry_interval(self) -> Optional[timedelta]: + """ + Interval to wait between tries to build and launch WebDriverAgent. + """ + value = self.get_capability(WDA_STARTUP_RETRY_INTERVAL) + return None if value is None else timedelta(milliseconds=value) + + @wda_startup_retry_interval.setter + def wda_startup_retry_interval(self, value: Union[timedelta, int]) -> None: + """ + Time interval to wait between tries to build and launch WebDriverAgent. + Defaults to 10000ms. + """ + self.set_capability( + WDA_STARTUP_RETRY_INTERVAL, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py b/appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py new file mode 100644 index 000000000..5dd89f2c6 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/web_driver_agent_url_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEB_DRIVER_AGENT_URL = 'webDriverAgentUrl' + + +class WebDriverAgentUrlOption(SupportsCapabilities): + @property + def web_driver_agent_url(self) -> Optional[str]: + """ + WedDriverAgent URL. + """ + return self.get_capability(WEB_DRIVER_AGENT_URL) + + @web_driver_agent_url.setter + def web_driver_agent_url(self, value: str) -> None: + """ + If provided, Appium will connect to an existing WebDriverAgent + instance at this URL instead of starting a new one. + """ + self.set_capability(WEB_DRIVER_AGENT_URL, value) diff --git a/appium/options/ios/xcuitest/wda/xcode_org_id_option.py b/appium/options/ios/xcuitest/wda/xcode_org_id_option.py new file mode 100644 index 000000000..d5c517935 --- /dev/null +++ b/appium/options/ios/xcuitest/wda/xcode_org_id_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +XCODE_ORG_ID = 'xcodeOrgId' + + +class XcodeOrgIdOption(SupportsCapabilities): + @property + def xcode_org_id(self) -> Optional[str]: + """ + Signing certificate organization id for WebDriverAgent compilation. + """ + return self.get_capability(XCODE_ORG_ID) + + @xcode_org_id.setter + def xcode_org_id(self, value: str) -> None: + """ + Provides a signing certificate organization id for WebDriverAgent compilation. + If signing id is not provided then it defaults to "iPhone Developer" + """ + self.set_capability(XCODE_ORG_ID, value) diff --git a/appium/options/ios/xcuitest/wda/xcode_signing_id_option.py b/appium/options/ios/xcuitest/wda/xcode_signing_id_option.py new file mode 100644 index 000000000..08ba1855e --- /dev/null +++ b/appium/options/ios/xcuitest/wda/xcode_signing_id_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +XCODE_SIGNING_ID = 'xcodeSigningId' + + +class XcodeSigningIdOption(SupportsCapabilities): + @property + def xcode_signing_id(self) -> Optional[str]: + """ + Signing certificate for WebDriverAgent compilation. + """ + return self.get_capability(XCODE_SIGNING_ID) + + @xcode_signing_id.setter + def xcode_signing_id(self, value: str) -> None: + """ + Provides a signing certificate for WebDriverAgent compilation. + If signing id is not provided then it defaults to "iPhone Developer" + """ + self.set_capability(XCODE_SIGNING_ID, value) diff --git a/appium/options/ios/xcuitest/webview/__init__.py b/appium/options/ios/xcuitest/webview/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/ios/xcuitest/webview/absolute_web_locations_option.py b/appium/options/ios/xcuitest/webview/absolute_web_locations_option.py new file mode 100644 index 000000000..114c737dc --- /dev/null +++ b/appium/options/ios/xcuitest/webview/absolute_web_locations_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ABSOLUTE_WEB_LOCATIONS = 'absoluteWebLocations' + + +class AbsoluteWebLocationsOption(SupportsCapabilities): + @property + def absolute_web_locations(self) -> Optional[bool]: + """ + Whether Get Element Location returns coordinates + relative to the page origin for web view elements. + """ + return self.get_capability(ABSOLUTE_WEB_LOCATIONS) + + @absolute_web_locations.setter + def absolute_web_locations(self, value: bool) -> None: + """ + This capability will direct the Get Element Location command, when used + within webviews, to return coordinates which are relative to the origin of + the page, rather than relative to the current scroll offset. This capability + has no effect outside of webviews. Defaults to false. + """ + self.set_capability(ABSOLUTE_WEB_LOCATIONS, value) diff --git a/appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py b/appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py new file mode 100644 index 000000000..6546c548f --- /dev/null +++ b/appium/options/ios/xcuitest/webview/additional_webview_bundle_ids_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import List, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ADDITIONAL_WEBVIEW_BUNDLE_IDS = 'additionalWebviewBundleIds' + + +class AdditionalWebviewBundleIdsOption(SupportsCapabilities): + @property + def additional_webview_bundle_ids(self) -> Optional[List[str]]: + """ + Array of possible bundle identifiers for webviews. + """ + return self.get_capability(ADDITIONAL_WEBVIEW_BUNDLE_IDS) + + @additional_webview_bundle_ids.setter + def additional_webview_bundle_ids(self, value: List[str]) -> None: + """ + Array of possible bundle identifiers for webviews. This is sometimes + necessary if the Web Inspector is found to be returning a modified + bundle identifier for the app. Defaults to []. + """ + self.set_capability(ADDITIONAL_WEBVIEW_BUNDLE_IDS, value) diff --git a/appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py b/appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py new file mode 100644 index 000000000..ae2a3dcc1 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/enable_async_execute_from_https_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ENABLE_ASYNC_EXECUTE_FROM_HTTPS = 'enableAsyncExecuteFromHttps' + + +class EnableAsyncExecuteFromHttpsOption(SupportsCapabilities): + @property + def enable_async_execute_from_https(self) -> Optional[bool]: + """ + Whether to allow simulators to execute async JavaScript on pages using HTTPS. + """ + return self.get_capability(ENABLE_ASYNC_EXECUTE_FROM_HTTPS) + + @enable_async_execute_from_https.setter + def enable_async_execute_from_https(self, value: bool) -> None: + """ + Capability to allow simulators to execute asynchronous JavaScript + on pages using HTTPS. Defaults to false. + """ + self.set_capability(ENABLE_ASYNC_EXECUTE_FROM_HTTPS, value) diff --git a/appium/options/ios/xcuitest/webview/full_context_list_option.py b/appium/options/ios/xcuitest/webview/full_context_list_option.py new file mode 100644 index 000000000..7bdd3102d --- /dev/null +++ b/appium/options/ios/xcuitest/webview/full_context_list_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +FULL_CONTEXT_LIST = 'fullContextList' + + +class FullContextListOption(SupportsCapabilities): + @property + def full_context_list(self) -> Optional[bool]: + """ + Whether to return the detailed information on contexts for the get available + context command. + """ + return self.get_capability(FULL_CONTEXT_LIST) + + @full_context_list.setter + def full_context_list(self, value: bool) -> None: + """ + Sets to return the detailed information on contexts for the get available + context command. If this capability is enabled, then each item in the returned + contexts list would additionally include WebView title, full URL and the bundle + identifier. Defaults to false. + """ + self.set_capability(FULL_CONTEXT_LIST, value) diff --git a/appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py b/appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py new file mode 100644 index 000000000..c21cb141c --- /dev/null +++ b/appium/options/ios/xcuitest/webview/include_safari_in_webviews_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INCLUDE_SAFARI_IN_WEBVIEWS = 'includeSafariInWebviews' + + +class IncludeSafariInWebviewsOption(SupportsCapabilities): + @property + def include_safari_in_webviews(self) -> Optional[bool]: + """ + Whether to add Safari web views to the list of contexts available + during a native/webview app test. + """ + return self.get_capability(INCLUDE_SAFARI_IN_WEBVIEWS) + + @include_safari_in_webviews.setter + def include_safari_in_webviews(self, value: bool) -> None: + """ + Add Safari web contexts to the list of contexts available during a + native/webview app test. This is useful if the test opens Safari and + needs to be able to interact with it. Defaults to false. + """ + self.set_capability(INCLUDE_SAFARI_IN_WEBVIEWS, value) diff --git a/appium/options/ios/xcuitest/webview/native_web_tap_option.py b/appium/options/ios/xcuitest/webview/native_web_tap_option.py new file mode 100644 index 000000000..02a40b98e --- /dev/null +++ b/appium/options/ios/xcuitest/webview/native_web_tap_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +NATIVE_WEB_TAP = 'nativeWebTap' + + +class NativeWebTapOption(SupportsCapabilities): + @property + def native_web_tap(self) -> Optional[bool]: + """ + Whether to enable native taps in web view mode. + """ + return self.get_capability(NATIVE_WEB_TAP) + + @native_web_tap.setter + def native_web_tap(self, value: bool) -> None: + """ + Enable native, non-javascript-based taps being in web context mode. Defaults + to false. Warning: sometimes the preciseness of native taps could be broken, + because there is no reliable way to map web element coordinates to native ones. + """ + self.set_capability(NATIVE_WEB_TAP, value) diff --git a/appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py b/appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py new file mode 100644 index 000000000..63aefc4c4 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_garbage_collect_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_GARBAGE_COLLECT = 'safariGarbageCollect' + + +class SafariGarbageCollectOption(SupportsCapabilities): + @property + def safari_garbage_collect(self) -> Optional[bool]: + """ + Whether to turn on garbage collection when executing scripts on Safari. + """ + return self.get_capability(SAFARI_GARBAGE_COLLECT) + + @safari_garbage_collect.setter + def safari_garbage_collect(self, value: bool) -> None: + """ + Turns on/off Web Inspector garbage collection when executing scripts on Safari. + Turning on may improve performance. Defaults to false. + """ + self.set_capability(SAFARI_GARBAGE_COLLECT, value) diff --git a/appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py b/appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py new file mode 100644 index 000000000..c600285d0 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_ignore_fraud_warning_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_IGNORE_FRAUD_WARNING = 'safariIgnoreFraudWarning' + + +class SafariIgnoreFraudWarningOption(SupportsCapabilities): + @property + def safari_ignore_fraud_warning(self) -> Optional[bool]: + """ + Whether to prevent Safari from showing a fraudulent website warning. + """ + return self.get_capability(SAFARI_IGNORE_FRAUD_WARNING) + + @safari_ignore_fraud_warning.setter + def safari_ignore_fraud_warning(self, value: bool) -> None: + """ + Prevent Safari from showing a fraudulent website warning. + Default keeps current sim setting.. + """ + self.set_capability(SAFARI_IGNORE_FRAUD_WARNING, value) diff --git a/appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py b/appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py new file mode 100644 index 000000000..9f48f8cf3 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_ignore_web_hostnames_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_IGNORE_WEB_HOSTNAMES = 'safariIgnoreWebHostnames' + + +class SafariIgnoreWebHostnamesOption(SupportsCapabilities): + @property + def safari_ignore_web_hostnames(self) -> Optional[str]: + """ + Comma-separated list of host names to be ignored. + """ + return self.get_capability(SAFARI_IGNORE_WEB_HOSTNAMES) + + @safari_ignore_web_hostnames.setter + def safari_ignore_web_hostnames(self, value: str) -> None: + """ + Provide a list of hostnames (comma-separated) that the Safari automation + tools should ignore. This is to provide a workaround to prevent a webkit + bug where the web context is unintentionally changed to a 3rd party website + and the test gets stuck. The common culprits are search engines (yahoo, bing, + google) and about:blank. + """ + self.set_capability(SAFARI_IGNORE_WEB_HOSTNAMES, value) diff --git a/appium/options/ios/xcuitest/webview/safari_initial_url_option.py b/appium/options/ios/xcuitest/webview/safari_initial_url_option.py new file mode 100644 index 000000000..79dc14fe3 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_initial_url_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_INITIAL_URL = 'safariInitialUrl' + + +class SafariInitialUrlOption(SupportsCapabilities): + @property + def safari_initial_url(self) -> Optional[str]: + """ + The initial safari url. + """ + return self.get_capability(SAFARI_INITIAL_URL) + + @safari_initial_url.setter + def safari_initial_url(self, value: str) -> None: + """ + Set initial safari url, default is a local welcome page. + """ + self.set_capability(SAFARI_INITIAL_URL, value) diff --git a/appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py b/appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py new file mode 100644 index 000000000..03b2e28cc --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_log_all_communication_hex_dump_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_LOG_ALL_COMMUNICATION_HEX_DUMP = 'safariLogAllCommunicationHexDump' + + +class SafariLogAllCommunicationHexDumpOption(SupportsCapabilities): + @property + def safari_log_all_communication_hex_dump(self) -> Optional[bool]: + """ + Whether to log of plists sent to and received from the Web Inspector + in hex dump format. + """ + return self.get_capability(SAFARI_LOG_ALL_COMMUNICATION_HEX_DUMP) + + @safari_log_all_communication_hex_dump.setter + def safari_log_all_communication_hex_dump(self, value: bool) -> None: + """ + Log all communication sent to and received from the Web Inspector, as raw + hex dump and printable characters. This logging is done before any data + manipulation, and so can elucidate some communication issues. Like + appium:safariLogAllCommunication, this can produce a lot of data in some cases, + so it is recommended to be used only when necessary. Defaults to false. + """ + self.set_capability(SAFARI_LOG_ALL_COMMUNICATION_HEX_DUMP, value) diff --git a/appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py b/appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py new file mode 100644 index 000000000..b022dba3d --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_log_all_communication_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_LOG_ALL_COMMUNICATION = 'safariLogAllCommunication' + + +class SafariLogAllCommunicationOption(SupportsCapabilities): + @property + def safari_log_all_communication(self) -> Optional[bool]: + """ + Whether to log of plists sent to and received from the Web Inspector. + """ + return self.get_capability(SAFARI_LOG_ALL_COMMUNICATION) + + @safari_log_all_communication.setter + def safari_log_all_communication(self, value: bool) -> None: + """ + Log all plists sent to and received from the Web Inspector, as plain text. + For some operations this can be a lot of data, so it is recommended to + be used only when necessary. Defaults to false. + """ + self.set_capability(SAFARI_LOG_ALL_COMMUNICATION, value) diff --git a/appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py b/appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py new file mode 100644 index 000000000..2ebb48011 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_open_links_in_background_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_OPEN_LINKS_IN_BACKGROUND = 'safariOpenLinksInBackground' + + +class SafariOpenLinksInBackgroundOption(SupportsCapabilities): + @property + def safari_open_links_in_background(self) -> Optional[bool]: + """ + Whether Safari should allow links to open in new windows. + """ + return self.get_capability(SAFARI_OPEN_LINKS_IN_BACKGROUND) + + @safari_open_links_in_background.setter + def safari_open_links_in_background(self, value: bool) -> None: + """ + Whether Safari should allow links to open in new windows. + Default keeps current sim setting. + """ + self.set_capability(SAFARI_OPEN_LINKS_IN_BACKGROUND, value) diff --git a/appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py b/appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py new file mode 100644 index 000000000..c33ea61d2 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_socket_chunk_size_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_SOCKET_CHUNK_SIZE = 'safariSocketChunkSize' + + +class SafariSocketChunkSizeOption(SupportsCapabilities): + @property + def safari_socket_chunk_size(self) -> Optional[int]: + """ + Get the size of a single remote debugger socket chunk. + """ + return self.get_capability(SAFARI_SOCKET_CHUNK_SIZE) + + @safari_socket_chunk_size.setter + def safari_socket_chunk_size(self, value: int) -> None: + """ + The size, in bytes, of the data to be sent to the Web Inspector on + iOS 11+ real devices. Some devices hang when sending large amounts of + data to the Web Inspector, and breaking them into smaller parts can be + helpful in those cases. Defaults to 16384 (also the maximum possible). + """ + self.set_capability(SAFARI_SOCKET_CHUNK_SIZE, value) diff --git a/appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py b/appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py new file mode 100644 index 000000000..d92fb2d4a --- /dev/null +++ b/appium/options/ios/xcuitest/webview/safari_web_inspector_max_frame_length_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SAFARI_WEB_INSPECTOR_MAX_FRAME_LENGTH = 'safariWebInspectorMaxFrameLength' + + +class SafariWebInspectorMaxFrameLengthOption(SupportsCapabilities): + @property + def safari_web_inspector_max_frame_length(self) -> Optional[int]: + """ + Maximum size in bytes of a single data frame for the Web Inspector. + """ + return self.get_capability(SAFARI_WEB_INSPECTOR_MAX_FRAME_LENGTH) + + @safari_web_inspector_max_frame_length.setter + def safari_web_inspector_max_frame_length(self, value: int) -> None: + """ + The maximum size in bytes of a single data frame for the Web Inspector. + Too high values could introduce slowness and/or memory leaks. + Too low values could introduce possible buffer overflow exceptions. + Defaults to 20MiB (20*1024*1024). + """ + self.set_capability(SAFARI_WEB_INSPECTOR_MAX_FRAME_LENGTH, value) diff --git a/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py b/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py new file mode 100644 index 000000000..8c09117c4 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEBKIT_RESPONSE_TIMEOUT = 'webkitResponseTimeout' + + +class WebkitResponseTimeoutOption(SupportsCapabilities): + @property + def webkit_response_timeout(self) -> Optional[timedelta]: + """ + Time to wait for a response from WebKit in a Safari session. + """ + value = self.get_capability(WEBKIT_RESPONSE_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @webkit_response_timeout.setter + def webkit_response_timeout(self, value: Union[timedelta, int]) -> None: + """ + (Real device only) Set the time to wait for a response from + WebKit in a Safari session. Defaults to 5000ms. + """ + self.set_capability( + WEBKIT_RESPONSE_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/webview/webview_connect_retries_option.py b/appium/options/ios/xcuitest/webview/webview_connect_retries_option.py new file mode 100644 index 000000000..7792f15b0 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/webview_connect_retries_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEBVIEW_CONNECT_RETRIES = 'webviewConnectRetries' + + +class WebviewConnectRetriesOption(SupportsCapabilities): + @property + def webview_connect_retries(self) -> Optional[int]: + """ + Number of retries to send connection message to remote debugger, + to get a webview. + """ + return self.get_capability(WEBVIEW_CONNECT_RETRIES) + + @webview_connect_retries.setter + def webview_connect_retries(self, value: int) -> None: + """ + Number of times to send connection message to remote debugger, + to get a webview. Default: 8. + """ + self.set_capability(WEBVIEW_CONNECT_RETRIES, value) diff --git a/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py b/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py new file mode 100644 index 000000000..c0915afc1 --- /dev/null +++ b/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEBVIEW_CONNECT_TIMEOUT = 'webviewConnectTimeout' + + +class WebviewConnectTimeoutOption(SupportsCapabilities): + @property + def webview_connect_timeout(self) -> Optional[timedelta]: + """ + Timeout to wait for the initial presence of webviews. + """ + value = self.get_capability(WEBVIEW_CONNECT_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @webview_connect_timeout.setter + def webview_connect_timeout(self, value: Union[timedelta, int]) -> None: + """ + The time to wait for the initial presence of webviews in + MobileSafari or hybrid apps. Defaults to 0ms. + """ + self.set_capability( + WEBVIEW_CONNECT_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py index 3b44aa91b..36077e52f 100644 --- a/appium/options/mac/mac2/base.py +++ b/appium/options/mac/mac2/base.py @@ -17,6 +17,8 @@ from typing import Dict +from options.common.bundle_id_option import BundleIdOption + from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import PLATFORM_NAME, AppiumOptions from appium.options.common.postrun_option import PostrunOption @@ -26,7 +28,6 @@ from .arguments_option import ArgumentsOption from .bootstrap_root_option import BootstrapRootOption -from .bundle_id_option import BundleIdOption from .environment_option import EnvironmentOption from .server_startup_timeout_option import ServerStartupTimeoutOption from .show_server_logs_option import ShowServerLogsOption @@ -90,6 +91,19 @@ def system_host(self, value: str) -> None: """ SystemHostOption.system_host.fset(self, value) # type: ignore + @BundleIdOption.bundle_id.setter # type: ignore + def bundle_id(self, value: str) -> None: + """ + Set the bundle identifier of the application to automate, for example + com.apple.TextEdit. This is an optional capability. If it is not provided + then the session will be started without an application under test + (actually, it will be Finder). If the application with the given + identifier is not installed then an error will be thrown on session + startup. If the application is already running then it will be moved to + the foreground. + """ + BundleIdOption.bundle_id.fset(self, value) # type: ignore + @property def default_capabilities(self) -> Dict: return { diff --git a/setup.cfg b/setup.cfg index 4f910ac4b..347162f59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,4 +16,4 @@ follow_imports = skip ignore_missing_imports = True strict_optional = True warn_redundant_casts = True -warn_unused_ignores = True +warn_unused_ignores = False From 50458bb3b7ebc74a8a1d417c450b95e43201f0b1 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Jun 2022 22:32:42 -0700 Subject: [PATCH 098/548] docs: Update changelog for 2.5.0 --- CHANGELOG.rst | 66 +++++++++++++++++++++++++++++++++++++++++++++++ appium/version.py | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55805c7fc..450899f6c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,72 @@ Changelog ========= +v2.5.0 (2022-06-25) +------------------- + +New +~~~ +- Feat: Add xcuitest driver options (#737) [Mykola Mokhnach] +- Feat: Add Gecko driver options (#735) [Mykola Mokhnach] +- Feat: Add Windows driver options (#732) [Mykola Mokhnach] +- Feat: Add Safari driver options (#731) [Mykola Mokhnach] +- Feat: Add Mac2Driver options (#730) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update selenium requirement from ~=4.2 to ~=4.3 (#736) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.2.0...selenium-4.3.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.2 to ~=2.14.3 + (#733) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.2...v2.14.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Refactor: Make system_port and system_host options common (#734) + [Mykola Mokhnach] +- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 + (#725) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore: bump version to 2.4.0. [Kazuaki Matsuo] +- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 + (#725) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... + + v2.4.0 (2022-06-17) ------------------- diff --git a/appium/version.py b/appium/version.py index ecc158079..3d98bc1d1 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.4.0' +version = '2.5.0' From 4c166f45516a432ec807195f91fc2f208a3a3c08 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 26 Jun 2022 08:13:44 +0200 Subject: [PATCH 099/548] refactor: Remove previously deprecated methods and mark reset/close/launch APIs as deprecated (#738) --- appium/webdriver/common/multi_action.py | 6 +- appium/webdriver/common/touch_action.py | 6 +- appium/webdriver/extensions/applications.py | 23 +- .../extensions/search_context/__init__.py | 39 --- .../extensions/search_context/android.py | 237 ------------------ .../extensions/search_context/custom.py | 75 ------ .../extensions/search_context/ios.py | 155 ------------ .../extensions/search_context/mobile.py | 107 -------- .../extensions/search_context/windows.py | 61 ----- appium/webdriver/webdriver.py | 2 - appium/webdriver/webelement.py | 3 +- .../search_context/find_by_image_tests.py | 3 - .../webdriver/search_context/android_test.py | 6 +- 13 files changed, 35 insertions(+), 688 deletions(-) delete mode 100644 appium/webdriver/extensions/search_context/__init__.py delete mode 100644 appium/webdriver/extensions/search_context/android.py delete mode 100644 appium/webdriver/extensions/search_context/custom.py delete mode 100644 appium/webdriver/extensions/search_context/ios.py delete mode 100644 appium/webdriver/extensions/search_context/mobile.py delete mode 100644 appium/webdriver/extensions/search_context/windows.py diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 818c63d61..e416e2cf1 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -19,9 +19,9 @@ # chaining as the spec requires. import copy +import warnings from typing import TYPE_CHECKING, Dict, List, Optional, Union -from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: @@ -37,7 +37,9 @@ class MultiAction: """ def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: - logger.warning("[Deprecated] 'MultiAction' action is deprecated. Please use W3C actions instead.") + warnings.warn( + "[Deprecated] 'MultiAction' action is deprecated. Please use W3C actions instead.", DeprecationWarning + ) self._driver = driver self._element = element diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index ba36efff5..10060752c 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -24,9 +24,9 @@ # pylint: disable=no-self-use import copy +import warnings from typing import TYPE_CHECKING, Dict, List, Optional, Union -from appium.common.logger import logger from appium.webdriver.mobilecommand import MobileCommand as Command if TYPE_CHECKING: @@ -41,7 +41,9 @@ class TouchAction: """ def __init__(self, driver: Optional['WebDriver'] = None): - logger.warning("[Deprecated] 'TouchAction' action is deprecated. Please use W3C actions instead.") + warnings.warn( + "[Deprecated] 'TouchAction' action is deprecated. Please use W3C actions instead.", DeprecationWarning + ) self._driver = driver self._actions: List = [] diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index e545d77a0..537de6b62 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -11,7 +11,7 @@ # 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 warnings from typing import TYPE_CHECKING, Any, Dict from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands @@ -106,20 +106,34 @@ def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': def launch_app(self) -> 'WebDriver': """Start on the device the application specified in the desired capabilities. + deprecated:: 2.0.0 Returns: Union['WebDriver', 'Applications']: Self instance """ + warnings.warn( + 'The "launchApp" API is deprecated and will be removed in future versions. ' + 'See https://github.com/appium/appium/issues/15807', + DeprecationWarning, + ) + self.execute(Command.LAUNCH_APP) return self # type: ignore def close_app(self) -> 'WebDriver': """Stop the running application, specified in the desired capabilities, on the device. + deprecated:: 2.0.0 Returns: Union['WebDriver', 'Applications']: Self instance """ + warnings.warn( + 'The "closeApp" API is deprecated and will be removed in future versions. ' + 'See https://github.com/appium/appium/issues/15807', + DeprecationWarning, + ) + self.execute(Command.CLOSE_APP) return self # type: ignore @@ -194,10 +208,17 @@ def app_strings(self, language: str = None, string_file: str = None) -> Dict[str def reset(self) -> 'WebDriver': """Resets the current application on the device. + deprecated:: 2.0.0 Returns: Union['WebDriver', 'Applications']: Self instance """ + warnings.warn( + 'The "reset" API is deprecated and will be removed in future versions. ' + 'See https://github.com/appium/appium/issues/15807', + DeprecationWarning, + ) + self.execute(Command.RESET) return self # type: ignore diff --git a/appium/webdriver/extensions/search_context/__init__.py b/appium/webdriver/extensions/search_context/__init__.py deleted file mode 100644 index ed9ad3ece..000000000 --- a/appium/webdriver/extensions/search_context/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/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. - -from .android import AndroidSearchContext -from .custom import CustomSearchContext -from .ios import iOSSearchContext -from .mobile import MobileSearchContext -from .windows import WindowsSearchContext - - -class AppiumSearchContext( - AndroidSearchContext, - CustomSearchContext, - iOSSearchContext, - MobileSearchContext, - WindowsSearchContext, -): - """Returns appium driver search context""" - - -class AppiumWebElementSearchContext( - AndroidSearchContext, - CustomSearchContext, - iOSSearchContext, - MobileSearchContext, - WindowsSearchContext, -): - """Returns appium web element search context""" diff --git a/appium/webdriver/extensions/search_context/android.py b/appium/webdriver/extensions/search_context/android.py deleted file mode 100644 index 7aea8219a..000000000 --- a/appium/webdriver/extensions/search_context/android.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/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 -from typing import TYPE_CHECKING, Any, List, Optional - -from appium.common.logger import logger -from appium.protocols.webdriver.can_find_elements import CanFindElements -from appium.webdriver.common.appiumby import AppiumBy - -if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement - - -# noinspection PyPep8Naming -class AndroidSearchContext(CanFindElements): - """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': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.ANDROID_VIEW_MATCHER' instead. - - 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: 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: The args provided to the method - className: 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' - ) - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEW_MATCHER' instead.") - - return self.find_element( - by=AppiumBy.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[Any] = None, className: Optional[str] = None - ) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.ANDROID_DATA_MATCHER' instead. - - 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). - - Args: - name: 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: The args provided to the method - className: 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` - - 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_data_matcher(name='hasEntry', args=['title', 'Animation']) - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_DATA_MATCHER' instead.") - - return self.find_element( - by=AppiumBy.ANDROID_DATA_MATCHER, value=self._build_data_matcher(name=name, args=args, className=className) - ) - - def find_elements_by_android_data_matcher( - self, name: Optional[str] = None, args: Optional[Any] = None, className: Optional[str] = None - ) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.ANDROID_DATA_MATCHER' instead. - - 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). - - Args: - name: 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: The args provided to the method - className: 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` - - Returns: - `appium.webdriver.webelement.WebElement`: The found elements - - Usage: - driver.find_elements_by_android_data_matcher(name='hasEntry', args=['title', 'Animation']) - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_DATA_MATCHER' instead.") - - return self.find_elements( - by=AppiumBy.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[Any] = None, className: Optional[str] = None - ) -> str: - result = {} - - for key, value in {'name': name, 'args': args, 'class': className}.items(): - if value is not None: - result[key] = value - - return json.dumps(result) - - def find_element_by_android_uiautomator(self, uia_string: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. - - Finds element by uiautomator in Android. - - Args: - uia_string: The element name in the Android UIAutomator library - - Usage: - driver.find_element_by_android_uiautomator('.elements()[1].cells()[2]') - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead.") - - return self.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) - - def find_elements_by_android_uiautomator(self, uia_string: str) -> List['WebElement']: - """ - [Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead. - - Finds elements by uiautomator in Android. - - Args: - uia_string: The element name in the Android UIAutomator library - - Usage: - driver.find_elements_by_android_uiautomator('.elements()[1].cells()[2]') - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ANDROID_UIAUTOMATOR' instead.") - - return self.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uia_string) - - def find_element_by_android_viewtag(self, tag: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead. - - 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). - - Args: - tag: The tag name of the view to look for - - Usage: - driver.find_element_by_android_viewtag('a tag name') - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead.") - - return self.find_element(by=AppiumBy.ANDROID_VIEWTAG, value=tag) - - def find_elements_by_android_viewtag(self, tag: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.ANDROID_VIEWTAG' instead. - - 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). - - Args: - tag: The tag name of the view to look for - - Usage: - driver.find_elements_by_android_viewtag('a tag name') - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ANDROID_VIEWTAG' instead.") - - return self.find_elements(by=AppiumBy.ANDROID_VIEWTAG, value=tag) diff --git a/appium/webdriver/extensions/search_context/custom.py b/appium/webdriver/extensions/search_context/custom.py deleted file mode 100644 index 93ba82d37..000000000 --- a/appium/webdriver/extensions/search_context/custom.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/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. - -from typing import TYPE_CHECKING, List - -from appium.common.logger import logger -from appium.protocols.webdriver.can_find_elements import CanFindElements -from appium.webdriver.common.appiumby import AppiumBy - -if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement - - -class CustomSearchContext(CanFindElements): - """Define search context for custom plugin""" - - def find_element_by_custom(self, selector: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.CUSTOM' instead. - - Finds an element in conjunction with a custom element finding plugin - - Args: - selector: a string of the form "module:selector", where "module" is - the shortcut name given in the customFindModules capability, and - "selector" is the string that will be passed to the custom element - finding plugin itself - - Usage: - driver.find_element_by_custom("foo:bar") - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.CUSTOM' instead.") - - return self.find_element(by=AppiumBy.CUSTOM, value=selector) - - def find_elements_by_custom(self, selector: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.CUSTOM' instead. - - Finds elements in conjunction with a custom element finding plugin - - Args: - selector: a string of the form "module:selector", where "module" is - the shortcut name given in the customFindModules capability, and - "selector" is the string that will be passed to the custom element - finding plugin itself - - Usage: - driver.find_elements_by_custom("foo:bar") - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.CUSTOM' instead.") - - return self.find_elements(by=AppiumBy.CUSTOM, value=selector) diff --git a/appium/webdriver/extensions/search_context/ios.py b/appium/webdriver/extensions/search_context/ios.py deleted file mode 100644 index eef6c4e2b..000000000 --- a/appium/webdriver/extensions/search_context/ios.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/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. - -from typing import TYPE_CHECKING, List - -from appium.common.logger import logger -from appium.protocols.webdriver.can_find_elements import CanFindElements -from appium.webdriver.common.appiumby import AppiumBy - -if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement - - -class iOSSearchContext(CanFindElements): - """Define search context for iOS""" - - def find_element_by_ios_uiautomation(self, uia_string: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.IOS_UIAUTOMATION' instead. - - Finds an element by uiautomation in iOS. - - Args: - uia_string: The element name in the iOS UIAutomation library - - Usage: - driver.find_element_by_ios_uiautomation('.elements()[1].cells()[2]') - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_UIAUTOMATION' instead.") - - return self.find_element(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) - - def find_elements_by_ios_uiautomation(self, uia_string: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.IOS_UIAUTOMATION' instead. - - Finds elements by uiautomation in iOS. - - Args: - uia_string: The element name in the iOS UIAutomation library - - Usage: - driver.find_elements_by_ios_uiautomation('.elements()[1].cells()[2]') - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_UIAUTOMATION' instead.") - - return self.find_elements(by=AppiumBy.IOS_UIAUTOMATION, value=uia_string) - - def find_element_by_ios_predicate(self, predicate_string: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.IOS_PREDICATE' instead. - - Find an element by ios predicate string. - - Args: - predicate_string: The predicate string - - Usage: - driver.find_element_by_ios_predicate('label == "myLabel"') - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_PREDICATE' instead.") - - return self.find_element(by=AppiumBy.IOS_PREDICATE, value=predicate_string) - - def find_elements_by_ios_predicate(self, predicate_string: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.IOS_PREDICATE' instead. - - Finds elements by ios predicate string. - - Args: - predicate_string: The predicate string - - Usage: - driver.find_elements_by_ios_predicate('label == "myLabel"') - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_PREDICATE' instead.") - - return self.find_elements(by=AppiumBy.IOS_PREDICATE, value=predicate_string) - - def find_element_by_ios_class_chain(self, class_chain_string: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.IOS_CLASS_CHAIN' instead. - - Find an element by ios class chain string. - - Args: - class_chain_string: The class chain string - - Usage: - driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/XCUIElementTypeButton[3]') - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.IOS_CLASS_CHAIN' instead.") - - return self.find_element(by=AppiumBy.IOS_CLASS_CHAIN, value=class_chain_string) - - def find_elements_by_ios_class_chain(self, class_chain_string: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.IOS_CLASS_CHAIN' instead. - - Finds elements by ios class chain string. - - Args: - class_chain_string: The class chain string - - Usage: - driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]') - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.IOS_CLASS_CHAIN' instead.") - - return self.find_elements(by=AppiumBy.IOS_CLASS_CHAIN, value=class_chain_string) diff --git a/appium/webdriver/extensions/search_context/mobile.py b/appium/webdriver/extensions/search_context/mobile.py deleted file mode 100644 index 3518df2f6..000000000 --- a/appium/webdriver/extensions/search_context/mobile.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/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 base64 -from typing import TYPE_CHECKING, List - -from appium.common.logger import logger -from appium.protocols.webdriver.can_find_elements import CanFindElements -from appium.webdriver.common.appiumby import AppiumBy - -if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement - - -class MobileSearchContext(CanFindElements): - """Define search context for Mobile(Android, iOS)""" - - def find_element_by_accessibility_id(self, accessibility_id: str) -> 'WebElement': - """ - deprecated:: 2.1.0 - Please use 'find_element' with 'AppiumBy.ACCESSIBILITY_ID' instead. - - Finds an element by accessibility id. - - Args: - accessibility_id: A string corresponding to a recursive element search using the - Id/Name that the native Accessibility options utilize - - Usage: - driver.find_element_by_accessibility_id() - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - - """ - - logger.warning("[Deprecated] Please use 'find_element' with 'AppiumBy.ACCESSIBILITY_ID' instead.") - - return self.find_element(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) - - def find_elements_by_accessibility_id(self, accessibility_id: str) -> List['WebElement']: - """ - deprecated:: 2.1.0 - Please use 'find_elements' with 'AppiumBy.ACCESSIBILITY_ID' instead. - - Finds elements by accessibility id. - - Args: - accessibility_id: a string corresponding to a recursive element search using the - Id/Name that the native Accessibility options utilize - - Usage: - driver.find_elements_by_accessibility_id() - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - - """ - - logger.warning("[Deprecated] Please use 'find_elements' with 'AppiumBy.ACCESSIBILITY_ID' instead.") - - return self.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id) - - 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. - - Args: - img_path: a string corresponding to the path of a image - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - """ - - with open(img_path, 'rb') as i_file: - b64_data = base64.b64encode(i_file.read()).decode('UTF-8') - - return self.find_element(by=AppiumBy.IMAGE, value=b64_data) - - 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 - only ever return at most one element - - Args: - img_path: a string corresponding to the path of a image - - Return: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - with open(img_path, 'rb') as i_file: - b64_data = base64.b64encode(i_file.read()).decode('UTF-8') - - return self.find_elements(by=AppiumBy.IMAGE, value=b64_data) diff --git a/appium/webdriver/extensions/search_context/windows.py b/appium/webdriver/extensions/search_context/windows.py deleted file mode 100644 index 016f28c29..000000000 --- a/appium/webdriver/extensions/search_context/windows.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/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. - -from typing import TYPE_CHECKING, List - -from appium.common.logger import logger -from appium.protocols.webdriver.can_find_elements import CanFindElements -from appium.webdriver.common.appiumby import AppiumBy - -if TYPE_CHECKING: - from appium.webdriver.webelement import WebElement - - -class WindowsSearchContext(CanFindElements): - """Define search context for Windows""" - - def find_element_by_windows_uiautomation(self, win_uiautomation: str) -> 'WebElement': - """[Deprecated] Finds an element by windows uiautomation - - Args: - win_uiautomation: The element name in the windows UIAutomation selector - - Usage: - driver.find_element_by_windows_uiautomation() - - Returns: - `appium.webdriver.webelement.WebElement`: The found element - - """ - logger.warning( - "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." - ) - return self.find_element(by=AppiumBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) - - def find_elements_by_windows_uiautomation(self, win_uiautomation: str) -> List['WebElement']: - """[Deprecated] Finds elements by windows uiautomation - - Args: - win_uiautomation: The element name in the windows UIAutomation selector - - Usage: - driver.find_elements_by_windows_uiautomation() - - Returns: - :obj:`list` of :obj:`appium.webdriver.webelement.WebElement`: The found elements - """ - logger.warning( - "[Deprecated] '-windows uiautomation' selector is deprecated. Please use other locators like id." - ) - return self.find_elements(by=AppiumBy.WINDOWS_UI_AUTOMATION, value=win_uiautomation) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index e26d00770..5815069bd 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -52,7 +52,6 @@ from .extensions.log_event import LogEvent from .extensions.remote_fs import RemoteFS from .extensions.screen_record import ScreenRecord -from .extensions.search_context import AppiumSearchContext from .extensions.session import Session from .extensions.settings import Settings from .mobilecommand import MobileCommand as Command @@ -172,7 +171,6 @@ def add_command(self) -> Tuple[str, str]: class WebDriver( webdriver.Remote, - AppiumSearchContext, ActionHelpers, Activities, Applications, diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 2e4fca90c..67f9a27c1 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -20,11 +20,10 @@ from appium.webdriver.common.appiumby import AppiumBy -from .extensions.search_context import AppiumWebElementSearchContext from .mobilecommand import MobileCommand as Command -class WebElement(SeleniumWebElement, AppiumWebElementSearchContext): +class WebElement(SeleniumWebElement): _execute: Callable _id: str 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 ab12e8969..2ac4354ab 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -73,6 +73,3 @@ def test_find_throws_no_such_element(self) -> None: with pytest.raises(TimeoutException): wait_for_element(self.driver, AppiumBy.IMAGE, b64_data, timeout_sec=3) - - with pytest.raises(NoSuchElementException): - self.driver.find_element_by_image(image_path) diff --git a/test/unit/webdriver/search_context/android_test.py b/test/unit/webdriver/search_context/android_test.py index 9932945ba..c40735fc0 100644 --- a/test/unit/webdriver/search_context/android_test.py +++ b/test/unit/webdriver/search_context/android_test.py @@ -49,7 +49,8 @@ def test_find_elements_by_android_data_matcher(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/elements'), - body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "element-id1"}, {"element-6066-11e4-a52e-4f735466cecf": "element-id2"}]}', + body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "element-id1"}, ' + '{"element-6066-11e4-a52e-4f735466cecf": "element-id2"}]}', ) els = driver.find_elements( by=AppiumBy.ANDROID_DATA_MATCHER, value=json.dumps({'name': 'title', 'args': ['title', 'Animation']}) @@ -103,7 +104,8 @@ def test_find_elements_by_android_data_matcher(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/element/element_id/elements'), - body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "child-element-id1"}, {"element-6066-11e4-a52e-4f735466cecf": "child-element-id2"}]}', + body='{"value": [{"element-6066-11e4-a52e-4f735466cecf": "child-element-id1"}, ' + '{"element-6066-11e4-a52e-4f735466cecf": "child-element-id2"}]}', ) els = element.find_elements( by=AppiumBy.ANDROID_DATA_MATCHER, value=json.dumps({'name': 'title', 'args': ['title', 'Animation']}) From ce4de83b443b050e295c5c1af0938ce720198bc8 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 27 Jun 2022 07:50:18 +0200 Subject: [PATCH 100/548] chore: Improve autocompletion for methods returning self instance (#739) --- appium/options/common/base.py | 4 +-- appium/webdriver/extensions/action_helpers.py | 26 +++++++------- .../extensions/android/activities.py | 9 +++-- appium/webdriver/extensions/android/common.py | 9 +++-- appium/webdriver/extensions/android/gsm.py | 17 +++++----- .../webdriver/extensions/android/network.py | 13 ++++--- appium/webdriver/extensions/android/power.py | 13 ++++--- appium/webdriver/extensions/android/sms.py | 9 +++-- appium/webdriver/extensions/applications.py | 34 +++++++++---------- appium/webdriver/extensions/clipboard.py | 18 ++++------ .../extensions/execute_mobile_command.py | 9 +++-- appium/webdriver/extensions/hw_actions.py | 25 +++++++------- appium/webdriver/extensions/ime.py | 14 ++++---- appium/webdriver/extensions/keyboard.py | 25 ++++++-------- appium/webdriver/extensions/location.py | 16 ++++----- appium/webdriver/extensions/log_event.py | 9 +++-- appium/webdriver/extensions/remote_fs.py | 11 +++--- appium/webdriver/extensions/settings.py | 7 ++-- appium/webdriver/switch_to.py | 16 +++++---- appium/webdriver/webdriver.py | 7 ++-- 20 files changed, 136 insertions(+), 155 deletions(-) diff --git a/appium/options/common/base.py b/appium/options/common/base.py index 41d36d06d..cf8b61c7f 100644 --- a/appium/options/common/base.py +++ b/appium/options/common/base.py @@ -67,7 +67,7 @@ def __init__(self) -> None: # FIXME: https://github.com/SeleniumHQ/selenium/issues/10755 self._ignore_local_proxy = False - def set_capability(self, name: str, value: Any) -> T: + def set_capability(self: T, name: str, value: Any) -> T: w3c_name = name if name in self.W3C_CAPABILITY_NAMES or ':' in name else f'{APPIUM_PREFIX}{name}' if value is None: if w3c_name in self._caps: @@ -80,7 +80,7 @@ def get_capability(self, name: str) -> Any: """Fetches capability value or None if the capability is not set""" return self._caps[name] if name in self._caps else self._caps.get(f'{APPIUM_PREFIX}{name}') - def load_capabilities(self, caps: Dict[str, Any]) -> T: + def load_capabilities(self: T, caps: Dict[str, Any]) -> T: """Sets multiple capabilities""" for name, value in caps.items(): self.set_capability(name, value) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 64faa2055..84d730aef 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List, Optional, Tuple +from typing import List, Optional, Tuple, TypeVar from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.actions import interaction @@ -20,14 +20,14 @@ from selenium.webdriver.common.actions.mouse_button import MouseButton from selenium.webdriver.common.actions.pointer_input import PointerInput +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.webelement import WebElement -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class ActionHelpers: - def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> 'WebDriver': + def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> T: """Scrolls from one element to another Args: @@ -68,9 +68,9 @@ def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Op actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': + def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: """Drag the origin element to the destination element Args: @@ -86,9 +86,9 @@ def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'W actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> 'WebDriver': + def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> T: """Taps on an particular place with up to five fingers, holding for a certain time @@ -136,9 +136,9 @@ def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) new_input.create_pause(0.1) new_input.create_pointer_up(MouseButton.LEFT) actions.perform() - return self # type: ignore + return self - def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> 'WebDriver': + def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: """Swipe from one point to another point, for an optional duration. Args: @@ -162,9 +162,9 @@ def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: in actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self - def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDriver': + def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: """Flick from one point to another point. Args: @@ -186,4 +186,4 @@ def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDrive actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self # type: ignore + return self diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index ae25178bf..5a9f2cc95 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait @@ -20,12 +20,11 @@ from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Activities(CanExecuteCommands): - def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'WebDriver': + def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> T: """Opens an arbitrary activity during a test. If the activity belongs to another application, that application is started and the activity is opened. @@ -58,7 +57,7 @@ def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'W if key in opts: data[value] = opts[key] self.execute(Command.START_ACTIVITY, data) - return self # type: ignore + return self @property def current_activity(self) -> str: diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index a6eeabf5c..b121d339f 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any +from typing import Any, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Common(CanExecuteCommands): @@ -41,14 +40,14 @@ def end_test_coverage(self, intent: str, path: str) -> Any: # TODO Check return } return self.execute(Command.END_TEST_COVERAGE, data)['value'] - def open_notifications(self) -> 'WebDriver': + def open_notifications(self: T) -> T: """Open notification shade in Android (API Level 18 and above) Returns: Union['WebDriver', 'Common']: Self instance """ self.execute(Command.OPEN_NOTIFICATIONS, {}) - return self # type: ignore + return self @property def current_package(self) -> str: diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index 1df33ac7a..0dae0b4ba 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class GsmCallActions: @@ -49,7 +48,7 @@ class GsmVoiceState: class Gsm(CanExecuteCommands): - def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': + def make_gsm_call(self: T, phone_number: str, action: str) -> T: """Make GSM call (Emulator only) Android only. @@ -72,9 +71,9 @@ def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': f'(e.g. {GsmCallActions.__name__}.CALL)' ) self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) - return self # type: ignore + return self - def set_gsm_signal(self, strength: int) -> 'WebDriver': + def set_gsm_signal(self: T, strength: int) -> T: """Set GSM signal strength (Emulator only) Android only. @@ -96,9 +95,9 @@ def set_gsm_signal(self, strength: int) -> 'WebDriver': f'(e.g. {GsmSignalStrength.__name__}.GOOD)' ) self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) - return self # type: ignore + return self - def set_gsm_voice(self, state: str) -> 'WebDriver': + def set_gsm_voice(self: T, state: str) -> T: """Set GSM voice state (Emulator only) Android only. @@ -120,7 +119,7 @@ def set_gsm_voice(self, state: str) -> 'WebDriver': f'(e.g. {GsmVoiceState.__name__}.HOME)' ) self.execute(Command.SET_GSM_VOICE, {'state': state}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 70a32802d..46bfce8b0 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class NetSpeed: @@ -75,16 +74,16 @@ def set_network_connection(self, connection_type: int) -> int: data = {'parameters': {'type': connection_type}} return self.execute(Command.SET_NETWORK_CONNECTION, data)['value'] - def toggle_wifi(self) -> 'WebDriver': + def toggle_wifi(self: T) -> T: """Toggle the wifi on the device, Android only. Returns: Union['WebDriver', 'Network']: Self instance """ self.execute(Command.TOGGLE_WIFI, {}) - return self # type: ignore + return self - def set_network_speed(self, speed_type: str) -> 'WebDriver': + def set_network_speed(self: T, speed_type: str) -> T: """Set the network speed emulation. Android Emulator only. @@ -107,7 +106,7 @@ def set_network_speed(self, speed_type: str) -> 'WebDriver': ) self.execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index a74d5e82f..c3d762f8d 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -12,20 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Power(CanExecuteCommands): AC_OFF, AC_ON = 'off', 'on' - def set_power_capacity(self, percent: int) -> 'WebDriver': + def set_power_capacity(self: T, percent: int) -> T: """Emulate power capacity change on the connected emulator. Android only. @@ -40,9 +39,9 @@ def set_power_capacity(self, percent: int) -> 'WebDriver': Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_CAPACITY, {'percent': percent}) - return self # type: ignore + return self - def set_power_ac(self, ac_state: str) -> 'WebDriver': + def set_power_ac(self: T, ac_state: str) -> T: """Emulate power state change on the connected emulator. Android only. @@ -58,7 +57,7 @@ def set_power_ac(self, ac_state: str) -> 'WebDriver': Union['WebDriver', 'Power']: Self instance """ self.execute(Command.SET_POWER_AC, {'state': ac_state}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/sms.py b/appium/webdriver/extensions/android/sms.py index 0fc10f398..013494dd1 100644 --- a/appium/webdriver/extensions/android/sms.py +++ b/appium/webdriver/extensions/android/sms.py @@ -12,17 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Sms(CanExecuteCommands): - def send_sms(self, phone_number: str, message: str) -> 'WebDriver': + def send_sms(self: T, phone_number: str, message: str) -> T: """Emulate send SMS event on the connected emulator. Android only. @@ -38,7 +37,7 @@ def send_sms(self, phone_number: str, message: str) -> 'WebDriver': Union['WebDriver', 'Sms']: Self instance """ self.execute(Command.SEND_SMS, {'phoneNumber': phone_number, 'message': message}) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index 537de6b62..cbd5055b1 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import warnings -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Applications(CanExecuteCommands): - def background_app(self, seconds: int) -> 'WebDriver': + def background_app(self: T, seconds: int) -> T: """Puts the application in the background on the device for a certain duration. Args: @@ -37,7 +35,7 @@ def background_app(self, seconds: int) -> 'WebDriver': 'seconds': seconds, } self.execute(Command.BACKGROUND, data) - return self # type: ignore + return self def is_app_installed(self, bundle_id: str) -> bool: """Checks whether the application specified by `bundle_id` is installed on the device. @@ -53,7 +51,7 @@ def is_app_installed(self, bundle_id: str) -> bool: } return self.execute(Command.IS_APP_INSTALLED, data)['value'] - def install_app(self, app_path: str, **options: Any) -> 'WebDriver': + def install_app(self: T, app_path: str, **options: Any) -> T: """Install the application found at `app_path` on the device. Args: @@ -79,9 +77,9 @@ def install_app(self, app_path: str, **options: Any) -> 'WebDriver': if options: data.update({'options': options}) self.execute(Command.INSTALL_APP, data) - return self # type: ignore + return self - def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': + def remove_app(self: T, app_id: str, **options: Any) -> T: """Remove the specified application from the device. Args: @@ -102,9 +100,9 @@ def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': if options: data.update({'options': options}) self.execute(Command.REMOVE_APP, data) - return self # type: ignore + return self - def launch_app(self) -> 'WebDriver': + def launch_app(self: T) -> T: """Start on the device the application specified in the desired capabilities. deprecated:: 2.0.0 @@ -118,9 +116,9 @@ def launch_app(self) -> 'WebDriver': ) self.execute(Command.LAUNCH_APP) - return self # type: ignore + return self - def close_app(self) -> 'WebDriver': + def close_app(self: T) -> T: """Stop the running application, specified in the desired capabilities, on the device. deprecated:: 2.0.0 @@ -135,7 +133,7 @@ def close_app(self) -> 'WebDriver': ) self.execute(Command.CLOSE_APP) - return self # type: ignore + return self def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. @@ -157,7 +155,7 @@ def terminate_app(self, app_id: str, **options: Any) -> bool: data.update({'options': options}) return self.execute(Command.TERMINATE_APP, data)['value'] - def activate_app(self, app_id: str) -> 'WebDriver': + def activate_app(self: T, app_id: str) -> T: """Activates the application if it is not running or is running in the background. @@ -171,7 +169,7 @@ def activate_app(self, app_id: str) -> 'WebDriver': 'appId': app_id, } self.execute(Command.ACTIVATE_APP, data) - return self # type: ignore + return self def query_app_state(self, app_id: str) -> int: """Queries the state of the application. @@ -206,7 +204,7 @@ def app_strings(self, language: str = None, string_file: str = None) -> Dict[str data['stringFile'] = string_file return self.execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self) -> 'WebDriver': + def reset(self: T) -> T: """Resets the current application on the device. deprecated:: 2.0.0 @@ -220,7 +218,7 @@ def reset(self) -> 'WebDriver': ) self.execute(Command.RESET) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 587f5316d..4009a93c0 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,22 +13,20 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound='Clipboard') class Clipboard(CanExecuteCommands): def set_clipboard( - self, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None - ) -> 'WebDriver': + self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None + ) -> T: """Set the content of the system clipboard Args: @@ -47,9 +45,9 @@ def set_clipboard( if label: options['label'] = label self.execute(Command.SET_CLIPBOARD, options) - return self # type: ignore + return self - def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriver': + def set_clipboard_text(self: T, text: str, label: Optional[str] = None) -> T: """Copies the given text to the system clipboard Args: @@ -59,9 +57,7 @@ def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriv Returns: Union['WebDriver', 'Clipboard']: Self instance """ - - self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) - return self # type: ignore + return self.set_clipboard(bytes(str(text), 'UTF-8'), ClipboardContentType.PLAINTEXT, label) def get_clipboard(self, content_type: str = ClipboardContentType.PLAINTEXT) -> bytes: """Receives the content of the system clipboard diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 4152e471b..2518db4c7 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,16 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteScripts) class ExecuteMobileCommand(CanExecuteScripts): - def press_button(self, button_name: str) -> 'WebDriver': + def press_button(self: T, button_name: str) -> T: """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -37,7 +36,7 @@ def press_button(self, button_name: str) -> 'WebDriver': """ data = {'name': button_name} self.execute_script('mobile: pressButton', data) - return self # type: ignore + return self @property def battery_info(self) -> Dict[str, Any]: diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index 6cfb68888..75ca14821 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,18 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Optional +from typing import Any, Optional, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class HardwareActions(CanExecuteCommands): - def lock(self, seconds: Optional[int] = None) -> 'WebDriver': + def lock(self: T, seconds: Optional[int] = None) -> T: """Lock the device. No changes are made if the device is already unlocked. Args: @@ -39,16 +38,16 @@ def lock(self, seconds: Optional[int] = None) -> 'WebDriver': self.execute(Command.LOCK) else: self.execute(Command.LOCK, {'seconds': seconds}) - return self # type: ignore + return self - def unlock(self) -> 'WebDriver': + def unlock(self: T) -> T: """Unlock the device. No changes are made if the device is already locked. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.UNLOCK) - return self # type: ignore + return self def is_locked(self) -> bool: """Checks whether the device is locked. @@ -58,16 +57,16 @@ def is_locked(self) -> bool: """ return self.execute(Command.IS_LOCKED)['value'] - def shake(self) -> 'WebDriver': + def shake(self: T) -> T: """Shake the device. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.SHAKE) - return self # type: ignore + return self - def touch_id(self, match: bool) -> 'WebDriver': + def touch_id(self: T, match: bool) -> T: """Simulate touchId on iOS Simulator Args: @@ -78,16 +77,16 @@ def touch_id(self, match: bool) -> 'WebDriver': """ data = {'match': match} self.execute(Command.TOUCH_ID, data) - return self # type: ignore + return self - def toggle_touch_id_enrollment(self) -> 'WebDriver': + def toggle_touch_id_enrollment(self: T) -> T: """Toggle enroll touchId on iOS Simulator Returns: Union['WebDriver', 'HardwareActions']: Self instance """ self.execute(Command.TOGGLE_TOUCH_ID_ENROLLMENT) - return self # type: ignore + return self def finger_print(self, finger_id: int) -> Any: """Authenticate users by using their finger print scans on supported Android emulators. diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index c18376e71..24f1c6767 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -12,15 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, List +from typing import List, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class IME(CanExecuteCommands): @@ -45,7 +43,7 @@ def is_ime_active(self) -> bool: """ return self.execute(Command.IS_IME_ACTIVE, {})['value'] # pylint: disable=unsubscriptable-object - def activate_ime_engine(self, engine: str) -> 'WebDriver': + def activate_ime_engine(self: T, engine: str) -> T: """Activates the given IME engine on the device. Android only. @@ -59,9 +57,9 @@ def activate_ime_engine(self, engine: str) -> 'WebDriver': """ data = {'engine': engine} self.execute(Command.ACTIVATE_IME_ENGINE, data) - return self # type: ignore + return self - def deactivate_ime_engine(self) -> 'WebDriver': + def deactivate_ime_engine(self: T) -> T: """Deactivates the currently active IME engine on the device. Android only. @@ -70,7 +68,7 @@ def deactivate_ime_engine(self) -> 'WebDriver': Union['WebDriver', 'IME']: Self instance """ self.execute(Command.DEACTIVATE_IME_ENGINE, {}) - return self # type: ignore + return self @property def active_ime_engine(self) -> str: diff --git a/appium/webdriver/extensions/keyboard.py b/appium/webdriver/extensions/keyboard.py index 07a323ba2..9e7491a74 100644 --- a/appium/webdriver/extensions/keyboard.py +++ b/appium/webdriver/extensions/keyboard.py @@ -12,20 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Optional +from typing import Dict, Optional, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Keyboard(CanExecuteCommands): def hide_keyboard( - self, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None - ) -> 'WebDriver': + self: T, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None + ) -> T: """Hides the software keyboard on the device. In iOS, use `key_name` to press @@ -48,7 +47,7 @@ def hide_keyboard( strategy = 'tapOutside' data['strategy'] = strategy self.execute(Command.HIDE_KEYBOARD, data) - return self # type: ignore + return self def is_keyboard_shown(self) -> bool: """Attempts to detect whether a software keyboard is present @@ -58,7 +57,7 @@ def is_keyboard_shown(self) -> bool: """ return self.execute(Command.IS_KEYBOARD_SHOWN)['value'] - def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver': + def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. @@ -77,9 +76,9 @@ def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver' if metastate is not None: data['metastate'] = metastate self.execute(Command.KEY_EVENT, data) - return self # type: ignore + return self - def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> 'WebDriver': + def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: """Sends a keycode to the device. Android only. Possible keycodes can be found @@ -101,11 +100,9 @@ def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Op if flags is not None: data['flags'] = flags self.execute(Command.PRESS_KEYCODE, data) - return self # type: ignore + return self - def long_press_keycode( - self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None - ) -> 'WebDriver': + def long_press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: """Sends a long press of keycode to the device. Android only. Possible keycodes can be found in @@ -125,7 +122,7 @@ def long_press_keycode( if flags is not None: data['flags'] = flags self.execute(Command.LONG_PRESS_KEYCODE, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 0db48300c..aff5390cf 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Union +from typing import Dict, TypeVar, Union from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Location(CanExecuteCommands): - def toggle_location_services(self) -> 'WebDriver': + def toggle_location_services(self: T) -> T: """Toggle the location services on the device. Android only. @@ -33,16 +31,16 @@ def toggle_location_services(self) -> 'WebDriver': Union['WebDriver', 'Location']: Self instance """ self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) - return self # type: ignore + return self def set_location( - self, + self: T, latitude: Union[float, str], longitude: Union[float, str], altitude: Union[float, str, None] = None, speed: Union[float, str, None] = None, satellites: Union[float, str, None] = None, - ) -> 'WebDriver': + ) -> T: """Set the location of the device Args: @@ -68,7 +66,7 @@ def set_location( if satellites is not None: data['location']['satellites'] = satellites self.execute(Command.SET_LOCATION, data) - return self # type: ignore + return self @property def location(self) -> Dict[str, float]: diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index 50cdcb32d..028e4e51f 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, List, Union +from typing import Dict, List, TypeVar, Union from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class LogEvent(CanExecuteCommands): @@ -46,7 +45,7 @@ def get_events(self, type: List[str] = None) -> Dict[str, Union[str, int]]: data['type'] = type return self.execute(Command.GET_EVENTS, data)['value'] - def log_event(self, vendor: str, event: str) -> 'WebDriver': + def log_event(self: T, vendor: str, event: str) -> T: """Log a custom event on the Appium server. (Since Appium 1.16.0) @@ -62,7 +61,7 @@ def log_event(self, vendor: str, event: str) -> 'WebDriver': """ data = {'vendor': vendor, 'event': event} self.execute(Command.LOG_EVENT, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index 4d273c67f..9f65576df 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,7 +13,7 @@ # limitations under the License. import base64 -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar from selenium.common.exceptions import InvalidArgumentException @@ -21,8 +21,7 @@ from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class RemoteFS(CanExecuteCommands): @@ -55,8 +54,8 @@ def pull_folder(self, path: str) -> str: return self.execute(Command.PULL_FOLDER, data)['value'] def push_file( - self, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None - ) -> 'WebDriver': + self: T, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None + ) -> T: """Puts the data from the file at `source_path`, encoded as Base64, in the file specified as `path`. Specify either `base64data` or `source_path`, if both specified default to `source_path` @@ -87,7 +86,7 @@ def push_file( 'data': base64data, } self.execute(Command.PUSH_FILE, data) - return self # type: ignore + return self def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index 5815ab7a8..3c3026f70 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Any, Dict +from typing import Any, Dict, TypeVar from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver +T = TypeVar('T', bound=CanExecuteCommands) class Settings(CanExecuteCommands): @@ -34,7 +33,7 @@ def get_settings(self) -> Dict[str, Any]: """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self, settings: Dict[str, Any]) -> 'WebDriver': + def update_settings(self: T, settings: Dict[str, Any]) -> T: """Set settings for the current session. For more on settings, see: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index 64b3b40c7..e6d46ec82 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,22 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Optional +from typing import Optional, TypeVar from selenium.webdriver.remote.switch_to import SwitchTo +from appium.protocols.protocol import Protocol from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from .mobilecommand import MobileCommand -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - -class MobileSwitchTo(SwitchTo): +class HasDriver(Protocol): _driver: CanExecuteCommands - def context(self, context_name: Optional[str]) -> 'WebDriver': + +T = TypeVar('T', bound=HasDriver) + + +class MobileSwitchTo(SwitchTo, HasDriver): + def context(self: T, context_name: Optional[str]) -> T: """Sets the context for the current session. Passing `None` is equal to switching to native context. @@ -38,5 +41,4 @@ def context(self, context_name: Optional[str]) -> 'WebDriver': driver.switch_to.context('WEBVIEW_1') """ self._driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {'name': context_name}) - # noinspection PyTypeChecker return self diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 5815069bd..c64465072 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -14,7 +14,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union from selenium import webdriver from selenium.common.exceptions import InvalidArgumentException, SessionNotCreatedException, WebDriverException @@ -24,6 +24,7 @@ from appium.common.logger import logger from appium.options.common.base import AppiumOptions +from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection @@ -58,6 +59,8 @@ from .switch_to import MobileSwitchTo from .webelement import WebElement as MobileWebElement +T = TypeVar('T', bound=CanExecuteCommands) + class ExtensionBase: """ @@ -417,7 +420,7 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """ return MobileWebElement(self, element_id) - def set_value(self, element: MobileWebElement, value: str) -> 'WebDriver': + def set_value(self: T, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. Args: From 470e83674ecce5e5ff947427ed0c443cb7df4ae1 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 28 Jun 2022 10:58:47 +0200 Subject: [PATCH 101/548] feat: Add Android drivers options (#740) --- README.md | 20 ++- appium/options/android/common/__init__.py | 0 appium/options/android/common/adb/__init__.py | 0 .../common/adb/adb_exec_timeout_option.py | 41 +++++ .../android/common/adb/adb_port_option.py | 38 ++++ .../common/adb/allow_delay_adb_option.py | 39 ++++ .../common/adb/build_tools_version_option.py | 42 +++++ .../adb/clear_device_logs_on_start_option.py | 40 +++++ .../ignore_hidden_api_policy_error_option.py | 40 +++++ .../common/adb/logcat_filter_specs_option.py | 42 +++++ .../common/adb/logcat_format_option.py | 39 ++++ .../common/adb/mock_location_app_option.py | 42 +++++ .../common/adb/remote_adb_host_option.py | 39 ++++ .../common/adb/skip_logcat_capture_option.py | 40 +++++ .../common/adb/suppress_kill_server_option.py | 39 ++++ appium/options/android/common/app/__init__.py | 0 .../common/app/allow_test_packages_option.py | 40 +++++ .../app/android_install_timeout_option.py | 43 +++++ .../android/common/app/app_activity_option.py | 39 ++++ .../android/common/app/app_package_option.py | 39 ++++ .../common/app/app_wait_activity_option.py | 40 +++++ .../common/app/app_wait_duration_option.py | 41 +++++ .../common/app/app_wait_for_launch_option.py | 41 +++++ .../common/app/app_wait_package_option.py | 40 +++++ .../app/auto_grant_premissions_option.py | 40 +++++ .../common/app/enforce_app_install_option.py | 40 +++++ .../common/app/intent_action_option.py | 40 +++++ .../common/app/intent_category_option.py | 40 +++++ .../android/common/app/intent_flags_option.py | 40 +++++ .../app/optional_intent_arguments_option.py | 40 +++++ .../app/remote_apps_cache_limit_option.py | 42 +++++ .../app/uninstall_other_packages_option.py | 39 ++++ appium/options/android/common/avd/__init__.py | 0 .../android/common/avd/avd_args_option.py | 38 ++++ .../android/common/avd/avd_env_option.py | 38 ++++ .../common/avd/avd_launch_timeout_option.py | 41 +++++ .../options/android/common/avd/avd_option.py | 41 +++++ .../common/avd/avd_ready_timeout_option.py | 41 +++++ .../android/common/avd/gps_enabled_option.py | 39 ++++ .../common/avd/network_speed_option.py | 41 +++++ .../android/common/context/__init__.py | 0 .../context/auto_webview_timeout_option.py | 41 +++++ .../context/chrome_logging_prefs_option.py | 41 +++++ .../common/context/chrome_options_option.py | 40 +++++ .../context/chromedriver_args_option.py | 41 +++++ ...chromedriver_chrome_mapping_file_option.py | 43 +++++ ...chromedriver_disable_build_check_option.py | 41 +++++ .../chromedriver_executable_dir_option.py | 43 +++++ .../context/chromedriver_executable_option.py | 38 ++++ .../context/chromedriver_port_option.py | 39 ++++ .../context/chromedriver_ports_option.py | 39 ++++ ...romedriver_use_system_executable_option.py | 40 +++++ .../ensure_webviews_have_pages_option.py | 40 +++++ ...ndroid_package_from_context_name_option.py | 40 +++++ .../context/native_web_screenshot_option.py | 40 +++++ .../recreate_chrome_driver_sessions_option.py | 41 +++++ .../context/show_chromedriver_log_option.py | 39 ++++ .../context/webview_devtools_port_option.py | 40 +++++ .../android/common/localization/__init__.py | 0 .../localization/locale_script_option.py | 40 +++++ .../android/common/locking/__init__.py | 0 .../common/locking/skip_unlock_option.py | 42 +++++ .../common/locking/unlock_key_option.py | 40 +++++ .../common/locking/unlock_strategy_option.py | 40 +++++ .../locking/unlock_success_timeout_option.py | 43 +++++ .../common/locking/unlock_type_option.py | 40 +++++ .../options/android/common/mjpeg/__init__.py | 0 .../mjpeg/mjpeg_screenshot_url_option.py | 40 +++++ .../options/android/common/other/__init__.py | 0 ...e_suppress_accessibility_service_option.py | 40 +++++ .../common/other/user_profile_option.py | 42 +++++ .../android/common/signing/__init__.py | 0 .../common/signing/key_alias_option.py | 40 +++++ .../common/signing/key_password_option.py | 40 +++++ .../signing/keystore_password_option.py | 40 +++++ .../common/signing/keystore_path_option.py | 40 +++++ .../android/common/signing/no_sign_option.py | 42 +++++ .../common/signing/use_keystore_option.py | 42 +++++ .../espresso/activity_options_option.py | 41 +++++ .../android/espresso/app_locale_option.py | 44 +++++ appium/options/android/espresso/base.py | 169 ++++++++++++++++++ .../espresso/espresso_build_config_option.py | 48 +++++ .../espresso_server_launch_timeout_option.py | 43 +++++ .../espresso/force_espresso_rebuild_option.py | 41 +++++ .../android/espresso/intent_options_option.py | 41 +++++ .../espresso/show_gradle_log_option.py | 39 ++++ appium/options/android/uiautomator2/base.py | 163 +++++++++++++++++ .../disable_window_animation_option.py | 40 +++++ .../uiautomator2/mjpeg_server_port_option.py | 41 +++++ .../skip_device_initialization_option.py | 40 +++++ .../skip_server_installation_option.py | 44 +++++ ...utomator2_server_install_timeout_option.py | 43 +++++ ...automator2_server_launch_timeout_option.py | 43 +++++ ...uiautomator2_server_read_timeout_option.py | 45 +++++ 94 files changed, 3638 insertions(+), 8 deletions(-) create mode 100644 appium/options/android/common/__init__.py create mode 100644 appium/options/android/common/adb/__init__.py create mode 100644 appium/options/android/common/adb/adb_exec_timeout_option.py create mode 100644 appium/options/android/common/adb/adb_port_option.py create mode 100644 appium/options/android/common/adb/allow_delay_adb_option.py create mode 100644 appium/options/android/common/adb/build_tools_version_option.py create mode 100644 appium/options/android/common/adb/clear_device_logs_on_start_option.py create mode 100644 appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py create mode 100644 appium/options/android/common/adb/logcat_filter_specs_option.py create mode 100644 appium/options/android/common/adb/logcat_format_option.py create mode 100644 appium/options/android/common/adb/mock_location_app_option.py create mode 100644 appium/options/android/common/adb/remote_adb_host_option.py create mode 100644 appium/options/android/common/adb/skip_logcat_capture_option.py create mode 100644 appium/options/android/common/adb/suppress_kill_server_option.py create mode 100644 appium/options/android/common/app/__init__.py create mode 100644 appium/options/android/common/app/allow_test_packages_option.py create mode 100644 appium/options/android/common/app/android_install_timeout_option.py create mode 100644 appium/options/android/common/app/app_activity_option.py create mode 100644 appium/options/android/common/app/app_package_option.py create mode 100644 appium/options/android/common/app/app_wait_activity_option.py create mode 100644 appium/options/android/common/app/app_wait_duration_option.py create mode 100644 appium/options/android/common/app/app_wait_for_launch_option.py create mode 100644 appium/options/android/common/app/app_wait_package_option.py create mode 100644 appium/options/android/common/app/auto_grant_premissions_option.py create mode 100644 appium/options/android/common/app/enforce_app_install_option.py create mode 100644 appium/options/android/common/app/intent_action_option.py create mode 100644 appium/options/android/common/app/intent_category_option.py create mode 100644 appium/options/android/common/app/intent_flags_option.py create mode 100644 appium/options/android/common/app/optional_intent_arguments_option.py create mode 100644 appium/options/android/common/app/remote_apps_cache_limit_option.py create mode 100644 appium/options/android/common/app/uninstall_other_packages_option.py create mode 100644 appium/options/android/common/avd/__init__.py create mode 100644 appium/options/android/common/avd/avd_args_option.py create mode 100644 appium/options/android/common/avd/avd_env_option.py create mode 100644 appium/options/android/common/avd/avd_launch_timeout_option.py create mode 100644 appium/options/android/common/avd/avd_option.py create mode 100644 appium/options/android/common/avd/avd_ready_timeout_option.py create mode 100644 appium/options/android/common/avd/gps_enabled_option.py create mode 100644 appium/options/android/common/avd/network_speed_option.py create mode 100644 appium/options/android/common/context/__init__.py create mode 100644 appium/options/android/common/context/auto_webview_timeout_option.py create mode 100644 appium/options/android/common/context/chrome_logging_prefs_option.py create mode 100644 appium/options/android/common/context/chrome_options_option.py create mode 100644 appium/options/android/common/context/chromedriver_args_option.py create mode 100644 appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py create mode 100644 appium/options/android/common/context/chromedriver_disable_build_check_option.py create mode 100644 appium/options/android/common/context/chromedriver_executable_dir_option.py create mode 100644 appium/options/android/common/context/chromedriver_executable_option.py create mode 100644 appium/options/android/common/context/chromedriver_port_option.py create mode 100644 appium/options/android/common/context/chromedriver_ports_option.py create mode 100644 appium/options/android/common/context/chromedriver_use_system_executable_option.py create mode 100644 appium/options/android/common/context/ensure_webviews_have_pages_option.py create mode 100644 appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py create mode 100644 appium/options/android/common/context/native_web_screenshot_option.py create mode 100644 appium/options/android/common/context/recreate_chrome_driver_sessions_option.py create mode 100644 appium/options/android/common/context/show_chromedriver_log_option.py create mode 100644 appium/options/android/common/context/webview_devtools_port_option.py create mode 100644 appium/options/android/common/localization/__init__.py create mode 100644 appium/options/android/common/localization/locale_script_option.py create mode 100644 appium/options/android/common/locking/__init__.py create mode 100644 appium/options/android/common/locking/skip_unlock_option.py create mode 100644 appium/options/android/common/locking/unlock_key_option.py create mode 100644 appium/options/android/common/locking/unlock_strategy_option.py create mode 100644 appium/options/android/common/locking/unlock_success_timeout_option.py create mode 100644 appium/options/android/common/locking/unlock_type_option.py create mode 100644 appium/options/android/common/mjpeg/__init__.py create mode 100644 appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py create mode 100644 appium/options/android/common/other/__init__.py create mode 100644 appium/options/android/common/other/disable_suppress_accessibility_service_option.py create mode 100644 appium/options/android/common/other/user_profile_option.py create mode 100644 appium/options/android/common/signing/__init__.py create mode 100644 appium/options/android/common/signing/key_alias_option.py create mode 100644 appium/options/android/common/signing/key_password_option.py create mode 100644 appium/options/android/common/signing/keystore_password_option.py create mode 100644 appium/options/android/common/signing/keystore_path_option.py create mode 100644 appium/options/android/common/signing/no_sign_option.py create mode 100644 appium/options/android/common/signing/use_keystore_option.py create mode 100644 appium/options/android/espresso/activity_options_option.py create mode 100644 appium/options/android/espresso/app_locale_option.py create mode 100644 appium/options/android/espresso/espresso_build_config_option.py create mode 100644 appium/options/android/espresso/espresso_server_launch_timeout_option.py create mode 100644 appium/options/android/espresso/force_espresso_rebuild_option.py create mode 100644 appium/options/android/espresso/intent_options_option.py create mode 100644 appium/options/android/espresso/show_gradle_log_option.py create mode 100644 appium/options/android/uiautomator2/disable_window_animation_option.py create mode 100644 appium/options/android/uiautomator2/mjpeg_server_port_option.py create mode 100644 appium/options/android/uiautomator2/skip_device_initialization_option.py create mode 100644 appium/options/android/uiautomator2/skip_server_installation_option.py create mode 100644 appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py create mode 100644 appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py create mode 100644 appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py diff --git a/README.md b/README.md index e5e0201b5..8c6b113d2 100644 --- a/README.md +++ b/README.md @@ -111,10 +111,10 @@ from appium import webdriver from appium.options.android import UiAutomator2Options from appium.webdriver.common.appiumby import AppiumBy -options = UiAutomator2Options().\ - set_capability('platformVersion', '10').\ - set_capability('deviceName', 'Android Emulator').\ - set_capability('app', PATH('../../../apps/selendroid-test-app.apk')) +options = UiAutomator2Options() +options.platformVersion = '10' +options.udid = '123456789ABC' +options.app = PATH('../../../apps/test-app.apk') # Appium1 points to http://127.0.0.1:4723/wd/hub by default self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') @@ -130,10 +130,10 @@ from appium import webdriver from appium.options.ios import XCUITestOptions from appium.webdriver.common.appiumby import AppiumBy -options = XCUITestOptions().\ - set_capability('platformVersion', '13.4').\ - set_capability('deviceName', 'iPhone Simulator').\ - set_capability('app', PATH('../../apps/UICatalog.app.zip')) +options = XCUITestOptions() +options.platformVersion = '13.4' +options.udid = '123456789ABC' +options.app = PATH('../../apps/UICatalog.app.zip') # Appium1 points to http://127.0.0.1:4723/wd/hub by default self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') @@ -158,6 +158,8 @@ from appium import webdriver # instead: https://github.com/appium/python-client/pull/720 from appium.options.ios import XCUITestOptions +# load_capabilities API could be used to +# load options mapping stored in a dictionary options = XCUITestOptions().load_capabilities({ 'platformVersion': '13.4', 'deviceName': 'iPhone Simulator', @@ -186,6 +188,8 @@ from appium.options.common import AppiumOptions options = AppiumOptions() options.platform_name = 'mac' options.automation_name = 'safari' +# set_capability API allows to provide any custom option +# calls to it could be chained options.set_capability('browser_name', 'safari') # Appium1 points to http://127.0.0.1:4723/wd/hub by default diff --git a/appium/options/android/common/__init__.py b/appium/options/android/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/adb/__init__.py b/appium/options/android/common/adb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/adb/adb_exec_timeout_option.py b/appium/options/android/common/adb/adb_exec_timeout_option.py new file mode 100644 index 000000000..0c47f94db --- /dev/null +++ b/appium/options/android/common/adb/adb_exec_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ADB_EXEC_TIMEOUT = 'adbExecTimeout' + + +class AdbExecTimeoutOption(SupportsCapabilities): + @property + def adb_exec_timeout(self) -> Optional[timedelta]: + """ + Maximum time to wait until single ADB command is executed. + """ + value = self.get_capability(ADB_EXEC_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @adb_exec_timeout.setter + def adb_exec_timeout(self, value: Union[timedelta, int]) -> None: + """ + Maximum time to wait until single ADB command is executed. + 20000 ms by default. + """ + self.set_capability(ADB_EXEC_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/android/common/adb/adb_port_option.py b/appium/options/android/common/adb/adb_port_option.py new file mode 100644 index 000000000..4685c7a0f --- /dev/null +++ b/appium/options/android/common/adb/adb_port_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ADB_PORT = 'adbPort' + + +class AdbPortOption(SupportsCapabilities): + @property + def adb_port(self) -> Optional[int]: + """ + Number of the port where ADB is running. + """ + return self.get_capability(ADB_PORT) + + @adb_port.setter + def adb_port(self, value: int) -> None: + """ + Set number of the port where ADB is running. 5037 by default + """ + self.set_capability(ADB_PORT, value) diff --git a/appium/options/android/common/adb/allow_delay_adb_option.py b/appium/options/android/common/adb/allow_delay_adb_option.py new file mode 100644 index 000000000..cafd6ff3c --- /dev/null +++ b/appium/options/android/common/adb/allow_delay_adb_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ALLOW_DELAY_ADB = 'allowDelayAdb' + + +class AllowDelayAdbOption(SupportsCapabilities): + @property + def allow_delay_adb(self) -> Optional[bool]: + """ + Whether to prevent the emulator to use -delay-adb feature. + """ + return self.get_capability(ALLOW_DELAY_ADB) + + @allow_delay_adb.setter + def allow_delay_adb(self, value: bool) -> None: + """ + Being set to false prevents emulator to use -delay-adb feature to detect its startup. + See https://github.com/appium/appium/issues/14773 for more details. + """ + self.set_capability(ALLOW_DELAY_ADB, value) diff --git a/appium/options/android/common/adb/build_tools_version_option.py b/appium/options/android/common/adb/build_tools_version_option.py new file mode 100644 index 000000000..a5cd96c71 --- /dev/null +++ b/appium/options/android/common/adb/build_tools_version_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +BUILD_TOOLS_VERSION = 'buildToolsVersion' + + +class BuildToolsVersionOption(SupportsCapabilities): + @property + def build_tools_version(self) -> Optional[str]: + """ + Version of Android build tools to use. + """ + return self.get_capability(BUILD_TOOLS_VERSION) + + @build_tools_version.setter + def build_tools_version(self, value: str) -> None: + """ + The version of Android build tools to use. By default, UiAutomator2 + driver uses the most recent version of build tools installed on + the machine, but sometimes it might be necessary to give it a hint + (let say if there is a known bug in the most recent tools version). + Example: 28.0.3 + """ + self.set_capability(BUILD_TOOLS_VERSION, value) diff --git a/appium/options/android/common/adb/clear_device_logs_on_start_option.py b/appium/options/android/common/adb/clear_device_logs_on_start_option.py new file mode 100644 index 000000000..050cc6a7d --- /dev/null +++ b/appium/options/android/common/adb/clear_device_logs_on_start_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CLEAR_DEVICE_LOGS_ON_START = 'clearDeviceLogsOnStart' + + +class ClearDeviceLogsOnStartOption(SupportsCapabilities): + @property + def clear_device_logs_on_start(self) -> Optional[bool]: + """ + Makes the driver to delete all the existing logs in the + device buffer before starting a new test. + """ + return self.get_capability(CLEAR_DEVICE_LOGS_ON_START) + + @clear_device_logs_on_start.setter + def clear_device_logs_on_start(self, value: bool) -> None: + """ + If set to true then the driver deletes all the existing logs in the + device buffer before starting a new test. + """ + self.set_capability(CLEAR_DEVICE_LOGS_ON_START, value) diff --git a/appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py b/appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py new file mode 100644 index 000000000..4813ed246 --- /dev/null +++ b/appium/options/android/common/adb/ignore_hidden_api_policy_error_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +IGNORE_HIDDEN_API_POLICY_ERROR = 'ignoreHiddenApiPolicyError' + + +class IgnoreHiddenApiPolicyErrorOption(SupportsCapabilities): + @property + def ignore_hidden_api_policy_error(self) -> Optional[bool]: + """ + Whether to ignore a failure while changing hidden API access policies. + """ + return self.get_capability(IGNORE_HIDDEN_API_POLICY_ERROR) + + @ignore_hidden_api_policy_error.setter + def ignore_hidden_api_policy_error(self, value: bool) -> None: + """ + Being set to true ignores a failure while changing hidden API access policies. + Could be useful on some devices, where access to these policies has been locked by its vendor. + false by default. + """ + self.set_capability(IGNORE_HIDDEN_API_POLICY_ERROR, value) diff --git a/appium/options/android/common/adb/logcat_filter_specs_option.py b/appium/options/android/common/adb/logcat_filter_specs_option.py new file mode 100644 index 000000000..057d5921b --- /dev/null +++ b/appium/options/android/common/adb/logcat_filter_specs_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +LOGCAT_FILTER_SPECS = 'logcatFilterSpecs' + + +class LogcatFilterSpecsOption(SupportsCapabilities): + @property + def logcat_filter_specs(self) -> Optional[str]: + """ + Logcat filter format. + """ + return self.get_capability(LOGCAT_FILTER_SPECS) + + @logcat_filter_specs.setter + def logcat_filter_specs(self, value: str) -> None: + """ + Series of tag[:priority] where tag is a log component tag (or * for all) + and priority is: V Verbose, D Debug, I Info, W Warn, E Error, F Fatal, + S Silent (supress all output). '' means ':d' and tag by itself means tag:v. + If not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS. + If no filterspec is found, filter defaults to '*:I'. + """ + self.set_capability(LOGCAT_FILTER_SPECS, value) diff --git a/appium/options/android/common/adb/logcat_format_option.py b/appium/options/android/common/adb/logcat_format_option.py new file mode 100644 index 000000000..363a6f5a7 --- /dev/null +++ b/appium/options/android/common/adb/logcat_format_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +LOGCAT_FORMAT = 'logcatFormat' + + +class LogcatFormatOption(SupportsCapabilities): + @property + def logcat_format(self) -> Optional[str]: + """ + Log print format. + """ + return self.get_capability(LOGCAT_FORMAT) + + @logcat_format.setter + def logcat_format(self, value: str) -> None: + """ + The log print format, where format is one of: brief process tag thread raw time + threadtime long. threadtime is the default value. + """ + self.set_capability(LOGCAT_FORMAT, value) diff --git a/appium/options/android/common/adb/mock_location_app_option.py b/appium/options/android/common/adb/mock_location_app_option.py new file mode 100644 index 000000000..d263ea0e6 --- /dev/null +++ b/appium/options/android/common/adb/mock_location_app_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MOCK_LOCATION_APP = 'mockLocationApp' + + +class MockLocationAppOption(SupportsCapabilities): + @property + def mock_location_app(self) -> Optional[str]: + """ + Identifier of the app, which is used as a system mock location provider. + """ + return self.get_capability(MOCK_LOCATION_APP) + + @mock_location_app.setter + def mock_location_app(self, value: str) -> None: + """ + Sets the package identifier of the app, which is used as a system mock location + provider since Appium 1.18.0+. This capability has no effect on emulators. + If the value is set to null or an empty string, then Appium will skip the mocked + location provider setup procedure. Defaults to Appium Setting package + identifier (io.appium.settings). + """ + self.set_capability(MOCK_LOCATION_APP, value) diff --git a/appium/options/android/common/adb/remote_adb_host_option.py b/appium/options/android/common/adb/remote_adb_host_option.py new file mode 100644 index 000000000..1656e9bd5 --- /dev/null +++ b/appium/options/android/common/adb/remote_adb_host_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +REMOTE_ADB_HOST = 'remoteAdbHost' + + +class RemoteAdbHostOption(SupportsCapabilities): + @property + def remote_adb_host(self) -> Optional[str]: + """ + Address of the host where ADB is running. + """ + return self.get_capability(REMOTE_ADB_HOST) + + @remote_adb_host.setter + def remote_adb_host(self, value: str) -> None: + """ + Address of the host where ADB is running (the value of -H ADB command line option). + Localhost by default. + """ + self.set_capability(REMOTE_ADB_HOST, value) diff --git a/appium/options/android/common/adb/skip_logcat_capture_option.py b/appium/options/android/common/adb/skip_logcat_capture_option.py new file mode 100644 index 000000000..2f9f9074f --- /dev/null +++ b/appium/options/android/common/adb/skip_logcat_capture_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SKIP_LOGCAT_CAPTURE = 'skipLogcatCapture' + + +class SkipLogcatCaptureOption(SupportsCapabilities): + @property + def skip_logcat_capture(self) -> Optional[bool]: + """ + Whether to delete all the existing logs in the + device buffer before starting a new test. + """ + return self.get_capability(SKIP_LOGCAT_CAPTURE) + + @skip_logcat_capture.setter + def skip_logcat_capture(self, value: bool) -> None: + """ + Being set to true disables automatic logcat output collection during the test run. + false by default + """ + self.set_capability(SKIP_LOGCAT_CAPTURE, value) diff --git a/appium/options/android/common/adb/suppress_kill_server_option.py b/appium/options/android/common/adb/suppress_kill_server_option.py new file mode 100644 index 000000000..fb7738994 --- /dev/null +++ b/appium/options/android/common/adb/suppress_kill_server_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SUPPRESS_KILL_SERVER = 'suppressKillServer' + + +class SuppressKillServerOption(SupportsCapabilities): + @property + def suppress_kill_server(self) -> Optional[bool]: + """ + Prevents the driver from ever killing the ADB server explicitly. + """ + return self.get_capability(SUPPRESS_KILL_SERVER) + + @suppress_kill_server.setter + def suppress_kill_server(self, value: bool) -> None: + """ + Being set to true prevents the driver from ever killing the ADB server explicitly. + Could be useful if ADB is connected wirelessly. false by default. + """ + self.set_capability(SUPPRESS_KILL_SERVER, value) diff --git a/appium/options/android/common/app/__init__.py b/appium/options/android/common/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/app/allow_test_packages_option.py b/appium/options/android/common/app/allow_test_packages_option.py new file mode 100644 index 000000000..b09136932 --- /dev/null +++ b/appium/options/android/common/app/allow_test_packages_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ALLOW_TEST_PACKAGES = 'allowTestPackages' + + +class AllowTestPackagesOption(SupportsCapabilities): + @property + def allow_test_packages(self) -> Optional[bool]: + """ + Whether it is possible to use packages built with the test flag for + the automated testing (literally adds -t flag to the adb install command). + """ + return self.get_capability(ALLOW_TEST_PACKAGES) + + @allow_test_packages.setter + def allow_test_packages(self, value: bool) -> None: + """ + If set to true then it would be possible to use packages built with the test flag for + the automated testing (literally adds -t flag to the adb install command). false by default. + """ + self.set_capability(ALLOW_TEST_PACKAGES, value) diff --git a/appium/options/android/common/app/android_install_timeout_option.py b/appium/options/android/common/app/android_install_timeout_option.py new file mode 100644 index 000000000..c7919fcb5 --- /dev/null +++ b/appium/options/android/common/app/android_install_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ANDROID_INSTALL_TIMEOUT = 'androidInstallTimeout' + + +class AndroidInstallTimeoutOption(SupportsCapabilities): + @property + def android_install_timeout(self) -> Optional[timedelta]: + """ + Maximum amount of time to wait until the application under test is installed. + """ + value = self.get_capability(ANDROID_INSTALL_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @android_install_timeout.setter + def android_install_timeout(self, value: Union[timedelta, int]) -> None: + """ + Maximum amount of time to wait until the application under test is installed. + 90000 ms by default + """ + self.set_capability( + ANDROID_INSTALL_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/app/app_activity_option.py b/appium/options/android/common/app/app_activity_option.py new file mode 100644 index 000000000..41f66eed8 --- /dev/null +++ b/appium/options/android/common/app/app_activity_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_ACTIVITY = 'appActivity' + + +class AppActivityOption(SupportsCapabilities): + @property + def app_activity(self) -> Optional[str]: + """ + Name of the main app activity. + """ + return self.get_capability(APP_ACTIVITY) + + @app_activity.setter + def app_activity(self, value: str) -> None: + """ + Main application activity identifier. If not provided then the driver + will try to detect it automatically from the package provided by the app capability. + """ + self.set_capability(APP_ACTIVITY, value) diff --git a/appium/options/android/common/app/app_package_option.py b/appium/options/android/common/app/app_package_option.py new file mode 100644 index 000000000..a8c7b065c --- /dev/null +++ b/appium/options/android/common/app/app_package_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_PACKAGE = 'appPackage' + + +class AppPackageOption(SupportsCapabilities): + @property + def app_package(self) -> Optional[str]: + """ + App package identifier. + """ + return self.get_capability(APP_PACKAGE) + + @app_package.setter + def app_package(self, value: str) -> None: + """ + Application package identifier to be started. If not provided then the driver will + try to detect it automatically from the package provided by the app capability. + """ + self.set_capability(APP_PACKAGE, value) diff --git a/appium/options/android/common/app/app_wait_activity_option.py b/appium/options/android/common/app/app_wait_activity_option.py new file mode 100644 index 000000000..5d72f311d --- /dev/null +++ b/appium/options/android/common/app/app_wait_activity_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_WAIT_ACTIVITY = 'appWaitActivity' + + +class AppWaitActivityOption(SupportsCapabilities): + @property + def app_wait_activity(self) -> Optional[str]: + """ + Name of the app activity to wait for. + """ + return self.get_capability(APP_WAIT_ACTIVITY) + + @app_wait_activity.setter + def app_wait_activity(self, value: str) -> None: + """ + Identifier of the activity that the driver should wait for + (not necessarily the main one). + If not provided then defaults to appium:appActivity. + """ + self.set_capability(APP_WAIT_ACTIVITY, value) diff --git a/appium/options/android/common/app/app_wait_duration_option.py b/appium/options/android/common/app/app_wait_duration_option.py new file mode 100644 index 000000000..b06e65503 --- /dev/null +++ b/appium/options/android/common/app/app_wait_duration_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_WAIT_DURATION = 'appWaitDuration' + + +class AppWaitDurationOption(SupportsCapabilities): + @property + def app_wait_duration(self) -> Optional[timedelta]: + """ + Identifier of the app package to wait for. + """ + value = self.get_capability(APP_WAIT_DURATION) + return None if value is None else timedelta(milliseconds=value) + + @app_wait_duration.setter + def app_wait_duration(self, value: Union[timedelta, int]) -> None: + """ + Maximum amount of time to wait until the application under test is started + (e.g. an activity returns the control to the caller). 20000 ms by default. + """ + self.set_capability(APP_WAIT_DURATION, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/android/common/app/app_wait_for_launch_option.py b/appium/options/android/common/app/app_wait_for_launch_option.py new file mode 100644 index 000000000..5f5b48c51 --- /dev/null +++ b/appium/options/android/common/app/app_wait_for_launch_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_WAIT_FOR_LAUNCH = 'appWaitForLaunch' + + +class AppWaitForLaunchOption(SupportsCapabilities): + @property + def app_wait_for_launch(self) -> Optional[bool]: + """ + Whether to block until the app under test returns the control to the + caller after its activity has been started by Activity Manager. + """ + return self.get_capability(APP_WAIT_FOR_LAUNCH) + + @app_wait_for_launch.setter + def app_wait_for_launch(self, value: bool) -> None: + """ + Whether to block until the app under test returns the control to the + caller after its activity has been started by Activity Manager + (true, the default value) or to continue the test without waiting for that (false). + """ + self.set_capability(APP_WAIT_FOR_LAUNCH, value) diff --git a/appium/options/android/common/app/app_wait_package_option.py b/appium/options/android/common/app/app_wait_package_option.py new file mode 100644 index 000000000..9df4fa0d0 --- /dev/null +++ b/appium/options/android/common/app/app_wait_package_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_WAIT_PACKAGE = 'appWaitPackage' + + +class AppWaitPackageOption(SupportsCapabilities): + @property + def app_wait_package(self) -> Optional[str]: + """ + Identifier of the app package to wait for. + """ + return self.get_capability(APP_WAIT_PACKAGE) + + @app_wait_package.setter + def app_wait_package(self, value: str) -> None: + """ + Identifier of the package that the driver should wait for + (not necessarily the main one). + If not provided then defaults to appium:appPackage. + """ + self.set_capability(APP_WAIT_PACKAGE, value) diff --git a/appium/options/android/common/app/auto_grant_premissions_option.py b/appium/options/android/common/app/auto_grant_premissions_option.py new file mode 100644 index 000000000..e276e7da4 --- /dev/null +++ b/appium/options/android/common/app/auto_grant_premissions_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTO_GRANT_PERMISSIONS = 'autoGrantPermissions' + + +class AutoGrantPermissionsOption(SupportsCapabilities): + @property + def auto_grant_permissions(self) -> Optional[bool]: + """ + Whether to grant all the requested application permissions + automatically when a test starts. + """ + return self.get_capability(AUTO_GRANT_PERMISSIONS) + + @auto_grant_permissions.setter + def auto_grant_permissions(self, value: bool) -> None: + """ + Whether to grant all the requested application permissions + automatically when a test starts(true). false by default. + """ + self.set_capability(AUTO_GRANT_PERMISSIONS, value) diff --git a/appium/options/android/common/app/enforce_app_install_option.py b/appium/options/android/common/app/enforce_app_install_option.py new file mode 100644 index 000000000..85d128ed7 --- /dev/null +++ b/appium/options/android/common/app/enforce_app_install_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ENFORCE_APP_INSTALL = 'enforceAppInstall' + + +class EnforceAppInstallOption(SupportsCapabilities): + @property + def enforce_app_install(self) -> Optional[bool]: + """ + Whether the application under test is always reinstalled even + if a newer version of it already exists on the device under test. + """ + return self.get_capability(ENFORCE_APP_INSTALL) + + @enforce_app_install.setter + def enforce_app_install(self, value: bool) -> None: + """ + Allows setting whether the application under test is always reinstalled even + if a newer version of it already exists on the device under test. false by default. + """ + self.set_capability(ENFORCE_APP_INSTALL, value) diff --git a/appium/options/android/common/app/intent_action_option.py b/appium/options/android/common/app/intent_action_option.py new file mode 100644 index 000000000..8019bcc54 --- /dev/null +++ b/appium/options/android/common/app/intent_action_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INTENT_ACTION = 'intentAction' + + +class IntentActionOption(SupportsCapabilities): + @property + def intent_action(self) -> Optional[str]: + """ + Intent action to be applied when + starting the given appActivity by Activity Manager. + """ + return self.get_capability(INTENT_ACTION) + + @intent_action.setter + def intent_action(self, value: str) -> None: + """ + Set an optional intent action to be applied when + starting the given appActivity by Activity Manager. + """ + self.set_capability(INTENT_ACTION, value) diff --git a/appium/options/android/common/app/intent_category_option.py b/appium/options/android/common/app/intent_category_option.py new file mode 100644 index 000000000..f6f568c20 --- /dev/null +++ b/appium/options/android/common/app/intent_category_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INTENT_CATEGORY = 'intentCategory' + + +class IntentCategoryOption(SupportsCapabilities): + @property + def intent_category(self) -> Optional[str]: + """ + Intent category to be applied when + starting the given appActivity by Activity Manager. + """ + return self.get_capability(INTENT_CATEGORY) + + @intent_category.setter + def intent_category(self, value: str) -> None: + """ + Set an optional intent category to be applied when + starting the given appActivity by Activity Manager. + """ + self.set_capability(INTENT_CATEGORY, value) diff --git a/appium/options/android/common/app/intent_flags_option.py b/appium/options/android/common/app/intent_flags_option.py new file mode 100644 index 000000000..141e51c30 --- /dev/null +++ b/appium/options/android/common/app/intent_flags_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INTENT_FLAGS = 'intentFlags' + + +class IntentFlagsOption(SupportsCapabilities): + @property + def intent_flags(self) -> Optional[str]: + """ + Intent flags to be applied when + starting the given appActivity by Activity Manager. + """ + return self.get_capability(INTENT_FLAGS) + + @intent_flags.setter + def intent_flags(self, value: str) -> None: + """ + Set optional intent flags to be applied when + starting the given appActivity by Activity Manager. + """ + self.set_capability(INTENT_FLAGS, value) diff --git a/appium/options/android/common/app/optional_intent_arguments_option.py b/appium/options/android/common/app/optional_intent_arguments_option.py new file mode 100644 index 000000000..3411fe96c --- /dev/null +++ b/appium/options/android/common/app/optional_intent_arguments_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +OPTIONAL_INTENT_ARGUMENTS = 'optionalIntentArguments' + + +class OptionalIntentArgumentsOption(SupportsCapabilities): + @property + def optional_intent_arguments(self) -> Optional[str]: + """ + Intent arguments to be applied when + starting the given appActivity by Activity Manager. + """ + return self.get_capability(OPTIONAL_INTENT_ARGUMENTS) + + @optional_intent_arguments.setter + def optional_intent_arguments(self, value: str) -> None: + """ + Set optional intent arguments to be applied when + starting the given appActivity by Activity Manager. + """ + self.set_capability(OPTIONAL_INTENT_ARGUMENTS, value) diff --git a/appium/options/android/common/app/remote_apps_cache_limit_option.py b/appium/options/android/common/app/remote_apps_cache_limit_option.py new file mode 100644 index 000000000..0f21ec782 --- /dev/null +++ b/appium/options/android/common/app/remote_apps_cache_limit_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +REMOTE_APPS_CACHE_LIMIT = 'remoteAppsCacheLimit' + + +class RemoteAppsCacheLimitOption(SupportsCapabilities): + @property + def remote_apps_cache_limit(self) -> Optional[int]: + """ + Maximum amount of apps that could be cached on the remote device. + """ + return self.get_capability(REMOTE_APPS_CACHE_LIMIT) + + @remote_apps_cache_limit.setter + def remote_apps_cache_limit(self, value: int) -> None: + """ + Set the maximum amount of application packages to be cached on the device under test. + This is needed for devices that don't support streamed installs (Android 7 and below), + because ADB must push app packages to the device first in order to install them, + which takes some time. Setting this capability to zero disables apps caching. + 10 by default. + """ + self.set_capability(REMOTE_APPS_CACHE_LIMIT, value) diff --git a/appium/options/android/common/app/uninstall_other_packages_option.py b/appium/options/android/common/app/uninstall_other_packages_option.py new file mode 100644 index 000000000..2cd05cdf5 --- /dev/null +++ b/appium/options/android/common/app/uninstall_other_packages_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UNINSTALL_OTHER_PACKAGES = 'uninstallOtherPackages' + + +class UninstallOtherPackagesOption(SupportsCapabilities): + @property + def uninstall_other_packages(self) -> Optional[str]: + """ + Identifiers of packages to be uninstalled from the device before a test starts. + """ + return self.get_capability(UNINSTALL_OTHER_PACKAGES) + + @uninstall_other_packages.setter + def uninstall_other_packages(self, value: str) -> None: + """ + Allows to set one or more comma-separated package + identifiers to be uninstalled from the device before a test starts. + """ + self.set_capability(UNINSTALL_OTHER_PACKAGES, value) diff --git a/appium/options/android/common/avd/__init__.py b/appium/options/android/common/avd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/avd/avd_args_option.py b/appium/options/android/common/avd/avd_args_option.py new file mode 100644 index 000000000..a8b7b7506 --- /dev/null +++ b/appium/options/android/common/avd/avd_args_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AVD_ARGS = 'avdArgs' + + +class AvdArgsOption(SupportsCapabilities): + @property + def avd_args(self) -> Optional[str]: + """ + Emulator command line arguments. + """ + return self.get_capability(AVD_ARGS) + + @avd_args.setter + def avd_args(self, value: str) -> None: + """ + Set emulator command line arguments. + """ + self.set_capability(AVD_ARGS, value) diff --git a/appium/options/android/common/avd/avd_env_option.py b/appium/options/android/common/avd/avd_env_option.py new file mode 100644 index 000000000..a673a95fd --- /dev/null +++ b/appium/options/android/common/avd/avd_env_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AVD_ENV = 'avdEnv' + + +class AvdEnvOption(SupportsCapabilities): + @property + def avd_env(self) -> Optional[Dict[str, str]]: + """ + Mapping of emulator environment variables. + """ + return self.get_capability(AVD_ENV) + + @avd_env.setter + def avd_env(self, value: Dict[str, str]) -> None: + """ + Set the mapping of emulator environment variables. + """ + self.set_capability(AVD_ENV, value) diff --git a/appium/options/android/common/avd/avd_launch_timeout_option.py b/appium/options/android/common/avd/avd_launch_timeout_option.py new file mode 100644 index 000000000..58cad9d3b --- /dev/null +++ b/appium/options/android/common/avd/avd_launch_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AVD_LAUNCH_TIMEOUT = 'avdLaunchTimeout' + + +class AvdLaunchTimeoutOption(SupportsCapabilities): + @property + def avd_launch_timeout(self) -> Optional[timedelta]: + """ + Timeout to wait until Android Emulator is started. + """ + value = self.get_capability(AVD_LAUNCH_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @avd_launch_timeout.setter + def avd_launch_timeout(self, value: Union[timedelta, int]) -> None: + """ + Maximum timeout to wait until Android Emulator is started. + 60000 ms by default. + """ + self.set_capability(AVD_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/android/common/avd/avd_option.py b/appium/options/android/common/avd/avd_option.py new file mode 100644 index 000000000..42c543fc8 --- /dev/null +++ b/appium/options/android/common/avd/avd_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AVD = 'avd' + + +class AvdOption(SupportsCapabilities): + @property + def avd(self) -> Optional[str]: + """ + Name of Android emulator to run the test on. + """ + return self.get_capability(AVD) + + @avd.setter + def avd(self, value: str) -> None: + """ + The name of Android emulator to run the test on. + Names of currently installed emulators could be listed using + avdmanager list avd command. If the emulator with the given name + is not running then it is going to be started before a test. + """ + self.set_capability(AVD, value) diff --git a/appium/options/android/common/avd/avd_ready_timeout_option.py b/appium/options/android/common/avd/avd_ready_timeout_option.py new file mode 100644 index 000000000..25400f4e5 --- /dev/null +++ b/appium/options/android/common/avd/avd_ready_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AVD_READY_TIMEOUT = 'avdReadyTimeout' + + +class AvdReadyTimeoutOption(SupportsCapabilities): + @property + def avd_ready_timeout(self) -> Optional[timedelta]: + """ + Timeout to wait until Android Emulator is fully booted and is ready for usage. + """ + value = self.get_capability(AVD_READY_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @avd_ready_timeout.setter + def avd_ready_timeout(self, value: Union[timedelta, int]) -> None: + """ + Maximum timeout to wait until Android Emulator is fully booted and is ready for usage. + 60000 ms by default + """ + self.set_capability(AVD_READY_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/android/common/avd/gps_enabled_option.py b/appium/options/android/common/avd/gps_enabled_option.py new file mode 100644 index 000000000..c9b3c60b1 --- /dev/null +++ b/appium/options/android/common/avd/gps_enabled_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +GPS_ENABLED = 'gpsEnabled' + + +class GpsEnabledOption(SupportsCapabilities): + @property + def gps_enabled(self) -> Optional[bool]: + """ + State of the GPS service on emulator. + """ + return self.get_capability(GPS_ENABLED) + + @gps_enabled.setter + def gps_enabled(self, value: bool) -> None: + """ + Set whether to enable (true) or disable (false) GPS service in the Emulator. + Unset by default, which means to not change the current value. + """ + self.set_capability(GPS_ENABLED, value) diff --git a/appium/options/android/common/avd/network_speed_option.py b/appium/options/android/common/avd/network_speed_option.py new file mode 100644 index 000000000..1ab6c1cba --- /dev/null +++ b/appium/options/android/common/avd/network_speed_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +NETWORK_SPEED = 'networkSpeed' + + +class NetworkSpeedOption(SupportsCapabilities): + @property + def network_speed(self) -> Optional[str]: + """ + Desired network speed limit for the emulator. + """ + return self.get_capability(NETWORK_SPEED) + + @network_speed.setter + def network_speed(self, value: str) -> None: + """ + Sets the desired network speed limit for the emulator. + It is only applied if the emulator is not running before + the test starts. See emulator command line arguments description + for more details. + """ + self.set_capability(NETWORK_SPEED, value) diff --git a/appium/options/android/common/context/__init__.py b/appium/options/android/common/context/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/context/auto_webview_timeout_option.py b/appium/options/android/common/context/auto_webview_timeout_option.py new file mode 100644 index 000000000..4fdf4435e --- /dev/null +++ b/appium/options/android/common/context/auto_webview_timeout_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +AUTO_WEBVIEW_TIMEOUT = 'autoWebviewTimeout' + + +class AutoWebviewTimeoutOption(SupportsCapabilities): + @property + def auto_webview_timeout(self) -> Optional[timedelta]: + """ + Set the maximum timeout to wait until a web view is + available if autoWebview capability is set to true. 2000 ms by default. + """ + value = self.get_capability(AUTO_WEBVIEW_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @auto_webview_timeout.setter + def auto_webview_timeout(self, value: Union[timedelta, int]) -> None: + """ + Timeout to wait until a web view is available. + """ + self.set_capability(AUTO_WEBVIEW_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) diff --git a/appium/options/android/common/context/chrome_logging_prefs_option.py b/appium/options/android/common/context/chrome_logging_prefs_option.py new file mode 100644 index 000000000..0c2b69c4d --- /dev/null +++ b/appium/options/android/common/context/chrome_logging_prefs_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROME_LOGGING_PREFS = 'chromeLoggingPrefs' + + +class ChromeLoggingPrefsOption(SupportsCapabilities): + @property + def chrome_logging_prefs(self) -> Optional[Dict[str, Any]]: + """ + Chrome logging preferences. + """ + return self.get_capability(CHROME_LOGGING_PREFS) + + @chrome_logging_prefs.setter + def chrome_logging_prefs(self, value: Dict[str, Any]) -> None: + """ + Chrome logging preferences mapping. Basically the same as + [goog:loggingPrefs](https://newbedev.com/ + getting-console-log-output-from-chrome-with-selenium-python-api-bindings). + It is set to {"browser": "ALL"} by default. + """ + self.set_capability(CHROME_LOGGING_PREFS, value) diff --git a/appium/options/android/common/context/chrome_options_option.py b/appium/options/android/common/context/chrome_options_option.py new file mode 100644 index 000000000..3adb9cebb --- /dev/null +++ b/appium/options/android/common/context/chrome_options_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROME_OPTIONS = 'chromeOptions' + + +class ChromeOptionsOption(SupportsCapabilities): + @property + def chrome_options(self) -> Optional[Dict[str, Any]]: + """ + Chrome options. + """ + return self.get_capability(CHROME_OPTIONS) + + @chrome_options.setter + def chrome_options(self, value: Dict[str, Any]) -> None: + """ + A mapping, that allows to customize chromedriver options. + See https://chromedriver.chromium.org/capabilities for the list + of available entries. + """ + self.set_capability(CHROME_OPTIONS, value) diff --git a/appium/options/android/common/context/chromedriver_args_option.py b/appium/options/android/common/context/chromedriver_args_option.py new file mode 100644 index 000000000..b3633b105 --- /dev/null +++ b/appium/options/android/common/context/chromedriver_args_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import List, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_ARGS = 'chromedriverArgs' + + +class ChromedriverArgsOption(SupportsCapabilities): + @property + def chromedriver_args(self) -> Optional[List[str]]: + """ + Array of chromedriver CLI arguments. + """ + return self.get_capability(CHROMEDRIVER_ARGS) + + @chromedriver_args.setter + def chromedriver_args(self, value: List[str]) -> None: + """ + Array of chromedriver [command line + arguments](http://www.assertselenium.com/java/list-of-chrome-driver-command-line-arguments/). + Note, that not all command line arguments that are available for the desktop + browser are also available for the mobile one. + """ + self.set_capability(CHROMEDRIVER_ARGS, value) diff --git a/appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py b/appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py new file mode 100644 index 000000000..cc84bed1b --- /dev/null +++ b/appium/options/android/common/context/chromedriver_chrome_mapping_file_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_CHROME_MAPPING_FILE = 'chromedriverChromeMappingFile' + + +class ChromedriverChromeMappingFileOption(SupportsCapabilities): + @property + def chromedriver_chrome_mapping_file(self) -> Optional[str]: + """ + Full path to the chromedrivers mapping file is located. + """ + return self.get_capability(CHROMEDRIVER_CHROME_MAPPING_FILE) + + @chromedriver_chrome_mapping_file.setter + def chromedriver_chrome_mapping_file(self, value: str) -> None: + """ + Full path to the chromedrivers mapping file. This file is used to statically + map webview/browser versions to the chromedriver versions that are capable + of automating them. Read [Automatic Chromedriver Discovery](https://github.com/ + appium/appium/blob/master/docs/en/writing-running-appium/web/ + chromedriver.md#automatic-discovery-of-compatible-chromedriver) + article for more details. + """ + self.set_capability(CHROMEDRIVER_CHROME_MAPPING_FILE, value) diff --git a/appium/options/android/common/context/chromedriver_disable_build_check_option.py b/appium/options/android/common/context/chromedriver_disable_build_check_option.py new file mode 100644 index 000000000..16886ab1c --- /dev/null +++ b/appium/options/android/common/context/chromedriver_disable_build_check_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_DISABLE_BUILD_CHECK = 'chromedriverDisableBuildCheck' + + +class ChromedriverDisableBuildCheckOption(SupportsCapabilities): + @property + def chromedriver_disable_build_check(self) -> Optional[bool]: + """ + Whether to disable the compatibility validation between the current + chromedriver and the destination browser/web view. + """ + return self.get_capability(CHROMEDRIVER_DISABLE_BUILD_CHECK) + + @chromedriver_disable_build_check.setter + def chromedriver_disable_build_check(self, value: bool) -> None: + """ + Being set to true disables the compatibility validation between the current + chromedriver and the destination browser/web view. Use it with care. + false by default. + """ + self.set_capability(CHROMEDRIVER_DISABLE_BUILD_CHECK, value) diff --git a/appium/options/android/common/context/chromedriver_executable_dir_option.py b/appium/options/android/common/context/chromedriver_executable_dir_option.py new file mode 100644 index 000000000..64e96c98e --- /dev/null +++ b/appium/options/android/common/context/chromedriver_executable_dir_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_EXECUTABLE_DIR = 'chromedriverExecutableDir' + + +class ChromedriverExecutableDirOption(SupportsCapabilities): + @property + def chromedriver_executable_dir(self) -> Optional[str]: + """ + Full path to the folder where chromedriver executables are located. + """ + return self.get_capability(CHROMEDRIVER_EXECUTABLE_DIR) + + @chromedriver_executable_dir.setter + def chromedriver_executable_dir(self, value: str) -> None: + """ + Full path to the folder where chromedriver executables are located. + This folder is used then to store the downloaded chromedriver executables + if automatic download is enabled. Read [Automatic Chromedriver + Discovery](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/ + web/chromedriver.md#automatic-discovery-of-compatible-chromedriver) + article for more details. + """ + self.set_capability(CHROMEDRIVER_EXECUTABLE_DIR, value) diff --git a/appium/options/android/common/context/chromedriver_executable_option.py b/appium/options/android/common/context/chromedriver_executable_option.py new file mode 100644 index 000000000..79f30ca14 --- /dev/null +++ b/appium/options/android/common/context/chromedriver_executable_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_EXECUTABLE = 'chromedriverExecutable' + + +class ChromedriverExecutableOption(SupportsCapabilities): + @property + def chromedriver_executable(self) -> Optional[str]: + """ + Path to the chromedriver executable on the server file system. + """ + return self.get_capability(CHROMEDRIVER_EXECUTABLE) + + @chromedriver_executable.setter + def chromedriver_executable(self, value: str) -> None: + """ + Full path to the chromedriver executable on the server file system. + """ + self.set_capability(CHROMEDRIVER_EXECUTABLE, value) diff --git a/appium/options/android/common/context/chromedriver_port_option.py b/appium/options/android/common/context/chromedriver_port_option.py new file mode 100644 index 000000000..8a7ef8ab7 --- /dev/null +++ b/appium/options/android/common/context/chromedriver_port_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_PORT = 'chromedriverPort' + + +class ChromedriverPortOption(SupportsCapabilities): + @property + def chromedriver_port(self) -> Optional[int]: + """ + Local port number to use for Chromedriver communication. + """ + return self.get_capability(CHROMEDRIVER_PORT) + + @chromedriver_port.setter + def chromedriver_port(self, value: int) -> None: + """ + The port number to use for Chromedriver communication. + Any free port number is selected by default if unset. + """ + self.set_capability(CHROMEDRIVER_PORT, value) diff --git a/appium/options/android/common/context/chromedriver_ports_option.py b/appium/options/android/common/context/chromedriver_ports_option.py new file mode 100644 index 000000000..5e13c987b --- /dev/null +++ b/appium/options/android/common/context/chromedriver_ports_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import List, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_PORTS = 'chromedriverPorts' + + +class ChromedriverPortsOption(SupportsCapabilities): + @property + def chromedriver_ports(self) -> Optional[List[int]]: + """ + Local port numbers to use for Chromedriver communication. + """ + return self.get_capability(CHROMEDRIVER_PORTS) + + @chromedriver_ports.setter + def chromedriver_ports(self, value: List[int]) -> None: + """ + Array of possible port numbers to assign for Chromedriver communication. + If none of the port in this array is free then a server error is thrown. + """ + self.set_capability(CHROMEDRIVER_PORTS, value) diff --git a/appium/options/android/common/context/chromedriver_use_system_executable_option.py b/appium/options/android/common/context/chromedriver_use_system_executable_option.py new file mode 100644 index 000000000..b164ed13f --- /dev/null +++ b/appium/options/android/common/context/chromedriver_use_system_executable_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +CHROMEDRIVER_USE_SYSTEM_EXECUTABLE = 'chromedriverUseSystemExecutable' + + +class ChromedriverUseSystemExecutableOption(SupportsCapabilities): + @property + def chromedriver_use_system_executable(self) -> Optional[bool]: + """ + Whether to use the system chromedriver. + """ + return self.get_capability(CHROMEDRIVER_USE_SYSTEM_EXECUTABLE) + + @chromedriver_use_system_executable.setter + def chromedriver_use_system_executable(self, value: bool) -> None: + """ + Set it to true in order to enforce the usage of chromedriver, which gets + downloaded by Appium automatically upon installation. This driver might not + be compatible with the destination browser or a web view. false by default. + """ + self.set_capability(CHROMEDRIVER_USE_SYSTEM_EXECUTABLE, value) diff --git a/appium/options/android/common/context/ensure_webviews_have_pages_option.py b/appium/options/android/common/context/ensure_webviews_have_pages_option.py new file mode 100644 index 000000000..d1f549a9f --- /dev/null +++ b/appium/options/android/common/context/ensure_webviews_have_pages_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ENSURE_WEBVIEWS_HAVE_PAGES = 'ensureWebviewsHavePages' + + +class EnsureWebviewsHavePagesOption(SupportsCapabilities): + @property + def ensure_webviews_have_pages(self) -> Optional[bool]: + """ + Whether to ensure if web views have pages. + """ + return self.get_capability(ENSURE_WEBVIEWS_HAVE_PAGES) + + @ensure_webviews_have_pages.setter + def ensure_webviews_have_pages(self, value: bool) -> None: + """ + Whether to skip web views that have no pages from being shown in getContexts + output. The driver uses devtools connection to retrieve the information about + existing pages. true by default since Appium 1.19.0, false if lower than 1.19.0. + """ + self.set_capability(ENSURE_WEBVIEWS_HAVE_PAGES, value) diff --git a/appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py b/appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py new file mode 100644 index 000000000..95559072f --- /dev/null +++ b/appium/options/android/common/context/extract_chrome_android_package_from_context_name_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +EXTRACT_CHROME_ANDROID_PACKAGE_FROM_CONTEXT_NAME = 'extractChromeAndroidPackageFromContextName' + + +class ExtractChromeAndroidPackageFromContextNameOption(SupportsCapabilities): + @property + def extract_chrome_android_package_from_context_name(self) -> Optional[bool]: + """ + Whether to use the android package identifier associated with the context name. + """ + return self.get_capability(EXTRACT_CHROME_ANDROID_PACKAGE_FROM_CONTEXT_NAME) + + @extract_chrome_android_package_from_context_name.setter + def extract_chrome_android_package_from_context_name(self, value: bool) -> None: + """ + If set to true, tell chromedriver to attach to the android package we have associated + with the context name, rather than the package of the application under test. + false by default. + """ + self.set_capability(EXTRACT_CHROME_ANDROID_PACKAGE_FROM_CONTEXT_NAME, value) diff --git a/appium/options/android/common/context/native_web_screenshot_option.py b/appium/options/android/common/context/native_web_screenshot_option.py new file mode 100644 index 000000000..1c59a60b5 --- /dev/null +++ b/appium/options/android/common/context/native_web_screenshot_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +NATIVE_WEB_SCREENSHOT = 'nativeWebScreenshot' + + +class NativeWebScreenshotOption(SupportsCapabilities): + @property + def native_web_screenshot(self) -> Optional[bool]: + """ + Whether to use native screenshots in web view context. + """ + return self.get_capability(NATIVE_WEB_SCREENSHOT) + + @native_web_screenshot.setter + def native_web_screenshot(self, value: bool) -> None: + """ + Whether to use screenshoting endpoint provided by UiAutomator framework (true) + rather than the one provided by chromedriver (false, the default value). + Use it when you experience issues with the latter. + """ + self.set_capability(NATIVE_WEB_SCREENSHOT, value) diff --git a/appium/options/android/common/context/recreate_chrome_driver_sessions_option.py b/appium/options/android/common/context/recreate_chrome_driver_sessions_option.py new file mode 100644 index 000000000..4ef8d1096 --- /dev/null +++ b/appium/options/android/common/context/recreate_chrome_driver_sessions_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +RECREATE_CHROME_DRIVER_SESSIONS = 'recreateChromeDriverSessions' + + +class RecreateChromeDriverSessionsOption(SupportsCapabilities): + @property + def recreate_chrome_driver_sessions(self) -> Optional[bool]: + """ + Whether chromedriver sessions should be killed and then recreated instead + of just suspending it on context switch. + """ + return self.get_capability(RECREATE_CHROME_DRIVER_SESSIONS) + + @recreate_chrome_driver_sessions.setter + def recreate_chrome_driver_sessions(self, value: bool) -> None: + """ + If this capability is set to true then chromedriver session is always going + to be killed and then recreated instead of just suspending it on context + switching. false by default. + """ + self.set_capability(RECREATE_CHROME_DRIVER_SESSIONS, value) diff --git a/appium/options/android/common/context/show_chromedriver_log_option.py b/appium/options/android/common/context/show_chromedriver_log_option.py new file mode 100644 index 000000000..da4f283a4 --- /dev/null +++ b/appium/options/android/common/context/show_chromedriver_log_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOW_CHROMEDRIVER_LOG = 'showChromedriverLog' + + +class ShowChromedriverLogOption(SupportsCapabilities): + @property + def show_chromedriver_log(self) -> Optional[bool]: + """ + Whether to forward chromedriver output to the Appium server log. + """ + return self.get_capability(SHOW_CHROMEDRIVER_LOG) + + @show_chromedriver_log.setter + def show_chromedriver_log(self, value: bool) -> None: + """ + If set to true then all the output from chromedriver binary will be + forwarded to the Appium server log. false by default. + """ + self.set_capability(SHOW_CHROMEDRIVER_LOG, value) diff --git a/appium/options/android/common/context/webview_devtools_port_option.py b/appium/options/android/common/context/webview_devtools_port_option.py new file mode 100644 index 000000000..2f0e6d7a7 --- /dev/null +++ b/appium/options/android/common/context/webview_devtools_port_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +WEBVIEW_DEVTOOLS_PORT = 'webviewDevtoolsPort' + + +class WebviewDevtoolsPortOption(SupportsCapabilities): + @property + def webview_devtools_port(self) -> Optional[int]: + """ + Local port number to use for devtools communication. + """ + return self.get_capability(WEBVIEW_DEVTOOLS_PORT) + + @webview_devtools_port.setter + def webview_devtools_port(self, value: int) -> None: + """ + The local port number to use for devtools communication. By default, the first + free port from 10900..11000 range is selected. Consider setting the custom + value if you are running parallel tests. + """ + self.set_capability(WEBVIEW_DEVTOOLS_PORT, value) diff --git a/appium/options/android/common/localization/__init__.py b/appium/options/android/common/localization/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/localization/locale_script_option.py b/appium/options/android/common/localization/locale_script_option.py new file mode 100644 index 000000000..abcd08529 --- /dev/null +++ b/appium/options/android/common/localization/locale_script_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +LOCALE_SCRIPT = 'localeScript' + + +class LocaleScriptOption(SupportsCapabilities): + @property + def locale_script(self) -> Optional[str]: + """ + Canonical name of the locale to be set for the app under test. + """ + return self.get_capability(LOCALE_SCRIPT) + + @locale_script.setter + def locale_script(self, value: str) -> None: + """ + Set canonical name of the locale to be set for the app under test, + for example zh-Hans-CN. + See https://developer.android.com/reference/java/util/Locale.html for more details. + """ + self.set_capability(LOCALE_SCRIPT, value) diff --git a/appium/options/android/common/locking/__init__.py b/appium/options/android/common/locking/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/locking/skip_unlock_option.py b/appium/options/android/common/locking/skip_unlock_option.py new file mode 100644 index 000000000..c80a6efa7 --- /dev/null +++ b/appium/options/android/common/locking/skip_unlock_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SKIP_UNLOCK = 'skipUnlock' + + +class SkipUnlockOption(SupportsCapabilities): + @property + def skip_unlock(self) -> Optional[bool]: + """ + Whether to skip the check for lock screen presence. + """ + return self.get_capability(SKIP_UNLOCK) + + @skip_unlock.setter + def skip_unlock(self, value: bool) -> None: + """ + Whether to skip the check for lock screen presence (true). By default, + the driver tries to detect if the device's screen is locked + before starting the test and to unlock that (which sometimes might be unstable). + Note, that this operation takes some time, so it is highly recommended to set + this capability to true and disable screen locking on devices under test. + """ + self.set_capability(SKIP_UNLOCK, value) diff --git a/appium/options/android/common/locking/unlock_key_option.py b/appium/options/android/common/locking/unlock_key_option.py new file mode 100644 index 000000000..ffee5a1a5 --- /dev/null +++ b/appium/options/android/common/locking/unlock_key_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UNLOCK_KEY = 'unlockKey' + + +class UnlockKeyOption(SupportsCapabilities): + @property + def unlock_key(self) -> Optional[str]: + """ + Unlock key. + """ + return self.get_capability(UNLOCK_KEY) + + @unlock_key.setter + def unlock_key(self, value: str) -> None: + """ + Allows to set an unlock key. + Read [Unlock tutorial](https://github.com/appium/appium-android-driver/blob/master/docs/UNLOCK.md) + for more details. + """ + self.set_capability(UNLOCK_KEY, value) diff --git a/appium/options/android/common/locking/unlock_strategy_option.py b/appium/options/android/common/locking/unlock_strategy_option.py new file mode 100644 index 000000000..fb884518d --- /dev/null +++ b/appium/options/android/common/locking/unlock_strategy_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UNLOCK_STRATEGY = 'unlockStrategy' + + +class UnlockStrategyOption(SupportsCapabilities): + @property + def unlock_strategy(self) -> Optional[str]: + """ + Unlock strategy name. + """ + return self.get_capability(UNLOCK_STRATEGY) + + @unlock_strategy.setter + def unlock_strategy(self, value: str) -> None: + """ + Either 'locksettings' (default) or 'uiautomator'. + Setting it to 'uiautomator' will enforce the driver to avoid using special + ADB shortcuts in order to speed up the unlock procedure. + """ + self.set_capability(UNLOCK_STRATEGY, value) diff --git a/appium/options/android/common/locking/unlock_success_timeout_option.py b/appium/options/android/common/locking/unlock_success_timeout_option.py new file mode 100644 index 000000000..50f4a569b --- /dev/null +++ b/appium/options/android/common/locking/unlock_success_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UNLOCK_SUCCESS_TIMEOUT = 'unlockSuccessTimeout' + + +class UnlockSuccessTimeoutOption(SupportsCapabilities): + @property + def unlock_success_timeout(self) -> Optional[timedelta]: + """ + Timeout to wait until the device is unlocked. + """ + value = self.get_capability(UNLOCK_SUCCESS_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @unlock_success_timeout.setter + def unlock_success_timeout(self, value: Union[timedelta, int]) -> None: + """ + Maximum timeout to wait until the device is unlocked. + 2000 ms by default. + """ + self.set_capability( + UNLOCK_SUCCESS_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/locking/unlock_type_option.py b/appium/options/android/common/locking/unlock_type_option.py new file mode 100644 index 000000000..7026cf343 --- /dev/null +++ b/appium/options/android/common/locking/unlock_type_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UNLOCK_TYPE = 'unlockType' + + +class UnlockTypeOption(SupportsCapabilities): + @property + def unlock_type(self) -> Optional[str]: + """ + Unlock type. + """ + return self.get_capability(UNLOCK_TYPE) + + @unlock_type.setter + def unlock_type(self, value: str) -> None: + """ + Set one of the possible types of Android lock screens to unlock. + Read the [Unlock tutorial](https://github.com/appium/appium-android-driver/blob/master/docs/UNLOCK.md) + for more details. + """ + self.set_capability(UNLOCK_TYPE, value) diff --git a/appium/options/android/common/mjpeg/__init__.py b/appium/options/android/common/mjpeg/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py b/appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py new file mode 100644 index 000000000..70bad0177 --- /dev/null +++ b/appium/options/android/common/mjpeg/mjpeg_screenshot_url_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MJPEG_SCREENSHOT_URL = 'mjpegScreenshotUrl' + + +class MjpegScreenshotUrlOption(SupportsCapabilities): + @property + def mjpeg_screenshot_url(self) -> Optional[str]: + """ + URL of a service that provides realtime device screenshots in MJPEG format. + """ + return self.get_capability(MJPEG_SCREENSHOT_URL) + + @mjpeg_screenshot_url.setter + def mjpeg_screenshot_url(self, value: str) -> None: + """ + The URL of a service that provides realtime device screenshots in MJPEG format. + If provided then the actual command to retrieve a screenshot will be + requesting pictures from this service rather than directly from the server. + """ + self.set_capability(MJPEG_SCREENSHOT_URL, value) diff --git a/appium/options/android/common/other/__init__.py b/appium/options/android/common/other/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/other/disable_suppress_accessibility_service_option.py b/appium/options/android/common/other/disable_suppress_accessibility_service_option.py new file mode 100644 index 000000000..980da567c --- /dev/null +++ b/appium/options/android/common/other/disable_suppress_accessibility_service_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DISABLE_SUPPRESS_ACCESSIBILITY_SERVICE = 'disableSuppressAccessibilityService' + + +class DisableSuppressAccessibilityServiceOption(SupportsCapabilities): + @property + def disable_suppress_accessibility_service(self) -> Optional[bool]: + """ + Whether to suppress accessibility services. + """ + return self.get_capability(DISABLE_SUPPRESS_ACCESSIBILITY_SERVICE) + + @disable_suppress_accessibility_service.setter + def disable_suppress_accessibility_service(self, value: bool) -> None: + """ + Being set to true tells the instrumentation process to not suppress + accessibility services during the automated test. This might be useful + if your automated test needs these services. false by default. + """ + self.set_capability(DISABLE_SUPPRESS_ACCESSIBILITY_SERVICE, value) diff --git a/appium/options/android/common/other/user_profile_option.py b/appium/options/android/common/other/user_profile_option.py new file mode 100644 index 000000000..76871396a --- /dev/null +++ b/appium/options/android/common/other/user_profile_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USER_PROFILE = 'userProfile' + + +class UserProfileOption(SupportsCapabilities): + @property + def user_profile(self) -> Optional[int]: + """ + Integer identifier of a user profile. + """ + return self.get_capability(USER_PROFILE) + + @user_profile.setter + def user_profile(self, value: int) -> None: + """ + Integer identifier of a user profile. By default, the app under test is + installed for the currently active user, but in case it is necessary to + test how the app performs while being installed for a user profile, + which is different from the current one, this capability might + come in handy. + """ + self.set_capability(USER_PROFILE, value) diff --git a/appium/options/android/common/signing/__init__.py b/appium/options/android/common/signing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appium/options/android/common/signing/key_alias_option.py b/appium/options/android/common/signing/key_alias_option.py new file mode 100644 index 000000000..471b28ee8 --- /dev/null +++ b/appium/options/android/common/signing/key_alias_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEY_ALIAS = 'keyAlias' + + +class KeyAliasOption(SupportsCapabilities): + @property + def key_alias(self) -> Optional[str]: + """ + Keystore key alias. + """ + return self.get_capability(KEY_ALIAS) + + @key_alias.setter + def key_alias(self, value: str) -> None: + """ + The alias of the key in the keystore file provided in keystorePath capability. + This option is used in combination with useKeystore, keystorePath, + keystorePassword, keyAlias and keyPassword options. Unset by default + """ + self.set_capability(KEY_ALIAS, value) diff --git a/appium/options/android/common/signing/key_password_option.py b/appium/options/android/common/signing/key_password_option.py new file mode 100644 index 000000000..5ed91d1c1 --- /dev/null +++ b/appium/options/android/common/signing/key_password_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEY_PASSWORD = 'keyPassword' + + +class KeyPasswordOption(SupportsCapabilities): + @property + def key_password(self) -> Optional[str]: + """ + Keystore key password. + """ + return self.get_capability(KEY_PASSWORD) + + @key_password.setter + def key_password(self, value: str) -> None: + """ + The password of the key in the keystore file provided in keystorePath capability. + This option is used in combination with useKeystore, keystorePath, + keystorePassword, keyAlias and keyPassword options. Unset by default + """ + self.set_capability(KEY_PASSWORD, value) diff --git a/appium/options/android/common/signing/keystore_password_option.py b/appium/options/android/common/signing/keystore_password_option.py new file mode 100644 index 000000000..8b7667117 --- /dev/null +++ b/appium/options/android/common/signing/keystore_password_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEYSTORE_PASSWORD = 'keystorePassword' + + +class KeystorePasswordOption(SupportsCapabilities): + @property + def keystore_password(self) -> Optional[str]: + """ + Keystore password. + """ + return self.get_capability(KEYSTORE_PASSWORD) + + @keystore_password.setter + def keystore_password(self, value: str) -> None: + """ + The password to the keystore file provided in keystorePath capability. + This option is used in combination with useKeystore, keystorePath, + keystorePassword, keyAlias and keyPassword options. Unset by default + """ + self.set_capability(KEYSTORE_PASSWORD, value) diff --git a/appium/options/android/common/signing/keystore_path_option.py b/appium/options/android/common/signing/keystore_path_option.py new file mode 100644 index 000000000..3489efe5c --- /dev/null +++ b/appium/options/android/common/signing/keystore_path_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +KEYSTORE_PATH = 'keystorePath' + + +class KeystorePathOption(SupportsCapabilities): + @property + def keystore_path(self) -> Optional[str]: + """ + The path to keystore. + """ + return self.get_capability(KEYSTORE_PATH) + + @keystore_path.setter + def keystore_path(self, value: str) -> None: + """ + The full path to the keystore file on the server filesystem. + This option is used in combination with useKeystore, keystorePath, + keystorePassword, keyAlias and keyPassword options. Unset by default + """ + self.set_capability(KEYSTORE_PATH, value) diff --git a/appium/options/android/common/signing/no_sign_option.py b/appium/options/android/common/signing/no_sign_option.py new file mode 100644 index 000000000..a5a52841b --- /dev/null +++ b/appium/options/android/common/signing/no_sign_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +NO_SIGN = 'noSign' + + +class NoSignOption(SupportsCapabilities): + @property + def no_sign(self) -> Optional[bool]: + """ + Whether to skip application signing. + """ + return self.get_capability(NO_SIGN) + + @no_sign.setter + def no_sign(self, value: bool) -> None: + """ + Set it to true in order to skip application signing. By default + all apps are always signed with the default Appium debug signature + if they don't have any. This capability cancels all the signing checks + and makes the driver to use the application package as is. This option + does not affect .apks packages as these are expected to be already signed. + """ + self.set_capability(NO_SIGN, value) diff --git a/appium/options/android/common/signing/use_keystore_option.py b/appium/options/android/common/signing/use_keystore_option.py new file mode 100644 index 000000000..f8a947a5c --- /dev/null +++ b/appium/options/android/common/signing/use_keystore_option.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +USE_KEYSTORE = 'useKeystore' + + +class UseKeystoreOption(SupportsCapabilities): + @property + def use_keystore(self) -> Optional[bool]: + """ + Whether to use custom keystore. + """ + return self.get_capability(USE_KEYSTORE) + + @use_keystore.setter + def use_keystore(self, value: bool) -> None: + """ + Whether to use a custom keystore to sign the app under test. + false by default, which means apps are always signed with the default A + ppium debug certificate (unless canceled by noSign capability). + This option is used in combination with keystorePath, keystorePassword, + keyAlias and keyPassword options. + """ + self.set_capability(USE_KEYSTORE, value) diff --git a/appium/options/android/espresso/activity_options_option.py b/appium/options/android/espresso/activity_options_option.py new file mode 100644 index 000000000..a15935feb --- /dev/null +++ b/appium/options/android/espresso/activity_options_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ACTIVITY_OPTIONS = 'activityOptions' + + +class ActivityOptionsOption(SupportsCapabilities): + @property + def activity_options(self) -> Optional[Dict]: + """ + Activity options. + """ + return self.get_capability(ACTIVITY_OPTIONS) + + @activity_options.setter + def activity_options(self, value: Dict) -> None: + """ + The mapping of custom options for the main app activity that is going to + be started. Check + https://github.com/appium/appium-espresso-driver#activity-options + for more details. + """ + self.set_capability(ACTIVITY_OPTIONS, value) diff --git a/appium/options/android/espresso/app_locale_option.py b/appium/options/android/espresso/app_locale_option.py new file mode 100644 index 000000000..01ae27545 --- /dev/null +++ b/appium/options/android/espresso/app_locale_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_LOCALE = 'appLocale' + + +class AppLocaleOption(SupportsCapabilities): + @property + def app_locale(self) -> Optional[Dict[str, str]]: + """ + Locale for the app under test. + """ + return self.get_capability(APP_LOCALE) + + @app_locale.setter + def app_locale(self, value: Dict[str, str]) -> None: + """ + Sets the locale for the app under test. The main difference between this option + and the above ones is that this option only changes the locale for the application + under test and does not affect other parts of the system. Also, it only uses + public APIs for its purpose. See + https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers to get the + list of available language abbreviations. + Example: {"language": "zh", "country": "CN", "variant": "Hans"}. + """ + self.set_capability(APP_LOCALE, value) diff --git a/appium/options/android/espresso/base.py b/appium/options/android/espresso/base.py index 34ce50c22..0baaec1b5 100644 --- a/appium/options/android/espresso/base.py +++ b/appium/options/android/espresso/base.py @@ -17,18 +17,109 @@ from typing import Dict +from appium.options.android.common.adb.adb_exec_timeout_option import AdbExecTimeoutOption +from appium.options.android.common.adb.adb_port_option import AdbPortOption +from appium.options.android.common.adb.allow_delay_adb_option import AllowDelayAdbOption +from appium.options.android.common.adb.build_tools_version_option import BuildToolsVersionOption +from appium.options.android.common.adb.clear_device_logs_on_start_option import ClearDeviceLogsOnStartOption +from appium.options.android.common.adb.ignore_hidden_api_policy_error_option import IgnoreHiddenApiPolicyErrorOption +from appium.options.android.common.adb.logcat_filter_specs_option import LogcatFilterSpecsOption +from appium.options.android.common.adb.logcat_format_option import LogcatFormatOption +from appium.options.android.common.adb.mock_location_app_option import MockLocationAppOption +from appium.options.android.common.adb.remote_adb_host_option import RemoteAdbHostOption +from appium.options.android.common.adb.skip_logcat_capture_option import SkipLogcatCaptureOption +from appium.options.android.common.adb.suppress_kill_server_option import SuppressKillServerOption +from appium.options.android.common.app.allow_test_packages_option import AllowTestPackagesOption +from appium.options.android.common.app.android_install_timeout_option import AndroidInstallTimeoutOption +from appium.options.android.common.app.app_activity_option import AppActivityOption +from appium.options.android.common.app.app_package_option import AppPackageOption +from appium.options.android.common.app.app_wait_activity_option import AppWaitActivityOption +from appium.options.android.common.app.app_wait_duration_option import AppWaitDurationOption +from appium.options.android.common.app.app_wait_for_launch_option import AppWaitForLaunchOption +from appium.options.android.common.app.app_wait_package_option import AppWaitPackageOption +from appium.options.android.common.app.auto_grant_premissions_option import AutoGrantPermissionsOption +from appium.options.android.common.app.enforce_app_install_option import EnforceAppInstallOption +from appium.options.android.common.app.intent_action_option import IntentActionOption +from appium.options.android.common.app.intent_category_option import IntentCategoryOption +from appium.options.android.common.app.intent_flags_option import IntentFlagsOption +from appium.options.android.common.app.optional_intent_arguments_option import OptionalIntentArgumentsOption +from appium.options.android.common.app.remote_apps_cache_limit_option import RemoteAppsCacheLimitOption +from appium.options.android.common.app.uninstall_other_packages_option import UninstallOtherPackagesOption +from appium.options.android.common.avd.avd_args_option import AvdArgsOption +from appium.options.android.common.avd.avd_env_option import AvdEnvOption +from appium.options.android.common.avd.avd_launch_timeout_option import AvdLaunchTimeoutOption +from appium.options.android.common.avd.avd_option import AvdOption +from appium.options.android.common.avd.avd_ready_timeout_option import AvdReadyTimeoutOption +from appium.options.android.common.avd.gps_enabled_option import GpsEnabledOption +from appium.options.android.common.avd.network_speed_option import NetworkSpeedOption +from appium.options.android.common.context.auto_webview_timeout_option import AutoWebviewTimeoutOption +from appium.options.android.common.context.chrome_logging_prefs_option import ChromeLoggingPrefsOption +from appium.options.android.common.context.chrome_options_option import ChromeOptionsOption +from appium.options.android.common.context.chromedriver_args_option import ChromedriverArgsOption +from appium.options.android.common.context.chromedriver_chrome_mapping_file_option import ( + ChromedriverChromeMappingFileOption, +) +from appium.options.android.common.context.chromedriver_disable_build_check_option import ( + ChromedriverDisableBuildCheckOption, +) +from appium.options.android.common.context.chromedriver_executable_dir_option import ChromedriverExecutableDirOption +from appium.options.android.common.context.chromedriver_executable_option import ChromedriverExecutableOption +from appium.options.android.common.context.chromedriver_port_option import ChromedriverPortOption +from appium.options.android.common.context.chromedriver_ports_option import ChromedriverPortsOption +from appium.options.android.common.context.chromedriver_use_system_executable_option import ( + ChromedriverUseSystemExecutableOption, +) +from appium.options.android.common.context.ensure_webviews_have_pages_option import EnsureWebviewsHavePagesOption +from appium.options.android.common.context.extract_chrome_android_package_from_context_name_option import ( + ExtractChromeAndroidPackageFromContextNameOption, +) +from appium.options.android.common.context.native_web_screenshot_option import NativeWebScreenshotOption +from appium.options.android.common.context.recreate_chrome_driver_sessions_option import ( + RecreateChromeDriverSessionsOption, +) +from appium.options.android.common.context.show_chromedriver_log_option import ShowChromedriverLogOption +from appium.options.android.common.context.webview_devtools_port_option import WebviewDevtoolsPortOption +from appium.options.android.common.localization.locale_script_option import LocaleScriptOption +from appium.options.android.common.locking.skip_unlock_option import SkipUnlockOption +from appium.options.android.common.locking.unlock_key_option import UnlockKeyOption +from appium.options.android.common.locking.unlock_strategy_option import UnlockStrategyOption +from appium.options.android.common.locking.unlock_success_timeout_option import UnlockSuccessTimeoutOption +from appium.options.android.common.locking.unlock_type_option import UnlockTypeOption +from appium.options.android.common.mjpeg.mjpeg_screenshot_url_option import MjpegScreenshotUrlOption +from appium.options.android.common.other.disable_suppress_accessibility_service_option import ( + DisableSuppressAccessibilityServiceOption, +) +from appium.options.android.common.other.user_profile_option import UserProfileOption +from appium.options.android.common.signing.key_alias_option import KeyAliasOption +from appium.options.android.common.signing.key_password_option import KeyPasswordOption +from appium.options.android.common.signing.keystore_password_option import KeystorePasswordOption +from appium.options.android.common.signing.keystore_path_option import KeystorePathOption +from appium.options.android.common.signing.no_sign_option import NoSignOption +from appium.options.android.common.signing.use_keystore_option import UseKeystoreOption from appium.options.common.app_option import AppOption from appium.options.common.auto_web_view_option import AutoWebViewOption from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.clear_system_files_option import ClearSystemFilesOption from appium.options.common.device_name_option import DeviceNameOption +from appium.options.common.enable_performance_logging_option import EnablePerformanceLoggingOption from appium.options.common.is_headless_option import IsHeadlessOption from appium.options.common.language_option import LanguageOption from appium.options.common.locale_option import LocaleOption from appium.options.common.orientation_option import OrientationOption +from appium.options.common.other_apps_option import OtherAppsOption from appium.options.common.skip_log_capture_option import SkipLogCaptureOption +from appium.options.common.system_port_option import SystemPortOption from appium.options.common.udid_option import UdidOption +from .activity_options_option import ActivityOptionsOption +from .app_locale_option import AppLocaleOption +from .espresso_build_config_option import EspressoBuildConfigOption +from .espresso_server_launch_timeout_option import EspressoServerLaunchTimeoutOption +from .force_espresso_rebuild_option import ForceEspressoRebuildOption +from .intent_options_option import IntentOptionsOption +from .show_gradle_log_option import ShowGradleLogOption + class EspressoOptions( AppiumOptions, @@ -41,6 +132,84 @@ class EspressoOptions( SkipLogCaptureOption, AutoWebViewOption, DeviceNameOption, + ClearSystemFilesOption, + EnablePerformanceLoggingOption, + OtherAppsOption, + SystemPortOption, + AppPackageOption, + AppActivityOption, + AppWaitActivityOption, + AppWaitPackageOption, + AppWaitDurationOption, + AndroidInstallTimeoutOption, + AppWaitForLaunchOption, + IntentCategoryOption, + IntentActionOption, + IntentFlagsOption, + OptionalIntentArgumentsOption, + AutoGrantPermissionsOption, + UninstallOtherPackagesOption, + AllowTestPackagesOption, + RemoteAppsCacheLimitOption, + EnforceAppInstallOption, + LocaleScriptOption, + AdbPortOption, + RemoteAdbHostOption, + AdbExecTimeoutOption, + ClearDeviceLogsOnStartOption, + BuildToolsVersionOption, + SkipLogcatCaptureOption, + SuppressKillServerOption, + IgnoreHiddenApiPolicyErrorOption, + MockLocationAppOption, + LogcatFormatOption, + LogcatFilterSpecsOption, + AllowDelayAdbOption, + AvdOption, + AvdLaunchTimeoutOption, + AvdReadyTimeoutOption, + AvdArgsOption, + AvdEnvOption, + NetworkSpeedOption, + GpsEnabledOption, + UseKeystoreOption, + KeystorePathOption, + KeystorePasswordOption, + KeyAliasOption, + KeyPasswordOption, + NoSignOption, + SkipUnlockOption, + UnlockKeyOption, + UnlockStrategyOption, + UnlockSuccessTimeoutOption, + UnlockTypeOption, + MjpegScreenshotUrlOption, + AutoWebviewTimeoutOption, + ChromeLoggingPrefsOption, + ChromeOptionsOption, + ChromedriverArgsOption, + ChromedriverChromeMappingFileOption, + ChromedriverDisableBuildCheckOption, + ChromedriverExecutableDirOption, + ChromedriverExecutableOption, + ChromedriverPortOption, + ChromedriverPortsOption, + ChromedriverUseSystemExecutableOption, + EnsureWebviewsHavePagesOption, + ExtractChromeAndroidPackageFromContextNameOption, + NativeWebScreenshotOption, + RecreateChromeDriverSessionsOption, + ShowChromedriverLogOption, + WebviewDevtoolsPortOption, + DisableSuppressAccessibilityServiceOption, + UserProfileOption, + EspressoBuildConfigOption, + EspressoServerLaunchTimeoutOption, + ForceEspressoRebuildOption, + ShowGradleLogOption, + IntentOptionsOption, + ActivityOptionsOption, + AppLocaleOption, ): @property def default_capabilities(self) -> Dict: diff --git a/appium/options/android/espresso/espresso_build_config_option.py b/appium/options/android/espresso/espresso_build_config_option.py new file mode 100644 index 000000000..41586a993 --- /dev/null +++ b/appium/options/android/espresso/espresso_build_config_option.py @@ -0,0 +1,48 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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 +from typing import Any, Dict, Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ESPRESSO_BUILD_CONFIG = 'espressoBuildConfig' + + +class EspressoBuildConfigOption(SupportsCapabilities): + @property + def espresso_build_config(self) -> Optional[Union[Dict[str, Any], str]]: + """ + Espresso build config. + """ + value = self.get_capability(ESPRESSO_BUILD_CONFIG) + try: + return json.loads(value) + except Exception: + return value + + @espresso_build_config.setter + def espresso_build_config(self, value: Union[Dict[str, Any], str]) -> None: + """ + This config allows to customize several important properties of + Espresso server. Refer to + https://github.com/appium/appium-espresso-driver#espresso-build-config + for more information on how to properly construct such config. + """ + self.set_capability( + ESPRESSO_BUILD_CONFIG, value if isinstance(value, str) else json.dumps(value, ensure_ascii=False) + ) diff --git a/appium/options/android/espresso/espresso_server_launch_timeout_option.py b/appium/options/android/espresso/espresso_server_launch_timeout_option.py new file mode 100644 index 000000000..acacf57d2 --- /dev/null +++ b/appium/options/android/espresso/espresso_server_launch_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +ESPRESSO_SERVER_LAUNCH_TIMEOUT = 'espressoServerLaunchTimeout' + + +class EspressoServerLaunchTimeoutOption(SupportsCapabilities): + @property + def espresso_server_launch_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until Espresso server is listening on the device. + """ + value = self.get_capability(ESPRESSO_SERVER_LAUNCH_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @espresso_server_launch_timeout.setter + def espresso_server_launch_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the maximum timeout to wait util Espresso is listening on the device. + 45000 ms by default + """ + self.set_capability( + ESPRESSO_SERVER_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/espresso/force_espresso_rebuild_option.py b/appium/options/android/espresso/force_espresso_rebuild_option.py new file mode 100644 index 000000000..8971db795 --- /dev/null +++ b/appium/options/android/espresso/force_espresso_rebuild_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +FORCE_ESPRESSO_REBUILD = 'forceEspressoRebuild' + + +class ForceEspressoRebuildOption(SupportsCapabilities): + @property + def force_espresso_rebuild(self) -> Optional[bool]: + """ + Whether to force Espresso server rebuild on a new session startup. + """ + return self.get_capability(FORCE_ESPRESSO_REBUILD) + + @force_espresso_rebuild.setter + def force_espresso_rebuild(self, value: bool) -> None: + """ + Whether to always enforce Espresso server rebuild (true). + By default, Espresso caches the already built server apk and only rebuilds + it when it is necessary, because rebuilding process needs extra time. + false by default. + """ + self.set_capability(FORCE_ESPRESSO_REBUILD, value) diff --git a/appium/options/android/espresso/intent_options_option.py b/appium/options/android/espresso/intent_options_option.py new file mode 100644 index 000000000..59625bd91 --- /dev/null +++ b/appium/options/android/espresso/intent_options_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Any, Dict, Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +INTENT_OPTIONS = 'intentOptions' + + +class IntentOptionsOption(SupportsCapabilities): + @property + def intent_options(self) -> Optional[Dict[str, Any]]: + """ + Intent options. + """ + return self.get_capability(INTENT_OPTIONS) + + @intent_options.setter + def intent_options(self, value: Dict[str, Any]) -> None: + """ + The mapping of custom options for the intent that is going to be passed + to the main app activity. Check + https://github.com/appium/appium-espresso-driver#intent-options + for more details. + """ + self.set_capability(INTENT_OPTIONS, value) diff --git a/appium/options/android/espresso/show_gradle_log_option.py b/appium/options/android/espresso/show_gradle_log_option.py new file mode 100644 index 000000000..48d233dbf --- /dev/null +++ b/appium/options/android/espresso/show_gradle_log_option.py @@ -0,0 +1,39 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SHOW_GRADLE_LOG = 'showGradleLog' + + +class ShowGradleLogOption(SupportsCapabilities): + @property + def show_gradle_log(self) -> Optional[bool]: + """ + Whether to include Gradle log to the regular server log. + """ + return self.get_capability(SHOW_GRADLE_LOG) + + @show_gradle_log.setter + def show_gradle_log(self, value: bool) -> None: + """ + Whether to include Gradle log to the regular server logs while + building Espresso server. false by default. + """ + self.set_capability(SHOW_GRADLE_LOG, value) diff --git a/appium/options/android/uiautomator2/base.py b/appium/options/android/uiautomator2/base.py index 91727876e..f6a01d378 100644 --- a/appium/options/android/uiautomator2/base.py +++ b/appium/options/android/uiautomator2/base.py @@ -17,6 +17,85 @@ from typing import Dict +from appium.options.android.common.adb.adb_exec_timeout_option import AdbExecTimeoutOption +from appium.options.android.common.adb.adb_port_option import AdbPortOption +from appium.options.android.common.adb.allow_delay_adb_option import AllowDelayAdbOption +from appium.options.android.common.adb.build_tools_version_option import BuildToolsVersionOption +from appium.options.android.common.adb.clear_device_logs_on_start_option import ClearDeviceLogsOnStartOption +from appium.options.android.common.adb.ignore_hidden_api_policy_error_option import IgnoreHiddenApiPolicyErrorOption +from appium.options.android.common.adb.logcat_filter_specs_option import LogcatFilterSpecsOption +from appium.options.android.common.adb.logcat_format_option import LogcatFormatOption +from appium.options.android.common.adb.mock_location_app_option import MockLocationAppOption +from appium.options.android.common.adb.remote_adb_host_option import RemoteAdbHostOption +from appium.options.android.common.adb.skip_logcat_capture_option import SkipLogcatCaptureOption +from appium.options.android.common.adb.suppress_kill_server_option import SuppressKillServerOption +from appium.options.android.common.app.allow_test_packages_option import AllowTestPackagesOption +from appium.options.android.common.app.android_install_timeout_option import AndroidInstallTimeoutOption +from appium.options.android.common.app.app_activity_option import AppActivityOption +from appium.options.android.common.app.app_package_option import AppPackageOption +from appium.options.android.common.app.app_wait_activity_option import AppWaitActivityOption +from appium.options.android.common.app.app_wait_duration_option import AppWaitDurationOption +from appium.options.android.common.app.app_wait_for_launch_option import AppWaitForLaunchOption +from appium.options.android.common.app.app_wait_package_option import AppWaitPackageOption +from appium.options.android.common.app.auto_grant_premissions_option import AutoGrantPermissionsOption +from appium.options.android.common.app.enforce_app_install_option import EnforceAppInstallOption +from appium.options.android.common.app.intent_action_option import IntentActionOption +from appium.options.android.common.app.intent_category_option import IntentCategoryOption +from appium.options.android.common.app.intent_flags_option import IntentFlagsOption +from appium.options.android.common.app.optional_intent_arguments_option import OptionalIntentArgumentsOption +from appium.options.android.common.app.remote_apps_cache_limit_option import RemoteAppsCacheLimitOption +from appium.options.android.common.app.uninstall_other_packages_option import UninstallOtherPackagesOption +from appium.options.android.common.avd.avd_args_option import AvdArgsOption +from appium.options.android.common.avd.avd_env_option import AvdEnvOption +from appium.options.android.common.avd.avd_launch_timeout_option import AvdLaunchTimeoutOption +from appium.options.android.common.avd.avd_option import AvdOption +from appium.options.android.common.avd.avd_ready_timeout_option import AvdReadyTimeoutOption +from appium.options.android.common.avd.gps_enabled_option import GpsEnabledOption +from appium.options.android.common.avd.network_speed_option import NetworkSpeedOption +from appium.options.android.common.context.auto_webview_timeout_option import AutoWebviewTimeoutOption +from appium.options.android.common.context.chrome_logging_prefs_option import ChromeLoggingPrefsOption +from appium.options.android.common.context.chrome_options_option import ChromeOptionsOption +from appium.options.android.common.context.chromedriver_args_option import ChromedriverArgsOption +from appium.options.android.common.context.chromedriver_chrome_mapping_file_option import ( + ChromedriverChromeMappingFileOption, +) +from appium.options.android.common.context.chromedriver_disable_build_check_option import ( + ChromedriverDisableBuildCheckOption, +) +from appium.options.android.common.context.chromedriver_executable_dir_option import ChromedriverExecutableDirOption +from appium.options.android.common.context.chromedriver_executable_option import ChromedriverExecutableOption +from appium.options.android.common.context.chromedriver_port_option import ChromedriverPortOption +from appium.options.android.common.context.chromedriver_ports_option import ChromedriverPortsOption +from appium.options.android.common.context.chromedriver_use_system_executable_option import ( + ChromedriverUseSystemExecutableOption, +) +from appium.options.android.common.context.ensure_webviews_have_pages_option import EnsureWebviewsHavePagesOption +from appium.options.android.common.context.extract_chrome_android_package_from_context_name_option import ( + ExtractChromeAndroidPackageFromContextNameOption, +) +from appium.options.android.common.context.native_web_screenshot_option import NativeWebScreenshotOption +from appium.options.android.common.context.recreate_chrome_driver_sessions_option import ( + RecreateChromeDriverSessionsOption, +) +from appium.options.android.common.context.show_chromedriver_log_option import ShowChromedriverLogOption +from appium.options.android.common.context.webview_devtools_port_option import WebviewDevtoolsPortOption +from appium.options.android.common.localization.locale_script_option import LocaleScriptOption +from appium.options.android.common.locking.skip_unlock_option import SkipUnlockOption +from appium.options.android.common.locking.unlock_key_option import UnlockKeyOption +from appium.options.android.common.locking.unlock_strategy_option import UnlockStrategyOption +from appium.options.android.common.locking.unlock_success_timeout_option import UnlockSuccessTimeoutOption +from appium.options.android.common.locking.unlock_type_option import UnlockTypeOption +from appium.options.android.common.mjpeg.mjpeg_screenshot_url_option import MjpegScreenshotUrlOption +from appium.options.android.common.other.disable_suppress_accessibility_service_option import ( + DisableSuppressAccessibilityServiceOption, +) +from appium.options.android.common.other.user_profile_option import UserProfileOption +from appium.options.android.common.signing.key_alias_option import KeyAliasOption +from appium.options.android.common.signing.key_password_option import KeyPasswordOption +from appium.options.android.common.signing.keystore_password_option import KeystorePasswordOption +from appium.options.android.common.signing.keystore_path_option import KeystorePathOption +from appium.options.android.common.signing.no_sign_option import NoSignOption +from appium.options.android.common.signing.use_keystore_option import UseKeystoreOption from appium.options.common.app_option import AppOption from appium.options.common.auto_web_view_option import AutoWebViewOption from appium.options.common.automation_name_option import AUTOMATION_NAME @@ -30,8 +109,17 @@ from appium.options.common.orientation_option import OrientationOption from appium.options.common.other_apps_option import OtherAppsOption from appium.options.common.skip_log_capture_option import SkipLogCaptureOption +from appium.options.common.system_port_option import SystemPortOption from appium.options.common.udid_option import UdidOption +from .disable_window_animation_option import DisableWindowAnimationOption +from .mjpeg_server_port_option import MjpegServerPortOption +from .skip_device_initialization_option import SkipDeviceInitializationOption +from .skip_server_installation_option import SkipServerInstallationOption +from .uiautomator2_server_install_timeout_option import Uiautomator2ServerInstallTimeoutOption +from .uiautomator2_server_launch_timeout_option import Uiautomator2ServerLaunchTimeoutOption +from .uiautomator2_server_read_timeout_option import Uiautomator2ServerReadTimeoutOption + class UiAutomator2Options( AppiumOptions, @@ -47,6 +135,81 @@ class UiAutomator2Options( EnablePerformanceLoggingOption, OtherAppsOption, DeviceNameOption, + SystemPortOption, + SkipServerInstallationOption, + Uiautomator2ServerInstallTimeoutOption, + Uiautomator2ServerLaunchTimeoutOption, + Uiautomator2ServerReadTimeoutOption, + DisableWindowAnimationOption, + SkipDeviceInitializationOption, + AppPackageOption, + AppActivityOption, + AppWaitActivityOption, + AppWaitPackageOption, + AppWaitDurationOption, + AndroidInstallTimeoutOption, + AppWaitForLaunchOption, + IntentCategoryOption, + IntentActionOption, + IntentFlagsOption, + OptionalIntentArgumentsOption, + AutoGrantPermissionsOption, + UninstallOtherPackagesOption, + AllowTestPackagesOption, + RemoteAppsCacheLimitOption, + EnforceAppInstallOption, + LocaleScriptOption, + AdbPortOption, + RemoteAdbHostOption, + AdbExecTimeoutOption, + ClearDeviceLogsOnStartOption, + SkipLogcatCaptureOption, + BuildToolsVersionOption, + SuppressKillServerOption, + IgnoreHiddenApiPolicyErrorOption, + MockLocationAppOption, + LogcatFormatOption, + LogcatFilterSpecsOption, + AllowDelayAdbOption, + AvdOption, + AvdLaunchTimeoutOption, + AvdReadyTimeoutOption, + AvdArgsOption, + AvdEnvOption, + NetworkSpeedOption, + GpsEnabledOption, + UseKeystoreOption, + KeystorePathOption, + KeystorePasswordOption, + KeyAliasOption, + KeyPasswordOption, + NoSignOption, + SkipUnlockOption, + UnlockKeyOption, + UnlockStrategyOption, + UnlockSuccessTimeoutOption, + UnlockTypeOption, + MjpegServerPortOption, + MjpegScreenshotUrlOption, + AutoWebviewTimeoutOption, + ChromeLoggingPrefsOption, + ChromeOptionsOption, + ChromedriverArgsOption, + ChromedriverChromeMappingFileOption, + ChromedriverDisableBuildCheckOption, + ChromedriverExecutableDirOption, + ChromedriverExecutableOption, + ChromedriverPortOption, + ChromedriverPortsOption, + ChromedriverUseSystemExecutableOption, + EnsureWebviewsHavePagesOption, + ExtractChromeAndroidPackageFromContextNameOption, + NativeWebScreenshotOption, + RecreateChromeDriverSessionsOption, + ShowChromedriverLogOption, + WebviewDevtoolsPortOption, + DisableSuppressAccessibilityServiceOption, + UserProfileOption, ): @property def default_capabilities(self) -> Dict: diff --git a/appium/options/android/uiautomator2/disable_window_animation_option.py b/appium/options/android/uiautomator2/disable_window_animation_option.py new file mode 100644 index 000000000..5ff614b49 --- /dev/null +++ b/appium/options/android/uiautomator2/disable_window_animation_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +DISABLE_WINDOWS_ANIMATION = 'disableWindowAnimation' + + +class DisableWindowAnimationOption(SupportsCapabilities): + @property + def disable_window_animation(self) -> Optional[bool]: + """ + Whether window animations when starting the instrumentation process + are disabled. + """ + return self.get_capability(DISABLE_WINDOWS_ANIMATION) + + @disable_window_animation.setter + def disable_window_animation(self, value: bool) -> None: + """ + Set whether to disable window animations when starting the instrumentation process. + false by default + """ + self.set_capability(DISABLE_WINDOWS_ANIMATION, value) diff --git a/appium/options/android/uiautomator2/mjpeg_server_port_option.py b/appium/options/android/uiautomator2/mjpeg_server_port_option.py new file mode 100644 index 000000000..6c702646f --- /dev/null +++ b/appium/options/android/uiautomator2/mjpeg_server_port_option.py @@ -0,0 +1,41 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +MJPEG_SERVER_PORT = 'mjpegServerPort' + + +class MjpegServerPortOption(SupportsCapabilities): + @property + def mjpeg_server_port(self) -> Optional[int]: + """ + Number of the port UiAutomator2 server starts the MJPEG server on. + """ + return self.get_capability(MJPEG_SERVER_PORT) + + @mjpeg_server_port.setter + def mjpeg_server_port(self, value: int) -> None: + """ + The number of the port UiAutomator2 server starts the MJPEG server on. + If not provided then the screenshots broadcasting service on the remote + device does not get exposed to a local port (e.g. no adb port forwarding + is happening). + """ + self.set_capability(MJPEG_SERVER_PORT, value) diff --git a/appium/options/android/uiautomator2/skip_device_initialization_option.py b/appium/options/android/uiautomator2/skip_device_initialization_option.py new file mode 100644 index 000000000..eda37be30 --- /dev/null +++ b/appium/options/android/uiautomator2/skip_device_initialization_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SKIP_DEVICE_INITIALIZATION = 'skipDeviceInitialization' + + +class SkipDeviceInitializationOption(SupportsCapabilities): + @property + def skip_device_initialization(self) -> Optional[bool]: + """ + Whether initial device startup checks by the server are disabled. + """ + return self.get_capability(SKIP_DEVICE_INITIALIZATION) + + @skip_device_initialization.setter + def skip_device_initialization(self, value: bool) -> None: + """ + If set to true then device startup checks (whether it is ready and whether + Settings app is installed) will be canceled on session creation. + Could speed up the session creation if you know what you are doing. false by default + """ + self.set_capability(SKIP_DEVICE_INITIALIZATION, value) diff --git a/appium/options/android/uiautomator2/skip_server_installation_option.py b/appium/options/android/uiautomator2/skip_server_installation_option.py new file mode 100644 index 000000000..c343f78f1 --- /dev/null +++ b/appium/options/android/uiautomator2/skip_server_installation_option.py @@ -0,0 +1,44 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +SKIP_SERVER_INSTALLATION = 'skipServerInstallation' + + +class SkipServerInstallationOption(SupportsCapabilities): + @property + def skip_server_installation(self) -> Optional[bool]: + """ + Whether to skip the server components installation + on the device under test and all the related checks. + """ + return self.get_capability(SKIP_SERVER_INSTALLATION) + + @skip_server_installation.setter + def skip_server_installation(self, value: bool) -> None: + """ + Set whether to skip the server components installation + on the device under test and all the related checks. + This could help to speed up the session startup if you know for sure the + correct server version is installed on the device. + In case the server is not installed or an incorrect version of it is installed + then you may get an unexpected error later. + """ + self.set_capability(SKIP_SERVER_INSTALLATION, value) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py new file mode 100644 index 000000000..a34b6f819 --- /dev/null +++ b/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UIAUTOMATOR2_SERVER_INSTALL_TIMEOUT = 'uiautomator2ServerInstallTimeout' + + +class Uiautomator2ServerInstallTimeoutOption(SupportsCapabilities): + @property + def uiautomator2_server_install_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until UiAutomator2 server is installed on the device. + """ + value = self.get_capability(UIAUTOMATOR2_SERVER_INSTALL_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @uiautomator2_server_install_timeout.setter + def uiautomator2_server_install_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the maximum timeout to wait util UiAutomator2 server is installed on the device. + 20000 ms by default + """ + self.set_capability( + UIAUTOMATOR2_SERVER_INSTALL_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py new file mode 100644 index 000000000..89a274836 --- /dev/null +++ b/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py @@ -0,0 +1,43 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UIAUTOMATOR2_SERVER_LAUNCH_TIMEOUT = 'uiautomator2ServerLaunchTimeout' + + +class Uiautomator2ServerLaunchTimeoutOption(SupportsCapabilities): + @property + def uiautomator2_server_launch_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait until UiAutomator2 server is listening on the device. + """ + value = self.get_capability(UIAUTOMATOR2_SERVER_LAUNCH_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @uiautomator2_server_launch_timeout.setter + def uiautomator2_server_launch_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the maximum timeout to wait util UiAutomator2Server is listening on + the device. 30000 ms by default + """ + self.set_capability( + UIAUTOMATOR2_SERVER_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py new file mode 100644 index 000000000..2d92a8b37 --- /dev/null +++ b/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py @@ -0,0 +1,45 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from datetime import timedelta +from typing import Optional, Union + +from appium.options.common.supports_capabilities import SupportsCapabilities + +UIAUTOMATOR2_SERVER_READ_TIMEOUT = 'uiautomator2ServerReadTimeout' + + +class Uiautomator2ServerReadTimeoutOption(SupportsCapabilities): + @property + def uiautomator2_server_read_timeout(self) -> Optional[timedelta]: + """ + Maximum timeout to wait for an HTTP response from UiAutomator2Server. + """ + value = self.get_capability(UIAUTOMATOR2_SERVER_READ_TIMEOUT) + return None if value is None else timedelta(milliseconds=value) + + @uiautomator2_server_read_timeout.setter + def uiautomator2_server_read_timeout(self, value: Union[timedelta, int]) -> None: + """ + Set the maximum timeout to wait for a HTTP response from UiAutomator2Server. + Only values greater than zero are accepted. If the given value is too low + then expect driver commands to fail with timeout of Xms exceeded error. + 240000 ms by default + """ + self.set_capability( + UIAUTOMATOR2_SERVER_READ_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ) From ac39368b57bcb91ab72bdf09ed7b19f96a9f6544 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:44:39 +0200 Subject: [PATCH 102/548] chore(deps): bump black from 22.3.0 to 22.6.0 (#741) Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.3.0...22.6.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 60967d29b..cdc933457 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.19" [packages] selenium = "~=4.3" -black = "==22.3.0" +black = "==22.6.0" pytest = "~=7.1" pytest-cov = "~=3.0" From 970f8533e4b75c01fd592912a446344327dc0ac4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 28 Jun 2022 11:09:42 -0700 Subject: [PATCH 103/548] docs: Update changelog for 2.6.0 --- CHANGELOG.rst | 29 +++++++++++++++++++++++++++++ appium/version.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 450899f6c..e7377382c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,35 @@ Changelog ========= +v2.6.0 (2022-06-28) +------------------- + +New +~~~ +- Feat: Add Android drivers options (#740) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): bump black from 22.3.0 to 22.6.0 (#741) [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.3.0...22.6.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Chore: Improve autocompletion for methods returning self instance + (#739) [Mykola Mokhnach] +- Refactor: Remove previously deprecated methods and mark + reset/close/launch APIs as deprecated (#738) [Mykola Mokhnach] +- Docs: Update changelog for 2.5.0. [Kazuaki Matsuo] + + v2.5.0 (2022-06-25) ------------------- diff --git a/appium/version.py b/appium/version.py index 3d98bc1d1..e74262074 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.5.0' +version = '2.6.0' From 23ed3be57d613aae4dc7b1737f35e2577ffcc706 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jun 2022 19:35:43 -0700 Subject: [PATCH 104/548] chore(deps): update pylint requirement from ~=2.14.3 to ~=2.14.4 (#742) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.3...v2.14.4) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index cdc933457..b6fa0cb60 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.3" +pylint = "~=2.14.4" astroid = "~=2.9" isort = "~=5.10" From 8f53696db85f00dcdaf7d8b969c30bcc24ce7d1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 02:13:44 +0900 Subject: [PATCH 105/548] chore(deps): update typing-extensions requirement from ~=4.2 to ~=4.3 (#745) Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - [Release notes](https://github.com/python/typing_extensions/releases) - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.2.0...4.3.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b6fa0cb60..d22bd612b 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.1" pytest-cov = "~=3.0" tox = "~=3.25" -typing-extensions = "~=4.2" +typing-extensions = "~=4.3" httpretty = "~=1.1" python-dateutil = "~=2.8" From 217acf168e4c772d67a745509a9e51e70e643c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 09:37:18 -0700 Subject: [PATCH 106/548] chore(deps-dev): update pre-commit requirement from ~=2.19 to ~=2.20 (#746) 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/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.19.0...v2.20.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index d22bd612b..e1d42150b 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pre-commit = "~=2.19" +pre-commit = "~=2.20" [packages] selenium = "~=4.3" From 8e10ad83ad1fc0948759a3830ce3d90cd0f53b46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 13:59:11 -0700 Subject: [PATCH 107/548] chore(deps): update pylint requirement from ~=2.14.4 to ~=2.14.5 (#747) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.4...v2.14.5) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e1d42150b..5afe37dc5 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.4" +pylint = "~=2.14.5" astroid = "~=2.9" isort = "~=5.10" From 2a41c398b588d88030f5fd128487b9d3eeb0afb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 10:16:53 -0700 Subject: [PATCH 108/548] chore(deps): update mypy requirement from ~=0.961 to ~=0.971 (#749) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.961...v0.971) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5afe37dc5..9d8f0bbf9 100644 --- a/Pipfile +++ b/Pipfile @@ -26,4 +26,4 @@ pylint = "~=2.14.5" astroid = "~=2.9" isort = "~=5.10" -mypy = "~=0.961" +mypy = "~=0.971" From c0b80dc8e8acf2f1c79a9e871f4ca23fb6d0b71d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 25 Jul 2022 21:06:44 +0200 Subject: [PATCH 109/548] fix: typos/copypaste in various options (#750) --- appium/options/common/no_reset_option.py | 6 +++--- appium/options/ios/xcuitest/base.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/appium/options/common/no_reset_option.py b/appium/options/common/no_reset_option.py index 62b5e0d5f..ee2dd9988 100644 --- a/appium/options/common/no_reset_option.py +++ b/appium/options/common/no_reset_option.py @@ -24,14 +24,14 @@ class NoResetOption(SupportsCapabilities): @property - def full_reset(self) -> Optional[bool]: + def no_reset(self) -> Optional[bool]: """ Whether the driver should not perform a reset. """ return self.get_capability(NO_RESET) - @full_reset.setter - def full_reset(self, value: bool) -> None: + @no_reset.setter + def no_reset(self, value: bool) -> None: """ Set whether the driver should not perform a reset. """ diff --git a/appium/options/ios/xcuitest/base.py b/appium/options/ios/xcuitest/base.py index 5ee96acaa..54d254ba2 100644 --- a/appium/options/ios/xcuitest/base.py +++ b/appium/options/ios/xcuitest/base.py @@ -21,6 +21,7 @@ from appium.options.common.auto_web_view_option import AutoWebViewOption from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.bundle_id_option import BundleIdOption from appium.options.common.clear_system_files_option import ClearSystemFilesOption from appium.options.common.device_name_option import DeviceNameOption from appium.options.common.enable_performance_logging_option import EnablePerformanceLoggingOption @@ -118,6 +119,7 @@ class XCUITestOptions( AppiumOptions, AppOption, + BundleIdOption, ClearSystemFilesOption, OrientationOption, UdidOption, From af83bba3b75d501bb063313e7edb3216765a9042 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 26 Jul 2022 09:18:17 +0200 Subject: [PATCH 110/548] fix: Move py.typed to the hierarchy root (#751) --- appium/{webdriver => }/py.typed | 0 setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename appium/{webdriver => }/py.typed (100%) diff --git a/appium/webdriver/py.typed b/appium/py.typed similarity index 100% rename from appium/webdriver/py.typed rename to appium/py.typed diff --git a/setup.py b/setup.py index dba676df2..cc0c2eb8d 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ author_email='isaac@saucelabs.com', maintainer='Kazuaki Matsuo, Mykola Mokhnach, Mori Atsushi', url='http://appium.io/', - package_data={'appium': ['webdriver/py.typed']}, + package_data={'appium': ['py.typed']}, packages=find_packages(include=['appium*']), license='Apache 2.0', classifiers=[ From 677c1a2121053a0435f3e0d8f7798077a41aa067 Mon Sep 17 00:00:00 2001 From: jatalahd Date: Thu, 4 Aug 2022 10:06:58 +0300 Subject: [PATCH 111/548] fix: Backwards compatible behaviour of swipe and scroll in action_helpers (#744) * Backwards compatible behaviour of swipe and scroll in action_helpers - Fixed handling the duration argument in swipe() and scroll() helpers - Functionality is now the same as in older versions using TouchActions Fixes #743 * Backwards compatible behaviour of swipe and scroll in action_helpers - Fixed handling the duration argument in swipe() and scroll() helpers - Functionality is now the same as in older versions using TouchActions Fixes #743 * Backwards compatible behaviour of swipe and scroll in action_helpers - Fixed handling the duration argument in swipe() and scroll() helpers - Functionality is now the same as in older versions using TouchActions Fixes #743 * Backwards compatible behaviour of swipe and scroll in action_helpers - Fixed handling the duration argument in swipe() and scroll() helpers - Functionality is now the same as in older versions using TouchActions Fixes #743 * Backwards compatible behaviour of swipe and scroll in action_helpers - Fixed handling the duration argument in swipe() and scroll() helpers - Functionality is now the same as in older versions using TouchActions Fixes #743 --- appium/webdriver/extensions/action_helpers.py | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 84d730aef..e124450db 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -31,9 +31,9 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: """Scrolls from one element to another Args: - origin_el: the element from which to being scrolling - destination_el: the element to scroll to - duration: a duration after pressing originalEl and move the element to destinationEl. + origin_el: the element from which to begin scrolling (center of element) + destination_el: the element to scroll to (center of element) + duration: defines speed of scroll action when moving from originalEl to destinationEl. Default is 600 ms for W3C spec. Usage: @@ -42,32 +42,23 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ - # XCUITest x W3C spec has no duration by default in server side if duration is None: duration = 600 - # w3c action requires seconds - duration_sec = duration / 1000 + touch_input = PointerInput(interaction.POINTER_TOUCH, "touch") actions = ActionChains(self) - actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) - dest_el_rect = destination_el.rect + actions.w3c_actions = ActionBuilder(self, mouse=touch_input) # https://github.com/SeleniumHQ/selenium/blob/3c82c868d4f2a7600223a1b3817301d0b04d28e4/py/selenium/webdriver/common/actions/pointer_actions.py#L83 - if duration_sec is None: - actions.w3c_actions.pointer_action.move_to(origin_el) - actions.w3c_actions.pointer_action.pointer_down() - actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) - actions.w3c_actions.pointer_action.release() - actions.perform() - else: - actions.w3c_actions.pointer_action.move_to(origin_el) - actions.w3c_actions.pointer_action.pointer_down() - actions.w3c_actions.pointer_action.pause(duration_sec) - actions.w3c_actions.pointer_action.move_to_location(dest_el_rect['x'], dest_el_rect['y']) - actions.w3c_actions.pointer_action.release() - actions.perform() + actions.w3c_actions.pointer_action.move_to(origin_el) + actions.w3c_actions.pointer_action.pointer_down() + # setup duration for second move only, assuming duration always has atleast default value + actions.w3c_actions = ActionBuilder(self, mouse=touch_input, duration=duration) + actions.w3c_actions.pointer_action.move_to(destination_el) + actions.w3c_actions.pointer_action.release() + actions.perform() return self def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: @@ -146,7 +137,7 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: start_y: y-coordinate at which to start end_x: x-coordinate at which to stop end_y: y-coordinate at which to stop - duration: time to take the swipe, in ms. + duration: defines the swipe speed as time taken to swipe from point a to point b, in ms. Usage: driver.swipe(100, 100, 100, 400) @@ -154,11 +145,14 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ + touch_input = PointerInput(interaction.POINTER_TOUCH, "touch") + actions = ActionChains(self) - actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) + actions.w3c_actions = ActionBuilder(self, mouse=touch_input) actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) actions.w3c_actions.pointer_action.pointer_down() - actions.w3c_actions.pointer_action.pause(duration / 1000) + if duration > 0: + actions.w3c_actions = ActionBuilder(self, mouse=touch_input, duration=duration) actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() From ff50af081c75817f7e4bbfc582fcb0755214eac3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Aug 2022 22:58:52 -0700 Subject: [PATCH 112/548] chore(deps): update selenium requirement from ~=4.3 to ~=4.4 (#757) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.3.0...selenium-4.4.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 9d8f0bbf9..53176018e 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true pre-commit = "~=2.20" [packages] -selenium = "~=4.3" +selenium = "~=4.4" black = "==22.6.0" From 0b711aeb304e272f7b1a84ba59aa2bbd1edb4fb0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 11 Aug 2022 00:45:44 -0700 Subject: [PATCH 113/548] fix: fix options in mac2 (#759) --- appium/options/mac/mac2/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appium/options/mac/mac2/base.py b/appium/options/mac/mac2/base.py index 36077e52f..d322ca87f 100644 --- a/appium/options/mac/mac2/base.py +++ b/appium/options/mac/mac2/base.py @@ -17,10 +17,9 @@ from typing import Dict -from options.common.bundle_id_option import BundleIdOption - from appium.options.common.automation_name_option import AUTOMATION_NAME from appium.options.common.base import PLATFORM_NAME, AppiumOptions +from appium.options.common.bundle_id_option import BundleIdOption from appium.options.common.postrun_option import PostrunOption from appium.options.common.prerun_option import PrerunOption from appium.options.common.system_host_option import SystemHostOption From 0a929c6dc3a98a191995d70ba0160aaf256313f7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 11 Aug 2022 00:48:34 -0700 Subject: [PATCH 114/548] docs: Update changelog for 2.6.1 --- CHANGELOG.rst | 125 ++++++++++++++++++++++++++++++++++++++++++++++ appium/version.py | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e7377382c..9c18d2ea7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,131 @@ Changelog ========= +v2.6.1 (2022-08-11) +------------------- + +Fix +~~~ +- Fix options in mac2 (#759) [Kazuaki Matsuo] +- Backwards compatible behaviour of swipe and scroll in action_helpers + (#744) [jatalahd] + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 +- Move py.typed to the hierarchy root (#751) [Mykola Mokhnach] +- Typos/copypaste in various options (#750) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update selenium requirement from ~=4.3 to ~=4.4 (#757) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.3.0...selenium-4.4.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.961 to ~=0.971 (#749) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.961...v0.971) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.4 to ~=2.14.5 + (#747) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.4...v2.14.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.19 to ~=2.20 + (#746) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.19.0...v2.20.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update typing-extensions requirement from ~=4.2 to ~=4.3 + (#745) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.2.0...4.3.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.3 to ~=2.14.4 + (#742) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.3...v2.14.4) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.0. [Kazuaki Matsuo] + + v2.6.0 (2022-06-28) ------------------- diff --git a/appium/version.py b/appium/version.py index e74262074..7fd16ea3d 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.6.0' +version = '2.6.1' From 130766c97910d44c997bffbb563edb2c8d6629b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 18:26:57 +0200 Subject: [PATCH 115/548] chore(deps): update pylint requirement from ~=2.14.5 to ~=2.15.0 (#761) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.5...v2.15.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 53176018e..ec345eb40 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.14.5" +pylint = "~=2.15.0" astroid = "~=2.9" isort = "~=5.10" From 806d5dff7f5b60723d68f249e5da7656db6a836b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 17:56:45 -0700 Subject: [PATCH 116/548] chore(deps): bump black from 22.6.0 to 22.8.0 (#763) Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ec345eb40..99546ad9c 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ pre-commit = "~=2.20" [packages] selenium = "~=4.4" -black = "==22.6.0" +black = "==22.8.0" pytest = "~=7.1" pytest-cov = "~=3.0" From 89193b9b677cd4bb7c163a778450d17723c89798 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 17:57:12 -0700 Subject: [PATCH 117/548] chore(deps): update astroid requirement from ~=2.9 to ~=2.12 (#762) Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - [Release notes](https://github.com/PyCQA/astroid/releases) - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/astroid/compare/v2.9.0...v2.12.5) --- updated-dependencies: - dependency-name: astroid dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 99546ad9c..887df7fa2 100644 --- a/Pipfile +++ b/Pipfile @@ -23,7 +23,7 @@ types-python-dateutil = "~=2.8" mock = "~=4.0" pylint = "~=2.15.0" -astroid = "~=2.9" +astroid = "~=2.12" isort = "~=5.10" mypy = "~=0.971" From 047e9277db99c97b00f3fed7e008e3227a8e9f34 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 7 Sep 2022 08:59:46 +0200 Subject: [PATCH 118/548] ci: Add Conventional commit format validation (#764) * ci: Add Conventional commit format validation * Rename --- .github/workflows/pr-title.yml | 13 +++++++++++++ azure-pipelines.yml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-title.yml diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 000000000..93840452e --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,13 @@ +name: Conventional Commits +on: + pull_request: + + +jobs: + lint: + name: https://www.conventionalcommits.org + runs-on: ubuntu-latest + steps: + - uses: beemojs/conventional-pr-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a4c355e51..bb67de846 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,7 @@ jobs: - template: ./ci-jobs/functional_test.yml - job: 'unitTests' pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-latest' strategy: matrix: Python37: From 7734ea2fe3668809545db2729bb41c0f383a77cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:17:43 -0700 Subject: [PATCH 119/548] chore(deps): update pylint requirement from ~=2.15.0 to ~=2.15.2 (#765) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.0...v2.15.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 887df7fa2..d10c09a5e 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.15.0" +pylint = "~=2.15.2" astroid = "~=2.12" isort = "~=5.10" From fbfcccab17b49aa77063a80969a013b8246be861 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:17:56 -0700 Subject: [PATCH 120/548] chore(deps): update tox requirement from ~=3.25 to ~=3.26 (#766) 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.25.0...3.26.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index d10c09a5e..8283b108f 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ black = "==22.8.0" pytest = "~=7.1" pytest-cov = "~=3.0" -tox = "~=3.25" +tox = "~=3.26" typing-extensions = "~=4.3" httpretty = "~=1.1" From 98ab48d6a3ebf108a4c45de20ef55b4aec5b09ea Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 13 Sep 2022 16:49:58 +0200 Subject: [PATCH 121/548] ci: Update Conventional Commits config preset --- .github/workflows/pr-title.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 93840452e..4182ee0a1 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -6,8 +6,10 @@ on: jobs: lint: name: https://www.conventionalcommits.org - runs-on: ubuntu-latest + runs-on: ubuntu-lates steps: - uses: beemojs/conventional-pr-action@v2 + with: + config-preset: angular env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b31b5ebe19eeeaf4216c712d213ae93f576f5943 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 16 Sep 2022 09:09:45 +0200 Subject: [PATCH 122/548] fix: Use total_seconds property of timedelta (#767) --- appium/options/android/common/adb/adb_exec_timeout_option.py | 4 +++- .../android/common/app/android_install_timeout_option.py | 2 +- appium/options/android/common/app/app_wait_duration_option.py | 4 +++- .../options/android/common/avd/avd_launch_timeout_option.py | 4 +++- appium/options/android/common/avd/avd_ready_timeout_option.py | 4 +++- .../android/common/context/auto_webview_timeout_option.py | 4 +++- .../android/common/locking/unlock_success_timeout_option.py | 2 +- .../android/espresso/espresso_server_launch_timeout_option.py | 2 +- .../uiautomator2_server_install_timeout_option.py | 3 ++- .../uiautomator2/uiautomator2_server_launch_timeout_option.py | 3 ++- .../uiautomator2/uiautomator2_server_read_timeout_option.py | 3 ++- appium/options/common/new_command_timeout_option.py | 2 +- appium/options/ios/xcuitest/app/app_push_timeout_option.py | 4 +++- appium/options/ios/xcuitest/other/command_timeouts_option.py | 4 ++-- .../xcuitest/simulator/simulator_startup_timeout_option.py | 2 +- .../options/ios/xcuitest/wda/wait_for_idle_timeout_option.py | 2 +- .../options/ios/xcuitest/wda/wda_connection_timeout_option.py | 2 +- .../ios/xcuitest/wda/wda_eventloop_idle_delay_option.py | 2 +- appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py | 4 +++- .../ios/xcuitest/wda/wda_startup_retry_interval_option.py | 2 +- .../ios/xcuitest/webview/webkit_response_timeout_option.py | 2 +- .../ios/xcuitest/webview/webview_connect_timeout_option.py | 2 +- appium/options/mac/mac2/server_startup_timeout_option.py | 2 +- .../options/windows/windows/create_session_timeout_option.py | 2 +- appium/options/windows/windows/wait_for_app_launch_option.py | 2 +- 25 files changed, 43 insertions(+), 26 deletions(-) diff --git a/appium/options/android/common/adb/adb_exec_timeout_option.py b/appium/options/android/common/adb/adb_exec_timeout_option.py index 0c47f94db..ec2d93226 100644 --- a/appium/options/android/common/adb/adb_exec_timeout_option.py +++ b/appium/options/android/common/adb/adb_exec_timeout_option.py @@ -38,4 +38,6 @@ def adb_exec_timeout(self, value: Union[timedelta, int]) -> None: Maximum time to wait until single ADB command is executed. 20000 ms by default. """ - self.set_capability(ADB_EXEC_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + ADB_EXEC_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/app/android_install_timeout_option.py b/appium/options/android/common/app/android_install_timeout_option.py index c7919fcb5..38a997648 100644 --- a/appium/options/android/common/app/android_install_timeout_option.py +++ b/appium/options/android/common/app/android_install_timeout_option.py @@ -39,5 +39,5 @@ def android_install_timeout(self, value: Union[timedelta, int]) -> None: 90000 ms by default """ self.set_capability( - ANDROID_INSTALL_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ANDROID_INSTALL_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/android/common/app/app_wait_duration_option.py b/appium/options/android/common/app/app_wait_duration_option.py index b06e65503..82bba7606 100644 --- a/appium/options/android/common/app/app_wait_duration_option.py +++ b/appium/options/android/common/app/app_wait_duration_option.py @@ -38,4 +38,6 @@ def app_wait_duration(self, value: Union[timedelta, int]) -> None: Maximum amount of time to wait until the application under test is started (e.g. an activity returns the control to the caller). 20000 ms by default. """ - self.set_capability(APP_WAIT_DURATION, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + APP_WAIT_DURATION, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/avd/avd_launch_timeout_option.py b/appium/options/android/common/avd/avd_launch_timeout_option.py index 58cad9d3b..d791c3a8a 100644 --- a/appium/options/android/common/avd/avd_launch_timeout_option.py +++ b/appium/options/android/common/avd/avd_launch_timeout_option.py @@ -38,4 +38,6 @@ def avd_launch_timeout(self, value: Union[timedelta, int]) -> None: Maximum timeout to wait until Android Emulator is started. 60000 ms by default. """ - self.set_capability(AVD_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + AVD_LAUNCH_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/avd/avd_ready_timeout_option.py b/appium/options/android/common/avd/avd_ready_timeout_option.py index 25400f4e5..2a56b45af 100644 --- a/appium/options/android/common/avd/avd_ready_timeout_option.py +++ b/appium/options/android/common/avd/avd_ready_timeout_option.py @@ -38,4 +38,6 @@ def avd_ready_timeout(self, value: Union[timedelta, int]) -> None: Maximum timeout to wait until Android Emulator is fully booted and is ready for usage. 60000 ms by default """ - self.set_capability(AVD_READY_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + AVD_READY_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/context/auto_webview_timeout_option.py b/appium/options/android/common/context/auto_webview_timeout_option.py index 4fdf4435e..c71e1efe8 100644 --- a/appium/options/android/common/context/auto_webview_timeout_option.py +++ b/appium/options/android/common/context/auto_webview_timeout_option.py @@ -38,4 +38,6 @@ def auto_webview_timeout(self, value: Union[timedelta, int]) -> None: """ Timeout to wait until a web view is available. """ - self.set_capability(AUTO_WEBVIEW_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + AUTO_WEBVIEW_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/android/common/locking/unlock_success_timeout_option.py b/appium/options/android/common/locking/unlock_success_timeout_option.py index 50f4a569b..488ae5f9e 100644 --- a/appium/options/android/common/locking/unlock_success_timeout_option.py +++ b/appium/options/android/common/locking/unlock_success_timeout_option.py @@ -39,5 +39,5 @@ def unlock_success_timeout(self, value: Union[timedelta, int]) -> None: 2000 ms by default. """ self.set_capability( - UNLOCK_SUCCESS_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + UNLOCK_SUCCESS_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/android/espresso/espresso_server_launch_timeout_option.py b/appium/options/android/espresso/espresso_server_launch_timeout_option.py index acacf57d2..9335358de 100644 --- a/appium/options/android/espresso/espresso_server_launch_timeout_option.py +++ b/appium/options/android/espresso/espresso_server_launch_timeout_option.py @@ -39,5 +39,5 @@ def espresso_server_launch_timeout(self, value: Union[timedelta, int]) -> None: 45000 ms by default """ self.set_capability( - ESPRESSO_SERVER_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + ESPRESSO_SERVER_LAUNCH_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py index a34b6f819..4b25638f8 100644 --- a/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py +++ b/appium/options/android/uiautomator2/uiautomator2_server_install_timeout_option.py @@ -39,5 +39,6 @@ def uiautomator2_server_install_timeout(self, value: Union[timedelta, int]) -> N 20000 ms by default """ self.set_capability( - UIAUTOMATOR2_SERVER_INSTALL_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + UIAUTOMATOR2_SERVER_INSTALL_TIMEOUT, + int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value, ) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py index 89a274836..ce74c4473 100644 --- a/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py +++ b/appium/options/android/uiautomator2/uiautomator2_server_launch_timeout_option.py @@ -39,5 +39,6 @@ def uiautomator2_server_launch_timeout(self, value: Union[timedelta, int]) -> No the device. 30000 ms by default """ self.set_capability( - UIAUTOMATOR2_SERVER_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + UIAUTOMATOR2_SERVER_LAUNCH_TIMEOUT, + int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value, ) diff --git a/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py b/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py index 2d92a8b37..cb8a39c25 100644 --- a/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py +++ b/appium/options/android/uiautomator2/uiautomator2_server_read_timeout_option.py @@ -41,5 +41,6 @@ def uiautomator2_server_read_timeout(self, value: Union[timedelta, int]) -> None 240000 ms by default """ self.set_capability( - UIAUTOMATOR2_SERVER_READ_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + UIAUTOMATOR2_SERVER_READ_TIMEOUT, + int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value, ) diff --git a/appium/options/common/new_command_timeout_option.py b/appium/options/common/new_command_timeout_option.py index ce71907c1..377ef7193 100644 --- a/appium/options/common/new_command_timeout_option.py +++ b/appium/options/common/new_command_timeout_option.py @@ -38,4 +38,4 @@ def new_command_timeout(self, value: Union[timedelta, int]) -> None: Set the allowed time before seeing a new server command. The value could either be provided as timedelta instance or an integer number of seconds. """ - self.set_capability(NEW_COMMAND_TIMEOUT, value.seconds if isinstance(value, timedelta) else value) + self.set_capability(NEW_COMMAND_TIMEOUT, value.total_seconds() if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/app/app_push_timeout_option.py b/appium/options/ios/xcuitest/app/app_push_timeout_option.py index 15ae4c247..261265d19 100644 --- a/appium/options/ios/xcuitest/app/app_push_timeout_option.py +++ b/appium/options/ios/xcuitest/app/app_push_timeout_option.py @@ -39,4 +39,6 @@ def app_push_timeout(self, value: Union[timedelta, int]) -> None: Works for real devices only. The default value is 30000ms. """ - self.set_capability(APP_PUSH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + APP_PUSH_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/other/command_timeouts_option.py b/appium/options/ios/xcuitest/other/command_timeouts_option.py index 7c98d5cf0..420b35039 100644 --- a/appium/options/ios/xcuitest/other/command_timeouts_option.py +++ b/appium/options/ios/xcuitest/other/command_timeouts_option.py @@ -50,8 +50,8 @@ def command_timeouts(self, value: Union[Dict[str, timedelta], timedelta, int]) - were not explicitly mentioned as dictionary keys """ if isinstance(value, dict): - self.set_capability(COMMAND_TIMEOUTS, {k: v.microseconds // 1000 for k, v in value.items()}) + self.set_capability(COMMAND_TIMEOUTS, {k: int(v.total_seconds() * 1000) for k, v in value.items()}) elif isinstance(value, timedelta): - self.set_capability(COMMAND_TIMEOUTS, f'{value.microseconds // 1000}') + self.set_capability(COMMAND_TIMEOUTS, f'{int(value.total_seconds() * 1000)}') else: self.set_capability(COMMAND_TIMEOUTS, value) diff --git a/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py b/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py index ad14462d4..29353498d 100644 --- a/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py +++ b/appium/options/ios/xcuitest/simulator/simulator_startup_timeout_option.py @@ -42,5 +42,5 @@ def simulator_startup_timeout(self, value: Union[timedelta, int]) -> None: during the boot up procedure. """ self.set_capability( - SIMULATOR_STARTUP_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + SIMULATOR_STARTUP_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py b/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py index 3cf75ec42..ff994d43e 100644 --- a/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py +++ b/appium/options/ios/xcuitest/wda/wait_for_idle_timeout_option.py @@ -42,4 +42,4 @@ def wait_for_idle_timeout(self, value: Union[timedelta, float]) -> None: (not recommended) and has the same effect as setting waitForQuiescence to false. Available since Appium 1.20.0. """ - self.set_capability(WAIT_FOR_IDLE_TIMEOUT, value.seconds if isinstance(value, timedelta) else value) + self.set_capability(WAIT_FOR_IDLE_TIMEOUT, value.total_seconds() if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py b/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py index 09364f58b..e21bb1834 100644 --- a/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py +++ b/appium/options/ios/xcuitest/wda/wda_connection_timeout_option.py @@ -39,5 +39,5 @@ def wda_connection_timeout(self, value: Union[timedelta, int]) -> None: Defaults to 240000ms. """ self.set_capability( - WDA_CONNECTION_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + WDA_CONNECTION_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py b/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py index aa1a134df..e957f2690 100644 --- a/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py +++ b/appium/options/ios/xcuitest/wda/wda_eventloop_idle_delay_option.py @@ -43,4 +43,4 @@ def wda_eventloop_idle_delay(self, value: Union[timedelta, float]) -> None: If you enable this capability start with at least 3 seconds and try increasing it, if creating the session still fails. Defaults to 0. """ - self.set_capability(WDA_EVENTLOOP_IDLE_DELAY, value.seconds if isinstance(value, timedelta) else value) + self.set_capability(WDA_EVENTLOOP_IDLE_DELAY, value.total_seconds() if isinstance(value, timedelta) else value) diff --git a/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py b/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py index 62a029ec7..035cdb401 100644 --- a/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py +++ b/appium/options/ios/xcuitest/wda/wda_launch_timeout_option.py @@ -38,4 +38,6 @@ def wda_launch_timeout(self, value: Union[timedelta, int]) -> None: Timeout to wait for WebDriverAgent to be pingable, after its building is finished. Defaults to 60000ms. """ - self.set_capability(WDA_LAUNCH_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value) + self.set_capability( + WDA_LAUNCH_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value + ) diff --git a/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py b/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py index b033f878e..99d89daa6 100644 --- a/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py +++ b/appium/options/ios/xcuitest/wda/wda_startup_retry_interval_option.py @@ -39,5 +39,5 @@ def wda_startup_retry_interval(self, value: Union[timedelta, int]) -> None: Defaults to 10000ms. """ self.set_capability( - WDA_STARTUP_RETRY_INTERVAL, value.microseconds // 1000 if isinstance(value, timedelta) else value + WDA_STARTUP_RETRY_INTERVAL, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py b/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py index 8c09117c4..1e274bf01 100644 --- a/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py +++ b/appium/options/ios/xcuitest/webview/webkit_response_timeout_option.py @@ -39,5 +39,5 @@ def webkit_response_timeout(self, value: Union[timedelta, int]) -> None: WebKit in a Safari session. Defaults to 5000ms. """ self.set_capability( - WEBKIT_RESPONSE_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + WEBKIT_RESPONSE_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py b/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py index c0915afc1..9fa742020 100644 --- a/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py +++ b/appium/options/ios/xcuitest/webview/webview_connect_timeout_option.py @@ -39,5 +39,5 @@ def webview_connect_timeout(self, value: Union[timedelta, int]) -> None: MobileSafari or hybrid apps. Defaults to 0ms. """ self.set_capability( - WEBVIEW_CONNECT_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + WEBVIEW_CONNECT_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/mac/mac2/server_startup_timeout_option.py b/appium/options/mac/mac2/server_startup_timeout_option.py index c5b224edf..32e13f79a 100644 --- a/appium/options/mac/mac2/server_startup_timeout_option.py +++ b/appium/options/mac/mac2/server_startup_timeout_option.py @@ -40,5 +40,5 @@ def server_startup_timeout(self, value: Union[int, timedelta]) -> None: project is built and started. """ self.set_capability( - SERVER_STARTUP_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + SERVER_STARTUP_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/windows/windows/create_session_timeout_option.py b/appium/options/windows/windows/create_session_timeout_option.py index 161bd0e9c..acf1289a8 100644 --- a/appium/options/windows/windows/create_session_timeout_option.py +++ b/appium/options/windows/windows/create_session_timeout_option.py @@ -41,5 +41,5 @@ def create_session_timeout(self, value: Union[timedelta, int]) -> None: with appId: TestCompany.my_app4!App, and processId: 8480). """ self.set_capability( - CREATE_SESSION_TIMEOUT, value.microseconds // 1000 if isinstance(value, timedelta) else value + CREATE_SESSION_TIMEOUT, int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value ) diff --git a/appium/options/windows/windows/wait_for_app_launch_option.py b/appium/options/windows/windows/wait_for_app_launch_option.py index 0b9cb0fa8..9631b9231 100644 --- a/appium/options/windows/windows/wait_for_app_launch_option.py +++ b/appium/options/windows/windows/wait_for_app_launch_option.py @@ -40,4 +40,4 @@ def wait_for_app_launch(self, value: Union[timedelta, int]) -> None: a defined amount of time after an app launch is initiated prior to attaching to the application session. The limit for this is 50 seconds. """ - self.set_capability(WAIT_FOR_APP_LAUNCH, value.seconds if isinstance(value, timedelta) else value) + self.set_capability(WAIT_FOR_APP_LAUNCH, value.total_seconds() if isinstance(value, timedelta) else value) From 7a5fa26f3d066d311952a1b39983afa3e2c44547 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 16 Sep 2022 01:40:38 -0700 Subject: [PATCH 123/548] docs: Update changelog for 2.6.2 --- CHANGELOG.rst | 90 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9c18d2ea7..68f2042f3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,91 @@ Changelog ========= +v2.6.2 (2022-09-16) +------------------- + +Fix +~~~ +- Use total_seconds property of timedelta (#767) [Mykola Mokhnach] + +Test +~~~~ +- Ci: Update Conventional Commits config preset. [Mykola Mokhnach] +- Ci: Add Conventional commit format validation (#764) [Mykola Mokhnach] + + * ci: Add Conventional commit format validation + + * Rename + +Other +~~~~~ +- Chore(deps): update tox requirement from ~=3.25 to ~=3.26 (#766) + [dependabot[bot], dependabot[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.25.0...3.26.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.15.0 to ~=2.15.2 + (#765) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.0...v2.15.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.9 to ~=2.12 (#762) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/v2.9.0...v2.12.5) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Chore(deps): bump black from 22.6.0 to 22.8.0 (#763) [dependabot[bot], + dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Chore(deps): update pylint requirement from ~=2.14.5 to ~=2.15.0 + (#761) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.5...v2.15.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.1. [Kazuaki Matsuo] + + v2.6.1 (2022-08-11) ------------------- @@ -2620,11 +2705,6 @@ Other ~~~~~ - Update changelog for 0.38. [Kazuaki Matsuo] - Bump 0.38. [Kazuaki Matsuo] - - -v0.38 (2019-02-11) ------------------- -- Bump 0.38. [Kazuaki Matsuo] - Remove io.open from getting version code (#334) [Kazuaki Matsuo] * remove io.open From 02a39a31b9829635e91498ceea2099af5a4a493f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 19 Sep 2022 16:19:20 +0200 Subject: [PATCH 124/548] ci: Fix runner name --- .github/workflows/pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 4182ee0a1..9a192aec5 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -6,7 +6,7 @@ on: jobs: lint: name: https://www.conventionalcommits.org - runs-on: ubuntu-lates + runs-on: ubuntu-latest steps: - uses: beemojs/conventional-pr-action@v2 with: From 85cb1042373012d4c21fab2482090fe25a9422ac Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 19 Sep 2022 20:24:14 +0200 Subject: [PATCH 125/548] feat: Add appArguments option to WindowsOptions (#768) --- .../windows/windows/app_arguments_option.py | 40 +++++++++++++++++++ appium/options/windows/windows/base.py | 2 + 2 files changed, 42 insertions(+) create mode 100644 appium/options/windows/windows/app_arguments_option.py diff --git a/appium/options/windows/windows/app_arguments_option.py b/appium/options/windows/windows/app_arguments_option.py new file mode 100644 index 000000000..665977d37 --- /dev/null +++ b/appium/options/windows/windows/app_arguments_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from appium.options.common.supports_capabilities import SupportsCapabilities + +APP_ARGUMENTS = 'appArguments' + + +class AppArgumentsOption(SupportsCapabilities): + @property + def app_arguments(self) -> Optional[str]: + """ + Application arguments string, for example `/?`. + """ + return self.get_capability(APP_ARGUMENTS) + + @app_arguments.setter + def app_arguments(self, value: str) -> None: + """ + Set application arguments string, for example `/argone "/arg two"`. + Make sure arguments are quoted/escaped properly if necessary: + https://ss64.com/nt/syntax-esc.html + """ + self.set_capability(APP_ARGUMENTS, value) diff --git a/appium/options/windows/windows/base.py b/appium/options/windows/windows/base.py index 21cf43f28..52de731e6 100644 --- a/appium/options/windows/windows/base.py +++ b/appium/options/windows/windows/base.py @@ -24,6 +24,7 @@ from appium.options.common.prerun_option import PrerunOption from appium.options.common.system_port_option import SystemPortOption +from .app_arguments_option import AppArgumentsOption from .app_top_level_window_option import AppTopLevelWindowOption from .app_working_dir_option import AppWorkingDirOption from .create_session_timeout_option import CreateSessionTimeoutOption @@ -42,6 +43,7 @@ class WindowsOptions( ExperimentalWebDriverOption, SystemPortOption, WaitForAppLaunchOption, + AppArgumentsOption, ): @AppOption.app.setter # type: ignore def app(self, value: str) -> None: From 83f3c817bd6f979b1febdf4d643640b2d6c49d82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 21:41:26 -0700 Subject: [PATCH 126/548] chore(deps): update pylint requirement from ~=2.15.2 to ~=2.15.3 (#770) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8283b108f..cff148a20 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" mock = "~=4.0" -pylint = "~=2.15.2" +pylint = "~=2.15.3" astroid = "~=2.12" isort = "~=5.10" From 1a0246cec0c3bf5f98fbf8ac20e1d5258f8e3e4f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 22 Sep 2022 19:16:06 +0200 Subject: [PATCH 127/548] fix: Move dev-only dependencies to [dev-packages] section (#772) --- Pipfile | 25 +++++++++---------------- tox.ini | 2 +- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index cff148a20..25cc18932 100644 --- a/Pipfile +++ b/Pipfile @@ -4,26 +4,19 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +black = "~=22.8.0" +httpretty = "~=1.1" +isort = "~=5.10" +mypy = "~=0.971" +mock = "~=4.0" pre-commit = "~=2.20" - -[packages] -selenium = "~=4.4" - -black = "==22.8.0" - +pylint = "~=2.15.2" pytest = "~=7.1" pytest-cov = "~=3.0" - +python-dateutil = "~=2.8" tox = "~=3.26" typing-extensions = "~=4.3" - -httpretty = "~=1.1" -python-dateutil = "~=2.8" types-python-dateutil = "~=2.8" -mock = "~=4.0" -pylint = "~=2.15.3" -astroid = "~=2.12" -isort = "~=5.10" - -mypy = "~=0.971" +[packages] +selenium = "~=4.4" diff --git a/tox.ini b/tox.ini index ae9069ebf..6823808bd 100644 --- a/tox.ini +++ b/tox.ini @@ -10,5 +10,5 @@ deps = pipenv commands = pipenv lock --clear - pipenv install + pipenv install --dev ./ci.sh From f6021000416a6cd234122ac7c35abcb183b3c781 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 22 Sep 2022 23:48:20 -0700 Subject: [PATCH 128/548] Update changelog for 2.6.3 --- CHANGELOG.rst | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 68f2042f3..d4d55b86c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,40 @@ Changelog ========= +v2.7.0 (2022-09-22) +------------------- + +New +~~~ +- Feat: Add appArguments option to WindowsOptions (#768) [Mykola + Mokhnach] + +Fix +~~~ +- Move dev-only dependencies to [dev-packages] section (#772) [Mykola + Mokhnach] + +Test +~~~~ +- Ci: Fix runner name. [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update pylint requirement from ~=2.15.2 to ~=2.15.3 + (#770) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.2. [Kazuaki Matsuo] + + v2.6.2 (2022-09-16) ------------------- @@ -2705,6 +2739,11 @@ Other ~~~~~ - Update changelog for 0.38. [Kazuaki Matsuo] - Bump 0.38. [Kazuaki Matsuo] + + +v0.38 (2019-02-11) +------------------ +- Bump 0.38. [Kazuaki Matsuo] - Remove io.open from getting version code (#334) [Kazuaki Matsuo] * remove io.open From bc2d70d54451f86a6c2d64ff64be5ec00287df18 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 22 Sep 2022 23:48:50 -0700 Subject: [PATCH 129/548] Bump 2.7.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 7fd16ea3d..9c033f037 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.6.1' +version = '2.7.0' From 7d82821a03a42827ebe13c71c2742e175545a192 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Sep 2022 12:22:36 -0700 Subject: [PATCH 130/548] chore(deps-dev): update pylint requirement from ~=2.15.2 to ~=2.15.3 (#774) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 25cc18932..5aeb9ad39 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.971" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.2" +pylint = "~=2.15.3" pytest = "~=7.1" pytest-cov = "~=3.0" python-dateutil = "~=2.8" From 46a09b81028c0d1b87f21d4d1c42de674c074e07 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 24 Sep 2022 01:05:11 -0700 Subject: [PATCH 131/548] ci: run unit tests on actions (#773) * ci: run unit tests on actions * ci: remove unit test section * ci: comment out win for now * ci: tweak trigger --- .github/workflows/unit-test.yml | 41 +++++++++++++++++++++++++++++++++ azure-pipelines.yml | 28 ---------------------- 2 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/unit-test.yml diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml new file mode 100644 index 000000000..e480d5ae9 --- /dev/null +++ b/.github/workflows/unit-test.yml @@ -0,0 +1,41 @@ +name: Unit tests + +on: + workflow_dispatch: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test-ubuntu: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install tox + run: pip install tox + - name: Run Tox + run: tox -e py + + # test-win: + # runs-on: windows-latest + # strategy: + # matrix: + # python-version: ["3.7", "3.8", "3.9", "3.10"] + # steps: + # - uses: actions/checkout@v3 + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v3 + # with: + # python-version: ${{ matrix.python-version }} + # - name: Install tox + # run: pip install tox + # - name: Run Tox + # run: tox -e py diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb67de846..ba9dffb28 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,32 +4,4 @@ # https://docs.microsoft.com/azure/devops/pipelines/languages/xcode jobs: - template: ./ci-jobs/functional_test.yml - - job: 'unitTests' - pool: - vmImage: 'ubuntu-latest' - strategy: - matrix: - Python37: - python.version: '3.7' - Python38: - python.version: '3.8' - Python39: - python.version: '3.9' - steps: - - task: UsePythonVersion@0 - displayName: 'Use Python $(python.version)' - inputs: - versionSpec: '$(python.version)' - - - script: pip install tox - displayName: 'Install Tox' - - - script: tox -e py - displayName: 'Run Tox' - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/junit.xml' - testRunTitle: 'Publish test results for Python $(python.version)' From 38c1fb31199cb22952e1089cb5a8da583f1dfff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 09:47:41 -0700 Subject: [PATCH 132/548] chore(deps-dev): update mypy requirement from ~=0.971 to ~=0.981 (#777) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.971...v0.981) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5aeb9ad39..41c15d6c4 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.8.0" httpretty = "~=1.1" isort = "~=5.10" -mypy = "~=0.971" +mypy = "~=0.981" mock = "~=4.0" pre-commit = "~=2.20" pylint = "~=2.15.3" From 905eca69daa252238def9618ddd52588e5a28976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:58:44 +0900 Subject: [PATCH 133/548] chore(deps): update selenium requirement from ~=4.4 to ~=4.5 (#780) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.4.0...selenium-4.5.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 41c15d6c4..f5ee7e725 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ typing-extensions = "~=4.3" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.4" +selenium = "~=4.5" From 62bc72ca114967490784823b1e1da6d11791f492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:59:28 +0900 Subject: [PATCH 134/548] chore(deps-dev): update pytest-cov requirement from ~=3.0 to ~=4.0 (#779) 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/v3.0.0...v4.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index f5ee7e725..77c9fc9f9 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ mock = "~=4.0" pre-commit = "~=2.20" pylint = "~=2.15.3" pytest = "~=7.1" -pytest-cov = "~=3.0" +pytest-cov = "~=4.0" python-dateutil = "~=2.8" tox = "~=3.26" typing-extensions = "~=4.3" From d7edba49973613d5d6d74bebdb6e3c7b75f0dcec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:13:06 -0700 Subject: [PATCH 135/548] chore(deps-dev): update mypy requirement from ~=0.981 to ~=0.982 (#782) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.981...v0.982) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 77c9fc9f9..2ddcd09e2 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.8.0" httpretty = "~=1.1" isort = "~=5.10" -mypy = "~=0.981" +mypy = "~=0.982" mock = "~=4.0" pre-commit = "~=2.20" pylint = "~=2.15.3" From c9e2632e64214c96c1c19aad53734016dff59dfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 19:47:23 -0700 Subject: [PATCH 136/548] chore(deps-dev): update black requirement from ~=22.8.0 to ~=22.10.0 (#784) Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.8.0...22.10.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 2ddcd09e2..0df846016 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -black = "~=22.8.0" +black = "~=22.10.0" httpretty = "~=1.1" isort = "~=5.10" mypy = "~=0.982" From 1848fdf915dc57d22cc04964b69239f6bc3c7250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 20:07:40 -0700 Subject: [PATCH 137/548] chore(deps-dev): update typing-extensions requirement (#783) Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - [Release notes](https://github.com/python/typing_extensions/releases) - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.3.0...4.4.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 0df846016..9c382447d 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.1" pytest-cov = "~=4.0" python-dateutil = "~=2.8" tox = "~=3.26" -typing-extensions = "~=4.3" +typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" [packages] From 033071d0603b9bade3bc49c92459b064ae574a02 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 11 Oct 2022 07:13:30 +0200 Subject: [PATCH 138/548] refactor: Make service startup failures more helpful (#786) --- appium/webdriver/appium_service.py | 228 ++++++++++++++++------------- 1 file changed, 130 insertions(+), 98 deletions(-) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index e99c1ad0b..c3541fed0 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -17,9 +17,9 @@ import subprocess as sp import sys import time -from typing import Any, List, Optional, Union +from typing import Any, List, Optional, Set -import urllib3 +from selenium.webdriver.remote.remote_connection import urllib3 DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 4723 @@ -29,6 +29,14 @@ DEFAULT_BASE_PATH = '/' +class AppiumServiceError(RuntimeError): + pass + + +class AppiumStartupError(RuntimeError): + pass + + def find_executable(executable: str) -> Optional[str]: path = os.environ['PATH'] paths = path.split(os.pathsep) @@ -47,97 +55,110 @@ def find_executable(executable: str) -> Optional[str]: return None -def poll_url(host: str, port: int, path: str, timeout_ms: int) -> bool: - time_started_sec = time.time() - conn = urllib3.PoolManager(timeout=1.0) - while time.time() < time_started_sec + timeout_ms / 1000.0: +def get_node() -> str: + result = find_executable('node') + if result is None: + raise AppiumServiceError( + 'NodeJS main executable cannot be found. Make sure it is installed and present in PATH' + ) + return result + + +def get_npm() -> str: + result = find_executable('npm.cmd' if sys.platform == 'win32' else 'npm') + if result is None: + raise AppiumServiceError( + 'Node Package Manager executable cannot be found. Make sure it is installed and present in PATH' + ) + return result + + +def get_main_script(node: Optional[str], npm: Optional[str]) -> str: + result: Optional[str] = None + npm_path = npm or get_npm() + for args in [['root', '-g'], ['root']]: + try: + modules_root = sp.check_output([npm_path] + args).strip().decode('utf-8') + if os.path.exists(os.path.join(modules_root, MAIN_SCRIPT_PATH)): + result = os.path.join(modules_root, MAIN_SCRIPT_PATH) + break + except sp.CalledProcessError: + continue + if result is None: + node_path = node or get_node() try: - resp = conn.request('HEAD', f'http://{host}:{port}{path}') - if resp.status < 400: - return True - except urllib3.exceptions.HTTPError: - pass - time.sleep(1.0) - return False + result = ( + sp.check_output([node_path, '-e', f'console.log(require.resolve("{MAIN_SCRIPT_PATH}"))']) + .decode('utf-8') + .strip() + ) + except sp.CalledProcessError as e: + raise AppiumServiceError(e.output) from e + return result -class AppiumServiceError(RuntimeError): - pass +def parse_arg_value(args: List[str], arg_names: Set[str], default: str) -> str: + for idx, arg in enumerate(args): + if arg in arg_names and idx < len(args) - 1: + return args[idx + 1] + return default + + +def parse_port(args: List[str]) -> int: + return int(parse_arg_value(args, {'--port', '-p'}, str(DEFAULT_PORT))) + + +def parse_base_path(args: List[str]) -> str: + return parse_arg_value(args, {'--base-path', '-pa'}, DEFAULT_BASE_PATH) + + +def parse_host(args: List[str]) -> str: + return parse_arg_value(args, {'--address', '-a'}, DEFAULT_HOST) + + +def make_status_url(args: List[str]) -> str: + base_path = parse_base_path(args) + return STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"/+$", "", base_path)}{STATUS_URL}' class AppiumService: def __init__(self) -> None: self._process: Optional[sp.Popen] = None - self._cmd: Optional[List] = None - - def _get_node(self) -> str: - if not hasattr(self, '_node_executable'): - self._node_executable = find_executable('node') - if self._node_executable is None: - raise AppiumServiceError( - 'NodeJS main executable cannot be found. ' + 'Make sure it is installed and present in PATH' - ) - return self._node_executable - - 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: - raise AppiumServiceError( - 'Node Package Manager executable cannot be found. ' + 'Make sure it is installed and present in PATH' - ) - return self._npm_executable - - def _get_main_script(self) -> Union[str, bytes]: - if not hasattr(self, '_main_script'): - for args in [['root', '-g'], ['root']]: - try: - modules_root = sp.check_output([self._get_npm()] + args).strip().decode('utf-8') - if os.path.exists(os.path.join(modules_root, MAIN_SCRIPT_PATH)): - self._main_script: Union[str, bytes] = os.path.join(modules_root, MAIN_SCRIPT_PATH) - break - except sp.CalledProcessError: - continue - if not hasattr(self, '_main_script'): - try: - self._main_script = sp.check_output( - [self._get_node(), '-e', f'console.log(require.resolve("{MAIN_SCRIPT_PATH}"))'] - ).strip() - except sp.CalledProcessError as e: - raise AppiumServiceError(e.output) from e - return self._main_script - - @staticmethod - 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_base_path(args: List[str]) -> str: - for idx, arg in enumerate(args or []): - if arg in ('--base-path', '-pa') and idx < len(args) - 1: - return args[idx + 1] - return DEFAULT_BASE_PATH - - @staticmethod - 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 + self._cmd: Optional[List[str]] = None + + def _poll_status(self, host: str, port: int, path: str, timeout_ms: int) -> bool: + time_started_sec = time.time() + conn = urllib3.PoolManager(timeout=1.0) + while time.time() < time_started_sec + timeout_ms / 1000.0: + if not self.is_running: + raise AppiumStartupError() + # noinspection PyUnresolvedReferences + try: + resp = conn.request('HEAD', f'http://{host}:{port}{path}') + if resp.status < 400: + return True + except urllib3.exceptions.HTTPError: + pass + time.sleep(1.0) + return False def start(self, **kwargs: Any) -> sp.Popen: """Starts Appium service with given arguments. + If you use the service to start Appium 1.x + then consider providing ['--base-path', '/wd/hub'] arguments. By default, + the service assumes Appium server listens on '/' path, which is the default path + for Appium 2. + The service will be forcefully restarted if it is already running. Keyword Args: env (dict): Environment variables mapping. The default system environment, which is inherited from the parent process, is assigned by default. node (str): The full path to the main NodeJS executable. The service will try - to retrieve it automatically by default. + to retrieve it automatically if not provided. + npm (str): The full path to the Node Package Manager (npm) script. The service will try + to retrieve it automatically if not provided. stdout (int): Check the documentation for subprocess.Popen for more details. The default value is subprocess.DEVNULL on Windows and subprocess.PIPE on other platforms. stderr (int): Check the documentation for subprocess.Popen for more details. @@ -146,7 +167,7 @@ def start(self, **kwargs: Any) -> sp.Popen: for HTTP connections. If set to zero or a negative number then no wait will be applied. 60000 ms by default. main_script (str): The full path to the main Appium executable - (usually located at build/lib/main.js). If this is not set + (usually located at build/lib/main.js). If not set then the service tries to detect the path automatically. args (str): List of Appium arguments (all must be strings). Check https://appium.io/docs/en/writing-running-appium/server-args/ for more details @@ -160,27 +181,38 @@ def start(self, **kwargs: Any) -> sp.Popen: self.stop() env = kwargs['env'] if 'env' in kwargs else None - node = kwargs['node'] if 'node' in kwargs else self._get_node() + node: str = kwargs.get('node') or get_node() + npm: str = kwargs.get('npm') or get_npm() + main_script: str = kwargs.get('main_script') or get_main_script(node, npm) # A workaround for https://github.com/appium/python-client/issues/534 default_std = sp.DEVNULL if sys.platform == 'win32' else sp.PIPE stdout = kwargs['stdout'] if 'stdout' in kwargs else default_std stderr = kwargs['stderr'] if 'stderr' in kwargs else default_std 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] + args: List[str] = [node, main_script] if 'args' in kwargs: args.extend(kwargs['args']) self._cmd = args self._process = sp.Popen(args=args, stdout=stdout, stderr=stderr, env=env) - host = self._parse_host(args) - port = self._parse_port(args) error_msg: Optional[str] = None - base_path = self._parse_base_path(args) - status_url_path = ( - STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"[/]+$", "", base_path)}{STATUS_URL}' + startup_failure_msg = ( + 'Appium server process is unable to start. Make sure proper values have been ' + f'provided to \'node\' ({node}), \'npm\' ({npm}) and \'main_script\' ({main_script}) ' + f'method arguments.' ) - if not self.is_running or (timeout_ms > 0 and not poll_url(host, port, status_url_path, timeout_ms)): - error_msg = f'Appium has failed to start on {host}:{port} within {timeout_ms}ms timeout' + if timeout_ms > 0: + status_url_path = make_status_url(args) + try: + if not self._poll_status(parse_host(args), parse_port(args), status_url_path, timeout_ms): + error_msg = ( + f'Appium server has started but is not listening on {status_url_path} ' + f'within {timeout_ms}ms timeout. Make sure proper values have been provided ' + f'to --base-path, --address and --port process arguments.' + ) + except AppiumStartupError: + error_msg = startup_failure_msg + elif not self.is_running: + error_msg = startup_failure_msg if error_msg is not None: if stderr == sp.PIPE and self._process.stderr is not None: err_output = self._process.stderr.read() @@ -201,7 +233,8 @@ def stop(self) -> bool: """ is_terminated = False if self.is_running: - self._process.terminate() # type: ignore + assert self._process + self._process.terminate() is_terminated = True self._process = None self._cmd = None @@ -214,28 +247,27 @@ def is_running(self) -> bool: Returns: bool: `True` if the service is running """ - return self._process is not None and self._process.poll() is None + return self._process is not None and self._cmd is not None and self._process.poll() is None @property 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. - the default host/port values can be customized by providing --address/--port - command line arguments while starting the service. + The default host/port/base path values can be customized by providing + --address/--port/--base-path command line arguments while starting the service. Returns: bool: `True` if the service is running and listening on the given/default host/port """ - if not self.is_running or self._cmd is None: + if not self.is_running: + return False + + assert self._cmd + try: + return self._poll_status(parse_host(self._cmd), parse_port(self._cmd), make_status_url(self._cmd), 1000) + except AppiumStartupError: return False - host = self._parse_host(self._cmd) - port = self._parse_port(self._cmd) - base_path = self._parse_base_path(self._cmd) - status_url_path = ( - STATUS_URL if base_path == DEFAULT_BASE_PATH else f'{re.sub(r"[/]+$", "", base_path)}{STATUS_URL}' - ) - return self.is_running and poll_url(host, port, status_url_path, 1000) if __name__ == '__main__': From 2b9de433c57b6b1519e92cd240146f71b6e2066d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 10 Oct 2022 22:39:58 -0700 Subject: [PATCH 139/548] Bump 2.7.1 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 9c033f037..5046abedd 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.7.0' +version = '2.7.1' From bb8e64254b311ad87ca9b2cc61af34f405af3066 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 10 Oct 2022 22:40:25 -0700 Subject: [PATCH 140/548] Update changelog for 2.7.1 --- CHANGELOG.rst | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d4d55b86c..287e7d3f3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,116 @@ Changelog ========= +v2.7.1 (2022-10-11) +------------------- + +Test +~~~~ +- Ci: run unit tests on actions (#773) [Kazuaki Matsuo] + + * ci: run unit tests on actions + + * ci: remove unit test section + + * ci: comment out win for now + + * ci: tweak trigger + +Other +~~~~~ +- Refactor: Make service startup failures more helpful (#786) [Mykola + Mokhnach] +- Chore(deps-dev): update typing-extensions requirement (#783) [Kazuaki + Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.3.0...4.4.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore(deps-dev): update black requirement from ~=22.8.0 to ~=22.10.0 + (#784) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.8.0...22.10.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=0.981 to ~=0.982 + (#782) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.981...v0.982) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pytest-cov requirement from ~=3.0 to ~=4.0 + (#779) [dependabot[bot], dependabot[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/v3.0.0...v4.0.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.4 to ~=4.5 (#780) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.4.0...selenium-4.5.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update mypy requirement from ~=0.971 to ~=0.981 + (#777) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.971...v0.981) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.2 to ~=2.15.3 + (#774) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Bump 2.7.0. [Kazuaki Matsuo] +- Update changelog for 2.6.3. [Kazuaki Matsuo] + + v2.7.0 (2022-09-22) ------------------- From c78e2406b07ffefceff35ed3ffd52e89ef521dfd Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 11 Oct 2022 01:14:44 -0700 Subject: [PATCH 141/548] chore: update precommit (#787) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 094d3bb1c..f88890791 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10 + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 hooks: - id: isort args: [ "." ] From a41b2fc6c42a6f6318a08b0a7c19f0785cff41d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:52:31 -0700 Subject: [PATCH 142/548] chore(deps-dev): update pylint requirement from ~=2.15.3 to ~=2.15.4 (#788) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.3...v2.15.4) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 9c382447d..fb022dca0 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.982" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.3" +pylint = "~=2.15.4" pytest = "~=7.1" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 6c2768931fc11a17dbed9f2b635ba4628c3426cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 12:28:18 -0700 Subject: [PATCH 143/548] chore(deps-dev): update pylint requirement from ~=2.15.4 to ~=2.15.5 (#790) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.4...v2.15.5) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index fb022dca0..2a9e2da15 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.982" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.4" +pylint = "~=2.15.5" pytest = "~=7.1" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From d5f7a2571f3bed15ad26edb5d1fd907c4508f965 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:01:48 -0700 Subject: [PATCH 144/548] chore(deps-dev): update pytest requirement from ~=7.1 to ~=7.2 (#791) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.2.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 2a9e2da15..1bbf8fcfc 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ mypy = "~=0.982" mock = "~=4.0" pre-commit = "~=2.20" pylint = "~=2.15.5" -pytest = "~=7.1" +pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" tox = "~=3.26" From df5bcb8725e3fead2ada8e0f368fa9cd24ac3555 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:10:41 +0900 Subject: [PATCH 145/548] chore(deps-dev): update tox requirement from ~=3.26 to ~=3.27 (#792) 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.26.0...3.27.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1bbf8fcfc..7ebdc172a 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.5" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=3.26" +tox = "~=3.27" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" From 47db48349d7b90bc18f0df1b926e24287ccb5a06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 09:00:15 -0800 Subject: [PATCH 146/548] chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 (#798) * chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.982...v0.991) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] * fix lint * apply black Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- Pipfile | 2 +- appium/protocols/webdriver/can_execute_commands.py | 4 ++-- appium/protocols/webdriver/can_find_elements.py | 4 ++-- appium/webdriver/extensions/android/performance.py | 4 +++- appium/webdriver/extensions/applications.py | 4 ++-- appium/webdriver/extensions/log_event.py | 2 +- appium/webdriver/webdriver.py | 12 ++++++------ appium/webdriver/webelement.py | 4 ++-- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index 7ebdc172a..9f7495efa 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.10.0" httpretty = "~=1.1" isort = "~=5.10" -mypy = "~=0.982" +mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" pylint = "~=2.15.5" diff --git a/appium/protocols/webdriver/can_execute_commands.py b/appium/protocols/webdriver/can_execute_commands.py index febd82ac3..1c763c6e9 100644 --- a/appium/protocols/webdriver/can_execute_commands.py +++ b/appium/protocols/webdriver/can_execute_commands.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict +from typing import Dict, Union from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -22,5 +22,5 @@ class CanExecuteCommands(Protocol): command_executor: RemoteConnection - def execute(self, driver_command: str, params: Dict = None) -> Dict: + def execute(self, driver_command: str, params: Union[Dict, None] = None) -> Dict: ... diff --git a/appium/protocols/webdriver/can_find_elements.py b/appium/protocols/webdriver/can_find_elements.py index 53c1c049a..8ab1bd644 100644 --- a/appium/protocols/webdriver/can_find_elements.py +++ b/appium/protocols/webdriver/can_find_elements.py @@ -21,8 +21,8 @@ class CanFindElements(Protocol): - def find_element(self, by: str, value: Union[str, Dict] = None) -> 'WebElement': + def find_element(self, by: str, value: Union[str, Dict, None] = None) -> 'WebElement': ... - def find_elements(self, by: str, value: Union[str, Dict] = None) -> List['WebElement']: + def find_elements(self, by: str, value: Union[str, Dict, None] = None) -> List['WebElement']: ... diff --git a/appium/webdriver/extensions/android/performance.py b/appium/webdriver/extensions/android/performance.py index ee144b608..2c4f46690 100644 --- a/appium/webdriver/extensions/android/performance.py +++ b/appium/webdriver/extensions/android/performance.py @@ -19,7 +19,9 @@ class Performance(CanExecuteCommands): - def get_performance_data(self, package_name: str, data_type: str, data_read_timeout: int = None) -> List[List[str]]: + def get_performance_data( + self, package_name: str, data_type: str, data_read_timeout: Union[int, None] = None + ) -> List[List[str]]: """Returns the information of the system state which is supported to read as like cpu, memory, network traffic, and battery. diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index cbd5055b1..ff9b7aecf 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import warnings -from typing import Any, Dict, TypeVar +from typing import Any, Dict, TypeVar, Union from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands @@ -186,7 +186,7 @@ class for more details. } return self.execute(Command.QUERY_APP_STATE, data)['value'] - def app_strings(self, language: str = None, string_file: str = None) -> Dict[str, str]: + def app_strings(self, language: Union[str, None] = None, string_file: Union[str, None] = None) -> Dict[str, str]: """Returns the application strings from the device for the specified language. diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index 028e4e51f..f24a8d304 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -22,7 +22,7 @@ class LogEvent(CanExecuteCommands): - def get_events(self, type: List[str] = None) -> Dict[str, Union[str, int]]: + def get_events(self, type: Union[List[str], None] = None) -> Dict[str, Union[str, int]]: """Retrieves events information from the current session (Since Appium 1.16.0) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index c64465072..00f0eaf9f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -149,7 +149,7 @@ def add_command(self): def __init__(self, execute: Callable[[str, Dict], Dict[str, Any]]): self._execute = execute - def execute(self, parameters: Dict[str, Any] = None) -> Any: + def execute(self, parameters: Union[Dict[str, Any], None] = None) -> Any: param = {} if parameters: param = parameters @@ -205,13 +205,13 @@ 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, + browser_profile: Union[str, None] = None, + proxy: Union[str, None] = None, keep_alive: bool = True, direct_connection: bool = True, extensions: Optional[List['WebDriver']] = None, strict_ssl: bool = True, - options: Union[AppiumOptions, List[AppiumOptions]] = None, + options: Union[AppiumOptions, List[AppiumOptions], None] = None, ): if strict_ssl is False: @@ -338,7 +338,7 @@ def start_session(self, capabilities: Union[Dict, AppiumOptions], browser_profil self.session_id = session_id self.caps = get_response_value('capabilities') or {} - def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> MobileWebElement: + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None) -> MobileWebElement: """ Find an element given a AppiumBy strategy and locator @@ -371,7 +371,7 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> return self.execute(RemoteCommand.FIND_ELEMENT, {'using': by, 'value': value})['value'] def find_elements( - self, by: str = AppiumBy.ID, value: Union[str, Dict] = None + self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None ) -> Union[List[MobileWebElement], List]: """ Find elements given a AppiumBy strategy and locator diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 67f9a27c1..128ceb0cc 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -79,7 +79,7 @@ def is_displayed(self) -> bool: """ return self._execute(Command.IS_ELEMENT_DISPLAYED)['value'] - def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> 'WebElement': + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None) -> 'WebElement': """Find an element given a AppiumBy strategy and locator Override for Appium @@ -113,7 +113,7 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] - def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict] = None) -> List['WebElement']: + def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None) -> List['WebElement']: """Find elements given a AppiumBy strategy and locator Args: From c8cb24a1a7c7e60821ef25462110283b4bc0408e Mon Sep 17 00:00:00 2001 From: wojciodataist <53021027+wojciodataist@users.noreply.github.com> Date: Sat, 19 Nov 2022 08:47:57 +0100 Subject: [PATCH 147/548] chore: update docstring in touch_action.py (#797) add detailed information to long_press duration param Co-authored-by: Kazuaki Matsuo --- appium/webdriver/common/touch_action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 10060752c..79d52a4d1 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -108,7 +108,7 @@ def long_press( el: the element to press x: x coordiate to press. If y is used, x must also be set y: y coordiate to press. If x is used, y must also be set - duration: Duration to press + duration: Duration to press, expressed in milliseconds Returns: `TouchAction`: Self instance From 63464f8a1c1e85afa2b03897e745f1c17d2fcb6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:11:13 -0800 Subject: [PATCH 148/548] chore(deps-dev): update pylint requirement from ~=2.15.5 to ~=2.15.6 (#799) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.5...v2.15.6) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 9f7495efa..5e3befad4 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.5" +pylint = "~=2.15.6" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 36c602c4efc8385ae0cbf916cfa4e8e50b7868c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Nov 2022 11:27:57 -0800 Subject: [PATCH 149/548] chore(deps-dev): update pylint requirement from ~=2.15.6 to ~=2.15.7 (#800) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.6...v2.15.7) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5e3befad4..699dbfcbf 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.6" +pylint = "~=2.15.7" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From ab13d7265d2956995806f73368e242170395d2b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Dec 2022 13:50:01 +0100 Subject: [PATCH 150/548] chore(deps): update selenium requirement from ~=4.5 to ~=4.7 (#801) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.5.0...selenium-4.7.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 699dbfcbf..b1af6cbab 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.5" +selenium = "~=4.7" From 3903e29d73cc6bd69fc9bf353a6c32714ec5ab97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 09:46:28 -0800 Subject: [PATCH 151/548] chore(deps-dev): update pylint requirement from ~=2.15.7 to ~=2.15.8 (#804) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b1af6cbab..d2cb8cab1 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.10" mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.7" +pylint = "~=2.15.8" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 45ca8cf6069dd515c1bd6bc4ae7656f7e2e7ed3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 15:36:42 -0800 Subject: [PATCH 152/548] chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 (#806) * chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/3.27.0...4.0.2) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] * add +allowlist_externals * use ci.sh Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- Pipfile | 2 +- tox.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index d2cb8cab1..3756b9b9c 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.8" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=3.27" +tox = "~=4.0" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" diff --git a/tox.ini b/tox.ini index 6823808bd..c4d757ab1 100644 --- a/tox.ini +++ b/tox.ini @@ -12,3 +12,4 @@ commands = pipenv lock --clear pipenv install --dev ./ci.sh +allowlist_externals = ./ci.sh From 8c51dc3d06f6ffdfe6f556176012998fe8db524f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 15:41:35 -0800 Subject: [PATCH 153/548] chore(deps-dev): update black requirement from ~=22.10.0 to ~=22.12.0 (#807) Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.10.0...22.12.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 3756b9b9c..52e6a1f52 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -black = "~=22.10.0" +black = "~=22.12.0" httpretty = "~=1.1" isort = "~=5.10" mypy = "~=0.991" From 8d8fb02bdb081f06cd51b8c39b74971c1ab81a55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 15:41:56 -0800 Subject: [PATCH 154/548] chore(deps-dev): update isort requirement from ~=5.10 to ~=5.11 (#808) Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.10.0...5.11.1) --- updated-dependencies: - dependency-name: isort dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kazuaki Matsuo --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 52e6a1f52..1de029400 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ verify_ssl = true [dev-packages] black = "~=22.12.0" httpretty = "~=1.1" -isort = "~=5.10" +isort = "~=5.11" mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" From 644aa724b7939aae01bdcd055b0541bf879bde2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 13:51:43 -0800 Subject: [PATCH 155/548] chore(deps-dev): update pylint requirement from ~=2.15.8 to ~=2.15.9 (#810) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.8...v2.15.9) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1de029400..57fba34d1 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=0.991" mock = "~=4.0" pre-commit = "~=2.20" -pylint = "~=2.15.8" +pylint = "~=2.15.9" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 55ac01faaff90dd14a87b94193fb30bb8511f124 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 05:42:15 +0900 Subject: [PATCH 156/548] chore(deps-dev): update pre-commit requirement from ~=2.20 to ~=2.21 (#811) 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/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.20.0...v2.21.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 57fba34d1..dd41ac0a6 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ httpretty = "~=1.1" isort = "~=5.11" mypy = "~=0.991" mock = "~=4.0" -pre-commit = "~=2.20" +pre-commit = "~=2.21" pylint = "~=2.15.9" pytest = "~=7.2" pytest-cov = "~=4.0" From 3c59823e94b5c19e60b00addc3454868897ac1df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 17:59:57 +0900 Subject: [PATCH 157/548] chore(deps-dev): update mock requirement from ~=4.0 to ~=5.0 (#812) 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/4.0.0...5.0.0) --- updated-dependencies: - dependency-name: mock dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index dd41ac0a6..5a4b9e57a 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,7 @@ black = "~=22.12.0" httpretty = "~=1.1" isort = "~=5.11" mypy = "~=0.991" -mock = "~=4.0" +mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.15.9" pytest = "~=7.2" From 827011e2dbe39c887edfb562848b9fb3eff54a1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 18:00:11 +0900 Subject: [PATCH 158/548] chore(deps-dev): update tox requirement from ~=4.0 to ~=4.1 (#813) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.0.0...4.1.1) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5a4b9e57a..ea446d5ce 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.9" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=4.0" +tox = "~=4.1" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" From 79412033c600a1369559d8b5cce084c471f072d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 01:39:36 -0800 Subject: [PATCH 159/548] chore(deps-dev): update tox requirement from ~=4.1 to ~=4.2 (#815) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.1.0...4.2.1) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ea446d5ce..8b8dbdb0b 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.9" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=4.1" +tox = "~=4.2" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" From 01d96fd8a4645c1ba0df89a8bdd372693f5c98b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 14:17:38 -0800 Subject: [PATCH 160/548] chore(deps-dev): update pylint requirement from ~=2.15.9 to ~=2.15.10 (#816) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.9...v2.15.10) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8b8dbdb0b..717cb16d0 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=0.991" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.15.9" +pylint = "~=2.15.10" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 8b96d054f2474f1a2349f144cd3f30d8613962b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 07:25:49 -1000 Subject: [PATCH 161/548] chore(deps): update sphinx requirement from <6.0,>=4.0 to >=4.0,<7.0 (#814) --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 536f4d5e1..76dee66b9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx >= 4.0, < 6.0 +sphinx >= 4.0, < 7.0 sphinx_rtd_theme < 2.0 From 0651afcf2350e8dec41779d242d7c5972b39b174 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 00:11:59 -0800 Subject: [PATCH 162/548] chore(deps-dev): update tox requirement from ~=4.2 to ~=4.3 (#817) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.2.0...4.3.1) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 717cb16d0..b13667470 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.10" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=4.2" +tox = "~=4.3" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" From 431aba1de859df4d5b34bd1d0216d4a9caa53a0d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 20 Jan 2023 10:39:53 -1000 Subject: [PATCH 163/548] feat: add status tentatively (#820) * feat: add status tentatively * update test * fix docstring * fix typo * fix lint --- appium/webdriver/mobilecommand.py | 2 ++ appium/webdriver/webdriver.py | 14 ++++++++++++++ test/unit/webdriver/webelement_test.py | 13 +++++++++++++ 3 files changed, 29 insertions(+) diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index 334f4710c..2a83f1a41 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -18,6 +18,8 @@ class MobileCommand: GET_SESSION = 'getSession' GET_ALL_SESSIONS = 'getAllSessions' + GET_STATUS = 'getStatus' + ## MJSONWP for Selenium v4 GET_LOCATION = 'getLocation' SET_LOCATION = 'setLocation' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 00f0eaf9f..2866b9e9f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -338,6 +338,18 @@ def start_session(self, capabilities: Union[Dict, AppiumOptions], browser_profil self.session_id = session_id self.caps = get_response_value('capabilities') or {} + def get_status(self) -> Dict: + """ + Get the Appium server status + + Usage: + driver.get_status() + Returns: + Dict: The status information + + """ + return self.execute(Command.GET_STATUS)['value'] + def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None) -> MobileWebElement: """ Find an element given a AppiumBy strategy and locator @@ -492,6 +504,8 @@ def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands + commands[Command.GET_STATUS] = ('GET', '/status') + # FIXME: remove after a while as MJSONWP commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py index 59464fd89..168a8ba0a 100644 --- a/test/unit/webdriver/webelement_test.py +++ b/test/unit/webdriver/webelement_test.py @@ -23,6 +23,19 @@ class TestWebElement(object): + @httpretty.activate + def test_status(self): + driver = android_w3c_driver() + response = {'ready': True, 'message': {'build': {'version': '2.0.0', 'revision': None}}} + httpretty.register_uri( + httpretty.GET, + appium_command('/status'), + body=json.dumps({"value": response}), + ) + s = driver.get_status() + + assert s == response + @httpretty.activate def test_set_value(self): driver = android_w3c_driver() From bd041df9be0ace407103af1e42c6a136bf5c4bd4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 20 Jan 2023 20:10:59 -0800 Subject: [PATCH 164/548] Update changelog for 2.8.1 --- CHANGELOG.rst | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 287e7d3f3..e49f62228 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,293 @@ Changelog ========= +v2.8.1 (2023-01-20) +------------------- + +New +~~~ +- Feat: add status tentatively (#820) [Kazuaki Matsuo] + + * feat: add status tentatively + + * update test + + * fix docstring + + * fix typo + + * fix lint + + +v2.8.0 (2023-01-20) +------------------- + +New +~~~ +- Feat: add status tentatively. [Kazuaki Matsuo] + +Fix +~~~ +- Fix lint. [Kazuaki Matsuo] +- Fix typo. [Kazuaki Matsuo] +- Fix docstring. [Kazuaki Matsuo] + +Other +~~~~~ +- Update test. [Kazuaki Matsuo] +- Chore(deps-dev): update tox requirement from ~=4.2 to ~=4.3 (#817) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.2.0...4.3.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps): update sphinx requirement from <6.0,>=4.0 to >=4.0,<7.0 + (#814) [dependabot[bot]] +- Chore(deps-dev): update pylint requirement from ~=2.15.9 to ~=2.15.10 + (#816) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.9...v2.15.10) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.1 to ~=4.2 (#815) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.1.0...4.2.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.0 to ~=4.1 (#813) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.0.0...4.1.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update mock requirement from ~=4.0 to ~=5.0 (#812) + [dependabot[bot], dependabot[bot]] + + 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/4.0.0...5.0.0) + + --- + updated-dependencies: + - dependency-name: mock + dependency-type: direct:development + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.20 to ~=2.21 + (#811) [dependabot[bot], dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.20.0...v2.21.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.8 to ~=2.15.9 + (#810) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.8...v2.15.9) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update isort requirement from ~=5.10 to ~=5.11 (#808) + [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.10.0...5.11.1) + + --- + updated-dependencies: + - dependency-name: isort + dependency-type: direct:development + ... +- Chore(deps-dev): update black requirement from ~=22.10.0 to ~=22.12.0 + (#807) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.10.0...22.12.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 (#806) + [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + * chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 + + 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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/3.27.0...4.0.2) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.7 to ~=2.15.8 + (#804) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.5 to ~=4.7 (#801) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.5.0...selenium-4.7.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.6 to ~=2.15.7 + (#800) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.6...v2.15.7) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.5 to ~=2.15.6 + (#799) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.5...v2.15.6) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: update docstring in touch_action.py (#797) [Kazuaki Matsuo, + wojciodataist] + + add detailed information to long_press duration param +- Chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 + (#798) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + * chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.982...v0.991) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=3.26 to ~=3.27 (#792) + [dependabot[bot], dependabot[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.26.0...3.27.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update pytest requirement from ~=7.1 to ~=7.2 (#791) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.2.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.4 to ~=2.15.5 + (#790) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.4...v2.15.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.3 to ~=2.15.4 + (#788) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.3...v2.15.4) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: update precommit (#787) [Kazuaki Matsuo] +- Update changelog for 2.7.1. [Kazuaki Matsuo] +- Bump 2.7.1. [Kazuaki Matsuo] + + v2.7.1 (2022-10-11) ------------------- From ec81af5cec22ea1f9b390b8147d95895040789bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jan 2023 22:41:39 -0800 Subject: [PATCH 165/548] chore(deps-dev): update tox requirement from ~=4.3 to ~=4.4 (#823) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.3.0...4.4.2) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b13667470..e6c44168b 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.15.10" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=4.3" +tox = "~=4.4" typing-extensions = "~=4.4" types-python-dateutil = "~=2.8" From 6acf1f0577940c3cdf216d27ba1cedf122abee60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 22:44:23 -0800 Subject: [PATCH 166/548] chore(deps-dev): update pylint requirement from ~=2.15.10 to ~=2.16.0 (#826) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e6c44168b..ce30907f1 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=0.991" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.15.10" +pylint = "~=2.16.0" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From cb7d8c508a23e5a57e70cfd6d942255c9109ef65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 12:12:42 +0900 Subject: [PATCH 167/548] chore(deps-dev): update pylint requirement from ~=2.16.0 to ~=2.16.1 (#827) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.0...v2.16.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ce30907f1..8678b1e4b 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=0.991" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.16.0" +pylint = "~=2.16.1" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 2e19f0d8139c2ab265346f033b809cac4b49d118 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:42:22 -0800 Subject: [PATCH 168/548] chore(deps-dev): update mypy requirement from ~=0.991 to ~=1.0 (#828) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8678b1e4b..a7f64a5b0 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.12.0" httpretty = "~=1.1" isort = "~=5.11" -mypy = "~=0.991" +mypy = "~=1.0" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.16.1" From 0cb646d28053afa82b16a3daea0a408498893de8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:33:17 -0800 Subject: [PATCH 169/548] chore(deps-dev): update pylint requirement from ~=2.16.1 to ~=2.16.2 (#829) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.1...v2.16.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index a7f64a5b0..0d7e9bb63 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.0" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.16.1" +pylint = "~=2.16.2" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From c2a80fa716d3ff12b850019dd6638bfde909408f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:33:32 -0800 Subject: [PATCH 170/548] chore(deps-dev): update typing-extensions requirement (#830) Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - [Release notes](https://github.com/python/typing_extensions/releases) - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.4.0...4.5.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 0d7e9bb63..55b61b0bd 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" tox = "~=4.4" -typing-extensions = "~=4.4" +typing-extensions = "~=4.5" types-python-dateutil = "~=2.8" [packages] From 91dc04bd4313227b860158cd0d72d45723bcc664 Mon Sep 17 00:00:00 2001 From: eyJhb Date: Tue, 21 Feb 2023 09:14:58 +0100 Subject: [PATCH 171/548] fix: set_value and set_text sent incorrect data (#831) --- appium/webdriver/webdriver.py | 14 ++++++++++---- appium/webdriver/webelement.py | 24 +++++++++++++++++++----- test/unit/webdriver/webelement_test.py | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 2866b9e9f..24b13ef54 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -14,6 +14,7 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use +import warnings from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union from selenium import webdriver @@ -434,6 +435,7 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: def set_value(self: T, element: MobileWebElement, value: str) -> T: """Set the value on an element in the application. + deprecated:: 2.8.1 Args: element: the element whose value will be set @@ -442,10 +444,14 @@ def set_value(self: T, element: MobileWebElement, value: str) -> T: Returns: `appium.webdriver.webdriver.WebDriver`: Self instance """ - data = { - 'id': element.id, - 'value': [value], - } + warnings.warn( + 'The "setValue" API is deprecated and will be removed in future versions. ' + 'Instead the "send_keys" API or W3C Actions can be used. ' + 'See https://github.com/appium/python-client/pull/831', + DeprecationWarning, + ) + + data = {'text': value} self.execute(Command.SET_IMMEDIATE_VALUE, data) return self diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 128ceb0cc..67e630eb7 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import warnings from typing import Callable, Dict, List, Optional, Union from selenium.webdriver.common.utils import keys_to_typing @@ -157,6 +158,7 @@ def clear(self) -> 'WebElement': def set_text(self, keys: str = '') -> 'WebElement': """Sends text to the element. + deprecated:: 2.8.1 Previous text is removed. Android only. @@ -170,7 +172,14 @@ def set_text(self, keys: str = '') -> 'WebElement': Returns: `appium.webdriver.webelement.WebElement` """ - data = {'id': self._id, 'value': [keys]} + warnings.warn( + 'The "setText" API is deprecated and will be removed in future versions. ' + 'Instead the "send_keys" API or W3C Actions can be used. ' + 'See https://github.com/appium/python-client/pull/831', + DeprecationWarning, + ) + + data = {'text': keys} self._execute(Command.REPLACE_KEYS, data) return self @@ -190,6 +199,7 @@ def location_in_view(self) -> Dict[str, int]: def set_value(self, value: str) -> 'WebElement': """Set the value on this element in the application + deprecated:: 2.8.1 Args: value: The value to be set @@ -197,10 +207,14 @@ def set_value(self, value: str) -> 'WebElement': Returns: `appium.webdriver.webelement.WebElement` """ - data = { - 'id': self.id, - 'value': [value], - } + warnings.warn( + 'The "setValue" API is deprecated and will be removed in future versions. ' + 'Instead the "send_keys" API or W3C Actions can be used. ' + 'See https://github.com/appium/python-client/pull/831', + DeprecationWarning, + ) + + data = {'text': value} self._execute(Command.SET_IMMEDIATE_VALUE, data) return self diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py index 168a8ba0a..aa6168f6b 100644 --- a/test/unit/webdriver/webelement_test.py +++ b/test/unit/webdriver/webelement_test.py @@ -46,7 +46,7 @@ def test_set_value(self): element.set_value(value) d = get_httpretty_request_body(httpretty.last_request()) - assert d['value'] == [value] + assert d['text'] == value @httpretty.activate def test_send_key(self): From 11beb71f7b9d9ae4b96abac62a8ef901165bff06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Mar 2023 23:08:39 -0800 Subject: [PATCH 172/548] chore(deps-dev): update pylint requirement from ~=2.16.2 to ~=2.16.3 (#834) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.2...v2.16.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 55b61b0bd..6cfe4daa3 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.0" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.16.2" +pylint = "~=2.16.3" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 8c845c1a33c9ed6045e7f60884608ba1f7430817 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 02:10:49 -0700 Subject: [PATCH 173/548] chore(deps-dev): update mypy requirement from ~=1.0 to ~=1.1 (#836) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.1.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 6cfe4daa3..4354c694a 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.12.0" httpretty = "~=1.1" isort = "~=5.11" -mypy = "~=1.0" +mypy = "~=1.1" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.16.3" From cbcc539827658c7aaa16147f8da7907405c59031 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 02:11:14 -0700 Subject: [PATCH 174/548] chore(deps-dev): update pylint requirement from ~=2.16.3 to ~=2.17.0 (#838) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.3...v2.17.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 4354c694a..328cdd304 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.1" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.16.3" +pylint = "~=2.17.0" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 6d558c0411e3e46d06f03c7fe72337e72699e599 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Mar 2023 17:12:04 -0700 Subject: [PATCH 175/548] chore(deps-dev): update pylint requirement from ~=2.17.0 to ~=2.17.1 (#843) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.0...v2.17.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 328cdd304..65012199e 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.1" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.17.0" +pylint = "~=2.17.1" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 2c92c04b3d470ec00ce96d2696483ae02f4df0d8 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 2 Apr 2023 05:27:12 +0900 Subject: [PATCH 176/548] feat: respect the given executor (#844) * feat: can provide a custom connection * add tests * tweak tests * lint * tweak * add comment * fix lint * tweak * add test * tweak review --- README.md | 66 ++++++++++++++++--- appium/webdriver/appium_connection.py | 19 +++++- appium/webdriver/webdriver.py | 7 ++- test/unit/webdriver/webdriver_test.py | 91 +++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8c6b113d2..8142fa473 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ options = UiAutomator2Options() options.platformVersion = '10' options.udid = '123456789ABC' options.app = PATH('../../../apps/test-app.apk') -# Appium1 points to http://127.0.0.1:4723/wd/hub by default +# Appium1 points to http://127.0.0.1:4723/wd/hub by default self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() @@ -134,7 +134,7 @@ options = XCUITestOptions() options.platformVersion = '13.4' options.udid = '123456789ABC' options.app = PATH('../../apps/UICatalog.app.zip') -# Appium1 points to http://127.0.0.1:4723/wd/hub by default +# Appium1 points to http://127.0.0.1:4723/wd/hub by default self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') el.click() @@ -158,7 +158,7 @@ from appium import webdriver # instead: https://github.com/appium/python-client/pull/720 from appium.options.ios import XCUITestOptions -# load_capabilities API could be used to +# load_capabilities API could be used to # load options mapping stored in a dictionary options = XCUITestOptions().load_capabilities({ 'platformVersion': '13.4', @@ -167,9 +167,9 @@ options = XCUITestOptions().load_capabilities({ }) self.driver = webdriver.Remote( - # Appium1 points to http://127.0.0.1:4723/wd/hub by default - 'http://127.0.0.1:4723', - options=options, + # Appium1 points to http://127.0.0.1:4723/wd/hub by default + 'http://127.0.0.1:4723', + options=options, direct_connection=True ) ``` @@ -192,10 +192,62 @@ options.automation_name = 'safari' # calls to it could be chained options.set_capability('browser_name', 'safari') -# Appium1 points to http://127.0.0.1:4723/wd/hub by default +# Appium1 points to http://127.0.0.1:4723/wd/hub by default self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=False) ``` +## Set custom `AppiumConnection` + +The first argument of `webdriver.Remote` can set an arbitrary command executor for you. + +1. Set init arguments for the pool manager Appium Python client uses to manage http requests. + +```python +from appium import webdriver +from appium.options.ios import XCUITestOptions + +import urllib3 +from appium.webdriver.appium_connection import AppiumConnection + +# Retry connection error up to 3 times. +init_args_for_pool_manage = { + 'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False) +} +appium_executor = AppiumConnection(remote_server_addr='http://127.0.0.1:4723', + init_args_for_pool_manage=init_args_for_pool_manage) + +options = XCUITestOptions() +options.platformVersion = '13.4' +options.udid = '123456789ABC' +options.app = PATH('../../apps/UICatalog.app.zip') +driver = webdriver.Remote(appium_executor, options=options) +``` + + +2. Define a subclass of `AppiumConnection` + +```python +from appium import webdriver +from appium.options.ios import XCUITestOptions + +from appium.webdriver.appium_connection import AppiumConnection + +class CustomAppiumConnection(AppiumConnection): + # Can add your own methods for the custom class + pass + +custom_executor = CustomAppiumConnection(remote_server_addr='http://127.0.0.1:4723') + +options = XCUITestOptions().load_capabilities({ + 'platformVersion': '13.4', + 'deviceName': 'iPhone Simulator', + 'app': PATH('../../apps/UICatalog.app.zip'), +}) +driver = webdriver.Remote(custom_executor, options=options) + +``` + + ## Documentation - https://appium.github.io/python-client-sphinx/ is detailed documentation diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 74b6c7d10..ce9f296fa 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -13,7 +13,7 @@ # limitations under the License. import uuid -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Union import urllib3 from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -25,10 +25,23 @@ class AppiumConnection(RemoteConnection): + def __init__( + self, + remote_server_addr: str, + keep_alive: bool = False, + ignore_proxy: Optional[bool] = False, + init_args_for_pool_manager: Union[Dict[str, Any], None] = None, + ): + + # Need to call before super().__init__ in order to pass arguments for the pool manager in the super. + self._init_args_for_pool_manager = init_args_for_pool_manager or {} + + super().__init__(remote_server_addr, keep_alive=keep_alive, ignore_proxy=ignore_proxy) + def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyManager]: # https://github.com/SeleniumHQ/selenium/blob/0e0194b0e52a34e7df4b841f1ed74506beea5c3e/py/selenium/webdriver/remote/remote_connection.py#L134 pool_manager_init_args = {'timeout': self._timeout} - # pylint: disable=E1101 + if self._ca_certs: pool_manager_init_args['cert_reqs'] = 'CERT_REQUIRED' pool_manager_init_args['ca_certs'] = self._ca_certs @@ -36,6 +49,8 @@ def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyMan # This line is necessary to disable certificate verification pool_manager_init_args['cert_reqs'] = 'CERT_NONE' + pool_manager_init_args.update(self._init_args_for_pool_manager) + return ( urllib3.PoolManager(**pool_manager_init_args) if self._proxy_url is None diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 24b13ef54..49f857fd7 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -204,7 +204,7 @@ class WebDriver( ): def __init__( self, - command_executor: str = 'http://127.0.0.1:4444/wd/hub', + command_executor: Union[str, AppiumConnection] = 'http://127.0.0.1:4444/wd/hub', desired_capabilities: Optional[Dict] = None, browser_profile: Union[str, None] = None, proxy: Union[str, None] = None, @@ -228,8 +228,11 @@ def __init__( AppiumConnection.set_certificate_bundle_path(None) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + if isinstance(command_executor, str): + command_executor = AppiumConnection(command_executor, keep_alive=keep_alive) + super().__init__( - command_executor=AppiumConnection(command_executor, keep_alive=keep_alive), + command_executor=command_executor, desired_capabilities=desired_capabilities, browser_profile=browser_profile, proxy=proxy, diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 51ea40d93..4b575bbb1 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -15,11 +15,13 @@ import json import httpretty +import urllib3 from mock import patch from appium import version as appium_version from appium import webdriver from appium.options.android import UiAutomator2Options +from appium.webdriver.appium_connection import AppiumConnection from appium.webdriver.webdriver import ExtensionBase, WebDriver from test.helpers.constants import SERVER_URL_BASE from test.unit.helper.test_helper import ( @@ -298,6 +300,95 @@ def add_command(self): assert result == {} driver.delete_extensions() + @httpretty.activate + def test_create_session_with_custom_connection(self): + httpretty.register_uri( + httpretty.POST, + f'{SERVER_URL_BASE}/session', + body='{ "value": {"sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}} }', + ) + + desired_caps = { + 'deviceName': 'Android Emulator', + 'app': 'path/to/app', + } + + class CustomAppiumConnection(AppiumConnection): + # To explicitly check if the given executor is used + pass + + init_args_for_pool_manager = {'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False)} + custom_appium_connection = CustomAppiumConnection( + remote_server_addr=SERVER_URL_BASE, init_args_for_pool_manager=init_args_for_pool_manager + ) + + driver = webdriver.Remote( + custom_appium_connection, options=UiAutomator2Options().load_capabilities(desired_caps) + ) + + request = httpretty.HTTPretty.latest_requests[0] + assert request.headers['content-type'] == 'application/json;charset=UTF-8' + assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] + + request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) + assert request_json.get('capabilities') is not None + assert request_json['capabilities']['alwaysMatch'] == { + 'platformName': 'Android', + 'appium:deviceName': 'Android Emulator', + 'appium:app': 'path/to/app', + 'appium:automationName': 'UIAutomator2', + } + assert request_json.get('desiredCapabilities') is None + assert driver.session_id == 'session-id' + + assert isinstance(driver.command_executor, CustomAppiumConnection) + + @httpretty.activate + def test_create_session_with_custom_connection_with_keepalive(self): + httpretty.register_uri( + httpretty.POST, + f'{SERVER_URL_BASE}/session', + body='{ "value": {"sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}} }', + ) + + desired_caps = { + 'deviceName': 'Android Emulator', + 'app': 'path/to/app', + } + + class CustomAppiumConnection(AppiumConnection): + # To explicitly check if the given executor is used + pass + + init_args_for_pool_manager = {'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False)} + custom_appium_connection = CustomAppiumConnection( + # keep alive has different route to set init args for the pool manager + keep_alive=True, + remote_server_addr=SERVER_URL_BASE, + init_args_for_pool_manager=init_args_for_pool_manager, + ) + + driver = webdriver.Remote( + custom_appium_connection, options=UiAutomator2Options().load_capabilities(desired_caps) + ) + + request = httpretty.HTTPretty.latest_requests[0] + assert request.headers['content-type'] == 'application/json;charset=UTF-8' + assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] + + request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) + assert request_json.get('capabilities') is not None + assert request_json['capabilities']['alwaysMatch'] == { + 'platformName': 'Android', + 'appium:deviceName': 'Android Emulator', + 'appium:app': 'path/to/app', + 'appium:automationName': 'UIAutomator2', + } + assert request_json.get('desiredCapabilities') is None + assert driver.session_id == 'session-id' + + assert isinstance(driver.command_executor, CustomAppiumConnection) + class SubWebDriver(WebDriver): def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None): From a98852dba80211582a9b70131883ecc0239bbbaf Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 1 Apr 2023 22:20:16 -0700 Subject: [PATCH 177/548] Update changelog for 2.8.0 --- CHANGELOG.rst | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e49f62228..c73712ef9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,164 @@ Changelog ========= +(unreleased) +------------ + +New +~~~ +- Feat: respect the given executor (#844) [Kazuaki Matsuo] + + * feat: can provide a custom connection + + * add tests + + * tweak tests + + * lint + + * tweak + + * add comment + + * fix lint + + * tweak + + * add test + + * tweak review + +Fix +~~~ +- Set_value and set_text sent incorrect data (#831) [eyJhb] + +Other +~~~~~ +- Chore(deps-dev): update pylint requirement from ~=2.17.0 to ~=2.17.1 + (#843) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.0...v2.17.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.3 to ~=2.17.0 + (#838) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.3...v2.17.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=1.0 to ~=1.1 (#836) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.1.1) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.2 to ~=2.16.3 + (#834) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.2...v2.16.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update typing-extensions requirement (#830) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.4.0...4.5.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.1 to ~=2.16.2 + (#829) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.1...v2.16.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=0.991 to ~=1.0 (#828) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.0 to ~=2.16.1 + (#827) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.0...v2.16.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.10 to ~=2.16.0 + (#826) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.3 to ~=4.4 (#823) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.3.0...4.4.2) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Update changelog for 2.8.1. [Kazuaki Matsuo] + + v2.8.1 (2023-01-20) ------------------- From 8711ca1712658c35f38419044f848225477d22eb Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 1 Apr 2023 22:24:34 -0700 Subject: [PATCH 178/548] Update changelog for 2.9.0 --- CHANGELOG.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c73712ef9..720c078fb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog ========= -(unreleased) ------------- +v2.9.0 (2023-04-02) +------------------- New ~~~ @@ -35,6 +35,7 @@ Fix Other ~~~~~ +- Update changelog for 2.8.0. [Kazuaki Matsuo] - Chore(deps-dev): update pylint requirement from ~=2.17.0 to ~=2.17.1 (#843) [dependabot[bot], dependabot[bot]] From 49d38ddb18fc7341bb901a6642f15823a7bc80b6 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 1 Apr 2023 22:38:16 -0700 Subject: [PATCH 179/548] chore: bump and correct version --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 5046abedd..716516129 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.7.1' +version = '2.9.0' From 37e357b1371f0e76ddbe3d0954d3315df19c15d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 23:15:29 -0700 Subject: [PATCH 180/548] chore(deps-dev): update pylint requirement from ~=2.17.1 to ~=2.17.2 (#847) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.1...v2.17.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 65012199e..52d4af3be 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.1" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.17.1" +pylint = "~=2.17.2" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From bb76339bc6b9bc3ae8eab7de1c416a1ff906317e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Apr 2023 08:36:22 +0900 Subject: [PATCH 181/548] chore(deps-dev): update mypy requirement from ~=1.1 to ~=1.2 (#848) --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 52d4af3be..ea013f22f 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ verify_ssl = true black = "~=22.12.0" httpretty = "~=1.1" isort = "~=5.11" -mypy = "~=1.1" +mypy = "~=1.2" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.17.2" From 790ffedc4440db92666fb1a5b17193e7b5b88343 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 00:42:59 -0700 Subject: [PATCH 182/548] chore(deps-dev): update tox requirement from ~=4.4 to ~=4.5 (#854) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.4.0...4.5.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ea013f22f..57715c788 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.17.2" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" -tox = "~=4.4" +tox = "~=4.5" typing-extensions = "~=4.5" types-python-dateutil = "~=2.8" From 4031de2aad7918da0e3b083fc2be5a37865e4d79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 00:43:29 -0700 Subject: [PATCH 183/548] chore(deps-dev): update pylint requirement from ~=2.17.2 to ~=2.17.3 (#853) Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.2...v2.17.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 57715c788..c58b1f534 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "~=5.11" mypy = "~=1.2" mock = "~=5.0" pre-commit = "~=2.21" -pylint = "~=2.17.2" +pylint = "~=2.17.3" pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" From 622f3df7f3871e7f3442af29ff274d855b10bb49 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 4 May 2023 06:27:51 +0200 Subject: [PATCH 184/548] refactor: Move driver-specific commands to use extensions (part1) (#856) --- Makefile | 2 +- Pipfile | 6 +- .../can_remember_extension_presence.py | 27 +++ appium/webdriver/appium_connection.py | 1 - appium/webdriver/extensions/action_helpers.py | 26 +-- appium/webdriver/extensions/android/power.py | 1 - appium/webdriver/extensions/applications.py | 195 ++++++++++++------ appium/webdriver/extensions/clipboard.py | 13 +- appium/webdriver/extensions/device_time.py | 21 +- appium/webdriver/extensions/execute_driver.py | 1 - .../extensions/execute_mobile_command.py | 9 +- appium/webdriver/extensions/hw_actions.py | 96 ++++++--- appium/webdriver/extensions/ime.py | 54 ++++- appium/webdriver/extensions/keyboard.py | 104 ++++++---- appium/webdriver/extensions/location.py | 27 ++- appium/webdriver/extensions/log_event.py | 9 +- appium/webdriver/extensions/remote_fs.py | 60 ++++-- appium/webdriver/extensions/session.py | 13 ++ appium/webdriver/extensions/settings.py | 13 +- appium/webdriver/switch_to.py | 12 +- appium/webdriver/webdriver.py | 44 +++- ci-jobs/functional/setup_appium.yml | 2 +- test/functional/ios/webdriver_tests.py | 1 - test/unit/webdriver/app_test.py | 11 + .../unit/webdriver/device/device_time_test.py | 17 +- .../unit/webdriver/device/fingerprint_test.py | 10 +- test/unit/webdriver/device/keyboard_test.py | 20 +- test/unit/webdriver/device/location_test.py | 1 + test/unit/webdriver/device/lock_test.py | 15 +- test/unit/webdriver/device/remote_fs_test.py | 30 ++- test/unit/webdriver/device/shake_test.py | 4 + 31 files changed, 606 insertions(+), 239 deletions(-) create mode 100644 appium/protocols/webdriver/can_remember_extension_presence.py diff --git a/Makefile b/Makefile index 0308854c3..0170afb21 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ check-all: ## Run all lint checks and unittest .PHONY: isort isort: ## Run isort - python -m isort $(ARGS) . + python -m isort --profile black $(ARGS) . .PHONY: black black: ## Run black diff --git a/Pipfile b/Pipfile index c58b1f534..66b0ac0f4 100644 --- a/Pipfile +++ b/Pipfile @@ -4,10 +4,10 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -black = "~=22.12.0" +black = "<24.0.0" httpretty = "~=1.1" -isort = "~=5.11" -mypy = "~=1.2" +isort = "<6.0" +mypy = "<2.0" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.17.3" diff --git a/appium/protocols/webdriver/can_remember_extension_presence.py b/appium/protocols/webdriver/can_remember_extension_presence.py new file mode 100644 index 000000000..bf0003938 --- /dev/null +++ b/appium/protocols/webdriver/can_remember_extension_presence.py @@ -0,0 +1,27 @@ +#!/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. + +from typing import TypeVar + +from ..protocol import Protocol + +T = TypeVar('T') + + +class CanRememberExtensionPresence(Protocol): + def assert_extension_exists(self: T, ext_name: str) -> T: + ... + + def mark_extension_absence(self: T, ext_name: str) -> T: + ... diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index ce9f296fa..87915e867 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -32,7 +32,6 @@ def __init__( ignore_proxy: Optional[bool] = False, init_args_for_pool_manager: Union[Dict[str, Any], None] = None, ): - # Need to call before super().__init__ in order to pass arguments for the pool manager in the super. self._init_args_for_pool_manager = init_args_for_pool_manager or {} diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index e124450db..8c424a409 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Optional, Tuple, TypeVar +from typing import TYPE_CHECKING, List, Optional, Tuple, cast from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.actions import interaction @@ -20,14 +20,14 @@ from selenium.webdriver.common.actions.mouse_button import MouseButton from selenium.webdriver.common.actions.pointer_input import PointerInput -from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.webelement import WebElement -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class ActionHelpers: - def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> T: + def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Optional[int] = None) -> 'WebDriver': """Scrolls from one element to another Args: @@ -59,9 +59,9 @@ def scroll(self: T, origin_el: WebElement, destination_el: WebElement, duration: actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return cast('WebDriver', self) - def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> T: + def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': """Drag the origin element to the destination element Args: @@ -77,9 +77,9 @@ def drag_and_drop(self: T, origin_el: WebElement, destination_el: WebElement) -> actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return cast('WebDriver', self) - def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> T: + def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) -> 'WebDriver': """Taps on an particular place with up to five fingers, holding for a certain time @@ -127,9 +127,9 @@ def tap(self: T, positions: List[Tuple[int, int]], duration: Optional[int] = Non new_input.create_pause(0.1) new_input.create_pointer_up(MouseButton.LEFT) actions.perform() - return self + return cast('WebDriver', self) - def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> T: + def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0) -> 'WebDriver': """Swipe from one point to another point, for an optional duration. Args: @@ -156,9 +156,9 @@ def swipe(self: T, start_x: int, start_y: int, end_x: int, end_y: int, duration: actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return cast('WebDriver', self) - def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: + def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDriver': """Flick from one point to another point. Args: @@ -180,4 +180,4 @@ def flick(self: T, start_x: int, start_y: int, end_x: int, end_y: int) -> T: actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() - return self + return cast('WebDriver', self) diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index c3d762f8d..c1b9b2116 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -21,7 +21,6 @@ class Power(CanExecuteCommands): - AC_OFF, AC_ON = 'off', 'on' def set_power_capacity(self: T, percent: int) -> T: diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index ff9b7aecf..d4951b9d8 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -12,30 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. import warnings -from typing import Any, Dict, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, Union, cast + +from selenium.common.exceptions import InvalidArgumentException, UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Applications(CanExecuteCommands): - def background_app(self: T, seconds: int) -> T: +class Applications(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def background_app(self, seconds: int) -> 'WebDriver': """Puts the application in the background on the device for a certain duration. Args: - seconds: the duration for the application to remain in the background + seconds: the duration for the application to remain in the background. + Providing a negative value will continue immediately after putting the app + under test to the background. Returns: Union['WebDriver', 'Applications']: Self instance """ - data = { - 'seconds': seconds, - } - self.execute(Command.BACKGROUND, data) - return self + ext_name = 'mobile: backgroundApp' + args = {'seconds': seconds} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.BACKGROUND, args) + return cast('WebDriver', self) def is_app_installed(self, bundle_id: str) -> bool: """Checks whether the application specified by `bundle_id` is installed on the device. @@ -46,12 +56,25 @@ def is_app_installed(self, bundle_id: str) -> bool: Returns: `True` if app is installed """ - data = { - 'bundleId': bundle_id, - } - return self.execute(Command.IS_APP_INSTALLED, data)['value'] - - def install_app(self: T, app_path: str, **options: Any) -> T: + ext_name = 'mobile: isAppInstalled' + try: + return self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'bundleId': bundle_id, + 'appId': bundle_id, + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute( + Command.IS_APP_INSTALLED, + { + 'bundleId': bundle_id, + }, + )['value'] + + def install_app(self, app_path: str, **options: Any) -> 'WebDriver': """Install the application found at `app_path` on the device. Args: @@ -71,15 +94,25 @@ def install_app(self: T, app_path: str, **options: Any) -> T: Returns: Union['WebDriver', 'Applications']: Self instance """ - data: Dict[str, Any] = { - 'appPath': app_path, - } - if options: - data.update({'options': options}) - self.execute(Command.INSTALL_APP, data) - return self - - def remove_app(self: T, app_id: str, **options: Any) -> T: + ext_name = 'mobile: installApp' + try: + self.assert_extension_exists(ext_name).execute_script( + 'mobile: installApp', + { + 'app': app_path, + 'appPath': app_path, + **(options or {}), + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + data: Dict[str, Any] = {'appPath': app_path} + if options: + data.update({'options': options}) + self.mark_extension_absence(ext_name).execute(Command.INSTALL_APP, data) + return cast('WebDriver', self) + + def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': """Remove the specified application from the device. Args: @@ -94,15 +127,25 @@ def remove_app(self: T, app_id: str, **options: Any) -> T: Returns: Union['WebDriver', 'Applications']: Self instance """ - data: Dict[str, Any] = { - 'appId': app_id, - } - if options: - data.update({'options': options}) - self.execute(Command.REMOVE_APP, data) - return self - - def launch_app(self: T) -> T: + ext_name = 'mobile: removeApp' + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'appId': app_id, + 'bundleId': app_id, + **(options or {}), + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + data: Dict[str, Any] = {'appId': app_id} + if options: + data.update({'options': options}) + self.mark_extension_absence(ext_name).execute(Command.REMOVE_APP, data) + return cast('WebDriver', self) + + def launch_app(self) -> 'WebDriver': """Start on the device the application specified in the desired capabilities. deprecated:: 2.0.0 @@ -116,9 +159,9 @@ def launch_app(self: T) -> T: ) self.execute(Command.LAUNCH_APP) - return self + return cast('WebDriver', self) - def close_app(self: T) -> T: + def close_app(self) -> 'WebDriver': """Stop the running application, specified in the desired capabilities, on the device. deprecated:: 2.0.0 @@ -133,7 +176,7 @@ def close_app(self: T) -> T: ) self.execute(Command.CLOSE_APP) - return self + return cast('WebDriver', self) def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. @@ -148,14 +191,24 @@ def terminate_app(self, app_id: str, **options: Any) -> bool: Returns: True if the app has been successfully terminated """ - 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: T, app_id: str) -> T: + ext_name = 'mobile: terminateApp' + try: + return self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'appId': app_id, + 'bundleId': app_id, + **(options or {}), + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + data: Dict[str, Any] = {'appId': app_id} + if options: + data.update({'options': options}) + return self.mark_extension_absence(ext_name).execute(Command.TERMINATE_APP, data)['value'] + + def activate_app(self, app_id: str) -> 'WebDriver': """Activates the application if it is not running or is running in the background. @@ -165,11 +218,19 @@ def activate_app(self: T, app_id: str) -> T: Returns: Union['WebDriver', 'Applications']: Self instance """ - data = { - 'appId': app_id, - } - self.execute(Command.ACTIVATE_APP, data) - return self + ext_name = 'mobile: activateApp' + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'appId': app_id, + 'bundleId': app_id, + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.ACTIVATE_APP, {'appId': app_id}) + return cast('WebDriver', self) def query_app_state(self, app_id: str) -> int: """Queries the state of the application. @@ -181,10 +242,23 @@ def query_app_state(self, app_id: str) -> int: One of possible application state constants. See ApplicationState class for more details. """ - data = { - 'appId': app_id, - } - return self.execute(Command.QUERY_APP_STATE, data)['value'] + ext_name = 'mobile: queryAppState' + try: + return self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'appId': app_id, + 'bundleId': app_id, + }, + ) + except (UnknownMethodException, InvalidArgumentException): + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute( + Command.QUERY_APP_STATE, + { + 'appId': app_id, + }, + )['value'] def app_strings(self, language: Union[str, None] = None, string_file: Union[str, None] = None) -> Dict[str, str]: """Returns the application strings from the device for the specified @@ -192,19 +266,24 @@ def app_strings(self, language: Union[str, None] = None, string_file: Union[str, Args: language: strings language code - string_file: the name of the string file to query + string_file: the name of the string file to query. Only relevant for XCUITest driver Returns: The key is string id and the value is the content. """ + ext_name = 'mobile: getAppStrings' data = {} if language is not None: data['language'] = language if string_file is not None: data['stringFile'] = string_file - return self.execute(Command.GET_APP_STRINGS, data)['value'] + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name, data) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self: T) -> T: + def reset(self) -> 'WebDriver': """Resets the current application on the device. deprecated:: 2.0.0 @@ -218,7 +297,7 @@ def reset(self: T) -> T: ) self.execute(Command.RESET) - return self + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/clipboard.py b/appium/webdriver/extensions/clipboard.py index 4009a93c0..de7f2390c 100644 --- a/appium/webdriver/extensions/clipboard.py +++ b/appium/webdriver/extensions/clipboard.py @@ -13,20 +13,21 @@ # limitations under the License. import base64 -from typing import Optional, TypeVar +from typing import TYPE_CHECKING, Optional, cast from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.clipboard_content_type import ClipboardContentType from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound='Clipboard') +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class Clipboard(CanExecuteCommands): def set_clipboard( - self: T, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None - ) -> T: + self, content: bytes, content_type: str = ClipboardContentType.PLAINTEXT, label: Optional[str] = None + ) -> 'WebDriver': """Set the content of the system clipboard Args: @@ -45,9 +46,9 @@ def set_clipboard( if label: options['label'] = label self.execute(Command.SET_CLIPBOARD, options) - return self + return cast('WebDriver', self) - def set_clipboard_text(self: T, text: str, label: Optional[str] = None) -> T: + def set_clipboard_text(self, text: str, label: Optional[str] = None) -> 'WebDriver': """Copies the given text to the system clipboard Args: diff --git a/appium/webdriver/extensions/device_time.py b/appium/webdriver/extensions/device_time.py index 3bf0d44e2..a81717eba 100644 --- a/appium/webdriver/extensions/device_time.py +++ b/appium/webdriver/extensions/device_time.py @@ -14,12 +14,16 @@ from typing import Optional +from selenium.common.exceptions import UnknownMethodException + from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from ..mobilecommand import MobileCommand as Command -class DeviceTime(CanExecuteCommands): +class DeviceTime(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): @property def device_time(self) -> str: """Returns the date and time from the device. @@ -27,7 +31,12 @@ def device_time(self) -> str: Return: str: The date and time """ - return self.execute(Command.GET_DEVICE_TIME_GET, {})['value'] + ext_name = 'mobile: getDeviceTime' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_DEVICE_TIME_GET, {})['value'] def get_device_time(self, format: Optional[str] = None) -> str: """Returns the date and time from the device. @@ -45,9 +54,15 @@ def get_device_time(self, format: Optional[str] = None) -> str: Return: str: The date and time """ + ext_name = 'mobile: getDeviceTime' if format is None: return self.device_time - return self.execute(Command.GET_DEVICE_TIME_POST, {'format': format})['value'] + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name, {'format': format}) + except UnknownMethodException: + return self.mark_extension_absence(ext_name).execute(Command.GET_DEVICE_TIME_POST, {'format': format})[ + 'value' + ] def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py index 010c297d0..fcfff2827 100644 --- a/appium/webdriver/extensions/execute_driver.py +++ b/appium/webdriver/extensions/execute_driver.py @@ -20,7 +20,6 @@ class ExecuteDriver(CanExecuteCommands): - # 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. diff --git a/appium/webdriver/extensions/execute_mobile_command.py b/appium/webdriver/extensions/execute_mobile_command.py index 2518db4c7..39f217d5d 100644 --- a/appium/webdriver/extensions/execute_mobile_command.py +++ b/appium/webdriver/extensions/execute_mobile_command.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, TypeVar +from typing import TYPE_CHECKING, Any, Dict, cast from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts -T = TypeVar('T', bound=CanExecuteScripts) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class ExecuteMobileCommand(CanExecuteScripts): - def press_button(self: T, button_name: str) -> T: + def press_button(self, button_name: str) -> 'WebDriver': """Sends a physical button name to the device to simulate the user pressing. iOS only. @@ -36,7 +37,7 @@ def press_button(self: T, button_name: str) -> T: """ data = {'name': button_name} self.execute_script('mobile: pressButton', data) - return self + return cast('WebDriver', self) @property def battery_info(self) -> Dict[str, Any]: diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index 75ca14821..2b2845118 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -12,17 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional, TypeVar +from typing import TYPE_CHECKING, Optional, cast + +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class HardwareActions(CanExecuteCommands): - def lock(self: T, seconds: Optional[int] = None) -> T: +class HardwareActions(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def lock(self, seconds: Optional[int] = None) -> 'WebDriver': """Lock the device. No changes are made if the device is already unlocked. Args: @@ -34,20 +39,30 @@ def lock(self: T, seconds: Optional[int] = None) -> T: Returns: Union['WebDriver', 'HardwareActions']: Self instance """ - if seconds is None: - self.execute(Command.LOCK) - else: - self.execute(Command.LOCK, {'seconds': seconds}) - return self - - def unlock(self: T) -> T: + ext_name = 'mobile: lock' + args = {'seconds': seconds or 0} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.LOCK, args) + return cast('WebDriver', self) + + def unlock(self) -> 'WebDriver': """Unlock the device. No changes are made if the device is already locked. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ - self.execute(Command.UNLOCK) - return self + ext_name = 'mobile: unlock' + try: + if not self.assert_extension_exists(ext_name).execute_script('mobile: isLocked'): + return cast('WebDriver', self) + self.execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.UNLOCK) + return cast('WebDriver', self) def is_locked(self) -> bool: """Checks whether the device is locked. @@ -55,18 +70,28 @@ def is_locked(self) -> bool: Returns: `True` if the device is locked """ - return self.execute(Command.IS_LOCKED)['value'] - - def shake(self: T) -> T: + ext_name = 'mobile: isLocked' + try: + return self.assert_extension_exists(ext_name).execute_script('mobile: isLocked') + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.IS_LOCKED)['value'] + + def shake(self) -> 'WebDriver': """Shake the device. Returns: Union['WebDriver', 'HardwareActions']: Self instance """ - self.execute(Command.SHAKE) - return self - - def touch_id(self: T, match: bool) -> T: + ext_name = 'mobile: shake' + try: + self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SHAKE) + return cast('WebDriver', self) + + def touch_id(self, match: bool) -> 'WebDriver': """Simulate touchId on iOS Simulator Args: @@ -75,29 +100,38 @@ def touch_id(self: T, match: bool) -> T: Returns: Union['WebDriver', 'HardwareActions']: Self instance """ - data = {'match': match} - self.execute(Command.TOUCH_ID, data) - return self + self.execute_script( + 'mobile: sendBiometricMatch', + { + 'type': 'touchId', + 'match': match, + }, + ) + return cast('WebDriver', self) - def toggle_touch_id_enrollment(self: T) -> T: + def toggle_touch_id_enrollment(self) -> 'WebDriver': """Toggle enroll touchId on iOS Simulator Returns: Union['WebDriver', 'HardwareActions']: Self instance """ - self.execute(Command.TOGGLE_TOUCH_ID_ENROLLMENT) - return self + is_enrolled = self.execute_script('mobile: isBiometricEnrolled') + self.execute_script('mobile: enrollBiometric', {'isEnabled': not is_enrolled}) + return cast('WebDriver', self) - def finger_print(self, finger_id: int) -> Any: + def finger_print(self, finger_id: int) -> 'WebDriver': """Authenticate users by using their finger print scans on supported Android emulators. Args: finger_id: Finger prints stored in Android Keystore system (from 1 to 10) - - Returns: - TODO """ - return self.execute(Command.FINGER_PRINT, {'fingerprintId': finger_id})['value'] + ext_name = 'mobile: fingerprint' + args = {'fingerprintId': finger_id} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + self.mark_extension_absence(ext_name).execute(Command.FINGER_PRINT, args) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py index 24f1c6767..2a5f188c5 100644 --- a/appium/webdriver/extensions/ime.py +++ b/appium/webdriver/extensions/ime.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, TypeVar +import warnings +from typing import TYPE_CHECKING, List, cast from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class IME(CanExecuteCommands): @@ -29,25 +31,43 @@ def available_ime_engines(self) -> List[str]: Package and activity are returned (e.g., ['com.android.inputmethod.latin/.LatinIME']) Android only. + deprecated:: 2.0.0 + Returns: :obj:`list` of :obj:`str`: The available input methods for an Android device """ + warnings.warn( + 'The "available_ime_engines" API is deprecated and will be removed in future versions. ' + 'Use "mobile: shell" extension instead', + DeprecationWarning, + ) + return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] # pylint: disable=unsubscriptable-object def is_ime_active(self) -> bool: """Checks whether the device has IME service active. Android only. + deprecated:: 2.0.0 + Returns: `True` if IME service is active """ + warnings.warn( + 'The "is_ime_active" API is deprecated and will be removed in future versions. ' + 'Use "mobile: shell" extension instead', + DeprecationWarning, + ) + return self.execute(Command.IS_IME_ACTIVE, {})['value'] # pylint: disable=unsubscriptable-object - def activate_ime_engine(self: T, engine: str) -> T: + def activate_ime_engine(self, engine: str) -> 'WebDriver': """Activates the given IME engine on the device. Android only. + deprecated:: 2.0.0 + Args: engine: the package and activity of the IME engine to activate (e.g., 'com.android.inputmethod.latin/.LatinIME') @@ -55,20 +75,34 @@ def activate_ime_engine(self: T, engine: str) -> T: Returns: Union['WebDriver', 'IME']: Self instance """ + warnings.warn( + 'The "activate_ime_engine" API is deprecated and will be removed in future versions. ' + 'Use "mobile: shell" extension instead', + DeprecationWarning, + ) + data = {'engine': engine} self.execute(Command.ACTIVATE_IME_ENGINE, data) - return self + return cast('WebDriver', self) - def deactivate_ime_engine(self: T) -> T: + def deactivate_ime_engine(self) -> 'WebDriver': """Deactivates the currently active IME engine on the device. Android only. + deprecated:: 2.0.0 + Returns: Union['WebDriver', 'IME']: Self instance """ + warnings.warn( + 'The "deactivate_ime_engine" API is deprecated and will be removed in future versions. ' + 'Use "mobile: shell" extension instead', + DeprecationWarning, + ) + self.execute(Command.DEACTIVATE_IME_ENGINE, {}) - return self + return cast('WebDriver', self) @property def active_ime_engine(self) -> str: @@ -77,9 +111,17 @@ def active_ime_engine(self) -> str: Android only. + deprecated:: 2.0.0 + Returns: str: The activity and package of the currently active IME engine """ + warnings.warn( + 'The "active_ime_engine" API is deprecated and will be removed in future versions. ' + 'Use "mobile: shell" extension instead', + DeprecationWarning, + ) + return self.execute(Command.GET_ACTIVE_IME_ENGINE, {})['value'] # pylint: disable=unsubscriptable-object def _add_commands(self) -> None: diff --git a/appium/webdriver/extensions/keyboard.py b/appium/webdriver/extensions/keyboard.py index 9e7491a74..7dfba4f7e 100644 --- a/appium/webdriver/extensions/keyboard.py +++ b/appium/webdriver/extensions/keyboard.py @@ -12,19 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, Optional, TypeVar +from typing import TYPE_CHECKING, Dict, Optional, cast + +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Keyboard(CanExecuteCommands): +class Keyboard(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): def hide_keyboard( - self: T, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None - ) -> T: + self, key_name: Optional[str] = None, key: Optional[str] = None, strategy: Optional[str] = None + ) -> 'WebDriver': """Hides the software keyboard on the device. In iOS, use `key_name` to press @@ -38,16 +43,23 @@ def hide_keyboard( Returns: Union['WebDriver', 'Keyboard']: Self instance """ - data: Dict[str, Optional[str]] = {} - if key_name is not None: - data['keyName'] = key_name - elif key is not None: - data['key'] = key - elif strategy is None: - strategy = 'tapOutside' - data['strategy'] = strategy - self.execute(Command.HIDE_KEYBOARD, data) - return self + ext_name = 'mobile: hideKeyboard' + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, {**({'keys': [key or key_name]} if key or key_name else {})} + ) + except UnknownMethodException: + # TODO: Remove the fallback + data: Dict[str, Optional[str]] = {} + if key_name is not None: + data['keyName'] = key_name + elif key is not None: + data['key'] = key + elif strategy is None: + strategy = 'tapOutside' + data['strategy'] = strategy + self.mark_extension_absence(ext_name).execute(Command.HIDE_KEYBOARD, data) + return cast('WebDriver', self) def is_keyboard_shown(self) -> bool: """Attempts to detect whether a software keyboard is present @@ -55,9 +67,13 @@ def is_keyboard_shown(self) -> bool: Returns: `True` if keyboard is shown """ - return self.execute(Command.IS_KEYBOARD_SHOWN)['value'] + ext_name = 'mobile: isKeyboardShown' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + return self.mark_extension_absence(ext_name).execute(Command.IS_KEYBOARD_SHOWN)['value'] - def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: + def keyevent(self, keycode: int, metastate: Optional[int] = None) -> 'WebDriver': """Sends a keycode to the device. Android only. @@ -70,15 +86,9 @@ def keyevent(self: T, keycode: int, metastate: Optional[int] = None) -> T: Returns: Union['WebDriver', 'Keyboard']: Self instance """ - data = { - 'keycode': keycode, - } - if metastate is not None: - data['metastate'] = metastate - self.execute(Command.KEY_EVENT, data) - return self + return self.press_keycode(keycode=keycode, metastate=metastate) - def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: + def press_keycode(self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> 'WebDriver': """Sends a keycode to the device. Android only. Possible keycodes can be found @@ -92,17 +102,22 @@ def press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Returns: Union['WebDriver', 'Keyboard']: Self instance """ - data = { - 'keycode': keycode, - } + ext_name = 'mobile: pressKey' + args = {'keycode': keycode} if metastate is not None: - data['metastate'] = metastate + args['metastate'] = metastate if flags is not None: - data['flags'] = flags - self.execute(Command.PRESS_KEYCODE, data) - return self - - def long_press_keycode(self: T, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None) -> T: + args['flags'] = flags + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.PRESS_KEYCODE, args) + return cast('WebDriver', self) + + def long_press_keycode( + self, keycode: int, metastate: Optional[int] = None, flags: Optional[int] = None + ) -> 'WebDriver': """Sends a long press of keycode to the device. Android only. Possible keycodes can be found in @@ -116,13 +131,24 @@ def long_press_keycode(self: T, keycode: int, metastate: Optional[int] = None, f Returns: Union['WebDriver', 'Keyboard']: Self instance """ - data = {'keycode': keycode} + ext_name = 'mobile: pressKey' + args = {'keycode': keycode} if metastate is not None: - data['metastate'] = metastate + args['metastate'] = metastate if flags is not None: - data['flags'] = flags - self.execute(Command.LONG_PRESS_KEYCODE, data) - return self + args['flags'] = flags + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + **args, + 'isLongPress': True, + }, + ) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.LONG_PRESS_KEYCODE, args) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index aff5390cf..906e44376 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -12,35 +12,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, TypeVar, Union +from typing import TYPE_CHECKING, Dict, Union, cast + +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Location(CanExecuteCommands): - def toggle_location_services(self: T) -> T: +class Location(CanExecuteCommands, CanExecuteScripts): + def toggle_location_services(self) -> 'WebDriver': """Toggle the location services on the device. + This API only reliably since Android 12 (API level 31) Android only. Returns: Union['WebDriver', 'Location']: Self instance """ - self.execute(Command.TOGGLE_LOCATION_SERVICES, {}) - return self + try: + self.execute_script('mobile: toggleGps') + except UnknownMethodException: + # TODO: Remove the fallback + self.execute(Command.TOGGLE_LOCATION_SERVICES) + return cast('WebDriver', self) def set_location( - self: T, + self, latitude: Union[float, str], longitude: Union[float, str], altitude: Union[float, str, None] = None, speed: Union[float, str, None] = None, satellites: Union[float, str, None] = None, - ) -> T: + ) -> 'WebDriver': """Set the location of the device Args: @@ -66,7 +75,7 @@ def set_location( if satellites is not None: data['location']['satellites'] = satellites self.execute(Command.SET_LOCATION, data) - return self + return cast('WebDriver', self) @property def location(self) -> Dict[str, float]: diff --git a/appium/webdriver/extensions/log_event.py b/appium/webdriver/extensions/log_event.py index f24a8d304..0ff1d1f6c 100644 --- a/appium/webdriver/extensions/log_event.py +++ b/appium/webdriver/extensions/log_event.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, TypeVar, Union +from typing import TYPE_CHECKING, Dict, List, Union, cast from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class LogEvent(CanExecuteCommands): @@ -45,7 +46,7 @@ def get_events(self, type: Union[List[str], None] = None) -> Dict[str, Union[str data['type'] = type return self.execute(Command.GET_EVENTS, data)['value'] - def log_event(self: T, vendor: str, event: str) -> T: + def log_event(self, vendor: str, event: str) -> 'WebDriver': """Log a custom event on the Appium server. (Since Appium 1.16.0) @@ -61,7 +62,7 @@ def log_event(self: T, vendor: str, event: str) -> T: """ data = {'vendor': vendor, 'event': event} self.execute(Command.LOG_EVENT, data) - return self + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/remote_fs.py b/appium/webdriver/extensions/remote_fs.py index 9f65576df..a7beeb229 100644 --- a/appium/webdriver/extensions/remote_fs.py +++ b/appium/webdriver/extensions/remote_fs.py @@ -13,18 +13,21 @@ # limitations under the License. import base64 -from typing import Optional, TypeVar +from typing import TYPE_CHECKING, Optional, cast -from selenium.common.exceptions import InvalidArgumentException +from selenium.common.exceptions import InvalidArgumentException, UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class RemoteFS(CanExecuteCommands): +class RemoteFS(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): def pull_file(self, path: str) -> str: """Retrieves the file at `path`. @@ -34,10 +37,12 @@ def pull_file(self, path: str) -> str: Returns: The file's contents encoded as Base64. """ - data = { - 'path': path, - } - return self.execute(Command.PULL_FILE, data)['value'] + ext_name = 'mobile: pullFile' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name, {'remotePath': path}) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.PULL_FILE, {'path': path})['value'] def pull_folder(self, path: str) -> str: """Retrieves a folder at `path`. @@ -48,14 +53,16 @@ def pull_folder(self, path: str) -> str: Returns: The folder's contents zipped and encoded as Base64. """ - data = { - 'path': path, - } - return self.execute(Command.PULL_FOLDER, data)['value'] + ext_name = 'mobile: pullFolder' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name, {'remotePath': path}) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.PULL_FOLDER, {'path': path})['value'] def push_file( - self: T, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None - ) -> T: + self, destination_path: str, base64data: Optional[str] = None, source_path: Optional[str] = None + ) -> 'WebDriver': """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` @@ -81,12 +88,25 @@ def push_file( raise InvalidArgumentException(message) from e base64data = base64.b64encode(file_data).decode('utf-8') - data = { - 'path': destination_path, - 'data': base64data, - } - self.execute(Command.PUSH_FILE, data) - return self + ext_name = 'mobile: pushFile' + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'remotePath': destination_path, + 'payload': base64data, + }, + ) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute( + Command.PUSH_FILE, + { + 'path': destination_path, + 'data': base64data, + }, + ) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index e632f47ac..dfd387cab 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import warnings from typing import Any, Dict, List from appium.common.logger import logger @@ -24,6 +25,7 @@ class Session(CanExecuteCommands): @property def session(self) -> Dict[str, Any]: """Retrieves session information from the current session + deprecated:: 2.0.0 Usage: session = driver.session @@ -31,11 +33,17 @@ def session(self) -> Dict[str, Any]: Returns: `dict`: containing information from the current session """ + warnings.warn( + 'The "session" API is deprecated and will be removed in future versions', + DeprecationWarning, + ) + return self.execute(Command.GET_SESSION)['value'] @property def all_sessions(self) -> List[Dict[str, Any]]: """Retrieves all sessions that are open + deprecated:: 2.0.0 Usage: sessions = driver.all_sessions @@ -43,6 +51,11 @@ def all_sessions(self) -> List[Dict[str, Any]]: Returns: :obj:`list` of :obj:`dict`: containing all open sessions """ + warnings.warn( + 'The "all_sessions" API is deprecated and will be removed in future versions', + DeprecationWarning, + ) + return self.execute(Command.GET_ALL_SESSIONS)['value'] @property diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index 3c3026f70..c45e819c6 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, TypeVar +from typing import TYPE_CHECKING, Any, Dict, cast from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from ..mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class Settings(CanExecuteCommands): @@ -33,7 +34,7 @@ def get_settings(self) -> Dict[str, Any]: """ return self.execute(Command.GET_SETTINGS, {})['value'] - def update_settings(self: T, settings: Dict[str, Any]) -> T: + def update_settings(self, settings: Dict[str, Any]) -> 'WebDriver': """Set settings for the current session. For more on settings, see: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md @@ -41,10 +42,8 @@ def update_settings(self: T, settings: Dict[str, Any]) -> T: Args: settings: dictionary of settings to apply to the current test session """ - data = {"settings": settings} - - self.execute(Command.UPDATE_SETTINGS, data) - return self # type: ignore + self.execute(Command.UPDATE_SETTINGS, {"settings": settings}) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index e6d46ec82..21e9393e3 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, TypeVar +from typing import TYPE_CHECKING, Optional, cast from selenium.webdriver.remote.switch_to import SwitchTo @@ -21,16 +21,16 @@ from .mobilecommand import MobileCommand +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + class HasDriver(Protocol): _driver: CanExecuteCommands -T = TypeVar('T', bound=HasDriver) - - class MobileSwitchTo(SwitchTo, HasDriver): - def context(self: T, context_name: Optional[str]) -> T: + def context(self, context_name: Optional[str]) -> 'WebDriver': """Sets the context for the current session. Passing `None` is equal to switching to native context. @@ -41,4 +41,4 @@ def context(self: T, context_name: Optional[str]) -> T: driver.switch_to.context('WEBVIEW_1') """ self._driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {'name': context_name}) - return self + return cast('WebDriver', self) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 49f857fd7..2af8220ad 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -15,17 +15,21 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use import warnings -from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from selenium import webdriver -from selenium.common.exceptions import InvalidArgumentException, SessionNotCreatedException, WebDriverException +from selenium.common.exceptions import ( + InvalidArgumentException, + SessionNotCreatedException, + UnknownMethodException, + WebDriverException, +) from selenium.webdriver.common.by import By from selenium.webdriver.remote.command import Command as RemoteCommand from selenium.webdriver.remote.remote_connection import RemoteConnection from appium.common.logger import logger from appium.options.common.base import AppiumOptions -from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from appium.webdriver.common.appiumby import AppiumBy from .appium_connection import AppiumConnection @@ -60,8 +64,6 @@ from .switch_to import MobileSwitchTo from .webelement import WebElement as MobileWebElement -T = TypeVar('T', bound=CanExecuteCommands) - class ExtensionBase: """ @@ -214,7 +216,6 @@ def __init__( strict_ssl: bool = True, options: Union[AppiumOptions, List[AppiumOptions], None] = None, ): - if strict_ssl is False: # pylint: disable=E1101 # noinspection PyPackageRequirements @@ -258,6 +259,8 @@ def __init__( By.IMAGE = AppiumBy.IMAGE By.CUSTOM = AppiumBy.CUSTOM + self._absent_extensions: Set[str] = set() + self._extensions = extensions or [] for extension in self._extensions: instance = extension(self.execute) @@ -436,7 +439,7 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """ return MobileWebElement(self, element_id) - def set_value(self: T, element: MobileWebElement, value: str) -> T: + def set_value(self, element: MobileWebElement, value: str) -> 'WebDriver': """Set the value on an element in the application. deprecated:: 2.8.1 @@ -497,9 +500,34 @@ def orientation(self, value: str) -> None: if value.upper() in allowed_values: self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value}) else: - raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + def assert_extension_exists(self, ext_name: str) -> 'WebDriver': + """ + Verifies if the given extension is not present in the list of absent extensions + for the given driver instance. + This API is designed for private usage. + + :param ext_name: extension name + :return: self instance for chaining + :raise UnknownMethodException: If the extension has been marked as absent once + """ + if ext_name in self._absent_extensions: + raise UnknownMethodException() + return self + + def mark_extension_absence(self, ext_name: str) -> 'WebDriver': + """ + Marks the given extension as absent for the given driver instance. + This API is designed for private usage. + + :param ext_name: extension name + :return: self instance for chaining + """ + logger.debug(f'Marking driver extension "{ext_name}" as absent for the current instance') + self._absent_extensions.add(ext_name) + return self + def _add_commands(self) -> None: # call the overridden command binders from all mixin classes except for # appium.webdriver.webdriver.WebDriver and its sub-classes diff --git a/ci-jobs/functional/setup_appium.yml b/ci-jobs/functional/setup_appium.yml index 05931e40b..69ac8627d 100644 --- a/ci-jobs/functional/setup_appium.yml +++ b/ci-jobs/functional/setup_appium.yml @@ -12,7 +12,7 @@ steps: displayName: Resolve dependencies (Appium server) - script: | pip install --upgrade pip - pip install pipenv + pip install --upgrade pipenv pipenv lock --clear pipenv install --system displayName: Resolve dependencies (Python) diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 597c42f0a..cba162333 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -33,7 +33,6 @@ 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: diff --git a/test/unit/webdriver/app_test.py b/test/unit/webdriver/app_test.py index 02bbb8df0..1a8b0247b 100644 --- a/test/unit/webdriver/app_test.py +++ b/test/unit/webdriver/app_test.py @@ -37,6 +37,7 @@ def test_install_app(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/install_app'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') result = driver.install_app('path/to/app') assert {'app': 'path/to/app'}, get_httpretty_request_body(httpretty.last_request()) @@ -48,6 +49,7 @@ def test_remove_app(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/remove_app'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') result = driver.remove_app('com.app.id') assert {'app': 'com.app.id'}, get_httpretty_request_body(httpretty.last_request()) @@ -59,6 +61,9 @@ def test_app_installed(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/app_installed'), body='{"value": true}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": true}' + ) result = driver.is_app_installed("com.app.id") assert {'app': "com.app.id"}, get_httpretty_request_body(httpretty.last_request()) assert result is True @@ -69,6 +74,9 @@ def test_terminate_app(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/terminate_app'), body='{"value": true}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": true}' + ) result = driver.terminate_app("com.app.id") assert {'app': "com.app.id"}, get_httpretty_request_body(httpretty.last_request()) assert result is True @@ -79,6 +87,7 @@ def test_activate_app(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/activate_app'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') result = driver.activate_app("com.app.id") assert {'app': 'com.app.id'}, get_httpretty_request_body(httpretty.last_request()) @@ -90,6 +99,7 @@ def test_background_app(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/app/background'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') result = driver.background_app(0) assert {'app': 0}, get_httpretty_request_body(httpretty.last_request()) assert isinstance(result, WebDriver) @@ -116,6 +126,7 @@ def test_query_app_state(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/app_state'), body='{"value": 3 }' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": 3}') result = driver.query_app_state('com.app.id') assert {'app': 3}, get_httpretty_request_body(httpretty.last_request()) diff --git a/test/unit/webdriver/device/device_time_test.py b/test/unit/webdriver/device/device_time_test.py index 59b5dcb9e..8c6f5e0b9 100644 --- a/test/unit/webdriver/device/device_time_test.py +++ b/test/unit/webdriver/device/device_time_test.py @@ -26,6 +26,11 @@ def test_device_time(self): appium_command('/session/1234567890/appium/device/system_time'), body='{"value": "2019-01-05T14:46:44+09:00"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": "2019-01-05T14:46:44+09:00"}', + ) assert driver.device_time == '2019-01-05T14:46:44+09:00' @httpretty.activate @@ -36,6 +41,11 @@ def test_get_device_time(self): appium_command('/session/1234567890/appium/device/system_time'), body='{"value": "2019-01-05T14:46:44+09:00"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": "2019-01-05T14:46:44+09:00"}', + ) assert driver.get_device_time() == '2019-01-05T14:46:44+09:00' @httpretty.activate @@ -46,7 +56,12 @@ def test_get_formatted_device_time(self): appium_command('/session/1234567890/appium/device/system_time'), body='{"value": "2019-01-08"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": "2019-01-08"}', + ) assert driver.get_device_time('YYYY-MM-DD') == '2019-01-08' d = get_httpretty_request_body(httpretty.last_request()) - assert d['format'] == 'YYYY-MM-DD' + assert d.get('format', d['args'][0]['format']) == 'YYYY-MM-DD' diff --git a/test/unit/webdriver/device/fingerprint_test.py b/test/unit/webdriver/device/fingerprint_test.py index b4d3b6e70..e6facf86d 100644 --- a/test/unit/webdriver/device/fingerprint_test.py +++ b/test/unit/webdriver/device/fingerprint_test.py @@ -14,6 +14,7 @@ import httpretty +from appium.webdriver.webdriver import WebDriver from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body @@ -26,8 +27,13 @@ def test_finger_print(self): appium_command('/session/1234567890/appium/device/finger_print'), # body is None ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + # body is None + ) - assert driver.finger_print(1) is None + assert isinstance(driver.finger_print(1), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['fingerprintId'] == 1 + assert d.get('fingerprintId', d['args'][0]['fingerprintId']) == 1 diff --git a/test/unit/webdriver/device/keyboard_test.py b/test/unit/webdriver/device/keyboard_test.py index 91de8cd85..e3401839e 100644 --- a/test/unit/webdriver/device/keyboard_test.py +++ b/test/unit/webdriver/device/keyboard_test.py @@ -23,6 +23,7 @@ class TestWebDriverKeyboard(object): def test_hide_keyboard(self): driver = android_w3c_driver() httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/appium/device/hide_keyboard')) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.hide_keyboard(), WebDriver) @httpretty.activate @@ -31,9 +32,12 @@ def test_press_keycode(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/press_keycode'), body='{"value": "86"}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": "86"}' + ) driver.press_keycode(86) d = get_httpretty_request_body((httpretty.last_request())) - assert d['keycode'] == 86 + assert d.get('keycode', d['args'][0]['keycode']) == 86 @httpretty.activate def test_long_press_keycode(self): @@ -43,9 +47,12 @@ def test_long_press_keycode(self): appium_command('/session/1234567890/appium/device/long_press_keycode'), body='{"value": "86"}', ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": "86"}' + ) driver.long_press_keycode(86) d = get_httpretty_request_body((httpretty.last_request())) - assert d['keycode'] == 86 + assert d.get('keycode', d['args'][0]['keycode']) == 86 @httpretty.activate def test_keyevent(self): @@ -53,6 +60,9 @@ def test_keyevent(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/keyevent'), body='{keycode: 86}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": "86"}' + ) assert isinstance(driver.keyevent(86), WebDriver) @httpretty.activate @@ -63,10 +73,11 @@ def test_press_keycode_with_flags(self): appium_command('/session/1234567890/appium/device/press_keycode'), body='{keycode: 86, metastate: 2097153, flags: 44}', ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) # metastate is META_SHIFT_ON and META_NUM_LOCK_ON # flags is CANCELFLAG_CANCELEDED, FLAG_KEEP_TOUCH_MODE, FLAG_FROM_SYSTEM assert isinstance( - driver.press_keycode(86, metastate=[0x00000001, 0x00200000], flags=[0x20, 0x00000004, 0x00000008]), + driver.press_keycode(86, metastate=0x00000001 | 0x00200000, flags=0x20 | 0x00000004 | 0x00000008), WebDriver, ) @@ -78,9 +89,10 @@ def test_long_press_keycode_with_flags(self): appium_command('/session/1234567890/appium/device/long_press_keycode'), body='{keycode: 86, metastate: 2097153, flags: 44}', ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) # metastate is META_SHIFT_ON and META_NUM_LOCK_ON # flags is CANCELFLAG_CANCELEDED, FLAG_KEEP_TOUCH_MODE, FLAG_FROM_SYSTEM assert isinstance( - driver.long_press_keycode(86, metastate=[0x00000001, 0x00200000], flags=[0x20, 0x00000004, 0x00000008]), + driver.long_press_keycode(86, metastate=0x00000001 | 0x00200000, flags=0x20 | 0x00000004 | 0x00000008), WebDriver, ) diff --git a/test/unit/webdriver/device/location_test.py b/test/unit/webdriver/device/location_test.py index c351c2f4f..512bdb958 100644 --- a/test/unit/webdriver/device/location_test.py +++ b/test/unit/webdriver/device/location_test.py @@ -27,6 +27,7 @@ def test_toggle_location_services(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/toggle_location_services') ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.toggle_location_services(), WebDriver) @httpretty.activate diff --git a/test/unit/webdriver/device/lock_test.py b/test/unit/webdriver/device/lock_test.py index cecc56830..1e8ce09ca 100644 --- a/test/unit/webdriver/device/lock_test.py +++ b/test/unit/webdriver/device/lock_test.py @@ -25,10 +25,11 @@ def test_lock(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/lock'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') driver.lock(1) d = get_httpretty_request_body(httpretty.last_request()) - assert d['seconds'] == 1 + assert d.get('seconds', d['args'][0]['seconds']) == 1 @httpretty.activate def test_lock_no_args(self): @@ -36,17 +37,18 @@ def test_lock_no_args(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/lock'), body='{"value": ""}' ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": ""}') driver.lock() - d = get_httpretty_request_body(httpretty.last_request()) - assert d == {} - @httpretty.activate def test_islocked_false(self): driver = android_w3c_driver() httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/is_locked'), body='{"value": false}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": false}' + ) assert driver.is_locked() is False @httpretty.activate @@ -55,7 +57,9 @@ def test_islocked_true(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/appium/device/is_locked'), body='{"value": true}' ) - + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": true}' + ) assert driver.is_locked() is True @httpretty.activate @@ -65,4 +69,5 @@ def test_unlock(self): httpretty.POST, appium_command('/session/1234567890/appium/device/unlock'), ) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.unlock(), WebDriver) diff --git a/test/unit/webdriver/device/remote_fs_test.py b/test/unit/webdriver/device/remote_fs_test.py index cab63f9f5..4de3a85fe 100644 --- a/test/unit/webdriver/device/remote_fs_test.py +++ b/test/unit/webdriver/device/remote_fs_test.py @@ -30,14 +30,18 @@ def test_push_file(self): httpretty.POST, appium_command('/session/1234567890/appium/device/push_file'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) dest_path = '/path/to/file.txt' data = base64.b64encode(bytes('HelloWorld', 'utf-8')).decode('utf-8') assert isinstance(driver.push_file(dest_path, data), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['path'] == dest_path - assert d['data'] == str(data) + assert d.get('path', d['args'][0]['remotePath']) == dest_path + assert d.get('data', d['args'][0]['payload']) == str(data) @httpretty.activate def test_push_file_invalid_arg_exception_without_src_path_and_base64data(self): @@ -46,6 +50,10 @@ def test_push_file_invalid_arg_exception_without_src_path_and_base64data(self): httpretty.POST, appium_command('/session/1234567890/appium/device/push_file'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) dest_path = '/path/to/file.txt' with pytest.raises(InvalidArgumentException): @@ -58,6 +66,10 @@ def test_push_file_invalid_arg_exception_with_src_file_not_found(self): httpretty.POST, appium_command('/session/1234567890/appium/device/push_file'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/appium/device/push_file'), + ) dest_path = '/dest_path/to/file.txt' src_path = '/src_path/to/file.txt' @@ -72,12 +84,17 @@ def test_pull_file(self): appium_command('/session/1234567890/appium/device/pull_file'), body='{"value": "SGVsbG9Xb3JsZA=="}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": "SGVsbG9Xb3JsZA=="}', + ) dest_path = '/path/to/file.txt' 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 + assert d.get('path', d['args'][0]['remotePath']) == dest_path @httpretty.activate def test_pull_folder(self): @@ -87,9 +104,14 @@ def test_pull_folder(self): appium_command('/session/1234567890/appium/device/pull_folder'), body='{"value": "base64EncodedZippedFolderData"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": "base64EncodedZippedFolderData"}', + ) dest_path = '/path/to/file.txt' assert driver.pull_folder(dest_path) == 'base64EncodedZippedFolderData' d = get_httpretty_request_body(httpretty.last_request()) - assert d['path'] == dest_path + assert d.get('path', d['args'][0]['remotePath']) == dest_path diff --git a/test/unit/webdriver/device/shake_test.py b/test/unit/webdriver/device/shake_test.py index d60e70b50..0371852f1 100644 --- a/test/unit/webdriver/device/shake_test.py +++ b/test/unit/webdriver/device/shake_test.py @@ -27,4 +27,8 @@ def test_shake(self): httpretty.POST, appium_command('/session/1234567890/appium/device/shake'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.shake(), WebDriver) From 0cfa3ef79ae93cb917ff76d446b57cefbec5ed8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 11:02:30 +0200 Subject: [PATCH 185/548] chore(deps): update selenium requirement from ~=4.7 to ~=4.9 (#852) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.7.0...selenium-4.9.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 66b0ac0f4..b9156d6a8 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ typing-extensions = "~=4.5" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.7" +selenium = "~=4.9" From d988f3c51c03f61cca39289450c009aafb0fe30a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 7 May 2023 08:20:31 +0200 Subject: [PATCH 186/548] refactor: Move driver-specific commands to use extensions (part2) (#859) --- .../extensions/android/activities.py | 29 +++++-- appium/webdriver/extensions/android/common.py | 48 ++++++++---- .../webdriver/extensions/android/display.py | 13 +++- appium/webdriver/extensions/android/gsm.py | 50 ++++++++---- .../webdriver/extensions/android/network.py | 78 +++++++++++++++---- .../extensions/android/performance.py | 28 +++++-- appium/webdriver/extensions/android/power.py | 37 ++++++--- appium/webdriver/extensions/android/sms.py | 23 ++++-- .../extensions/android/system_bars.py | 13 +++- test/functional/android/common_tests.py | 5 -- test/unit/webdriver/device/activities_test.py | 51 +++--------- test/unit/webdriver/device/common_test.py | 6 ++ test/unit/webdriver/device/display_test.py | 3 + test/unit/webdriver/device/gsm_test.py | 21 +++-- test/unit/webdriver/device/power_test.py | 12 ++- test/unit/webdriver/device/sms_test.py | 8 +- .../unit/webdriver/device/system_bars_test.py | 16 ++-- test/unit/webdriver/network_test.py | 23 +++++- test/unit/webdriver/performance_test.py | 15 +++- 19 files changed, 342 insertions(+), 137 deletions(-) diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index 5a9f2cc95..e0cb84d59 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,21 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar +import warnings +from typing import TYPE_CHECKING, cast -from selenium.common.exceptions import TimeoutException +from selenium.common.exceptions import TimeoutException, UnknownMethodException from selenium.webdriver.support.ui import WebDriverWait from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Activities(CanExecuteCommands): - def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> T: +class Activities(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'WebDriver': """Opens an arbitrary activity during a test. If the activity belongs to another application, that application is started and the activity is opened. + deprecated:: 2.9.0 This is an Android-only method. @@ -43,6 +48,11 @@ def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> optional_intent_arguments (str): Optional arguments to the intent. dont_stop_app_on_reset (str): Should the app be stopped on reset? """ + warnings.warn( + 'The "session" API is deprecated. Use "mobile: startActivity" extension instead.', + DeprecationWarning, + ) + data = {'appPackage': app_package, 'appActivity': app_activity} arguments = { 'app_wait_package': 'appWaitPackage', @@ -57,7 +67,7 @@ def start_activity(self: T, app_package: str, app_activity: str, **opts: str) -> if key in opts: data[value] = opts[key] self.execute(Command.START_ACTIVITY, data) - return self + return cast('WebDriver', self) @property def current_activity(self) -> str: @@ -66,7 +76,12 @@ def current_activity(self) -> str: Returns: str: The current activity name running on the device """ - return self.execute(Command.GET_CURRENT_ACTIVITY)['value'] + ext_name = 'mobile: getCurrentActivity' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_CURRENT_ACTIVITY)['value'] def wait_activity(self, activity: str, timeout: int, interval: int = 1) -> bool: """Wait for an activity: block until target activity presents or time out. diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index b121d339f..f6aa92708 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -11,18 +11,24 @@ # 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 warnings +from typing import TYPE_CHECKING, Any, cast -from typing import Any, TypeVar +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Common(CanExecuteCommands): - def end_test_coverage(self, intent: str, path: str) -> Any: # TODO Check return type +class Common(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def end_test_coverage(self, intent: str, path: str) -> Any: """Ends the coverage collection and pull the coverage.ec file from the device. + deprecated:: 2.9.0 Android only. See https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-coverage.md @@ -34,25 +40,41 @@ def end_test_coverage(self, intent: str, path: str) -> Any: # TODO Check return Returns: TODO """ - data = { - 'intent': intent, - 'path': path, - } - return self.execute(Command.END_TEST_COVERAGE, data)['value'] + warnings.warn( + 'This API is deprecated and will be removed in future versions', + DeprecationWarning, + ) + return self.execute( + Command.END_TEST_COVERAGE, + { + 'intent': intent, + 'path': path, + }, + )['value'] - def open_notifications(self: T) -> T: + def open_notifications(self) -> 'WebDriver': """Open notification shade in Android (API Level 18 and above) Returns: Union['WebDriver', 'Common']: Self instance """ - self.execute(Command.OPEN_NOTIFICATIONS, {}) - return self + ext_name = 'mobile: openNotifications' + try: + self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.OPEN_NOTIFICATIONS, {}) + return cast('WebDriver', self) @property def current_package(self) -> str: """Retrieves the current package running on the device.""" - return self.execute(Command.GET_CURRENT_PACKAGE)['value'] + ext_name = 'mobile: getCurrentPackage' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_CURRENT_PACKAGE)['value'] def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/display.py b/appium/webdriver/extensions/android/display.py index 9822182aa..e04bd1bef 100644 --- a/appium/webdriver/extensions/android/display.py +++ b/appium/webdriver/extensions/android/display.py @@ -12,11 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from selenium.common.exceptions import UnknownMethodException + from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -class Display(CanExecuteCommands): +class Display(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): def get_display_density(self) -> int: """Get the display density, Android only @@ -29,7 +33,12 @@ def get_display_density(self) -> int: Return: int: The display density """ - return self.execute(Command.GET_DISPLAY_DENSITY)['value'] + ext_name = 'mobile: getDisplayDensity' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_DISPLAY_DENSITY)['value'] def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/gsm.py b/appium/webdriver/extensions/android/gsm.py index 0dae0b4ba..7d97ac2de 100644 --- a/appium/webdriver/extensions/android/gsm.py +++ b/appium/webdriver/extensions/android/gsm.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar +from typing import TYPE_CHECKING, cast + +from selenium.common.exceptions import UnknownMethodException from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class GsmCallActions: @@ -47,8 +52,8 @@ class GsmVoiceState: ON = 'on' -class Gsm(CanExecuteCommands): - def make_gsm_call(self: T, phone_number: str, action: str) -> T: +class Gsm(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def make_gsm_call(self, phone_number: str, action: str) -> 'WebDriver': """Make GSM call (Emulator only) Android only. @@ -64,16 +69,22 @@ def make_gsm_call(self: T, phone_number: str, action: str) -> T: Returns: Union['WebDriver', 'Gsm']: Self instance """ + ext_name = 'mobile: gsmCall' constants = extract_const_attributes(GsmCallActions) if action not in constants.values(): logger.warning( f'{action} is unknown. Consider using one of {list(constants.keys())} constants. ' f'(e.g. {GsmCallActions.__name__}.CALL)' ) - self.execute(Command.MAKE_GSM_CALL, {'phoneNumber': phone_number, 'action': action}) - return self - - def set_gsm_signal(self: T, strength: int) -> T: + args = {'phoneNumber': phone_number, 'action': action} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.MAKE_GSM_CALL, args) + return cast('WebDriver', self) + + def set_gsm_signal(self, strength: int) -> 'WebDriver': """Set GSM signal strength (Emulator only) Android only. @@ -88,16 +99,23 @@ def set_gsm_signal(self: T, strength: int) -> T: Returns: Union['WebDriver', 'Gsm']: Self instance """ + ext_name = 'mobile: gsmSignal' constants = extract_const_attributes(GsmSignalStrength) if strength not in constants.values(): logger.warning( f'{strength} is out of range. Consider using one of {list(constants.keys())} constants. ' f'(e.g. {GsmSignalStrength.__name__}.GOOD)' ) - self.execute(Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength}) - return self + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, {'strength': strength}) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute( + Command.SET_GSM_SIGNAL, {'signalStrength': strength, 'signalStrengh': strength} + ) + return cast('WebDriver', self) - def set_gsm_voice(self: T, state: str) -> T: + def set_gsm_voice(self, state: str) -> 'WebDriver': """Set GSM voice state (Emulator only) Android only. @@ -112,14 +130,20 @@ def set_gsm_voice(self: T, state: str) -> T: Returns: Union['WebDriver', 'Gsm']: Self instance """ + ext_name = 'mobile: gmsVoice' constants = extract_const_attributes(GsmVoiceState) if state not in constants.values(): logger.warning( f'{state} is unknown. Consider using one of {list(constants.keys())} constants. ' f'(e.g. {GsmVoiceState.__name__}.HOME)' ) - self.execute(Command.SET_GSM_VOICE, {'state': state}) - return self + args = {'state': state} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SET_GSM_VOICE, args) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/network.py b/appium/webdriver/extensions/android/network.py index 46bfce8b0..aa6bca3d0 100644 --- a/appium/webdriver/extensions/android/network.py +++ b/appium/webdriver/extensions/android/network.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar +from typing import TYPE_CHECKING, cast + +from selenium.common.exceptions import UnknownMethodException from appium.common.helper import extract_const_attributes from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver class NetSpeed: @@ -34,15 +39,34 @@ class NetSpeed: FULL = 'full' # No limit, the default (up: 0.0, down: 0.0) -class Network(CanExecuteCommands): +class NetworkMask: + WIFI = 0b010 + DATA = 0b100 + AIRPLANE_MODE = 0b001 + + +class Network(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): @property def network_connection(self) -> int: """Returns an integer bitmask specifying the network connection type. Android only. Possible values are available through the enumeration `appium.webdriver.ConnectionType` + + This API only works reliably on emulators (any version) and real devices + since API level 31. """ - return self.execute(Command.GET_NETWORK_CONNECTION, {})['value'] + ext_name = 'mobile: getConnectivity' + try: + result_map = self.assert_extension_exists(ext_name).execute_script(ext_name) + return ( + (NetworkMask.WIFI if result_map['wifi'] else 0) + | (NetworkMask.DATA if result_map['data'] else 0) + | (NetworkMask.AIRPLANE_MODE if result_map['airplaneMode'] else 0) + ) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_NETWORK_CONNECTION, {})['value'] def set_network_connection(self, connection_type: int) -> int: """Sets the network connection type. Android only. @@ -65,25 +89,49 @@ def set_network_connection(self, connection_type: int) -> int: These are available through the enumeration `appium.webdriver.ConnectionType` + This API only works reliably on emulators (any version) and real devices + since API level 31. + Args: connection_type: a member of the enum `appium.webdriver.ConnectionType` Return: int: Set network connection type """ - data = {'parameters': {'type': connection_type}} - return self.execute(Command.SET_NETWORK_CONNECTION, data)['value'] + ext_name = 'mobile: setConnectivity' + try: + return self.assert_extension_exists(ext_name).execute_script( + ext_name, + { + 'wifi': bool(connection_type & NetworkMask.WIFI), + 'data': bool(connection_type & NetworkMask.DATA), + 'airplaneMode': bool(connection_type & NetworkMask.AIRPLANE_MODE), + }, + ) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute( + Command.SET_NETWORK_CONNECTION, {'parameters': {'type': connection_type}} + )['value'] - def toggle_wifi(self: T) -> T: + def toggle_wifi(self) -> 'WebDriver': """Toggle the wifi on the device, Android only. + This API only works reliably on emulators (any version) and real devices + since API level 31. Returns: Union['WebDriver', 'Network']: Self instance """ - self.execute(Command.TOGGLE_WIFI, {}) - return self + ext_name = 'mobile: setConnectivity' + try: + self.assert_extension_exists(ext_name).execute_script( + ext_name, {'wifi': not (self.network_connection & NetworkMask.WIFI)} + ) + except UnknownMethodException: + self.mark_extension_absence(ext_name).execute(Command.TOGGLE_WIFI, {}) + return cast('WebDriver', self) - def set_network_speed(self: T, speed_type: str) -> T: + def set_network_speed(self, speed_type: str) -> 'WebDriver': """Set the network speed emulation. Android Emulator only. @@ -104,9 +152,13 @@ def set_network_speed(self: T, speed_type: str) -> T: f'{speed_type} is unknown. Consider using one of {list(constants.keys())} constants. ' f'(e.g. {NetSpeed.__name__}.LTE)' ) - - self.execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) - return self + ext_name = 'mobile: networkSpeed' + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, {'speed': speed_type}) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SET_NETWORK_SPEED, {'netspeed': speed_type}) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/performance.py b/appium/webdriver/extensions/android/performance.py index 2c4f46690..0611c751a 100644 --- a/appium/webdriver/extensions/android/performance.py +++ b/appium/webdriver/extensions/android/performance.py @@ -14,11 +14,15 @@ from typing import Dict, List, Union +from selenium.common.exceptions import UnknownMethodException + from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -class Performance(CanExecuteCommands): +class Performance(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): def get_performance_data( self, package_name: str, data_type: str, data_read_timeout: Union[int, None] = None ) -> List[List[str]]: @@ -40,12 +44,17 @@ def get_performance_data( Returns: The data along to `data_type` """ - data: Dict[str, Union[str, int]] = {'packageName': package_name, 'dataType': data_type} - if data_read_timeout is not None: - data['dataReadTimeout'] = data_read_timeout - return self.execute(Command.GET_PERFORMANCE_DATA, data)['value'] + ext_name = 'mobile: getPerformanceData' + args: Dict[str, Union[str, int]] = {'packageName': package_name, 'dataType': data_type} + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + if data_read_timeout is not None: + args['dataReadTimeout'] = data_read_timeout + return self.mark_extension_absence(ext_name).execute(Command.GET_PERFORMANCE_DATA, args)['value'] - def get_performance_data_types(self) -> List: + def get_performance_data_types(self) -> List[str]: """Returns the information types of the system state which is supported to read as like cpu, memory, network traffic, and battery. Android only. @@ -56,7 +65,12 @@ def get_performance_data_types(self) -> List: Returns: Available data types """ - return self.execute(Command.GET_PERFORMANCE_DATA_TYPES)['value'] + ext_name = 'mobile: getPerformanceDataTypes' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_PERFORMANCE_DATA_TYPES)['value'] def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/power.py b/appium/webdriver/extensions/android/power.py index c1b9b2116..8913526a5 100644 --- a/appium/webdriver/extensions/android/power.py +++ b/appium/webdriver/extensions/android/power.py @@ -12,18 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar +from typing import TYPE_CHECKING, cast + +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Power(CanExecuteCommands): +class Power(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): AC_OFF, AC_ON = 'off', 'on' - def set_power_capacity(self: T, percent: int) -> T: + def set_power_capacity(self, percent: int) -> 'WebDriver': """Emulate power capacity change on the connected emulator. Android only. @@ -37,10 +42,16 @@ def set_power_capacity(self: T, percent: int) -> T: Returns: Union['WebDriver', 'Power']: Self instance """ - self.execute(Command.SET_POWER_CAPACITY, {'percent': percent}) - return self - - def set_power_ac(self: T, ac_state: str) -> T: + ext_name = 'mobile: powerCapacity' + args = {'percent': percent} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SET_POWER_CAPACITY, args) + return cast('WebDriver', self) + + def set_power_ac(self, ac_state: str) -> 'WebDriver': """Emulate power state change on the connected emulator. Android only. @@ -55,8 +66,14 @@ def set_power_ac(self: T, ac_state: str) -> T: Returns: Union['WebDriver', 'Power']: Self instance """ - self.execute(Command.SET_POWER_AC, {'state': ac_state}) - return self + ext_name = 'mobile: powerAC' + args = {'state': ac_state} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SET_POWER_AC, args) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/sms.py b/appium/webdriver/extensions/android/sms.py index 013494dd1..24b76ca6a 100644 --- a/appium/webdriver/extensions/android/sms.py +++ b/appium/webdriver/extensions/android/sms.py @@ -12,16 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar +from typing import TYPE_CHECKING, cast + +from selenium.common.exceptions import UnknownMethodException from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -T = TypeVar('T', bound=CanExecuteCommands) +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver -class Sms(CanExecuteCommands): - def send_sms(self: T, phone_number: str, message: str) -> T: +class Sms(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): + def send_sms(self, phone_number: str, message: str) -> 'WebDriver': """Emulate send SMS event on the connected emulator. Android only. @@ -36,8 +41,14 @@ def send_sms(self: T, phone_number: str, message: str) -> T: Returns: Union['WebDriver', 'Sms']: Self instance """ - self.execute(Command.SEND_SMS, {'phoneNumber': phone_number, 'message': message}) - return self + ext_name = 'mobile: sendSms' + args = {'phoneNumber': phone_number, 'message': message} + try: + self.assert_extension_exists(ext_name).execute_script(ext_name, args) + except UnknownMethodException: + # TODO: Remove the fallback + self.mark_extension_absence(ext_name).execute(Command.SEND_SMS, args) + return cast('WebDriver', self) def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/appium/webdriver/extensions/android/system_bars.py b/appium/webdriver/extensions/android/system_bars.py index b894fe7c5..d2fed74f4 100644 --- a/appium/webdriver/extensions/android/system_bars.py +++ b/appium/webdriver/extensions/android/system_bars.py @@ -14,11 +14,15 @@ from typing import Dict, Union +from selenium.common.exceptions import UnknownMethodException + from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands +from appium.protocols.webdriver.can_execute_scripts import CanExecuteScripts +from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -class SystemBars(CanExecuteCommands): +class SystemBars(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): def get_system_bars(self) -> Dict[str, Dict[str, Union[int, bool]]]: """Retrieve visibility and bounds information of the status and navigation bars. @@ -39,7 +43,12 @@ def get_system_bars(self) -> Dict[str, Dict[str, Union[int, bool]]]: - width - height """ - return self.execute(Command.GET_SYSTEM_BARS)['value'] + ext_name = 'mobile: getSystemBars' + try: + return self.assert_extension_exists(ext_name).execute_script(ext_name) + except UnknownMethodException: + # TODO: Remove the fallback + return self.mark_extension_absence(ext_name).execute(Command.GET_SYSTEM_BARS)['value'] def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index 6fdbd3e1f..66aea810a 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -29,11 +29,6 @@ class TestCommon(BaseTestCase): def test_current_package(self) -> None: 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.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: diff --git a/test/unit/webdriver/device/activities_test.py b/test/unit/webdriver/device/activities_test.py index b64a10d64..2374ba6cc 100644 --- a/test/unit/webdriver/device/activities_test.py +++ b/test/unit/webdriver/device/activities_test.py @@ -14,49 +14,10 @@ import httpretty -from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body +from test.unit.helper.test_helper import android_w3c_driver, appium_command class TestWebDriverActivities(object): - @httpretty.activate - def test_start_activity(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/appium/device/start_activity'), body='{"value": ""}' - ) - driver.start_activity('com.example.myapp', '.ExampleActivity') - - d = get_httpretty_request_body(httpretty.last_request()) - assert d['appPackage'] == 'com.example.myapp' - assert d['appActivity'] == '.ExampleActivity' - - @httpretty.activate - def test_start_activity_with_opts(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/appium/device/start_activity'), body='{"value": ""}' - ) - driver.start_activity( - app_package='com.example.myapp', - app_activity='.ExampleActivity', - app_wait_package='com.example.waitapp', - intent_action='android.intent.action.MAIN', - intent_category='android.intent.category.LAUNCHER', - intent_flags='0x10200000', - optional_intent_arguments='--es "activity" ".ExampleActivity"', - dont_stop_app_on_reset=True, - ) - - d = get_httpretty_request_body(httpretty.last_request()) - assert d['appPackage'] == 'com.example.myapp' - assert d['appActivity'] == '.ExampleActivity' - assert d['appWaitPackage'] == 'com.example.waitapp' - assert d['intentAction'] == 'android.intent.action.MAIN' - assert d['intentCategory'] == 'android.intent.category.LAUNCHER' - assert d['intentFlags'] == '0x10200000' - assert d['optionalIntentArguments'] == '--es "activity" ".ExampleActivity"' - assert d['dontStopAppOnReset'] is True - @httpretty.activate def test_current_activity(self): driver = android_w3c_driver() @@ -65,6 +26,11 @@ def test_current_activity(self): appium_command('/session/1234567890/appium/device/current_activity'), body='{"value": ".ExampleActivity"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": ".ExampleActivity"}', + ) assert driver.current_activity == '.ExampleActivity' @httpretty.activate @@ -75,4 +41,9 @@ def test_wait_activity(self): appium_command('/session/1234567890/appium/device/current_activity'), body='{"value": ".ExampleActivity"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": ".ExampleActivity"}', + ) assert driver.wait_activity('.ExampleActivity', 1) is True diff --git a/test/unit/webdriver/device/common_test.py b/test/unit/webdriver/device/common_test.py index 3d9b22834..c4065e472 100644 --- a/test/unit/webdriver/device/common_test.py +++ b/test/unit/webdriver/device/common_test.py @@ -23,6 +23,7 @@ class TestWebDriverCommon(object): def test_open_notifications(self): driver = android_w3c_driver() httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/appium/device/open_notifications')) + httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.open_notifications(), WebDriver) @httpretty.activate @@ -33,4 +34,9 @@ def test_current_package(self): appium_command('/session/1234567890/appium/device/current_package'), body='{"value": ".ExamplePackage"}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": ".ExamplePackage"}', + ) assert driver.current_package == '.ExamplePackage' diff --git a/test/unit/webdriver/device/display_test.py b/test/unit/webdriver/device/display_test.py index 927896071..96adab424 100644 --- a/test/unit/webdriver/device/display_test.py +++ b/test/unit/webdriver/device/display_test.py @@ -24,4 +24,7 @@ def test_get_display_density(self): httpretty.register_uri( httpretty.GET, appium_command('/session/1234567890/appium/device/display_density'), body='{"value": 560}' ) + httpretty.register_uri( + httpretty.POST, appium_command('/session/1234567890/execute/sync'), body='{"value": 560}' + ) assert driver.get_display_density() == 560 diff --git a/test/unit/webdriver/device/gsm_test.py b/test/unit/webdriver/device/gsm_test.py index f6ad5ee05..cd776a94a 100644 --- a/test/unit/webdriver/device/gsm_test.py +++ b/test/unit/webdriver/device/gsm_test.py @@ -27,11 +27,15 @@ def test_make_gsm_call(self): httpretty.POST, appium_command('/session/1234567890/appium/device/gsm_call'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.make_gsm_call('5551234567', GsmCallActions.CALL), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['phoneNumber'] == '5551234567' - assert d['action'] == GsmCallActions.CALL + assert d['args'][0]['phoneNumber'] == '5551234567' + assert d['args'][0]['action'] == GsmCallActions.CALL @httpretty.activate def test_set_gsm_signal(self): @@ -40,11 +44,14 @@ def test_set_gsm_signal(self): httpretty.POST, appium_command('/session/1234567890/appium/device/gsm_signal'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.set_gsm_signal(GsmSignalStrength.GREAT), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['signalStrength'] == GsmSignalStrength.GREAT - assert d['signalStrengh'] == GsmSignalStrength.GREAT + assert d['args'][0]['strength'] == GsmSignalStrength.GREAT @httpretty.activate def test_set_gsm_voice(self): @@ -53,7 +60,11 @@ def test_set_gsm_voice(self): httpretty.POST, appium_command('/session/1234567890/appium/device/gsm_voice'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.set_gsm_voice(GsmVoiceState.ROAMING), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['state'] == GsmVoiceState.ROAMING + assert d['args'][0]['state'] == GsmVoiceState.ROAMING diff --git a/test/unit/webdriver/device/power_test.py b/test/unit/webdriver/device/power_test.py index 7a5254c40..2d87a8d4c 100644 --- a/test/unit/webdriver/device/power_test.py +++ b/test/unit/webdriver/device/power_test.py @@ -27,10 +27,14 @@ def test_set_power_capacity(self): httpretty.POST, appium_command('/session/1234567890/appium/device/power_capacity'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.set_power_capacity(50), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['percent'] == 50 + assert d['args'][0]['percent'] == 50 @httpretty.activate def test_set_power_ac(self): @@ -39,7 +43,11 @@ def test_set_power_ac(self): httpretty.POST, appium_command('/session/1234567890/appium/device/power_ac'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.set_power_ac(Power.AC_ON), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['state'] == Power.AC_ON + assert d['args'][0]['state'] == Power.AC_ON diff --git a/test/unit/webdriver/device/sms_test.py b/test/unit/webdriver/device/sms_test.py index 5e3fb2001..1323f50c4 100644 --- a/test/unit/webdriver/device/sms_test.py +++ b/test/unit/webdriver/device/sms_test.py @@ -26,8 +26,12 @@ def test_send_sms(self): httpretty.POST, appium_command('/session/1234567890/appium/device/send_sms'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.send_sms('555-123-4567', 'Hey lol'), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['phoneNumber'] == '555-123-4567' - assert d['message'] == 'Hey lol' + assert d['args'][0]['phoneNumber'] == '555-123-4567' + assert d['args'][0]['message'] == 'Hey lol' diff --git a/test/unit/webdriver/device/system_bars_test.py b/test/unit/webdriver/device/system_bars_test.py index 6799733ea..2706be3db 100644 --- a/test/unit/webdriver/device/system_bars_test.py +++ b/test/unit/webdriver/device/system_bars_test.py @@ -21,14 +21,20 @@ class TestWebDriverSystemBars(object): @httpretty.activate def test_get_system_bars(self): driver = android_w3c_driver() + body = '''{"value": + {"statusBar": + {"visible": true, "x": 0, "y": 0, "width": 1080, "height": 1920}, + "navigationBar": + {"visible": true, "x": 0, "y": 0, "width": 1080, "height": 126}}}''' httpretty.register_uri( httpretty.GET, appium_command('/session/1234567890/appium/device/system_bars'), - body=''' {"value": - {"statusBar": - {"visible": true, "x": 0, "y": 0, "width": 1080, "height": 1920}, - "navigationBar": - {"visible": true, "x": 0, "y": 0, "width": 1080, "height": 126}}}''', + body=body, + ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body=body, ) d = driver.get_system_bars() diff --git a/test/unit/webdriver/network_test.py b/test/unit/webdriver/network_test.py index c903db402..d586fcbd4 100644 --- a/test/unit/webdriver/network_test.py +++ b/test/unit/webdriver/network_test.py @@ -27,6 +27,11 @@ def test_network_connection(self): httpretty.register_uri( httpretty.GET, appium_command('/session/1234567890/network_connection'), body='{"value": 2}' ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": {"wifi": true, "data": false, "airplaneMode": false}}', + ) assert driver.network_connection == 2 @httpretty.activate @@ -35,10 +40,15 @@ def test_set_network_connection(self): httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/network_connection'), body='{"value": ""}' ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": {"wifi": true, "data": false, "airplaneMode": false}}', + ) driver.set_network_connection(2) d = get_httpretty_request_body(httpretty.last_request()) - assert d['parameters']['type'] == 2 + assert d['args'][0]['wifi'] is True @httpretty.activate def test_set_network_speed(self): @@ -47,10 +57,14 @@ def test_set_network_speed(self): httpretty.POST, appium_command('/session/1234567890/appium/device/network_speed'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + ) assert isinstance(driver.set_network_speed(NetSpeed.LTE), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d['netspeed'] == NetSpeed.LTE + assert d['args'][0]['speed'] == NetSpeed.LTE @httpretty.activate def test_toggle_wifi(self): @@ -59,4 +73,9 @@ def test_toggle_wifi(self): httpretty.POST, appium_command('/session/1234567890/appium/device/toggle_wifi'), ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": {"wifi": true, "data": false, "airplaneMode": false}}', + ) assert isinstance(driver.toggle_wifi(), WebDriver) diff --git a/test/unit/webdriver/performance_test.py b/test/unit/webdriver/performance_test.py index 78d62756f..d4534c903 100644 --- a/test/unit/webdriver/performance_test.py +++ b/test/unit/webdriver/performance_test.py @@ -26,12 +26,16 @@ def test_get_performance_data(self): appium_command('/session/1234567890/appium/getPerformanceData'), body='{"value": [["user", "kernel"], ["2.5", "1.3"]]}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": [["user", "kernel"], ["2.5", "1.3"]]}', + ) assert driver.get_performance_data('my.app.package', 'cpuinfo', 5) == [['user', 'kernel'], ['2.5', '1.3']] d = get_httpretty_request_body(httpretty.last_request()) - assert d['packageName'] == 'my.app.package' - assert d['dataType'] == 'cpuinfo' - assert d['dataReadTimeout'] == 5 + assert d['args'][0]['packageName'] == 'my.app.package' + assert d['args'][0]['dataType'] == 'cpuinfo' @httpretty.activate def test_get_performance_data_types(self): @@ -41,4 +45,9 @@ def test_get_performance_data_types(self): appium_command('/session/1234567890/appium/performanceData/types'), body='{"value": ["cpuinfo", "memoryinfo", "batteryinfo", "networkinfo"]}', ) + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + body='{"value": ["cpuinfo", "memoryinfo", "batteryinfo", "networkinfo"]}', + ) assert driver.get_performance_data_types() == ['cpuinfo', 'memoryinfo', 'batteryinfo', 'networkinfo'] From 2dbce790fe6b87ff489a3dcf1d4872f9e2873595 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 11 May 2023 09:46:52 +0200 Subject: [PATCH 187/548] fix: Update connection manager creation (#864) --- appium/webdriver/appium_connection.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 87915e867..5b567609d 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -25,6 +25,8 @@ class AppiumConnection(RemoteConnection): + _proxy_url: Optional[str] + def __init__( self, remote_server_addr: str, @@ -39,7 +41,7 @@ def __init__( def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyManager]: # https://github.com/SeleniumHQ/selenium/blob/0e0194b0e52a34e7df4b841f1ed74506beea5c3e/py/selenium/webdriver/remote/remote_connection.py#L134 - pool_manager_init_args = {'timeout': self._timeout} + pool_manager_init_args = {'timeout': self.get_timeout()} if self._ca_certs: pool_manager_init_args['cert_reqs'] = 'CERT_REQUIRED' @@ -50,11 +52,17 @@ def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyMan pool_manager_init_args.update(self._init_args_for_pool_manager) - return ( - urllib3.PoolManager(**pool_manager_init_args) - if self._proxy_url is None - else urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args) - ) + if self._proxy_url: + if self._proxy_url.lower().startswith('sock'): + from urllib3.contrib.socks import SOCKSProxyManager + + return SOCKSProxyManager(self._proxy_url, **pool_manager_init_args) + if self._identify_http_proxy_auth(): + self._proxy_url, self._basic_proxy_auth = self._separate_http_proxy_auth() + pool_manager_init_args['proxy_headers'] = urllib3.make_headers(proxy_basic_auth=self._basic_proxy_auth) + return urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args) + + return urllib3.PoolManager(**pool_manager_init_args) @classmethod def get_remote_connection_headers(cls, parsed_url: 'ParseResult', keep_alive: bool = True) -> Dict[str, Any]: From a369b6fa9efee502a31349a6a84dd1cdd9c5d070 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 11 May 2023 22:50:41 -0700 Subject: [PATCH 188/548] Update changelog for 2.10.0 --- CHANGELOG.rst | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 720c078fb..aa3fa8a41 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,74 @@ Changelog ========= +v2.10.0 (2023-05-11) +-------------------- + +Fix +~~~ +- Update connection manager creation (#864) [Mykola Mokhnach] + +Other +~~~~~ +- Refactor: Move driver-specific commands to use extensions (part2) + (#859) [Mykola Mokhnach] +- Chore(deps): update selenium requirement from ~=4.7 to ~=4.9 (#852) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.7.0...selenium-4.9.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Refactor: Move driver-specific commands to use extensions (part1) + (#856) [Mykola Mokhnach] +- Chore(deps-dev): update pylint requirement from ~=2.17.2 to ~=2.17.3 + (#853) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.2...v2.17.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.4 to ~=4.5 (#854) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.4.0...4.5.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=1.1 to ~=1.2 (#848) + [dependabot[bot]] +- Chore(deps-dev): update pylint requirement from ~=2.17.1 to ~=2.17.2 + (#847) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.1...v2.17.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: bump and correct version. [Kazuaki Matsuo] +- Update changelog for 2.9.0. [Kazuaki Matsuo] + + v2.9.0 (2023-04-02) ------------------- From 87f5d592a51a0c844c0e564d0bfff08aae991212 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 11 May 2023 22:57:16 -0700 Subject: [PATCH 189/548] chrom: bump the version --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 716516129..725205ac4 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.9.0' +version = '2.10.0' From 5c20a358ae94c996ea3ddd4964ad828005b17801 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 20 May 2023 06:38:36 +0200 Subject: [PATCH 190/548] fix: W3C errors to exception classes mapping (#869) * fix: W3C errors to exception classes mapping * Imports * Tune --- appium/webdriver/errorhandler.py | 110 ++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 9 deletions(-) diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index fd582645b..632cd44e7 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -12,19 +12,111 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict +import json +from typing import Any, Dict, List, Sequence, Type, Union -from selenium.common.exceptions import WebDriverException +import selenium.common.exceptions as sel_exceptions from selenium.webdriver.remote import errorhandler -from appium.common.exceptions import NoSuchContextException +import appium.common.exceptions as appium_exceptions + +ERROR_TO_EXC_MAPPING: Dict[str, Type[sel_exceptions.WebDriverException]] = { + 'element click intercepted': sel_exceptions.ElementClickInterceptedException, + 'element not interactable': sel_exceptions.ElementNotInteractableException, + 'insecure certificate': sel_exceptions.InsecureCertificateException, + 'invalid argument': sel_exceptions.InvalidArgumentException, + 'invalid cookie domain': sel_exceptions.InvalidCookieDomainException, + 'invalid element state': sel_exceptions.InvalidElementStateException, + 'invalid selector': sel_exceptions.InvalidSelectorException, + 'invalid session id': sel_exceptions.InvalidSessionIdException, + 'javascript error': sel_exceptions.JavascriptException, + 'move target out of bounds': sel_exceptions.MoveTargetOutOfBoundsException, + 'no such alert': sel_exceptions.NoAlertPresentException, + 'no such cookie': sel_exceptions.NoSuchCookieException, + 'no such element': sel_exceptions.NoSuchElementException, + 'no such frame': sel_exceptions.NoSuchFrameException, + 'no such window': sel_exceptions.NoSuchWindowException, + 'no such shadow root': sel_exceptions.NoSuchShadowRootException, + 'script timeout': sel_exceptions.TimeoutException, + 'session not created': sel_exceptions.SessionNotCreatedException, + 'stale element reference': sel_exceptions.StaleElementReferenceException, + 'detached shadow root': sel_exceptions.NoSuchShadowRootException, + 'timeout': sel_exceptions.TimeoutException, + 'unable to set cookie': sel_exceptions.UnableToSetCookieException, + 'unable to capture screen': sel_exceptions.ScreenshotException, + 'unexpected alert open': sel_exceptions.UnexpectedAlertPresentException, + 'unknown command': sel_exceptions.UnknownMethodException, + 'unknown error': sel_exceptions.WebDriverException, + 'unknown method': sel_exceptions.UnknownMethodException, + 'unsupported operation': sel_exceptions.UnknownMethodException, + 'element not visible': sel_exceptions.ElementNotVisibleException, + 'element not selectable': sel_exceptions.ElementNotSelectableException, + 'invalid coordinates': sel_exceptions.InvalidCoordinatesException, +} + + +def format_stacktrace(original: Union[None, str, Sequence]) -> List[str]: + if not original: + return [] + if isinstance(original, str): + return original.split('\n') + + result: List[str] = [] + try: + for frame in original: + if not isinstance(frame, dict): + continue + + line = frame.get('lineNumber', '') + file = frame.get('fileName', '') + if line: + file = f'{file}:{line}' + meth = frame.get('methodName', '') + if 'className' in frame: + meth = f'{frame["className"]}.{meth}' + result.append(f' at {meth} ({file})') + except TypeError: + pass + return result class MobileErrorHandler(errorhandler.ErrorHandler): - def check_response(self, response: Dict) -> None: + def check_response(self, response: Dict[str, Any]) -> None: + """ + https://www.w3.org/TR/webdriver/#errors + """ + payload = response.get('value', '') try: - super().check_response(response) - except WebDriverException as wde: - if wde.msg == 'No such context found.': - raise NoSuchContextException(wde.msg, wde.screen, wde.stacktrace) from wde - raise wde + payload_dict = json.loads(payload) + except (json.JSONDecodeError, TypeError): + return + if not isinstance(payload_dict, dict): + return + value = payload_dict.get('value') + if not isinstance(value, dict): + return + error = value.get('error') + if not error: + return + + message = value.get('message', error) + stacktrace = value.get('stacktrace', '') + # In theory, we should also be checking HTTP status codes. + # Java client, for example, prints a warning if the actual `error` + # value does not match to the response's HTTP status code. + exception_class: Type[sel_exceptions.WebDriverException] = ERROR_TO_EXC_MAPPING.get( + error, sel_exceptions.WebDriverException + ) + if exception_class is sel_exceptions.WebDriverException and message: + if message == 'No such context found.': + exception_class = appium_exceptions.NoSuchContextException + elif message == 'That command could not be executed in the current context.': + exception_class = appium_exceptions.InvalidSwitchToTargetException + + if exception_class is sel_exceptions.UnexpectedAlertPresentException: + raise sel_exceptions.UnexpectedAlertPresentException( + msg=message, + stacktrace=format_stacktrace(stacktrace), + alert_text=value.get('data'), + ) + raise exception_class(msg=message, stacktrace=format_stacktrace(stacktrace)) From eec7f89ad28fca29cbfe2277e3f9c13ef5c467b7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 19 May 2023 23:45:34 -0700 Subject: [PATCH 191/548] Update changelog for 2.10.1 --- CHANGELOG.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa3fa8a41..925a5c6fb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,25 @@ Changelog ========= +v2.10.1 (2023-05-20) +-------------------- + +Fix +~~~ +- W3C errors to exception classes mapping (#869) [Mykola Mokhnach] + + * fix: W3C errors to exception classes mapping + + * Imports + + * Tune + +Other +~~~~~ +- Chrom: bump the version. [Kazuaki Matsuo] +- Update changelog for 2.10.0. [Kazuaki Matsuo] + + v2.10.0 (2023-05-11) -------------------- From 8bb7b4ccac8f54d2922e90ace30108ef7dd70057 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 19 May 2023 23:47:52 -0700 Subject: [PATCH 192/548] chore: nump the version --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 725205ac4..d0edcc522 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.10.0' +version = '2.10.1' From 1e4c574718bdbbb00fb6c81b7499f1a746294ab3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 08:12:20 +0200 Subject: [PATCH 193/548] chore(deps-dev): update typing-extensions requirement (#871) Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.5.0...4.6.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b9156d6a8..121bfc4ca 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.2" pytest-cov = "~=4.0" python-dateutil = "~=2.8" tox = "~=4.5" -typing-extensions = "~=4.5" +typing-extensions = "~=4.6" types-python-dateutil = "~=2.8" [packages] From 74f39eda8129a45a9798b948733d726b76237973 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 08:12:42 +0200 Subject: [PATCH 194/548] chore(deps-dev): update pytest-cov requirement from ~=4.0 to ~=4.1 (#872) Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 121bfc4ca..6aa6973cf 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.17.3" pytest = "~=7.2" -pytest-cov = "~=4.0" +pytest-cov = "~=4.1" python-dateutil = "~=2.8" tox = "~=4.5" typing-extensions = "~=4.6" From 1f6dec384e7c911a134662ee2393221d0af298b9 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 26 May 2023 10:37:29 +0200 Subject: [PATCH 195/548] docs: Improve usage examples (#873) --- README.md | 131 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 8142fa473..d0a6b2ad4 100644 --- a/README.md +++ b/README.md @@ -103,41 +103,102 @@ As a base for the following code examples, the following sets up the [UnitTest]( environment: ```python -# Android environment -from appium import webdriver -# Options are only available since client version 2.3.0 -# If you use an older client then switch to desired_capabilities -# instead: https://github.com/appium/python-client/pull/720 -from appium.options.android import UiAutomator2Options -from appium.webdriver.common.appiumby import AppiumBy - -options = UiAutomator2Options() -options.platformVersion = '10' -options.udid = '123456789ABC' -options.app = PATH('../../../apps/test-app.apk') -# Appium1 points to http://127.0.0.1:4723/wd/hub by default -self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) -el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') -el.click() -``` +# Python/Pytest +import pytest -```python -# iOS environment from appium import webdriver # Options are only available since client version 2.3.0 # If you use an older client then switch to desired_capabilities # instead: https://github.com/appium/python-client/pull/720 +from appium.options.android import UiAutomator2Options from appium.options.ios import XCUITestOptions +from appium.webdriver.appium_service import AppiumService from appium.webdriver.common.appiumby import AppiumBy -options = XCUITestOptions() -options.platformVersion = '13.4' -options.udid = '123456789ABC' -options.app = PATH('../../apps/UICatalog.app.zip') -# Appium1 points to http://127.0.0.1:4723/wd/hub by default -self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options) -el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') -el.click() +APPIUM_PORT = 4723 +APPIUM_HOST = '127.0.0.1' + + +# HINT: fixtures below could be extracted into conftest.py +# HINT: and shared across all tests in the suite +@pytest.fixture(scope='session') +def appium_service(): + service = AppiumService() + service.start( + # Check the output of `appium server --help` for the complete list of + # server command line arguments + args=['--address', APPIUM_HOST, '-p', str(APPIUM_PORT)], + timeout_ms=20000, + ) + yield service + service.stop() + + +def create_ios_driver(custom_opts = None): + options = XCUITestOptions() + options.platformVersion = '13.4' + options.udid = '123456789ABC' + if custom_opts is not None: + options.load_capabilities(custom_opts) + # Appium1 points to http://127.0.0.1:4723/wd/hub by default + return webdriver.Remote(f'http://{APPIUM_HOST}:{APPIUM_PORT}', options=options) + + +def create_android_driver(custom_opts = None): + options = UiAutomator2Options() + options.platformVersion = '10' + options.udid = '123456789ABC' + if custom_opts is not None: + options.load_capabilities(custom_opts) + # Appium1 points to http://127.0.0.1:4723/wd/hub by default + return webdriver.Remote(f'http://{APPIUM_HOST}:{APPIUM_PORT}', options=options) + + +@pytest.fixture +def ios_driver_factory(): + return create_ios_driver + + +@pytest.fixture +def ios_driver(): + # prefer this fixture if there is no need to customize driver options in tests + driver = create_ios_driver() + yield driver + driver.quit() + + +@pytest.fixture +def android_driver_factory(): + return create_android_driver + + +@pytest.fixture +def android_driver(): + # prefer this fixture if there is no need to customize driver options in tests + driver = create_android_driver() + yield driver + driver.quit() + + +def test_ios_click(appium_service, ios_driver_factory): + # Usage of the context manager ensures the driver session is closed properly + # after the test completes. Otherwise, make sure to call `driver.quit()` on teardown. + with ios_driver_factory({ + 'appium:app': '/path/to/app/UICatalog.app.zip' + }) as driver: + el = driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') + el.click() + + +def test_android_click(appium_service, android_driver_factory): + # Usage of the context manager ensures the driver session is closed properly + # after the test completes. Otherwise, make sure to call `driver.quit()` on teardown. + with android_driver_factory({ + 'appium:app': '/path/to/app/test-app.apk', + 'appium:udid': '567890', + }) as driver: + el = driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item') + el.click() ``` ## Direct Connect URLs @@ -163,10 +224,10 @@ from appium.options.ios import XCUITestOptions options = XCUITestOptions().load_capabilities({ 'platformVersion': '13.4', 'deviceName': 'iPhone Simulator', - 'app': PATH('../../apps/UICatalog.app.zip'), + 'app': '/full/path/to/app/UICatalog.app.zip', }) -self.driver = webdriver.Remote( +driver = webdriver.Remote( # Appium1 points to http://127.0.0.1:4723/wd/hub by default 'http://127.0.0.1:4723', options=options, @@ -193,7 +254,7 @@ options.automation_name = 'safari' options.set_capability('browser_name', 'safari') # Appium1 points to http://127.0.0.1:4723/wd/hub by default -self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=False) +driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=False) ``` ## Set custom `AppiumConnection` @@ -213,13 +274,15 @@ from appium.webdriver.appium_connection import AppiumConnection init_args_for_pool_manage = { 'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False) } -appium_executor = AppiumConnection(remote_server_addr='http://127.0.0.1:4723', - init_args_for_pool_manage=init_args_for_pool_manage) +appium_executor = AppiumConnection( + remote_server_addr='http://127.0.0.1:4723', + init_args_for_pool_manage=init_args_for_pool_manage +) options = XCUITestOptions() options.platformVersion = '13.4' options.udid = '123456789ABC' -options.app = PATH('../../apps/UICatalog.app.zip') +options.app = '/full/path/to/app/UICatalog.app.zip' driver = webdriver.Remote(appium_executor, options=options) ``` @@ -241,7 +304,7 @@ custom_executor = CustomAppiumConnection(remote_server_addr='http://127.0.0.1:47 options = XCUITestOptions().load_capabilities({ 'platformVersion': '13.4', 'deviceName': 'iPhone Simulator', - 'app': PATH('../../apps/UICatalog.app.zip'), + 'app': '/full/path/to/app/UICatalog.app.zip', }) driver = webdriver.Remote(custom_executor, options=options) From 5a4b6d0729601b3fdb821e93ebe24bf2d248c65b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 27 May 2023 23:49:19 -0700 Subject: [PATCH 196/548] ci: add py11 for the unit test (#875) --- .github/workflows/unit-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e480d5ae9..a57bbc748 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 2e7a6a3e80852883d30d3d5e3859a5fdd1e29eb6 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 30 May 2023 14:27:39 -0700 Subject: [PATCH 197/548] docs: address version management recommendation in the readme (#874) * chore: set the max selenium deps version * ci: add python 11 * Update unit-test.yml * add note in the readme * Update README.md * docs: merge the matrix pr into README.md --- README.md | 17 +++++++++++++++++ setup.py | 2 ++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index d0a6b2ad4..ba55297b7 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,23 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). python setup.py install ``` +## Compatibility Matrix + +|Appium Python Client| Selenium binding| +|----|----| +|`2.10.0`+ |`4.1.0`+ | +|`2.2.0` - `2.9.0` |`4.1.0` - `4.9.0` | +|`2.0.0` - `2.1.4` |`4.0.0` | +|`1.1.0` and below|`3.x`| + +The Appium Python Client depends on [Selenium Python binding](https://pypi.org/project/selenium/), thus +the Selenium Python binding update might affect the Appium Python Client behavior. +For exampple, some changes in the Selenium binding could break the Appium client. + +> **Note** +> We strongly recommend you to manage dependencies with version management tools such as Pipenv and requirements.txt +> to keep compatible version combinations. + ## Usage The Appium Python Client is fully compliant with the WebDriver Protocol diff --git a/setup.py b/setup.py index cc0c2eb8d..b4a56f023 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,8 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Environment :: Console', 'Environment :: MacOS X', 'Environment :: Win32 (MS Windows)', From a4e411848f7e46af4d0484925dbbc55061aee5f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 08:45:32 +0200 Subject: [PATCH 198/548] chore(deps-dev): update tox requirement from ~=4.5 to ~=4.6 (#877) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.5.0...4.6.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 6aa6973cf..3df12115b 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=2.17.3" pytest = "~=7.2" pytest-cov = "~=4.1" python-dateutil = "~=2.8" -tox = "~=4.5" +tox = "~=4.6" typing-extensions = "~=4.6" types-python-dateutil = "~=2.8" From 2f45ef935c12dec2ab8de044ce6a1c1e0b9aa46f Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 7 Jun 2023 23:53:04 -0700 Subject: [PATCH 199/548] chore: remove duplicated clean command (#809) * remove clear * remove clear more --- appium/webdriver/mobilecommand.py | 1 - appium/webdriver/webdriver.py | 1 - appium/webdriver/webelement.py | 12 ------------ 3 files changed, 14 deletions(-) diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index 2a83f1a41..d794ce526 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -31,7 +31,6 @@ class MobileCommand: DEACTIVATE_IME_ENGINE = 'deactivateIMEEngine' GET_ACTIVE_IME_ENGINE = 'getActiveEngine' - CLEAR = 'clear' LOCATION_IN_VIEW = 'locationInView' CONTEXTS = 'getContexts' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 2af8220ad..34e452e9e 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -556,7 +556,6 @@ def _add_commands(self) -> None: 'POST', '/session/$sessionId/appium/element/$id/replace_value', ) - commands[Command.CLEAR] = ('POST', '/session/$sessionId/element/$id/clear') commands[Command.LOCATION_IN_VIEW] = ( 'GET', '/session/$sessionId/element/$id/location_in_view', diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 67e630eb7..f850e3b25 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -144,18 +144,6 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = N return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] - def clear(self) -> 'WebElement': - """Clears text. - - Override for Appium - - Returns: - `appium.webdriver.webelement.WebElement` - """ - data = {'id': self.id} - self._execute(Command.CLEAR, data) - return self - def set_text(self, keys: str = '') -> 'WebElement': """Sends text to the element. deprecated:: 2.8.1 From c0f38bf293fbae141bed8a9c49643a7b8b75efed Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 8 Jun 2023 10:08:02 +0000 Subject: [PATCH 200/548] fix: Update the constructor for compatibility with python client 4.10 (#879) --- Pipfile | 2 +- appium/webdriver/webdriver.py | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Pipfile b/Pipfile index 3df12115b..ff44a8119 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ typing-extensions = "~=4.6" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.9" +selenium = "~=4.10" diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 34e452e9e..084b8d714 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -90,8 +90,8 @@ def add_command(self): 2. Creates a session with the extension. # Appium capabilities - desired_caps = { ... } - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, + options = AppiumOptions() + driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options, extensions=[YourCustomCommand]) 3. Calls the custom command @@ -207,8 +207,11 @@ class WebDriver( def __init__( self, command_executor: Union[str, AppiumConnection] = 'http://127.0.0.1:4444/wd/hub', + # TODO: Remove the deprecated arg desired_capabilities: Optional[Dict] = None, + # TODO: Remove the deprecated arg browser_profile: Union[str, None] = None, + # TODO: Remove the deprecated arg proxy: Union[str, None] = None, keep_alive: bool = True, direct_connection: bool = True, @@ -232,12 +235,28 @@ def __init__( if isinstance(command_executor, str): command_executor = AppiumConnection(command_executor, keep_alive=keep_alive) + if browser_profile is not None: + warnings.warn('browser_profile argument is deprecated and has no effect', DeprecationWarning) + + if proxy is not None: + warnings.warn('proxy argument is deprecated and has no effect', DeprecationWarning) + + if desired_capabilities is not None: + warnings.warn( + 'desired_capabilities argument is deprecated and will be removed in future versions. ' + 'Use options instead.', + DeprecationWarning, + ) + # TODO: Remove the fallback after desired_capabilities removal + dst_options = ( + AppiumOptions().load_capabilities(desired_capabilities) + if desired_capabilities is not None and options is None + else options + ) + super().__init__( command_executor=command_executor, - desired_capabilities=desired_capabilities, - browser_profile=browser_profile, - proxy=proxy, - options=options, + options=dst_options, ) if hasattr(self, 'command_executor'): From 18cd642d6a7ea7099d2b704dbe646636db2a4701 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 8 Jun 2023 09:28:30 -0700 Subject: [PATCH 201/548] Bump 2.10.2 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index d0edcc522..d91c60ad1 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.10.1' +version = '2.10.2' From c39bd8e9f9efef871ba69b9469724252061939f3 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 8 Jun 2023 09:28:34 -0700 Subject: [PATCH 202/548] Update changelog for 2.10.2 --- CHANGELOG.rst | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 925a5c6fb..7799c9f1f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,82 @@ Changelog ========= +v2.10.2 (2023-06-08) +-------------------- + +Fix +~~~ +- Update the constructor for compatibility with python client 4.10 + (#879) [Mykola Mokhnach] + +Test +~~~~ +- Ci: add py11 for the unit test (#875) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.10.2. [Kazuaki Matsuo] +- Chore: remove duplicated clean command (#809) [Kazuaki Matsuo] + + * remove clear + + * remove clear more +- Chore(deps-dev): update tox requirement from ~=4.5 to ~=4.6 (#877) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.5.0...4.6.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Docs: address version management recommendation in the readme (#874) + [Kazuaki Matsuo] + + * chore: set the max selenium deps version + + * ci: add python 11 + + * Update unit-test.yml + + * add note in the readme + + * Update README.md + + * docs: merge the matrix pr into README.md +- Docs: Improve usage examples (#873) [Mykola Mokhnach] +- Chore(deps-dev): update pytest-cov requirement from ~=4.0 to ~=4.1 + (#872) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. + - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:development + ... +- Chore(deps-dev): update typing-extensions requirement (#871) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.5.0...4.6.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore: nump the version. [Kazuaki Matsuo] +- Update changelog for 2.10.1. [Kazuaki Matsuo] + + v2.10.1 (2023-05-20) -------------------- From f304f6509796580111240770a5f310fa6536f11c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 9 Jun 2023 01:03:33 -0700 Subject: [PATCH 203/548] feat: make the UA format with same as other clients (#793) * chore: set version with / * chore: update comment * update test * update tests --- appium/webdriver/appium_connection.py | 6 +++++- test/unit/webdriver/webdriver_test.py | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/appium/webdriver/appium_connection.py b/appium/webdriver/appium_connection.py index 5b567609d..e05274831 100644 --- a/appium/webdriver/appium_connection.py +++ b/appium/webdriver/appium_connection.py @@ -24,6 +24,9 @@ from urllib.parse import ParseResult +PREFIX_HEADER = 'appium/' + + class AppiumConnection(RemoteConnection): _proxy_url: Optional[str] @@ -68,7 +71,8 @@ def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyMan 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'] = f'appium/python {library_version()} ({headers["User-Agent"]})' + # e.g. appium/0.49 (selenium/3.141.0 (python linux)) + headers['User-Agent'] = f'{PREFIX_HEADER}{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()) diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 4b575bbb1..70b09600b 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -33,7 +33,7 @@ ) -class TestWebDriverWebDriver(object): +class TestWebDriverWebDriver: @httpretty.activate def test_create_session(self): httpretty.register_uri( @@ -54,7 +54,7 @@ def test_create_session(self): request = httpretty.HTTPretty.latest_requests[0] assert request.headers['content-type'] == 'application/json;charset=UTF-8' - assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] + assert f'appium/{appium_version.version} (selenium' in request.headers['user-agent'] request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) assert request_json.get('capabilities') is not None @@ -328,7 +328,7 @@ class CustomAppiumConnection(AppiumConnection): request = httpretty.HTTPretty.latest_requests[0] assert request.headers['content-type'] == 'application/json;charset=UTF-8' - assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] + assert f'appium/{appium_version.version} (selenium' in request.headers['user-agent'] request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) assert request_json.get('capabilities') is not None @@ -374,7 +374,7 @@ class CustomAppiumConnection(AppiumConnection): request = httpretty.HTTPretty.latest_requests[0] assert request.headers['content-type'] == 'application/json;charset=UTF-8' - assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent'] + assert f'appium/{appium_version.version} (selenium' in request.headers['user-agent'] request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) assert request_json.get('capabilities') is not None From a8db6a2092208c1a91e5553ad036f56d8292867e Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 9 Jun 2023 01:29:29 -0700 Subject: [PATCH 204/548] update changelog --- CHANGELOG.rst | 21 +++++++++++++++++++++ appium/version.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7799c9f1f..3ec106f33 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,27 @@ Changelog ========= +v2.11.0 (2023-06-09) +-------------------- + +New +~~~ +- Feat: make the UA format with same as other clients (#793) [Kazuaki + Matsuo] + + * chore: set version with / + + * chore: update comment + + * update test + + * update tests + +Other +~~~~~ +- Update changelog for 2.10.2. [Kazuaki Matsuo] + + v2.10.2 (2023-06-08) -------------------- diff --git a/appium/version.py b/appium/version.py index d91c60ad1..ab947aa86 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.10.2' +version = '2.11.0' From 54a082e3fd2b20cb505a9464d51c6be91c16a926 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 13 Jun 2023 10:30:33 -0700 Subject: [PATCH 205/548] Revert "chore: remove duplicated clean command" (#881) * Revert "chore: remove duplicated clean command (#809)" This reverts commit 2f45ef935c12dec2ab8de044ce6a1c1e0b9aa46f. * chore: left a comment * trim spaces --- appium/webdriver/mobilecommand.py | 1 + appium/webdriver/webdriver.py | 1 + appium/webdriver/webelement.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index d794ce526..2a83f1a41 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -31,6 +31,7 @@ class MobileCommand: DEACTIVATE_IME_ENGINE = 'deactivateIMEEngine' GET_ACTIVE_IME_ENGINE = 'getActiveEngine' + CLEAR = 'clear' LOCATION_IN_VIEW = 'locationInView' CONTEXTS = 'getContexts' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 084b8d714..aa4d2a0ba 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -575,6 +575,7 @@ def _add_commands(self) -> None: 'POST', '/session/$sessionId/appium/element/$id/replace_value', ) + commands[Command.CLEAR] = ('POST', '/session/$sessionId/element/$id/clear') commands[Command.LOCATION_IN_VIEW] = ( 'GET', '/session/$sessionId/element/$id/location_in_view', diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index f850e3b25..4dd1daf43 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -144,6 +144,21 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = N return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] + def clear(self) -> 'WebElement': + """Clears text. + + Override for Appium + + Returns: + `appium.webdriver.webelement.WebElement` + """ + + # NOTE: this method is overridden because the selenium client returned None instead of self. + # Appium python cleint would like to allow users to chain methods. + data = {'id': self.id} + self._execute(Command.CLEAR, data) + return self + def set_text(self, keys: str = '') -> 'WebElement': """Sends text to the element. deprecated:: 2.8.1 From 64966198b542cabd7bdb4d0dd6bdb1a4f8f0bf08 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 13 Jun 2023 13:57:52 -0700 Subject: [PATCH 206/548] docs: update changelogs and version --- CHANGELOG.rst | 15 +++++++++++++++ appium/version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3ec106f33..06f83b620 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,21 @@ Changelog ========= +v2.11.1 (2023-06-13) +-------------------- +- Revert "chore: remove duplicated clean command" (#881) [Kazuaki + Matsuo] + + * Revert "chore: remove duplicated clean command (#809)" + + This reverts commit 2f45ef935c12dec2ab8de044ce6a1c1e0b9aa46f. + + * chore: left a comment + + * trim spaces +- Update changelog. [Kazuaki Matsuo] + + v2.11.0 (2023-06-09) -------------------- diff --git a/appium/version.py b/appium/version.py index ab947aa86..cf22adfab 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.11.0' +version = '2.11.1' From 43ad4db272b8115e752177a8336a05dc3943e79e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:32:27 -0700 Subject: [PATCH 207/548] chore(deps-dev): update typing-extensions requirement (#885) Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - [Release notes](https://github.com/python/typing_extensions/releases) - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.6.0...4.7.0) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ff44a8119..f88565f0f 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest = "~=7.2" pytest-cov = "~=4.1" python-dateutil = "~=2.8" tox = "~=4.6" -typing-extensions = "~=4.6" +typing-extensions = "~=4.7" types-python-dateutil = "~=2.8" [packages] From fb8415edf11e5f7302e24df5c3354c0c69fe8776 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:07:03 -0700 Subject: [PATCH 208/548] chore(deps-dev): update pytest requirement from ~=7.2 to ~=7.4 (#884) --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index f88565f0f..e34a43a19 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ mypy = "<2.0" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.17.3" -pytest = "~=7.2" +pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" tox = "~=4.6" From b142e00e3f47065752c80f71f2f8cb80bf2500f7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 4 Jul 2023 01:28:24 -0700 Subject: [PATCH 209/548] ci: add pylint_quotes for pylint to use single quote as primary method (#886) --- .pylintrc | 4 ++++ Makefile | 2 +- Pipfile | 1 + appium/webdriver/common/multi_action.py | 2 +- appium/webdriver/common/touch_action.py | 2 +- appium/webdriver/extensions/action_helpers.py | 8 ++++---- appium/webdriver/extensions/location.py | 6 +++--- appium/webdriver/extensions/settings.py | 2 +- appium/webdriver/webdriver.py | 4 ++-- appium/webdriver/webelement.py | 4 ++-- test/functional/android/activities_tests.py | 8 ++++---- test/functional/android/context_switching_tests.py | 4 ++-- test/functional/android/helper/test_helper.py | 2 +- .../android/search_context/find_by_image_tests.py | 8 ++++---- .../android/search_context/find_by_uiautomator_tests.py | 2 +- test/functional/android/settings_tests.py | 4 ++-- test/functional/android/touch_action_tests.py | 2 +- test/functional/android/webelement_tests.py | 2 +- test/functional/ios/applications_tests.py | 2 +- test/functional/ios/helper/test_helper.py | 2 +- test/functional/ios/safari_tests.py | 2 +- test/functional/ios/webdriver_tests.py | 4 ++-- 22 files changed, 41 insertions(+), 36 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1d5306afa..c353824dd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -551,3 +551,7 @@ known-third-party=enchant # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception + +string-quote=single +triple-quote=single +docstring-quote=double diff --git a/Makefile b/Makefile index 0170afb21..e363d8163 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ black: ## Run black .PHONY: pylint pylint: ## Run pylint # TODO Remove --disable=E1136 when no errors in py39 - python -m pylint $(ARGS) --rcfile .pylintrc appium test --disable=E1136 + python -m pylint $(ARGS) --load-plugins pylint_quotes --rcfile .pylintrc appium test --disable=E1136 .PHONY: mypy mypy: ## Run mypy diff --git a/Pipfile b/Pipfile index e34a43a19..e05573ad6 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,7 @@ mypy = "<2.0" mock = "~=5.0" pre-commit = "~=2.21" pylint = "~=2.17.3" +pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index e416e2cf1..6800546ba 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -38,7 +38,7 @@ class MultiAction: def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: warnings.warn( - "[Deprecated] 'MultiAction' action is deprecated. Please use W3C actions instead.", DeprecationWarning + '[Deprecated] \'MultiAction\' action is deprecated. Please use W3C actions instead.', DeprecationWarning ) self._driver = driver diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index 79d52a4d1..aedda606b 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -42,7 +42,7 @@ class TouchAction: def __init__(self, driver: Optional['WebDriver'] = None): warnings.warn( - "[Deprecated] 'TouchAction' action is deprecated. Please use W3C actions instead.", DeprecationWarning + '[Deprecated] \'TouchAction\' action is deprecated. Please use W3C actions instead.', DeprecationWarning ) self._driver = driver diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 8c424a409..6a458b396 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -46,7 +46,7 @@ def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Op if duration is None: duration = 600 - touch_input = PointerInput(interaction.POINTER_TOUCH, "touch") + touch_input = PointerInput(interaction.POINTER_TOUCH, 'touch') actions = ActionChains(self) actions.w3c_actions = ActionBuilder(self, mouse=touch_input) @@ -96,7 +96,7 @@ def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) """ if len(positions) == 1: actions = ActionChains(self) - actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, 'touc')) x = positions[0][0] y = positions[0][1] actions.w3c_actions.pointer_action.move_to_location(x, y) @@ -145,7 +145,7 @@ def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: in Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ - touch_input = PointerInput(interaction.POINTER_TOUCH, "touch") + touch_input = PointerInput(interaction.POINTER_TOUCH, 'touch') actions = ActionChains(self) actions.w3c_actions = ActionBuilder(self, mouse=touch_input) @@ -174,7 +174,7 @@ def flick(self, start_x: int, start_y: int, end_x: int, end_y: int) -> 'WebDrive Union['WebDriver', 'ActionHelpers']: Self instance """ actions = ActionChains(self) - actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, 'touch')) actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) diff --git a/appium/webdriver/extensions/location.py b/appium/webdriver/extensions/location.py index 906e44376..b34c82726 100644 --- a/appium/webdriver/extensions/location.py +++ b/appium/webdriver/extensions/location.py @@ -63,9 +63,9 @@ def set_location( Union['WebDriver', 'Location']: Self instance """ data = { - "location": { - "latitude": latitude, - "longitude": longitude, + 'location': { + 'latitude': latitude, + 'longitude': longitude, } } if altitude is not None: diff --git a/appium/webdriver/extensions/settings.py b/appium/webdriver/extensions/settings.py index c45e819c6..3b44b84fc 100644 --- a/appium/webdriver/extensions/settings.py +++ b/appium/webdriver/extensions/settings.py @@ -42,7 +42,7 @@ def update_settings(self, settings: Dict[str, Any]) -> 'WebDriver': Args: settings: dictionary of settings to apply to the current test session """ - self.execute(Command.UPDATE_SETTINGS, {"settings": settings}) + self.execute(Command.UPDATE_SETTINGS, {'settings': settings}) return cast('WebDriver', self) def _add_commands(self) -> None: diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index aa4d2a0ba..48b7993ff 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -285,7 +285,7 @@ def __init__( instance = extension(self.execute) method_name = instance.method_name() if hasattr(WebDriver, method_name): - logger.debug(f"Overriding the method '{method_name}'") + logger.debug(f'Overriding the method \'{method_name}\'') # add a new method named 'instance.method_name()' and call it setattr(WebDriver, method_name, getattr(instance, method_name)) @@ -519,7 +519,7 @@ def orientation(self, value: str) -> None: if value.upper() in allowed_values: self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value}) else: - raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + raise WebDriverException('You can only set the orientation to \'LANDSCAPE\' and \'PORTRAIT\'') def assert_extension_exists(self, ext_name: str) -> 'WebDriver': """ diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 4dd1daf43..87b2dc9ab 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -112,7 +112,7 @@ def find_element(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = No # by = By.CSS_SELECTOR # value = '[name="%s"]' % value - return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {"using": by, "value": value})['value'] + return self._execute(RemoteCommand.FIND_CHILD_ELEMENT, {'using': by, 'value': value})['value'] def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = None) -> List['WebElement']: """Find elements given a AppiumBy strategy and locator @@ -142,7 +142,7 @@ def find_elements(self, by: str = AppiumBy.ID, value: Union[str, Dict, None] = N # by = By.CSS_SELECTOR # value = '[name="%s"]' % value - return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {"using": by, "value": value})['value'] + return self._execute(RemoteCommand.FIND_CHILD_ELEMENTS, {'using': by, 'value': value})['value'] def clear(self) -> 'WebElement': """Clears text. diff --git a/test/functional/android/activities_tests.py b/test/functional/android/activities_tests.py index 48910c041..948248e6e 100644 --- a/test/functional/android/activities_tests.py +++ b/test/functional/android/activities_tests.py @@ -22,17 +22,17 @@ def test_current_activity(self) -> None: assert '.ApiDemos' == activity def test_start_activity_this_app(self) -> None: - self.driver.start_activity(APIDEMO_PKG_NAME, ".ApiDemos") + self.driver.start_activity(APIDEMO_PKG_NAME, '.ApiDemos') self._assert_activity_contains('Demos') - self.driver.start_activity(APIDEMO_PKG_NAME, ".accessibility.AccessibilityNodeProviderActivity") + self.driver.start_activity(APIDEMO_PKG_NAME, '.accessibility.AccessibilityNodeProviderActivity') self._assert_activity_contains('Node') def test_start_activity_other_app(self) -> None: - self.driver.start_activity(APIDEMO_PKG_NAME, ".ApiDemos") + self.driver.start_activity(APIDEMO_PKG_NAME, '.ApiDemos') self._assert_activity_contains('Demos') - self.driver.start_activity("com.google.android.deskclock", "com.android.deskclock.DeskClock") + self.driver.start_activity('com.google.android.deskclock', 'com.android.deskclock.DeskClock') self._assert_activity_contains('Clock') def _assert_activity_contains(self, activity: str) -> None: diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index a8320d846..4f63a9cba 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -23,7 +23,7 @@ from .helper import desired_capabilities -@pytest.mark.skip(reason="Need to fix broken test") +@pytest.mark.skip(reason='Need to fix broken test') class TestContextSwitching(object): def setup_method(self) -> None: caps = desired_capabilities.get_desired_capabilities('selendroid-test-app.apk') @@ -44,7 +44,7 @@ def test_move_to_correct_context(self) -> None: def test_actually_in_webview(self) -> None: self._enter_webview() self.driver.find_element(by=AppiumBy.CSS_SELECTOR, value='input[type=submit]').click() - el = self.driver.find_element(by=AppiumBy.XPATH, value="//h1[contains(., 'This is my way')]") + el = self.driver.find_element(by=AppiumBy.XPATH, value='//h1[contains(., \'This is my way\')]') assert el is not None def test_move_back_to_native_context(self) -> None: diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index ddc784e7a..cb22e6353 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -44,6 +44,6 @@ def teardown_method(self, method) -> None: # type: ignore if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') - with open(video_path, "wb") as fd: + with open(video_path, 'wb') as fd: fd.write(base64.b64decode(payload)) self.driver.quit() 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 2ac4354ab..c15a0e8dc 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -32,7 +32,7 @@ def setup_method(self) -> None: # relax template matching self.driver.update_settings( - {"fixImageFindScreenshotDims": False, "fixImageTemplateSize": True, "autoUpdateImageElementPosition": True} + {'fixImageFindScreenshotDims': False, 'fixImageTemplateSize': True, 'autoUpdateImageElementPosition': True} ) def teardown_method(self) -> None: @@ -57,14 +57,14 @@ def test_find_based_on_image_template(self) -> None: assert rect['y'] is not None assert el.is_displayed() el.click() - wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, "Alarm") + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Alarm') def test_find_multiple_elements_by_image_just_returns_one(self) -> None: - wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, "App") + wait_for_element(self.driver, AppiumBy.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() - wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, "Alarm") + wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Alarm') def test_find_throws_no_such_element(self) -> None: image_path = desired_capabilities.PATH('file/find_by_image_failure.png') 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 5232ee2ba..de00ee4a8 100644 --- a/test/functional/android/search_context/find_by_uiautomator_tests.py +++ b/test/functional/android/search_context/find_by_uiautomator_tests.py @@ -19,7 +19,7 @@ from test.functional.android.helper.test_helper import BaseTestCase -@pytest.mark.skip(reason="Need to fix flaky test") +@pytest.mark.skip(reason='Need to fix flaky test') class TestFindByUIAutomator(BaseTestCase): def test_find_single_element(self) -> None: el = self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().text("Animation")') diff --git a/test/functional/android/settings_tests.py b/test/functional/android/settings_tests.py index ac600a3ca..66afa9532 100644 --- a/test/functional/android/settings_tests.py +++ b/test/functional/android/settings_tests.py @@ -22,6 +22,6 @@ def test_get_settings(self) -> None: assert settings is not None def test_update_settings(self) -> None: - self.driver.update_settings({"waitForIdleTimeout": 10001}) + self.driver.update_settings({'waitForIdleTimeout': 10001}) settings = self.driver.get_settings() - assert settings["waitForIdleTimeout"] == 10001 + assert settings['waitForIdleTimeout'] == 10001 diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index a97b992e0..a10a4b202 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -51,7 +51,7 @@ def test_tap_twice(self) -> None: action.tap(el, count=2).perform() els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') - assert '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=AppiumBy.ACCESSIBILITY_ID, value='Animation') diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index 989521e5a..d631c396f 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -43,7 +43,7 @@ def test_set_text(self) -> None: def test_send_keys(self) -> None: for text in ['App', 'Activity', 'Custom Title']: - wait_for_element(self.driver, AppiumBy.XPATH, f"//android.widget.TextView[@text='{text}']").click() + wait_for_element(self.driver, AppiumBy.XPATH, f'//android.widget.TextView[@text=\'{text}\']').click() el = wait_for_element(self.driver, AppiumBy.ID, '{}:id/left_text_edit'.format(APIDEMO_PKG_NAME)) el.send_keys(' text') diff --git a/test/functional/ios/applications_tests.py b/test/functional/ios/applications_tests.py index f098140bc..072059bf6 100644 --- a/test/functional/ios/applications_tests.py +++ b/test/functional/ios/applications_tests.py @@ -28,6 +28,6 @@ def test_app_management(self) -> None: self.driver.background_app(-1) assert wait_for_condition( lambda: self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND - ), "The app didn't go to background." + ), 'The app didn\'t go to background.' self.driver.activate_app(desired_capabilities.BUNDLE_ID) assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND diff --git a/test/functional/ios/helper/test_helper.py b/test/functional/ios/helper/test_helper.py index 2a975aa58..c3397d7d7 100644 --- a/test/functional/ios/helper/test_helper.py +++ b/test/functional/ios/helper/test_helper.py @@ -37,6 +37,6 @@ def teardown_method(self, method) -> None: # type: ignore if is_ci(): payload = self.driver.stop_recording_screen() video_path = os.path.join(os.getcwd(), method.__name__ + '.mp4') - with open(video_path, "wb") as fd: + with open(video_path, 'wb') as fd: fd.write(base64.b64decode(payload)) self.driver.quit() diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 312f6ceb4..ed71a19ad 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -36,7 +36,7 @@ def test_context(self) -> None: assert 'WEBVIEW_' in self.driver.current_context def test_get(self) -> None: - self.driver.get("http://google.com") + self.driver.get('http://google.com') for _ in range(5): time.sleep(0.5) if 'Google' == self.driver.title: diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index cba162333..8e26fc915 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -66,7 +66,7 @@ def test_app_management(self) -> None: assert wait_for_condition( lambda: self.driver.query_app_state(desired_capabilities.BUNDLE_ID) < ApplicationState.RUNNING_IN_FOREGROUND, - ), "The app didn't go to background." + ), 'The app didn\'t go to background.' self.driver.activate_app(desired_capabilities.BUNDLE_ID) assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND @@ -97,7 +97,7 @@ def test_clear(self) -> None: assert text == def_text def test_press_button(self) -> None: - self.driver.press_button("Home") + self.driver.press_button('Home') if float(desired_capabilities.get_desired_capabilities(desired_capabilities.BUNDLE_ID)['platformVersion']) < 11: return assert self.driver.query_app_state(desired_capabilities.BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND From 2853ac0b8814cea4a6192c69b56b508a667042c8 Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Sat, 8 Jul 2023 08:42:24 +0300 Subject: [PATCH 210/548] chore: update isort revision to 5.12.0 (#889) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f88890791..407b780ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort args: [ "." ] From 0cf35fc2be341beae51e5ec14407ca73f16eb29e Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 8 Jul 2023 13:29:40 -0700 Subject: [PATCH 211/548] chore: run pre-commit autoupdate (#890) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 407b780ab..a99b5b317 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,13 +5,13 @@ repos: - id: isort args: [ "." ] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v1.4.1 hooks: - id: mypy entry: mypy appium/ test/functional pass_filenames: false - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black args: [ ".", "-l", "120", "-S" ] From 78bbb73fdea80eac98468096d8e187f2def8f866 Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Tue, 11 Jul 2023 08:23:37 +0300 Subject: [PATCH 212/548] fix: Add missing dependencies for types-python-dateutil (#891) * fix: Add missing dependencies for types-python-dateutil * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py * Revert "test: replace usage of selendroid app from 'test_install_app' in applications_tests.py" This reverts commit dcdf08b67d24257700923c89ae2643f26af2892f. --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a99b5b317..d33175092 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,7 @@ repos: - id: mypy entry: mypy appium/ test/functional pass_filenames: false + additional_dependencies: [types-python-dateutil==2.8.19.13] - repo: https://github.com/psf/black rev: 23.3.0 hooks: From 9edf6ebfff75d4b2d84b67e6601bed764613aa8e Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 12 Jul 2023 06:43:58 +0000 Subject: [PATCH 213/548] fix: Handle the situation where payload is already a dictionary (#892) --- appium/webdriver/errorhandler.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index 632cd44e7..b2a5cab91 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -86,12 +86,15 @@ def check_response(self, response: Dict[str, Any]) -> None: https://www.w3.org/TR/webdriver/#errors """ payload = response.get('value', '') - try: - payload_dict = json.loads(payload) - except (json.JSONDecodeError, TypeError): - return - if not isinstance(payload_dict, dict): - return + if isinstance(payload, dict): + payload_dict = payload + else: + try: + payload_dict = json.loads(payload) + except (json.JSONDecodeError, TypeError): + return + if not isinstance(payload_dict, dict): + return value = payload_dict.get('value') if not isinstance(value, dict): return From 5f11530f93306a3c4c669fce327b2aef30c5e58c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:33:34 -0700 Subject: [PATCH 214/548] chore(deps-dev): update mock requirement from ~=5.0 to ~=5.1 (#893) Updates the requirements on [mock](https://github.com/testing-cabal/mock) to permit the latest version. - [Changelog](https://github.com/testing-cabal/mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/testing-cabal/mock/compare/5.0.0...5.1.0) --- updated-dependencies: - dependency-name: mock dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e05573ad6..711d9514b 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,7 @@ black = "<24.0.0" httpretty = "~=1.1" isort = "<6.0" mypy = "<2.0" -mock = "~=5.0" +mock = "~=5.1" pre-commit = "~=2.21" pylint = "~=2.17.3" pylint-quotes = "~=0.2.3" From 06fe1b5e558059c3608df9dcf3f66420e60952ee Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Mon, 17 Jul 2023 01:54:42 +0300 Subject: [PATCH 215/548] test: selendroid cleanup (#895) * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py * test: remove unused import pytest from applications_tests.py * test: fix broken TestContextSwitching by replacing selendroid with ApiDemos * test: remove selendroid-test-app.apk from apps folder --- test/apps/selendroid-test-app.apk | Bin 206346 -> 0 bytes test/functional/android/applications_tests.py | 10 +++---- .../android/context_switching_tests.py | 27 ++++++++++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) delete mode 100644 test/apps/selendroid-test-app.apk diff --git a/test/apps/selendroid-test-app.apk b/test/apps/selendroid-test-app.apk deleted file mode 100644 index 8c11242647d1c49ea9341e6af3c074abc41c5f0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 206346 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

0y@IG&Pb+}Q9upFCUwi7Z5nnXy~<7)BxiEBGgn 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 None: 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: - assert not self.driver.is_app_installed('io.selendroid.testapp') - self.driver.install_app(PATH(os.path.join('../..', 'apps', 'selendroid-test-app.apk'))) - assert self.driver.is_app_installed('io.selendroid.testapp') + self.driver.remove_app(APIDEMO_PKG_NAME) + assert not self.driver.is_app_installed(APIDEMO_PKG_NAME) + self.driver.install_app(PATH(os.path.join('../..', 'apps', 'ApiDemos-debug.apk.zip'))) + assert self.driver.is_app_installed(APIDEMO_PKG_NAME) def test_remove_app(self) -> None: assert self.driver.is_app_installed(APIDEMO_PKG_NAME) diff --git a/test/functional/android/context_switching_tests.py b/test/functional/android/context_switching_tests.py index 4f63a9cba..8151fcca9 100644 --- a/test/functional/android/context_switching_tests.py +++ b/test/functional/android/context_switching_tests.py @@ -13,6 +13,10 @@ # limitations under the License. import pytest +from selenium.webdriver import ActionChains +from selenium.webdriver.common.actions import interaction +from selenium.webdriver.common.actions.action_builder import ActionBuilder +from selenium.webdriver.common.actions.pointer_input import PointerInput from appium import webdriver from appium.common.exceptions import NoSuchContextException @@ -23,10 +27,9 @@ from .helper import desired_capabilities -@pytest.mark.skip(reason='Need to fix broken test') class TestContextSwitching(object): def setup_method(self) -> None: - caps = desired_capabilities.get_desired_capabilities('selendroid-test-app.apk') + caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') self.driver = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) def teardown_method(self) -> None: @@ -39,12 +42,11 @@ def test_contexts_list(self) -> None: def test_move_to_correct_context(self) -> None: self._enter_webview() - assert 'WEBVIEW_io.selendroid.testapp' == self.driver.current_context + assert 'WEBVIEW_io.appium.android.apis' == self.driver.current_context def test_actually_in_webview(self) -> None: self._enter_webview() - self.driver.find_element(by=AppiumBy.CSS_SELECTOR, value='input[type=submit]').click() - el = self.driver.find_element(by=AppiumBy.XPATH, value='//h1[contains(., \'This is my way\')]') + el = self.driver.find_element(by=AppiumBy.XPATH, value='//a[@id="i am a link"]') assert el is not None def test_move_back_to_native_context(self) -> None: @@ -57,6 +59,19 @@ def test_set_invalid_context(self) -> None: self.driver.switch_to.context('invalid name') def _enter_webview(self) -> None: - btn = self.driver.find_element(by=AppiumBy.NAME, value='buttonStartWebviewCD') + btn = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Views') btn.click() + self._scroll_to_webview() + self._scroll_to_webview() + btn_web_view = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='WebView') + btn_web_view.click() self.driver.switch_to.context('WEBVIEW') + + def _scroll_to_webview(self) -> None: + actions = ActionChains(self.driver) + actions.w3c_actions = ActionBuilder(self.driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) + actions.w3c_actions.pointer_action.move_to_location(393, 1915) + actions.w3c_actions.pointer_action.pointer_down() + actions.w3c_actions.pointer_action.move_to_location(462, 355) + actions.w3c_actions.pointer_action.release() + actions.perform() From a1792ffa51f472dd631c406dcf2f92fe78ae47e5 Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Tue, 1 Aug 2023 01:04:52 +0300 Subject: [PATCH 216/548] docs: Update README.md (#898) Some typos and grammar fixes --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ba55297b7..8b2eed5c2 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ An extension library for adding [WebDriver Protocol](https://www.w3.org/TR/webdr Since **v1.0.0**, only Python 3.7+ is supported. Since **v2.0.0**, the base selenium client version is v4. -The version only works in W3C WebDriver protocol format. -If you would like to use the old protocol (MJSONWP), please use v1 Appium Python client. +The version only works in the W3C WebDriver protocol format. +If you would like to use the old protocol (MJSONWP), please use the v1 Appium Python client. ### Quick migration guide from v1 to v2 - Enhancement @@ -23,7 +23,7 @@ If you would like to use the old protocol (MJSONWP), please use v1 Appium Python - Removed `forceMjsonwp` since Selenium v4 and Appium Python client v2 expect only W3C WebDriver protocol - Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend - Please check each behavior. Their behaviors could slightly differ. - - Added `strict_ssl` to relax SSL error such as self-signed ones + - Added `strict_ssl` to relax SSL errors such as self-signed ones - Deprecated - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. - e.g. @@ -32,11 +32,11 @@ If you would like to use the old protocol (MJSONWP), please use v1 Appium Python - https://www.youtube.com/watch?v=oAJ7jwMNFVU - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api - - `launch_app`, `close_app` and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details + - `launch_app`, `close_app`, and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details #### MultiAction/TouchAction to W3C actions -On UIA2, some elements can be handled with `touch` pointer action insead of the default `mouse` pointer action in the Selenium Python cleint. +On UIA2, some elements can be handled with `touch` pointer action instead of the default `mouse` pointer action in the Selenium Python client. For example, the below action builder is to replace the default one with the `touch` pointer action. ```python @@ -95,10 +95,10 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). The Appium Python Client depends on [Selenium Python binding](https://pypi.org/project/selenium/), thus the Selenium Python binding update might affect the Appium Python Client behavior. -For exampple, some changes in the Selenium binding could break the Appium client. +For example, some changes in the Selenium binding could break the Appium client. > **Note** -> We strongly recommend you to manage dependencies with version management tools such as Pipenv and requirements.txt +> We strongly recommend you manage dependencies with version management tools such as Pipenv and requirements.txt > to keep compatible version combinations. ## Usage @@ -116,7 +116,7 @@ from appium import webdriver From there much of your test code will work with no change. -As a base for the following code examples, the following sets up the [UnitTest](https://docs.python.org/3/library/unittest.html) +As a base for the following code examples, the following set up the [UnitTest](https://docs.python.org/3/library/unittest.html) environment: ```python @@ -278,7 +278,7 @@ driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=F The first argument of `webdriver.Remote` can set an arbitrary command executor for you. -1. Set init arguments for the pool manager Appium Python client uses to manage http requests. +1. Set init arguments for the pool manager Appium Python client uses to manage HTTP requests. ```python from appium import webdriver @@ -351,7 +351,7 @@ driver = webdriver.Remote(custom_executor, options=options) ### Run tests -You can run all of tests running on CI via `tox` in your local. +You can run all of the tests running on CI via `tox` in your local. ```bash $ tox @@ -389,7 +389,7 @@ $ pytest -n 2 test/functional/ios/search_context/find_by_ios_class_chain_tests.p ## Release -Follow below steps. +Follow the below steps. ```bash $ pip install twine From 60b8ed5f9e50d056171f45994d811f6970a3f7d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:05:04 -0700 Subject: [PATCH 217/548] chore(deps-dev): update pylint requirement from ~=2.17.3 to ~=2.17.5 (#897) Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.3...v2.17.5) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 711d9514b..35e785b60 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "<6.0" mypy = "<2.0" mock = "~=5.1" pre-commit = "~=2.21" -pylint = "~=2.17.3" +pylint = "~=2.17.5" pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" From 2223f11b213162b21984fbece871a210fea48e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:04:28 -0700 Subject: [PATCH 218/548] chore(deps): update selenium requirement from ~=4.10 to ~=4.11 (#899) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/commits) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 35e785b60..f423e1d85 100644 --- a/Pipfile +++ b/Pipfile @@ -20,4 +20,4 @@ typing-extensions = "~=4.7" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.10" +selenium = "~=4.11" From d5b84b899e4abad6afd914d0e5342476cb955de2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:46:21 -0700 Subject: [PATCH 219/548] chore(deps-dev): update tox requirement from ~=4.6 to ~=4.8 (#902) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.8.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index f423e1d85..e886a5bec 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" -tox = "~=4.6" +tox = "~=4.8" typing-extensions = "~=4.7" types-python-dateutil = "~=2.8" From 2e49569ed45751df4c6953466f9769336698c033 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 1 Sep 2023 13:52:27 +0200 Subject: [PATCH 220/548] feat!: Update selenium dependency to 4.12 (#908) BREAKING CHANGE: The minimum supported Python version set to 3.8 BREAKING CHANGE: The minimum supported selenium version set to 4.12 --- .github/workflows/unit-test.yml | 2 +- Pipfile | 3 +-- .../options/common/supports_capabilities.py | 4 +--- appium/protocols/protocol.py | 22 ------------------- .../webdriver/can_execute_commands.py | 4 +--- .../webdriver/can_execute_scripts.py | 4 +--- .../protocols/webdriver/can_find_elements.py | 4 +--- .../can_remember_extension_presence.py | 4 +--- appium/webdriver/switch_to.py | 3 +-- setup.py | 3 +-- tox.ini | 4 ++-- 11 files changed, 11 insertions(+), 46 deletions(-) delete mode 100644 appium/protocols/protocol.py diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index a57bbc748..c8123838b 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/Pipfile b/Pipfile index e886a5bec..b488c9c01 100644 --- a/Pipfile +++ b/Pipfile @@ -16,8 +16,7 @@ pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" tox = "~=4.8" -typing-extensions = "~=4.7" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.11" +selenium = "~=4.12" diff --git a/appium/options/common/supports_capabilities.py b/appium/options/common/supports_capabilities.py index b56f153ca..89bf0d7f6 100644 --- a/appium/options/common/supports_capabilities.py +++ b/appium/options/common/supports_capabilities.py @@ -15,9 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Any, TypeVar - -from appium.protocols.protocol import Protocol +from typing import Any, Protocol, TypeVar T = TypeVar('T') diff --git a/appium/protocols/protocol.py b/appium/protocols/protocol.py deleted file mode 100644 index 06daa903b..000000000 --- a/appium/protocols/protocol.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/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 sys - -if sys.version_info >= (3, 8): - # noinspection PyUnresolvedReferences - from typing import Protocol -else: - # noinspection PyUnresolvedReferences - from typing_extensions import Protocol diff --git a/appium/protocols/webdriver/can_execute_commands.py b/appium/protocols/webdriver/can_execute_commands.py index 1c763c6e9..d2673666f 100644 --- a/appium/protocols/webdriver/can_execute_commands.py +++ b/appium/protocols/webdriver/can_execute_commands.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, Union +from typing import Dict, Protocol, Union from selenium.webdriver.remote.remote_connection import RemoteConnection -from ..protocol import Protocol - class CanExecuteCommands(Protocol): command_executor: RemoteConnection diff --git a/appium/protocols/webdriver/can_execute_scripts.py b/appium/protocols/webdriver/can_execute_scripts.py index 661a38806..0c120a136 100644 --- a/appium/protocols/webdriver/can_execute_scripts.py +++ b/appium/protocols/webdriver/can_execute_scripts.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, List, Optional - -from ..protocol import Protocol +from typing import Any, List, Optional, Protocol class CanExecuteScripts(Protocol): diff --git a/appium/protocols/webdriver/can_find_elements.py b/appium/protocols/webdriver/can_find_elements.py index 8ab1bd644..5cd2a0410 100644 --- a/appium/protocols/webdriver/can_find_elements.py +++ b/appium/protocols/webdriver/can_find_elements.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, List, Union - -from ..protocol import Protocol +from typing import TYPE_CHECKING, Dict, List, Protocol, Union if TYPE_CHECKING: from appium.webdriver.webelement import WebElement diff --git a/appium/protocols/webdriver/can_remember_extension_presence.py b/appium/protocols/webdriver/can_remember_extension_presence.py index bf0003938..1674c62b6 100644 --- a/appium/protocols/webdriver/can_remember_extension_presence.py +++ b/appium/protocols/webdriver/can_remember_extension_presence.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TypeVar - -from ..protocol import Protocol +from typing import Protocol, TypeVar T = TypeVar('T') diff --git a/appium/webdriver/switch_to.py b/appium/webdriver/switch_to.py index 21e9393e3..9ad5667b0 100644 --- a/appium/webdriver/switch_to.py +++ b/appium/webdriver/switch_to.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Optional, cast +from typing import TYPE_CHECKING, Optional, Protocol, cast from selenium.webdriver.remote.switch_to import SwitchTo -from appium.protocols.protocol import Protocol from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands from .mobilecommand import MobileCommand diff --git a/setup.py b/setup.py index b4a56f023..628d08049 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ classifiers=[ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', @@ -50,5 +49,5 @@ 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ], - install_requires=['selenium ~= 4.1'], + install_requires=['selenium ~= 4.12'], ) diff --git a/tox.ini b/tox.ini index c4d757ab1..223cf5094 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] skipsdist = True envlist = - py37, py38, - py39 + py39, + py311 [testenv] deps = From 264f202ca5cd5cbcb1a139ef5cc29095d12e2cce Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 5 Sep 2023 20:06:24 +0200 Subject: [PATCH 221/548] refactor!: remove several previously deprecated APIs (#909) BREAKING CHANGE: Removed obsolete all_sessions and session properties BREAKING CHANGE: Removed the obsolete start_activity method BREAKING CHANGE: Removed the obsolete end_test_coverage method BREAKING CHANGE: Removed the following obsolete arguments from the driver constructor: desired_capabilities, browser_profile, proxy BREAKING CHANGE: Removed obsolete set_value and set_text methods BREAKING CHANGE: Removed the obsolete MobileBy class BREAKING CHANGE: Removed obsolete application management methods: launch_app, close_app, reset BREAKING CHANGE: Removed obsolete IME methods: available_ime_engines, is_ime_active, activate_ime_engine, deactivate_ime_engine, active_ime_engine --- appium/webdriver/common/mobileby.py | 22 --- .../extensions/android/activities.py | 51 ------- appium/webdriver/extensions/android/common.py | 30 ---- appium/webdriver/extensions/applications.py | 53 ------- appium/webdriver/extensions/ime.py | 141 ------------------ appium/webdriver/extensions/session.py | 43 +----- appium/webdriver/mobilecommand.py | 16 -- appium/webdriver/webdriver.py | 60 +------- appium/webdriver/webelement.py | 49 ------ test/functional/android/activities_tests.py | 28 +++- test/functional/android/applications_tests.py | 13 -- test/functional/android/ime_tests.py | 51 ------- test/functional/android/webelement_tests.py | 3 +- test/functional/ios/keyboard_tests.py | 8 +- test/functional/ios/webdriver_tests.py | 37 +---- test/unit/webdriver/app_test.py | 27 ---- test/unit/webdriver/ime_test.py | 76 ---------- test/unit/webdriver/webdriver_test.py | 29 +--- test/unit/webdriver/webelement_test.py | 13 -- 19 files changed, 36 insertions(+), 714 deletions(-) delete mode 100644 appium/webdriver/common/mobileby.py delete mode 100644 appium/webdriver/extensions/ime.py delete mode 100644 test/functional/android/ime_tests.py delete mode 100644 test/unit/webdriver/ime_test.py diff --git a/appium/webdriver/common/mobileby.py b/appium/webdriver/common/mobileby.py deleted file mode 100644 index 986ad736d..000000000 --- a/appium/webdriver/common/mobileby.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/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. - -from appium.webdriver.common.appiumby import AppiumBy - - -class MobileBy(AppiumBy): - """ - deprecated:: 2.1.0 - Please use 'from appium.webdriver.common.appiumby import AppiumBy' instead of 'MobileBy'. - """ diff --git a/appium/webdriver/extensions/android/activities.py b/appium/webdriver/extensions/android/activities.py index e0cb84d59..2e9ed68dc 100644 --- a/appium/webdriver/extensions/android/activities.py +++ b/appium/webdriver/extensions/android/activities.py @@ -12,9 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import warnings -from typing import TYPE_CHECKING, cast - from selenium.common.exceptions import TimeoutException, UnknownMethodException from selenium.webdriver.support.ui import WebDriverWait @@ -23,52 +20,8 @@ from appium.protocols.webdriver.can_remember_extension_presence import CanRememberExtensionPresence from appium.webdriver.mobilecommand import MobileCommand as Command -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - class Activities(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): - def start_activity(self, app_package: str, app_activity: str, **opts: str) -> 'WebDriver': - """Opens an arbitrary activity during a test. If the activity belongs to - another application, that application is started and the activity is opened. - deprecated:: 2.9.0 - - This is an Android-only method. - - Args: - app_package: The package containing the activity to start. - app_activity: The activity to start. - - Keyword Args: - app_wait_package (str): Begin automation after this package starts. - app_wait_activity (str): Begin automation after this activity starts. - intent_action (str): Intent to start. - intent_category (str): Intent category to start. - intent_flags (str): Flags to send to the intent. - optional_intent_arguments (str): Optional arguments to the intent. - dont_stop_app_on_reset (str): Should the app be stopped on reset? - """ - warnings.warn( - 'The "session" API is deprecated. Use "mobile: startActivity" extension instead.', - DeprecationWarning, - ) - - data = {'appPackage': app_package, 'appActivity': app_activity} - arguments = { - 'app_wait_package': 'appWaitPackage', - 'app_wait_activity': 'appWaitActivity', - 'intent_action': 'intentAction', - 'intent_category': 'intentCategory', - 'intent_flags': 'intentFlags', - 'optional_intent_arguments': 'optionalIntentArguments', - 'dont_stop_app_on_reset': 'dontStopAppOnReset', - } - for key, value in arguments.items(): - if key in opts: - data[value] = opts[key] - self.execute(Command.START_ACTIVITY, data) - return cast('WebDriver', self) - @property def current_activity(self) -> str: """Retrieves the current activity running on the device. @@ -109,7 +62,3 @@ def _add_commands(self) -> None: 'GET', '/session/$sessionId/appium/device/current_activity', ) - commands[Command.START_ACTIVITY] = ( - 'POST', - '/session/$sessionId/appium/device/start_activity', - ) diff --git a/appium/webdriver/extensions/android/common.py b/appium/webdriver/extensions/android/common.py index f6aa92708..d680220f4 100644 --- a/appium/webdriver/extensions/android/common.py +++ b/appium/webdriver/extensions/android/common.py @@ -26,32 +26,6 @@ class Common(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): - def end_test_coverage(self, intent: str, path: str) -> Any: - """Ends the coverage collection and pull the coverage.ec file from the device. - deprecated:: 2.9.0 - - Android only. - See https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-coverage.md - - Args: - intent: description of operation to be performed - path: path to coverage.ec file to be pulled from the device - - Returns: - TODO - """ - warnings.warn( - 'This API is deprecated and will be removed in future versions', - DeprecationWarning, - ) - return self.execute( - Command.END_TEST_COVERAGE, - { - 'intent': intent, - 'path': path, - }, - )['value'] - def open_notifications(self) -> 'WebDriver': """Open notification shade in Android (API Level 18 and above) @@ -83,10 +57,6 @@ def _add_commands(self) -> None: 'GET', '/session/$sessionId/appium/device/current_package', ) - commands[Command.END_TEST_COVERAGE] = ( - 'POST', - '/session/$sessionId/appium/app/end_test_coverage', - ) commands[Command.OPEN_NOTIFICATIONS] = ( 'POST', '/session/$sessionId/appium/device/open_notifications', diff --git a/appium/webdriver/extensions/applications.py b/appium/webdriver/extensions/applications.py index d4951b9d8..673c6fcaf 100644 --- a/appium/webdriver/extensions/applications.py +++ b/appium/webdriver/extensions/applications.py @@ -11,7 +11,6 @@ # 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 warnings from typing import TYPE_CHECKING, Any, Dict, Union, cast from selenium.common.exceptions import InvalidArgumentException, UnknownMethodException @@ -145,39 +144,6 @@ def remove_app(self, app_id: str, **options: Any) -> 'WebDriver': self.mark_extension_absence(ext_name).execute(Command.REMOVE_APP, data) return cast('WebDriver', self) - def launch_app(self) -> 'WebDriver': - """Start on the device the application specified in the desired capabilities. - deprecated:: 2.0.0 - - Returns: - Union['WebDriver', 'Applications']: Self instance - """ - warnings.warn( - 'The "launchApp" API is deprecated and will be removed in future versions. ' - 'See https://github.com/appium/appium/issues/15807', - DeprecationWarning, - ) - - self.execute(Command.LAUNCH_APP) - return cast('WebDriver', self) - - def close_app(self) -> 'WebDriver': - """Stop the running application, specified in the desired capabilities, on - the device. - deprecated:: 2.0.0 - - Returns: - Union['WebDriver', 'Applications']: Self instance - """ - warnings.warn( - 'The "closeApp" API is deprecated and will be removed in future versions. ' - 'See https://github.com/appium/appium/issues/15807', - DeprecationWarning, - ) - - self.execute(Command.CLOSE_APP) - return cast('WebDriver', self) - def terminate_app(self, app_id: str, **options: Any) -> bool: """Terminates the application if it is running. @@ -283,22 +249,6 @@ def app_strings(self, language: Union[str, None] = None, string_file: Union[str, # TODO: Remove the fallback return self.mark_extension_absence(ext_name).execute(Command.GET_APP_STRINGS, data)['value'] - def reset(self) -> 'WebDriver': - """Resets the current application on the device. - deprecated:: 2.0.0 - - Returns: - Union['WebDriver', 'Applications']: Self instance - """ - warnings.warn( - 'The "reset" API is deprecated and will be removed in future versions. ' - 'See https://github.com/appium/appium/issues/15807', - DeprecationWarning, - ) - - self.execute(Command.RESET) - return cast('WebDriver', self) - def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands @@ -322,6 +272,3 @@ def _add_commands(self) -> None: '/session/$sessionId/appium/device/app_state', ) commands[Command.GET_APP_STRINGS] = ('POST', '/session/$sessionId/appium/app/strings') - commands[Command.RESET] = ('POST', '/session/$sessionId/appium/app/reset') - commands[Command.LAUNCH_APP] = ('POST', '/session/$sessionId/appium/app/launch') - commands[Command.CLOSE_APP] = ('POST', '/session/$sessionId/appium/app/close') diff --git a/appium/webdriver/extensions/ime.py b/appium/webdriver/extensions/ime.py deleted file mode 100644 index 2a5f188c5..000000000 --- a/appium/webdriver/extensions/ime.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/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 warnings -from typing import TYPE_CHECKING, List, cast - -from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands - -from ..mobilecommand import MobileCommand as Command - -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - - -class IME(CanExecuteCommands): - @property - 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']) - Android only. - - deprecated:: 2.0.0 - - Returns: - :obj:`list` of :obj:`str`: The available input methods for an Android device - """ - warnings.warn( - 'The "available_ime_engines" API is deprecated and will be removed in future versions. ' - 'Use "mobile: shell" extension instead', - DeprecationWarning, - ) - - return self.execute(Command.GET_AVAILABLE_IME_ENGINES, {})['value'] # pylint: disable=unsubscriptable-object - - def is_ime_active(self) -> bool: - """Checks whether the device has IME service active. - Android only. - - deprecated:: 2.0.0 - - Returns: - `True` if IME service is active - """ - warnings.warn( - 'The "is_ime_active" API is deprecated and will be removed in future versions. ' - 'Use "mobile: shell" extension instead', - DeprecationWarning, - ) - - return self.execute(Command.IS_IME_ACTIVE, {})['value'] # pylint: disable=unsubscriptable-object - - def activate_ime_engine(self, engine: str) -> 'WebDriver': - """Activates the given IME engine on the device. - - Android only. - - deprecated:: 2.0.0 - - Args: - engine: the package and activity of the IME engine to activate - (e.g., 'com.android.inputmethod.latin/.LatinIME') - - Returns: - Union['WebDriver', 'IME']: Self instance - """ - warnings.warn( - 'The "activate_ime_engine" API is deprecated and will be removed in future versions. ' - 'Use "mobile: shell" extension instead', - DeprecationWarning, - ) - - data = {'engine': engine} - self.execute(Command.ACTIVATE_IME_ENGINE, data) - return cast('WebDriver', self) - - def deactivate_ime_engine(self) -> 'WebDriver': - """Deactivates the currently active IME engine on the device. - - Android only. - - deprecated:: 2.0.0 - - Returns: - Union['WebDriver', 'IME']: Self instance - """ - warnings.warn( - 'The "deactivate_ime_engine" API is deprecated and will be removed in future versions. ' - 'Use "mobile: shell" extension instead', - DeprecationWarning, - ) - - self.execute(Command.DEACTIVATE_IME_ENGINE, {}) - return cast('WebDriver', self) - - @property - 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. - - deprecated:: 2.0.0 - - Returns: - str: The activity and package of the currently active IME engine - """ - warnings.warn( - 'The "active_ime_engine" API is deprecated and will be removed in future versions. ' - 'Use "mobile: shell" extension instead', - DeprecationWarning, - ) - - return self.execute(Command.GET_ACTIVE_IME_ENGINE, {})['value'] # pylint: disable=unsubscriptable-object - - def _add_commands(self) -> None: - """Add IME commands. They are not in W3C spec.""" - # noinspection PyProtectedMember,PyUnresolvedReferences - commands = self.command_executor._commands - commands[Command.GET_AVAILABLE_IME_ENGINES] = ( - 'GET', - '/session/$sessionId/ime/available_engines', - ) - commands[Command.IS_IME_ACTIVE] = ('GET', '/session/$sessionId/ime/activated') - commands[Command.ACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/activate') - commands[Command.DEACTIVATE_IME_ENGINE] = ('POST', '/session/$sessionId/ime/deactivate') - commands[Command.GET_ACTIVE_IME_ENGINE] = ( - 'GET', - '/session/$sessionId/ime/active_engine', - ) diff --git a/appium/webdriver/extensions/session.py b/appium/webdriver/extensions/session.py index dfd387cab..8960066da 100644 --- a/appium/webdriver/extensions/session.py +++ b/appium/webdriver/extensions/session.py @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import warnings -from typing import Any, Dict, List +from typing import Dict from appium.common.logger import logger from appium.protocols.webdriver.can_execute_commands import CanExecuteCommands @@ -22,42 +21,6 @@ class Session(CanExecuteCommands): - @property - def session(self) -> Dict[str, Any]: - """Retrieves session information from the current session - deprecated:: 2.0.0 - - Usage: - session = driver.session - - Returns: - `dict`: containing information from the current session - """ - warnings.warn( - 'The "session" API is deprecated and will be removed in future versions', - DeprecationWarning, - ) - - return self.execute(Command.GET_SESSION)['value'] - - @property - def all_sessions(self) -> List[Dict[str, Any]]: - """Retrieves all sessions that are open - deprecated:: 2.0.0 - - Usage: - sessions = driver.all_sessions - - Returns: - :obj:`list` of :obj:`dict`: containing all open sessions - """ - warnings.warn( - 'The "all_sessions" API is deprecated and will be removed in future versions', - DeprecationWarning, - ) - - return self.execute(Command.GET_ALL_SESSIONS)['value'] - @property def events(self) -> Dict: """Retrieves events information from the current session @@ -69,8 +32,7 @@ def events(self) -> Dict: `dict`: containing events timing information from the current session """ try: - session = self.session - return session['events'] + return self.execute(Command.GET_SESSION)['value']['events'] except Exception as e: logger.warning('Could not find events information in the session. Error: %s', e) return {} @@ -79,4 +41,3 @@ def _add_commands(self) -> None: # noinspection PyProtectedMember,PyUnresolvedReferences commands = self.command_executor._commands commands[Command.GET_SESSION] = ('GET', '/session/$sessionId') - commands[Command.GET_ALL_SESSIONS] = ('GET', '/sessions') diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index 2a83f1a41..e1648242e 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -16,7 +16,6 @@ class MobileCommand: # Common GET_SESSION = 'getSession' - GET_ALL_SESSIONS = 'getAllSessions' GET_STATUS = 'getStatus' @@ -24,13 +23,6 @@ class MobileCommand: GET_LOCATION = 'getLocation' SET_LOCATION = 'setLocation' - ## MJSONWP for Selenium v4 - GET_AVAILABLE_IME_ENGINES = 'getAvailableIMEEngines' - IS_IME_ACTIVE = 'isIMEActive' - ACTIVATE_IME_ENGINE = 'activateIMEEngine' - DEACTIVATE_IME_ENGINE = 'deactivateIMEEngine' - GET_ACTIVE_IME_ENGINE = 'getActiveEngine' - CLEAR = 'clear' LOCATION_IN_VIEW = 'locationInView' @@ -41,12 +33,6 @@ class MobileCommand: TOUCH_ACTION = 'touchAction' MULTI_ACTION = 'multiAction' - SET_IMMEDIATE_VALUE = 'setImmediateValue' - REPLACE_KEYS = 'replaceKeys' - - LAUNCH_APP = 'launchApp' - CLOSE_APP = 'closeApp' - RESET = 'reset' BACKGROUND = 'background' GET_APP_STRINGS = 'getAppStrings' @@ -96,14 +82,12 @@ class MobileCommand: # Android OPEN_NOTIFICATIONS = 'openNotifications' - START_ACTIVITY = 'startActivity' GET_CURRENT_ACTIVITY = 'getCurrentActivity' GET_CURRENT_PACKAGE = 'getCurrentPackage' GET_SYSTEM_BARS = 'getSystemBars' GET_DISPLAY_DENSITY = 'getDisplayDensity' TOGGLE_WIFI = 'toggleWiFi' TOGGLE_LOCATION_SERVICES = 'toggleLocationServices' - END_TEST_COVERAGE = 'endTestCoverage' GET_PERFORMANCE_DATA_TYPES = 'getPerformanceDataTypes' GET_PERFORMANCE_DATA = 'getPerformanceData' GET_NETWORK_CONNECTION = 'getNetworkConnection' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 48b7993ff..86ca1d1a2 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -14,7 +14,6 @@ # pylint: disable=too-many-lines,too-many-public-methods,too-many-statements,no-self-use -import warnings from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from selenium import webdriver @@ -52,7 +51,6 @@ from .extensions.execute_mobile_command import ExecuteMobileCommand from .extensions.hw_actions import HardwareActions from .extensions.images_comparison import ImagesComparison -from .extensions.ime import IME from .extensions.keyboard import Keyboard from .extensions.location import Location from .extensions.log_event import LogEvent @@ -190,7 +188,6 @@ class WebDriver( Gsm, HardwareActions, ImagesComparison, - IME, Keyboard, Location, LogEvent, @@ -207,12 +204,6 @@ class WebDriver( def __init__( self, command_executor: Union[str, AppiumConnection] = 'http://127.0.0.1:4444/wd/hub', - # TODO: Remove the deprecated arg - desired_capabilities: Optional[Dict] = None, - # TODO: Remove the deprecated arg - browser_profile: Union[str, None] = None, - # TODO: Remove the deprecated arg - proxy: Union[str, None] = None, keep_alive: bool = True, direct_connection: bool = True, extensions: Optional[List['WebDriver']] = None, @@ -235,28 +226,9 @@ def __init__( if isinstance(command_executor, str): command_executor = AppiumConnection(command_executor, keep_alive=keep_alive) - if browser_profile is not None: - warnings.warn('browser_profile argument is deprecated and has no effect', DeprecationWarning) - - if proxy is not None: - warnings.warn('proxy argument is deprecated and has no effect', DeprecationWarning) - - if desired_capabilities is not None: - warnings.warn( - 'desired_capabilities argument is deprecated and will be removed in future versions. ' - 'Use options instead.', - DeprecationWarning, - ) - # TODO: Remove the fallback after desired_capabilities removal - dst_options = ( - AppiumOptions().load_capabilities(desired_capabilities) - if desired_capabilities is not None and options is None - else options - ) - super().__init__( command_executor=command_executor, - options=dst_options, + options=options, ) if hasattr(self, 'command_executor'): @@ -458,28 +430,6 @@ def create_web_element(self, element_id: Union[int, str]) -> MobileWebElement: """ return MobileWebElement(self, element_id) - def set_value(self, element: MobileWebElement, value: str) -> 'WebDriver': - """Set the value on an element in the application. - deprecated:: 2.8.1 - - Args: - element: the element whose value will be set - value: the value to set on the element - - Returns: - `appium.webdriver.webdriver.WebDriver`: Self instance - """ - warnings.warn( - 'The "setValue" API is deprecated and will be removed in future versions. ' - 'Instead the "send_keys" API or W3C Actions can be used. ' - 'See https://github.com/appium/python-client/pull/831', - DeprecationWarning, - ) - - data = {'text': value} - self.execute(Command.SET_IMMEDIATE_VALUE, data) - return self - @property def switch_to(self) -> MobileSwitchTo: """Returns an object containing all options to switch focus into @@ -565,16 +515,8 @@ def _add_commands(self) -> None: # FIXME: remove after a while as MJSONWP commands[Command.TOUCH_ACTION] = ('POST', '/session/$sessionId/touch/perform') commands[Command.MULTI_ACTION] = ('POST', '/session/$sessionId/touch/multi/perform') - commands[Command.SET_IMMEDIATE_VALUE] = ( - 'POST', - '/session/$sessionId/appium/element/$id/value', - ) # TODO Move commands for element to webelement - commands[Command.REPLACE_KEYS] = ( - 'POST', - '/session/$sessionId/appium/element/$id/replace_value', - ) commands[Command.CLEAR] = ('POST', '/session/$sessionId/element/$id/clear') commands[Command.LOCATION_IN_VIEW] = ( 'GET', diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 87b2dc9ab..b49258d24 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import warnings from typing import Callable, Dict, List, Optional, Union from selenium.webdriver.common.utils import keys_to_typing @@ -159,33 +158,6 @@ def clear(self) -> 'WebElement': self._execute(Command.CLEAR, data) return self - def set_text(self, keys: str = '') -> 'WebElement': - """Sends text to the element. - deprecated:: 2.8.1 - - Previous text is removed. - Android only. - - Args: - keys: the text to be sent to the element. - - Usage: - element.set_text('some text') - - Returns: - `appium.webdriver.webelement.WebElement` - """ - warnings.warn( - 'The "setText" API is deprecated and will be removed in future versions. ' - 'Instead the "send_keys" API or W3C Actions can be used. ' - 'See https://github.com/appium/python-client/pull/831', - DeprecationWarning, - ) - - data = {'text': keys} - self._execute(Command.REPLACE_KEYS, data) - return self - @property def location_in_view(self) -> Dict[str, int]: """Gets the location of an element relative to the view. @@ -200,27 +172,6 @@ def location_in_view(self) -> Dict[str, int]: """ return self._execute(Command.LOCATION_IN_VIEW)['value'] - def set_value(self, value: str) -> 'WebElement': - """Set the value on this element in the application - deprecated:: 2.8.1 - - Args: - value: The value to be set - - Returns: - `appium.webdriver.webelement.WebElement` - """ - warnings.warn( - 'The "setValue" API is deprecated and will be removed in future versions. ' - 'Instead the "send_keys" API or W3C Actions can be used. ' - 'See https://github.com/appium/python-client/pull/831', - DeprecationWarning, - ) - - data = {'text': value} - self._execute(Command.SET_IMMEDIATE_VALUE, data) - return self - # Override def send_keys(self, *value: str) -> 'WebElement': """Simulates typing into the element. diff --git a/test/functional/android/activities_tests.py b/test/functional/android/activities_tests.py index 948248e6e..4f60c5b7d 100644 --- a/test/functional/android/activities_tests.py +++ b/test/functional/android/activities_tests.py @@ -22,17 +22,37 @@ def test_current_activity(self) -> None: assert '.ApiDemos' == activity def test_start_activity_this_app(self) -> None: - self.driver.start_activity(APIDEMO_PKG_NAME, '.ApiDemos') + self.driver.execute_script( + 'mobile: startActivity', + { + 'component': f'{APIDEMO_PKG_NAME}/.ApiDemos', + }, + ) self._assert_activity_contains('Demos') - self.driver.start_activity(APIDEMO_PKG_NAME, '.accessibility.AccessibilityNodeProviderActivity') + self.driver.execute_script( + 'mobile: startActivity', + { + 'component': f'{APIDEMO_PKG_NAME}/.accessibility.AccessibilityNodeProviderActivity', + }, + ) self._assert_activity_contains('Node') def test_start_activity_other_app(self) -> None: - self.driver.start_activity(APIDEMO_PKG_NAME, '.ApiDemos') + self.driver.execute_script( + 'mobile: startActivity', + { + 'component': f'{APIDEMO_PKG_NAME}/.ApiDemos', + }, + ) self._assert_activity_contains('Demos') - self.driver.start_activity('com.google.android.deskclock', 'com.android.deskclock.DeskClock') + self.driver.execute_script( + 'mobile: startActivity', + { + 'component': f'{APIDEMO_PKG_NAME}/com.android.deskclock.DeskClock', + }, + ) self._assert_activity_contains('Clock') def _assert_activity_contains(self, activity: str) -> None: diff --git a/test/functional/android/applications_tests.py b/test/functional/android/applications_tests.py index c15a49f36..9035c04eb 100644 --- a/test/functional/android/applications_tests.py +++ b/test/functional/android/applications_tests.py @@ -14,7 +14,6 @@ # limitations under the License. import os -from time import sleep from appium.webdriver.applicationstate import ApplicationState @@ -25,8 +24,6 @@ class TestApplications(BaseTestCase): def test_background_app(self) -> None: self.driver.background_app(1) - sleep(3) - self.driver.launch_app() def test_is_app_installed(self) -> None: assert not self.driver.is_app_installed('sdfsdf') @@ -43,12 +40,6 @@ def test_remove_app(self) -> None: self.driver.remove_app(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 - assert '.ApiDemos' == activity - def test_app_management(self) -> None: app_id = self.driver.current_package assert self.driver.query_app_state(app_id) == ApplicationState.RUNNING_IN_FOREGROUND @@ -68,7 +59,3 @@ def test_app_strings_with_language(self) -> None: def test_app_strings_with_language_and_file(self) -> None: strings = self.driver.app_strings('en', 'some_file') 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() - assert self.driver.is_app_installed(APIDEMO_PKG_NAME) diff --git a/test/functional/android/ime_tests.py b/test/functional/android/ime_tests.py deleted file mode 100644 index ea0f6ea0f..000000000 --- a/test/functional/android/ime_tests.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# 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. - -from time import sleep - -from .helper.test_helper import BaseTestCase - -ANDROID_LATIN = 'com.android.inputmethod.latin/.LatinIME' # Android L/M/N -GOOGLE_LATIN = 'com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME' # Android O/P - - -class TestIME(BaseTestCase): - def test_available_ime_engines(self) -> None: - engines = self.driver.available_ime_engines - assert isinstance(engines, list) - assert ANDROID_LATIN in engines or GOOGLE_LATIN in engines - - def test_is_ime_active(self) -> None: - assert self.driver.is_ime_active() - - def test_active_ime_engine(self) -> None: - engines = self.driver.available_ime_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]) - 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]) - - assert self.driver.active_ime_engine == engines[-1] - - self.driver.deactivate_ime_engine() - sleep(1) - assert self.driver.active_ime_engine != engines[-1] diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index d631c396f..9a8ba1fd6 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -37,7 +37,8 @@ def test_set_text(self) -> None: el = wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.EditText') el.send_keys('original text') - el.set_text('new text') + el.clear() + el.send_keys('new text') assert 'new text' == el.text diff --git a/test/functional/ios/keyboard_tests.py b/test/functional/ios/keyboard_tests.py index 5397e2cec..d07e5e708 100644 --- a/test/functional/ios/keyboard_tests.py +++ b/test/functional/ios/keyboard_tests.py @@ -30,7 +30,7 @@ def test_hide_keyboard(self) -> None: el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() - el.set_value('Testing') + el.send_keys('Testing') assert self._get_keyboard_el().is_displayed() @@ -44,7 +44,7 @@ def test_hide_keyboard_presskey_strategy(self) -> None: el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() - el.set_value('Testing') + el.send_keys('Testing') assert self._get_keyboard_el().is_displayed() @@ -58,7 +58,7 @@ def test_hide_keyboard_no_key_name(self) -> None: el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() - el.set_value('Testing') + el.send_keys('Testing') assert self._get_keyboard_el().is_displayed() @@ -72,7 +72,7 @@ def test_is_keyboard_shown(self) -> None: el = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='XCUIElementTypeTextField')[0] el.click() - el.set_value('Testing') + el.send_keys('Testing') assert self.driver.is_keyboard_shown() def _get_keyboard_el(self) -> 'WebElement': diff --git a/test/functional/ios/webdriver_tests.py b/test/functional/ios/webdriver_tests.py index 8e26fc915..9cb8c62dd 100644 --- a/test/functional/ios/webdriver_tests.py +++ b/test/functional/ios/webdriver_tests.py @@ -12,50 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING - -import pytest -from selenium.webdriver.support.ui import WebDriverWait - -from appium import webdriver -from appium.options.common import AppiumOptions from appium.webdriver.applicationstate import ApplicationState from appium.webdriver.common.appiumby import AppiumBy from test.functional.ios.helper.test_helper import BaseTestCase -from test.functional.test_helper import get_available_from_port_range, wait_for_condition -from test.helpers.constants import SERVER_URL_BASE +from test.functional.test_helper import wait_for_condition -from ..test_helper import is_ci from .helper import desired_capabilities -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - 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: - port = get_available_from_port_range(8200, 8300) - caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') - caps['deviceName'] = 'iPhone Xs Max' - caps['wdaLocalPort'] = port - - class session_counts_is_two: - TIMEOUT = 10 - - def __call__(self, driver: 'WebDriver') -> bool: - return len(driver.all_sessions) == 2 - - driver2 = None - try: - driver2 = webdriver.Remote(SERVER_URL_BASE, options=AppiumOptions().load_capabilities(caps)) - WebDriverWait(driver2, session_counts_is_two.TIMEOUT).until(session_counts_is_two()) - assert len(self.driver.all_sessions) == 2 - finally: - if driver2 is not None: - driver2.quit() - 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/unit/webdriver/app_test.py b/test/unit/webdriver/app_test.py index 1a8b0247b..7cb7b6b38 100644 --- a/test/unit/webdriver/app_test.py +++ b/test/unit/webdriver/app_test.py @@ -20,17 +20,6 @@ class TestWebDriverApp(object): - @httpretty.activate - def test_reset(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/appium/app/reset'), body='{"value": ""}' - ) - result = driver.reset() - - assert {'sessionId': '1234567890'}, get_httpretty_request_body(httpretty.last_request()) - assert isinstance(result, WebDriver) - @httpretty.activate def test_install_app(self): driver = android_w3c_driver() @@ -104,22 +93,6 @@ def test_background_app(self): assert {'app': 0}, get_httpretty_request_body(httpretty.last_request()) assert isinstance(result, WebDriver) - @httpretty.activate - def test_launch_app(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/appium/app/launch'), body='{"value": }' - ) - assert isinstance(driver.launch_app(), WebDriver) - - @httpretty.activate - def test_close_app(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/appium/app/close'), body='{"value": }' - ) - assert isinstance(driver.close_app(), WebDriver) - @httpretty.activate def test_query_app_state(self): driver = android_w3c_driver() diff --git a/test/unit/webdriver/ime_test.py b/test/unit/webdriver/ime_test.py deleted file mode 100644 index d9e80c35c..000000000 --- a/test/unit/webdriver/ime_test.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/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 httpretty - -from appium.webdriver.webdriver import WebDriver -from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body - - -class TestWebDriverIme(object): - @httpretty.activate - def test_available_ime_engines(self): - ANDROID_LATIN = 'com.android.inputmethod.latin/.LatinIME' - GOOGLE_LATIN = 'com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME' - - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.GET, - appium_command('/session/1234567890/ime/available_engines'), - body=json.dumps({'value': [ANDROID_LATIN, GOOGLE_LATIN]}), - ) - assert driver.available_ime_engines == [ANDROID_LATIN, GOOGLE_LATIN] - - @httpretty.activate - def test_is_ime_active(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.GET, appium_command('/session/1234567890/ime/activated'), body=json.dumps({'value': True}) - ) - assert driver.is_ime_active() is True - - @httpretty.activate - def test_activate_ime_engine(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/ime/activate'), - ) - engine = 'com.android.inputmethod.latin/.LatinIME' - assert isinstance(driver.activate_ime_engine(engine), WebDriver) - - d = get_httpretty_request_body(httpretty.last_request()) - assert d['engine'] == 'com.android.inputmethod.latin/.LatinIME' - - @httpretty.activate - def test_deactivate_ime_engine(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/ime/deactivate'), - ) - assert isinstance(driver.deactivate_ime_engine(), WebDriver) - - @httpretty.activate - def test_active_ime_engine(self): - driver = android_w3c_driver() - httpretty.register_uri( - httpretty.GET, - appium_command('/session/1234567890/ime/active_engine'), - body=json.dumps({'value': 'com.android.inputmethod.latin/.LatinIME'}), - ) - assert driver.active_ime_engine == 'com.android.inputmethod.latin/.LatinIME' diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 70b09600b..8872a46c7 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -174,29 +174,6 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): assert SERVER_URL_BASE == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts - @httpretty.activate - def test_get_all_sessions(self): - driver = ios_w3c_driver() - httpretty.register_uri( - httpretty.GET, - appium_command('/sessions'), - body=json.dumps({'value': {'deviceName': 'iPhone Simulator', 'events': {'simStarted': [1234567891]}}}), - ) - session = driver.all_sessions - assert len(session) != 1 - - @httpretty.activate - def test_get_session(self): - driver = ios_w3c_driver() - httpretty.register_uri( - httpretty.GET, - appium_command('/session/1234567890'), - body=json.dumps({'value': {'deviceName': 'iPhone Simulator', 'events': {'simStarted': [1234567890]}}}), - ) - session = driver.session - assert session['deviceName'] == 'iPhone Simulator' - assert session['events']['simStarted'] == [1234567890] - @httpretty.activate def test_get_events(self): driver = ios_w3c_driver() @@ -391,20 +368,18 @@ class CustomAppiumConnection(AppiumConnection): class SubWebDriver(WebDriver): - def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None): + def __init__(self, command_executor, direct_connection=False, options=None): super().__init__( command_executor=command_executor, - desired_capabilities=desired_capabilities, direct_connection=direct_connection, options=options, ) class SubSubWebDriver(SubWebDriver): - def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None): + def __init__(self, command_executor, direct_connection=False, options=None): super().__init__( command_executor=command_executor, - desired_capabilities=desired_capabilities, direct_connection=direct_connection, options=options, ) diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py index aa6168f6b..3bcb11928 100644 --- a/test/unit/webdriver/webelement_test.py +++ b/test/unit/webdriver/webelement_test.py @@ -13,7 +13,6 @@ # limitations under the License. import json -import os import tempfile import httpretty @@ -36,18 +35,6 @@ def test_status(self): assert s == response - @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') - value = 'happy testing' - element.set_value(value) - - d = get_httpretty_request_body(httpretty.last_request()) - assert d['text'] == value - @httpretty.activate def test_send_key(self): driver = android_w3c_driver() From e344b7a27a55dceebcfda22842efa6dfe965e39b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 09:49:54 +0200 Subject: [PATCH 222/548] chore(deps-dev): update tox requirement from ~=4.8 to ~=4.11 (#906) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.8.0...4.11.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b488c9c01..552baa2d0 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" -tox = "~=4.8" +tox = "~=4.11" types-python-dateutil = "~=2.8" [packages] From 0166092649565da6ccae4cfe225c4b06e9ccc377 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 8 Sep 2023 09:35:02 -0700 Subject: [PATCH 223/548] Bump 3.0.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index cf22adfab..5918abd15 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '2.11.1' +version = '3.0.0' From 31eda4b110153e145976e5feea473212918641be Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 8 Sep 2023 09:35:27 -0700 Subject: [PATCH 224/548] Update changelog for 3.0.0 --- CHANGELOG.rst | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06f83b620..64de644fe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,144 @@ Changelog ========= +v3.0.0 (2023-09-08) +------------------- + +New +~~~ +- Feat!: Update selenium dependency to 4.12 (#908) [Mykola Mokhnach] + + BREAKING CHANGE: The minimum supported Python version set to 3.8 + BREAKING CHANGE: The minimum supported selenium version set to 4.12 + +Fix +~~~ +- Handle the situation where payload is already a dictionary (#892) + [Mykola Mokhnach] +- Add missing dependencies for types-python-dateutil (#891) [Dor + Blayzer] + + * fix: Add missing dependencies for types-python-dateutil + + * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py + + * Revert "test: replace usage of selendroid app from 'test_install_app' in applications_tests.py" + + This reverts commit dcdf08b67d24257700923c89ae2643f26af2892f. + +Test +~~~~ +- Test: selendroid cleanup (#895) [Dor Blayzer] + + * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py + + * test: remove unused import pytest from applications_tests.py + + * test: fix broken TestContextSwitching by replacing selendroid with ApiDemos + + * test: remove selendroid-test-app.apk from apps folder +- Ci: add pylint_quotes for pylint to use single quote as primary method + (#886) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 3.0.0. [Kazuaki Matsuo] +- Chore(deps-dev): update tox requirement from ~=4.8 to ~=4.11 (#906) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.8.0...4.11.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Refactor!: remove several previously deprecated APIs (#909) [Mykola + Mokhnach] + + BREAKING CHANGE: Removed obsolete all_sessions and session properties + BREAKING CHANGE: Removed the obsolete start_activity method + BREAKING CHANGE: Removed the obsolete end_test_coverage method + BREAKING CHANGE: Removed the following obsolete arguments from the driver constructor: desired_capabilities, browser_profile, proxy + BREAKING CHANGE: Removed obsolete set_value and set_text methods + BREAKING CHANGE: Removed the obsolete MobileBy class + BREAKING CHANGE: Removed obsolete application management methods: launch_app, close_app, reset + BREAKING CHANGE: Removed obsolete IME methods: available_ime_engines, is_ime_active, activate_ime_engine, deactivate_ime_engine, active_ime_engine +- Chore(deps-dev): update tox requirement from ~=4.6 to ~=4.8 (#902) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.8.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.10 to ~=4.11 (#899) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/commits) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pylint requirement from ~=2.17.3 to ~=2.17.5 + (#897) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. + - [Release notes](https://github.com/pylint-dev/pylint/releases) + - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.3...v2.17.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Docs: Update README.md (#898) [Dor Blayzer] + + Some typos and grammar fixes +- Chore(deps-dev): update mock requirement from ~=5.0 to ~=5.1 (#893) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mock](https://github.com/testing-cabal/mock) to permit the latest version. + - [Changelog](https://github.com/testing-cabal/mock/blob/master/CHANGELOG.rst) + - [Commits](https://github.com/testing-cabal/mock/compare/5.0.0...5.1.0) + + --- + updated-dependencies: + - dependency-name: mock + dependency-type: direct:development + ... +- Chore: run pre-commit autoupdate (#890) [Kazuaki Matsuo] +- Chore: update isort revision to 5.12.0 (#889) [Dor Blayzer] +- Chore(deps-dev): update pytest requirement from ~=7.2 to ~=7.4 (#884) + [dependabot[bot]] +- Chore(deps-dev): update typing-extensions requirement (#885) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.6.0...4.7.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Docs: update changelogs and version. [Kazuaki Matsuo] + + v2.11.1 (2023-06-13) -------------------- - Revert "chore: remove duplicated clean command" (#881) [Kazuaki From fa7ba6ee5ee91c914f69e34547993af62995462f Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 14 Sep 2023 23:29:58 -0400 Subject: [PATCH 225/548] docs: update README.md for v3 (#912) * docs: update README.md * Update README.md --- README.md | 108 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 8b2eed5c2..cdcde3b4e 100644 --- a/README.md +++ b/README.md @@ -9,51 +9,6 @@ An extension library for adding [WebDriver Protocol](https://www.w3.org/TR/webdriver/) and Appium commands to the Selenium Python language binding for use with the mobile testing framework [Appium](https://appium.io). -## Notice - -Since **v1.0.0**, only Python 3.7+ is supported. - -Since **v2.0.0**, the base selenium client version is v4. -The version only works in the W3C WebDriver protocol format. -If you would like to use the old protocol (MJSONWP), please use the v1 Appium Python client. - -### Quick migration guide from v1 to v2 -- Enhancement - - Updated base Selenium Python binding version to v4 - - Removed `forceMjsonwp` since Selenium v4 and Appium Python client v2 expect only W3C WebDriver protocol - - Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend - - Please check each behavior. Their behaviors could slightly differ. - - Added `strict_ssl` to relax SSL errors such as self-signed ones -- Deprecated - - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. - - e.g. - - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) - - https://www.selenium.dev/documentation/support_packages/mouse_and_keyboard_actions_in_detail/ - - https://www.youtube.com/watch?v=oAJ7jwMNFVU - - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api - - `launch_app`, `close_app`, and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details - -#### MultiAction/TouchAction to W3C actions - -On UIA2, some elements can be handled with `touch` pointer action instead of the default `mouse` pointer action in the Selenium Python client. -For example, the below action builder is to replace the default one with the `touch` pointer action. - -```python -from selenium.webdriver.common.actions import interaction -from selenium.webdriver.common.actions.action_builder import ActionBuilder - -actions = ActionChains(driver) -# override as 'touch' pointer action -actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) -actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) -actions.w3c_actions.pointer_action.pointer_down() -actions.w3c_actions.pointer_action.pause(2) -actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) -actions.w3c_actions.pointer_action.release() -actions.perform() -``` - ## Getting the Appium Python client There are three ways to install and use the Appium Python client. @@ -86,12 +41,14 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). ## Compatibility Matrix -|Appium Python Client| Selenium binding| -|----|----| -|`2.10.0`+ |`4.1.0`+ | -|`2.2.0` - `2.9.0` |`4.1.0` - `4.9.0` | -|`2.0.0` - `2.1.4` |`4.0.0` | -|`1.1.0` and below|`3.x`| +|Appium Python Client| Selenium binding| Python version | +|----|----|----| +|`3.0.0`+ |`4.12.0`+ | 3.8+ | +|`2.10.0` - `2.11.1` |`4.1.0` - `4.11.2` | 3.7+ | +|`2.2.0` - `2.9.0` |`4.1.0` - `4.9.0` | 3.7+ | +|`2.0.0` - `2.1.4` |`4.0.0` | 3.7+ | +|`1.0.0` - `1.1.0` |`3.x`| 3.7, 3.8 | +|`0.52` and below|`3.x`| 2.7, 3.4 - 3.7 | The Appium Python Client depends on [Selenium Python binding](https://pypi.org/project/selenium/), thus the Selenium Python binding update might affect the Appium Python Client behavior. @@ -101,6 +58,55 @@ For example, some changes in the Selenium binding could break the Appium client. > We strongly recommend you manage dependencies with version management tools such as Pipenv and requirements.txt > to keep compatible version combinations. + +### Quick migration guide from v2 to v3 +- Replacement + - `start_activity` method: Please use [`mobile: startActivity`](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-startactivity) + - `launch_app`, `close_app` and `reset` methods: Please refer to https://github.com/appium/appium/issues/15807 + - `available_ime_engines`, `is_ime_active`, `activate_ime_engine`, `deactivate_ime_engine` and `active_ime_engine` methods: Please use [`mobile: shell`](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-shell) + - `set_value` and `set_text` methods: Please use `element.send_keys` or `send_keys` by W3C Actions +- Removal + - `end_test_coverage` method is no longer available + - `session` properly is no longer available + - `all_sessions` properly is no longer available + +### Quick migration guide from v1 to v2 +- Enhancement + - Updated base Selenium Python binding version to v4 + - Removed `forceMjsonwp` since Selenium v4 and Appium Python client v2 expect only W3C WebDriver protocol + - Methods `ActionHelpers#scroll`, `ActionHelpers#drag_and_drop`, `ActionHelpers#tap`, `ActionHelpers#swipe` and `ActionHelpers#flick` now call W3C actions as its backend + - Please check each behavior. Their behaviors could slightly differ. + - Added `strict_ssl` to relax SSL errors such as self-signed ones +- Deprecated + - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. + - e.g. + - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) + - https://www.selenium.dev/documentation/support_packages/mouse_and_keyboard_actions_in_detail/ + - https://www.youtube.com/watch?v=oAJ7jwMNFVU + - https://appiumpro.com/editions/30-ios-specific-touch-action-methods + - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api + - `launch_app`, `close_app`, and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details + +#### MultiAction/TouchAction to W3C actions + +On UIA2, some elements can be handled with `touch` pointer action instead of the default `mouse` pointer action in the Selenium Python client. +For example, the below action builder is to replace the default one with the `touch` pointer action. + +```python +from selenium.webdriver.common.actions import interaction +from selenium.webdriver.common.actions.action_builder import ActionBuilder + +actions = ActionChains(driver) +# override as 'touch' pointer action +actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) +actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) +actions.w3c_actions.pointer_action.pointer_down() +actions.w3c_actions.pointer_action.pause(2) +actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) +actions.w3c_actions.pointer_action.release() +actions.perform() +``` + ## Usage The Appium Python Client is fully compliant with the WebDriver Protocol From 894380b697bef35f98510b41a8ddafb2a3d21aa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:22:56 +0200 Subject: [PATCH 226/548] chore(deps): update selenium requirement from ~=4.12 to ~=4.13 (#915) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.12.0...selenium-4.13.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 552baa2d0..b43ffd524 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.11" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.12" +selenium = "~=4.13" From 8d58eb7e31331ea473d209c5bc42346fd7b8fe3d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 6 Oct 2023 16:27:33 +0200 Subject: [PATCH 227/548] ci: Use appium from the release branch --- ci-jobs/functional/setup_appium.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci-jobs/functional/setup_appium.yml b/ci-jobs/functional/setup_appium.yml index 69ac8627d..cd59485b7 100644 --- a/ci-jobs/functional/setup_appium.yml +++ b/ci-jobs/functional/setup_appium.yml @@ -1,9 +1,9 @@ steps: - task: NodeTool@0 inputs: - versionSpec: '16.x' - displayName: Install Node 16.x -- script: npm install -g appium@next --chromedriver_version='2.44' + versionSpec: '18.x' + displayName: Install Node.js +- script: npm install -g appium displayName: Install appium - task: UsePythonVersion@0 inputs: From 3d7324e4aeae731e5eb01d3881f246a5bb753798 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:55:09 +0200 Subject: [PATCH 228/548] chore(deps-dev): update pylint requirement from ~=2.17.5 to ~=3.0.1 (#922) Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.5...v3.0.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index b43ffd524..e5dfc1dec 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "<6.0" mypy = "<2.0" mock = "~=5.1" pre-commit = "~=2.21" -pylint = "~=2.17.5" +pylint = "~=3.0.1" pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" From 6f1cf34aa61551aaf37eb68a534ce4f8aca6a683 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:55:43 +0200 Subject: [PATCH 229/548] chore(deps): update selenium requirement from ~=4.13 to ~=4.14 (#923) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.13.0...selenium-4.14.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e5dfc1dec..215173bcf 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.11" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.13" +selenium = "~=4.14" From d93a6caadb071598454fb7af0569576258ac4093 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 13 Oct 2023 16:27:15 +0200 Subject: [PATCH 230/548] feat: Add missing platformVersion and browserName options (#925) --- appium/options/android/espresso/base.py | 2 + appium/options/android/uiautomator2/base.py | 2 + appium/options/common/base.py | 4 +- appium/options/common/browser_name_option.py | 38 ++++++++++++++++++ .../options/common/platform_version_option.py | 40 +++++++++++++++++++ appium/options/ios/xcuitest/base.py | 2 + 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 appium/options/common/browser_name_option.py create mode 100644 appium/options/common/platform_version_option.py diff --git a/appium/options/android/espresso/base.py b/appium/options/android/espresso/base.py index 0baaec1b5..c725b63a7 100644 --- a/appium/options/android/espresso/base.py +++ b/appium/options/android/espresso/base.py @@ -108,6 +108,7 @@ from appium.options.common.locale_option import LocaleOption from appium.options.common.orientation_option import OrientationOption from appium.options.common.other_apps_option import OtherAppsOption +from appium.options.common.platform_version_option import PlatformVersionOption from appium.options.common.skip_log_capture_option import SkipLogCaptureOption from appium.options.common.system_port_option import SystemPortOption from appium.options.common.udid_option import UdidOption @@ -154,6 +155,7 @@ class EspressoOptions( EnforceAppInstallOption, LocaleScriptOption, AdbPortOption, + PlatformVersionOption, RemoteAdbHostOption, AdbExecTimeoutOption, ClearDeviceLogsOnStartOption, diff --git a/appium/options/android/uiautomator2/base.py b/appium/options/android/uiautomator2/base.py index f6a01d378..7efcb9036 100644 --- a/appium/options/android/uiautomator2/base.py +++ b/appium/options/android/uiautomator2/base.py @@ -108,6 +108,7 @@ from appium.options.common.locale_option import LocaleOption from appium.options.common.orientation_option import OrientationOption from appium.options.common.other_apps_option import OtherAppsOption +from appium.options.common.platform_version_option import PlatformVersionOption from appium.options.common.skip_log_capture_option import SkipLogCaptureOption from appium.options.common.system_port_option import SystemPortOption from appium.options.common.udid_option import UdidOption @@ -144,6 +145,7 @@ class UiAutomator2Options( SkipDeviceInitializationOption, AppPackageOption, AppActivityOption, + PlatformVersionOption, AppWaitActivityOption, AppWaitPackageOption, AppWaitDurationOption, diff --git a/appium/options/common/base.py b/appium/options/common/base.py index cf8b61c7f..efaa1ba1b 100644 --- a/appium/options/common/base.py +++ b/appium/options/common/base.py @@ -21,6 +21,7 @@ from selenium.webdriver.common.options import BaseOptions from .automation_name_option import AutomationNameOption +from .browser_name_option import BROWSER_NAME, BrowserNameOption from .event_timings_option import EventTimingsOption from .full_reset_option import FullResetOption from .new_command_timeout_option import NewCommandTimeoutOption @@ -34,6 +35,7 @@ class AppiumOptions( BaseOptions, + BrowserNameOption, AutomationNameOption, EventTimingsOption, PrintPageSourceOnFindFailureOption, @@ -45,7 +47,7 @@ class AppiumOptions( W3C_CAPABILITY_NAMES = frozenset( [ 'acceptInsecureCerts', - 'browserName', + BROWSER_NAME, 'browserVersion', PLATFORM_NAME, 'pageLoadStrategy', diff --git a/appium/options/common/browser_name_option.py b/appium/options/common/browser_name_option.py new file mode 100644 index 000000000..2b107972a --- /dev/null +++ b/appium/options/common/browser_name_option.py @@ -0,0 +1,38 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +BROWSER_NAME = 'browserName' + + +class BrowserNameOption(SupportsCapabilities): + @property + def browser_name(self) -> Optional[str]: + """ + The name of the browser to run the test on. + """ + return self.get_capability(BROWSER_NAME) + + @browser_name.setter + def browser_name(self, value: str) -> None: + """ + Set the name of the browser to run the test on. + """ + self.set_capability(BROWSER_NAME, value) diff --git a/appium/options/common/platform_version_option.py b/appium/options/common/platform_version_option.py new file mode 100644 index 000000000..d237cc280 --- /dev/null +++ b/appium/options/common/platform_version_option.py @@ -0,0 +1,40 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + +from typing import Optional + +from .supports_capabilities import SupportsCapabilities + +PLATFORM_VERSION = 'platformVersion' + + +class PlatformVersionOption(SupportsCapabilities): + @property + def platform_version(self) -> Optional[str]: + """ + The platform version of an emulator or a real device. + This capability is used for device autodetection if udid is not provided. + """ + return self.get_capability(PLATFORM_VERSION) + + @platform_version.setter + def platform_version(self, value: str) -> None: + """ + Set the platform version of an emulator or a real device. + This capability is used for device autodetection if udid is not provided. + """ + self.set_capability(PLATFORM_VERSION, value) diff --git a/appium/options/ios/xcuitest/base.py b/appium/options/ios/xcuitest/base.py index 54d254ba2..02143a095 100644 --- a/appium/options/ios/xcuitest/base.py +++ b/appium/options/ios/xcuitest/base.py @@ -30,6 +30,7 @@ from appium.options.common.locale_option import LocaleOption from appium.options.common.orientation_option import OrientationOption from appium.options.common.other_apps_option import OtherAppsOption +from appium.options.common.platform_version_option import PlatformVersionOption from appium.options.common.skip_log_capture_option import SkipLogCaptureOption from appium.options.common.udid_option import UdidOption @@ -120,6 +121,7 @@ class XCUITestOptions( AppiumOptions, AppOption, BundleIdOption, + PlatformVersionOption, ClearSystemFilesOption, OrientationOption, UdidOption, From db14b38e78f76d4556e7c8880e29ef7fee85b319 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 13 Oct 2023 09:54:29 -0700 Subject: [PATCH 231/548] Bump 3.1.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 5918abd15..64236b03e 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '3.0.0' +version = '3.1.0' From f18cebae4e506b3838151a72f54a7b4619a4c42d Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 13 Oct 2023 09:55:23 -0700 Subject: [PATCH 232/548] Update changelog for 3.1.0 --- CHANGELOG.rst | 4411 ------------------------------------------------- 1 file changed, 4411 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 64de644fe..e69de29bb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4411 +0,0 @@ -Changelog -========= - - -v3.0.0 (2023-09-08) -------------------- - -New -~~~ -- Feat!: Update selenium dependency to 4.12 (#908) [Mykola Mokhnach] - - BREAKING CHANGE: The minimum supported Python version set to 3.8 - BREAKING CHANGE: The minimum supported selenium version set to 4.12 - -Fix -~~~ -- Handle the situation where payload is already a dictionary (#892) - [Mykola Mokhnach] -- Add missing dependencies for types-python-dateutil (#891) [Dor - Blayzer] - - * fix: Add missing dependencies for types-python-dateutil - - * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py - - * Revert "test: replace usage of selendroid app from 'test_install_app' in applications_tests.py" - - This reverts commit dcdf08b67d24257700923c89ae2643f26af2892f. - -Test -~~~~ -- Test: selendroid cleanup (#895) [Dor Blayzer] - - * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py - - * test: remove unused import pytest from applications_tests.py - - * test: fix broken TestContextSwitching by replacing selendroid with ApiDemos - - * test: remove selendroid-test-app.apk from apps folder -- Ci: add pylint_quotes for pylint to use single quote as primary method - (#886) [Kazuaki Matsuo] - -Other -~~~~~ -- Bump 3.0.0. [Kazuaki Matsuo] -- Chore(deps-dev): update tox requirement from ~=4.8 to ~=4.11 (#906) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.8.0...4.11.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Refactor!: remove several previously deprecated APIs (#909) [Mykola - Mokhnach] - - BREAKING CHANGE: Removed obsolete all_sessions and session properties - BREAKING CHANGE: Removed the obsolete start_activity method - BREAKING CHANGE: Removed the obsolete end_test_coverage method - BREAKING CHANGE: Removed the following obsolete arguments from the driver constructor: desired_capabilities, browser_profile, proxy - BREAKING CHANGE: Removed obsolete set_value and set_text methods - BREAKING CHANGE: Removed the obsolete MobileBy class - BREAKING CHANGE: Removed obsolete application management methods: launch_app, close_app, reset - BREAKING CHANGE: Removed obsolete IME methods: available_ime_engines, is_ime_active, activate_ime_engine, deactivate_ime_engine, active_ime_engine -- Chore(deps-dev): update tox requirement from ~=4.6 to ~=4.8 (#902) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.8.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps): update selenium requirement from ~=4.10 to ~=4.11 (#899) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/commits) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps-dev): update pylint requirement from ~=2.17.3 to ~=2.17.5 - (#897) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. - - [Release notes](https://github.com/pylint-dev/pylint/releases) - - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.3...v2.17.5) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Docs: Update README.md (#898) [Dor Blayzer] - - Some typos and grammar fixes -- Chore(deps-dev): update mock requirement from ~=5.0 to ~=5.1 (#893) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [mock](https://github.com/testing-cabal/mock) to permit the latest version. - - [Changelog](https://github.com/testing-cabal/mock/blob/master/CHANGELOG.rst) - - [Commits](https://github.com/testing-cabal/mock/compare/5.0.0...5.1.0) - - --- - updated-dependencies: - - dependency-name: mock - dependency-type: direct:development - ... -- Chore: run pre-commit autoupdate (#890) [Kazuaki Matsuo] -- Chore: update isort revision to 5.12.0 (#889) [Dor Blayzer] -- Chore(deps-dev): update pytest requirement from ~=7.2 to ~=7.4 (#884) - [dependabot[bot]] -- Chore(deps-dev): update typing-extensions requirement (#885) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - - [Release notes](https://github.com/python/typing_extensions/releases) - - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - - [Commits](https://github.com/python/typing_extensions/compare/4.6.0...4.7.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:development - ... -- Docs: update changelogs and version. [Kazuaki Matsuo] - - -v2.11.1 (2023-06-13) --------------------- -- Revert "chore: remove duplicated clean command" (#881) [Kazuaki - Matsuo] - - * Revert "chore: remove duplicated clean command (#809)" - - This reverts commit 2f45ef935c12dec2ab8de044ce6a1c1e0b9aa46f. - - * chore: left a comment - - * trim spaces -- Update changelog. [Kazuaki Matsuo] - - -v2.11.0 (2023-06-09) --------------------- - -New -~~~ -- Feat: make the UA format with same as other clients (#793) [Kazuaki - Matsuo] - - * chore: set version with / - - * chore: update comment - - * update test - - * update tests - -Other -~~~~~ -- Update changelog for 2.10.2. [Kazuaki Matsuo] - - -v2.10.2 (2023-06-08) --------------------- - -Fix -~~~ -- Update the constructor for compatibility with python client 4.10 - (#879) [Mykola Mokhnach] - -Test -~~~~ -- Ci: add py11 for the unit test (#875) [Kazuaki Matsuo] - -Other -~~~~~ -- Bump 2.10.2. [Kazuaki Matsuo] -- Chore: remove duplicated clean command (#809) [Kazuaki Matsuo] - - * remove clear - - * remove clear more -- Chore(deps-dev): update tox requirement from ~=4.5 to ~=4.6 (#877) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.5.0...4.6.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Docs: address version management recommendation in the readme (#874) - [Kazuaki Matsuo] - - * chore: set the max selenium deps version - - * ci: add python 11 - - * Update unit-test.yml - - * add note in the readme - - * Update README.md - - * docs: merge the matrix pr into README.md -- Docs: Improve usage examples (#873) [Mykola Mokhnach] -- Chore(deps-dev): update pytest-cov requirement from ~=4.0 to ~=4.1 - (#872) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. - - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) - - --- - updated-dependencies: - - dependency-name: pytest-cov - dependency-type: direct:development - ... -- Chore(deps-dev): update typing-extensions requirement (#871) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - - [Commits](https://github.com/python/typing_extensions/compare/4.5.0...4.6.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:development - ... -- Chore: nump the version. [Kazuaki Matsuo] -- Update changelog for 2.10.1. [Kazuaki Matsuo] - - -v2.10.1 (2023-05-20) --------------------- - -Fix -~~~ -- W3C errors to exception classes mapping (#869) [Mykola Mokhnach] - - * fix: W3C errors to exception classes mapping - - * Imports - - * Tune - -Other -~~~~~ -- Chrom: bump the version. [Kazuaki Matsuo] -- Update changelog for 2.10.0. [Kazuaki Matsuo] - - -v2.10.0 (2023-05-11) --------------------- - -Fix -~~~ -- Update connection manager creation (#864) [Mykola Mokhnach] - -Other -~~~~~ -- Refactor: Move driver-specific commands to use extensions (part2) - (#859) [Mykola Mokhnach] -- Chore(deps): update selenium requirement from ~=4.7 to ~=4.9 (#852) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.7.0...selenium-4.9.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Refactor: Move driver-specific commands to use extensions (part1) - (#856) [Mykola Mokhnach] -- Chore(deps-dev): update pylint requirement from ~=2.17.2 to ~=2.17.3 - (#853) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.2...v2.17.3) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=4.4 to ~=4.5 (#854) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.4.0...4.5.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps-dev): update mypy requirement from ~=1.1 to ~=1.2 (#848) - [dependabot[bot]] -- Chore(deps-dev): update pylint requirement from ~=2.17.1 to ~=2.17.2 - (#847) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.1...v2.17.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore: bump and correct version. [Kazuaki Matsuo] -- Update changelog for 2.9.0. [Kazuaki Matsuo] - - -v2.9.0 (2023-04-02) -------------------- - -New -~~~ -- Feat: respect the given executor (#844) [Kazuaki Matsuo] - - * feat: can provide a custom connection - - * add tests - - * tweak tests - - * lint - - * tweak - - * add comment - - * fix lint - - * tweak - - * add test - - * tweak review - -Fix -~~~ -- Set_value and set_text sent incorrect data (#831) [eyJhb] - -Other -~~~~~ -- Update changelog for 2.8.0. [Kazuaki Matsuo] -- Chore(deps-dev): update pylint requirement from ~=2.17.0 to ~=2.17.1 - (#843) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.0...v2.17.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.16.3 to ~=2.17.0 - (#838) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.3...v2.17.0) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update mypy requirement from ~=1.0 to ~=1.1 (#836) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.1.1) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.16.2 to ~=2.16.3 - (#834) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.2...v2.16.3) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update typing-extensions requirement (#830) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - - [Release notes](https://github.com/python/typing_extensions/releases) - - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - - [Commits](https://github.com/python/typing_extensions/compare/4.4.0...4.5.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.16.1 to ~=2.16.2 - (#829) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.1...v2.16.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update mypy requirement from ~=0.991 to ~=1.0 (#828) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.16.0 to ~=2.16.1 - (#827) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.0...v2.16.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.10 to ~=2.16.0 - (#826) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=4.3 to ~=4.4 (#823) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.3.0...4.4.2) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Update changelog for 2.8.1. [Kazuaki Matsuo] - - -v2.8.1 (2023-01-20) -------------------- - -New -~~~ -- Feat: add status tentatively (#820) [Kazuaki Matsuo] - - * feat: add status tentatively - - * update test - - * fix docstring - - * fix typo - - * fix lint - - -v2.8.0 (2023-01-20) -------------------- - -New -~~~ -- Feat: add status tentatively. [Kazuaki Matsuo] - -Fix -~~~ -- Fix lint. [Kazuaki Matsuo] -- Fix typo. [Kazuaki Matsuo] -- Fix docstring. [Kazuaki Matsuo] - -Other -~~~~~ -- Update test. [Kazuaki Matsuo] -- Chore(deps-dev): update tox requirement from ~=4.2 to ~=4.3 (#817) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.2.0...4.3.1) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps): update sphinx requirement from <6.0,>=4.0 to >=4.0,<7.0 - (#814) [dependabot[bot]] -- Chore(deps-dev): update pylint requirement from ~=2.15.9 to ~=2.15.10 - (#816) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.9...v2.15.10) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=4.1 to ~=4.2 (#815) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.1.0...4.2.1) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=4.0 to ~=4.1 (#813) - [dependabot[bot], dependabot[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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/4.0.0...4.1.1) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps-dev): update mock requirement from ~=4.0 to ~=5.0 (#812) - [dependabot[bot], dependabot[bot]] - - 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/4.0.0...5.0.0) - - --- - updated-dependencies: - - dependency-name: mock - dependency-type: direct:development - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.20 to ~=2.21 - (#811) [dependabot[bot], dependabot[bot]] - - 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/main/CHANGELOG.md) - - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.20.0...v2.21.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.8 to ~=2.15.9 - (#810) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.8...v2.15.9) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update isort requirement from ~=5.10 to ~=5.11 (#808) - [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.10.0...5.11.1) - - --- - updated-dependencies: - - dependency-name: isort - dependency-type: direct:development - ... -- Chore(deps-dev): update black requirement from ~=22.10.0 to ~=22.12.0 - (#807) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] - - Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/compare/22.10.0...22.12.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 (#806) - [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] - - * chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 - - 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/main/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/3.27.0...4.0.2) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.7 to ~=2.15.8 - (#804) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps): update selenium requirement from ~=4.5 to ~=4.7 (#801) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.5.0...selenium-4.7.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.6 to ~=2.15.7 - (#800) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.6...v2.15.7) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.5 to ~=2.15.6 - (#799) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.5...v2.15.6) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore: update docstring in touch_action.py (#797) [Kazuaki Matsuo, - wojciodataist] - - add detailed information to long_press duration param -- Chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 - (#798) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] - - * chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.982...v0.991) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:development - ... -- Chore(deps-dev): update tox requirement from ~=3.26 to ~=3.27 (#792) - [dependabot[bot], dependabot[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.26.0...3.27.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:development - ... -- Chore(deps-dev): update pytest requirement from ~=7.1 to ~=7.2 (#791) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - - [Release notes](https://github.com/pytest-dev/pytest/releases) - - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.2.0) - - --- - updated-dependencies: - - dependency-name: pytest - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.4 to ~=2.15.5 - (#790) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.4...v2.15.5) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.3 to ~=2.15.4 - (#788) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.3...v2.15.4) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Chore: update precommit (#787) [Kazuaki Matsuo] -- Update changelog for 2.7.1. [Kazuaki Matsuo] -- Bump 2.7.1. [Kazuaki Matsuo] - - -v2.7.1 (2022-10-11) -------------------- - -Test -~~~~ -- Ci: run unit tests on actions (#773) [Kazuaki Matsuo] - - * ci: run unit tests on actions - - * ci: remove unit test section - - * ci: comment out win for now - - * ci: tweak trigger - -Other -~~~~~ -- Refactor: Make service startup failures more helpful (#786) [Mykola - Mokhnach] -- Chore(deps-dev): update typing-extensions requirement (#783) [Kazuaki - Matsuo, dependabot[bot], dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - - [Release notes](https://github.com/python/typing_extensions/releases) - - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - - [Commits](https://github.com/python/typing_extensions/compare/4.3.0...4.4.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:development - ... -- Chore(deps-dev): update black requirement from ~=22.8.0 to ~=22.10.0 - (#784) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/compare/22.8.0...22.10.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:development - ... -- Chore(deps-dev): update mypy requirement from ~=0.981 to ~=0.982 - (#782) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.981...v0.982) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:development - ... -- Chore(deps-dev): update pytest-cov requirement from ~=3.0 to ~=4.0 - (#779) [dependabot[bot], dependabot[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/v3.0.0...v4.0.0) - - --- - updated-dependencies: - - dependency-name: pytest-cov - dependency-type: direct:development - ... -- Chore(deps): update selenium requirement from ~=4.4 to ~=4.5 (#780) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.4.0...selenium-4.5.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps-dev): update mypy requirement from ~=0.971 to ~=0.981 - (#777) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.971...v0.981) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:development - ... -- Chore(deps-dev): update pylint requirement from ~=2.15.2 to ~=2.15.3 - (#774) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:development - ... -- Bump 2.7.0. [Kazuaki Matsuo] -- Update changelog for 2.6.3. [Kazuaki Matsuo] - - -v2.7.0 (2022-09-22) -------------------- - -New -~~~ -- Feat: Add appArguments option to WindowsOptions (#768) [Mykola - Mokhnach] - -Fix -~~~ -- Move dev-only dependencies to [dev-packages] section (#772) [Mykola - Mokhnach] - -Test -~~~~ -- Ci: Fix runner name. [Mykola Mokhnach] - -Other -~~~~~ -- Chore(deps): update pylint requirement from ~=2.15.2 to ~=2.15.3 - (#770) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Docs: Update changelog for 2.6.2. [Kazuaki Matsuo] - - -v2.6.2 (2022-09-16) -------------------- - -Fix -~~~ -- Use total_seconds property of timedelta (#767) [Mykola Mokhnach] - -Test -~~~~ -- Ci: Update Conventional Commits config preset. [Mykola Mokhnach] -- Ci: Add Conventional commit format validation (#764) [Mykola Mokhnach] - - * ci: Add Conventional commit format validation - - * Rename - -Other -~~~~~ -- Chore(deps): update tox requirement from ~=3.25 to ~=3.26 (#766) - [dependabot[bot], dependabot[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.25.0...3.26.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.15.0 to ~=2.15.2 - (#765) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.0...v2.15.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update astroid requirement from ~=2.9 to ~=2.12 (#762) - [dependabot[bot], dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/v2.9.0...v2.12.5) - - --- - updated-dependencies: - - dependency-name: astroid - dependency-type: direct:production - ... -- Chore(deps): bump black from 22.6.0 to 22.8.0 (#763) [dependabot[bot], - dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - update-type: version-update:semver-minor - ... -- Chore(deps): update pylint requirement from ~=2.14.5 to ~=2.15.0 - (#761) [dependabot[bot], dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.5...v2.15.0) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Docs: Update changelog for 2.6.1. [Kazuaki Matsuo] - - -v2.6.1 (2022-08-11) -------------------- - -Fix -~~~ -- Fix options in mac2 (#759) [Kazuaki Matsuo] -- Backwards compatible behaviour of swipe and scroll in action_helpers - (#744) [jatalahd] - - * Backwards compatible behaviour of swipe and scroll in action_helpers - - - Fixed handling the duration argument in swipe() and scroll() helpers - - Functionality is now the same as in older versions using TouchActions - - Fixes #743 - - * Backwards compatible behaviour of swipe and scroll in action_helpers - - - Fixed handling the duration argument in swipe() and scroll() helpers - - Functionality is now the same as in older versions using TouchActions - - Fixes #743 - - * Backwards compatible behaviour of swipe and scroll in action_helpers - - - Fixed handling the duration argument in swipe() and scroll() helpers - - Functionality is now the same as in older versions using TouchActions - - Fixes #743 - - * Backwards compatible behaviour of swipe and scroll in action_helpers - - - Fixed handling the duration argument in swipe() and scroll() helpers - - Functionality is now the same as in older versions using TouchActions - - Fixes #743 - - * Backwards compatible behaviour of swipe and scroll in action_helpers - - - Fixed handling the duration argument in swipe() and scroll() helpers - - Functionality is now the same as in older versions using TouchActions - - Fixes #743 -- Move py.typed to the hierarchy root (#751) [Mykola Mokhnach] -- Typos/copypaste in various options (#750) [Mykola Mokhnach] - -Other -~~~~~ -- Chore(deps): update selenium requirement from ~=4.3 to ~=4.4 (#757) - [dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.3.0...selenium-4.4.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps): update mypy requirement from ~=0.961 to ~=0.971 (#749) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.961...v0.971) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.14.4 to ~=2.14.5 - (#747) [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.4...v2.14.5) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.19 to ~=2.20 - (#746) [dependabot[bot]] - - 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/main/CHANGELOG.md) - - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.19.0...v2.20.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update typing-extensions requirement from ~=4.2 to ~=4.3 - (#745) [dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. - - [Release notes](https://github.com/python/typing_extensions/releases) - - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - - [Commits](https://github.com/python/typing_extensions/compare/4.2.0...4.3.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.14.3 to ~=2.14.4 - (#742) [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.3...v2.14.4) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Docs: Update changelog for 2.6.0. [Kazuaki Matsuo] - - -v2.6.0 (2022-06-28) -------------------- - -New -~~~ -- Feat: Add Android drivers options (#740) [Mykola Mokhnach] - -Other -~~~~~ -- Chore(deps): bump black from 22.3.0 to 22.6.0 (#741) [dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/compare/22.3.0...22.6.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - update-type: version-update:semver-minor - ... -- Chore: Improve autocompletion for methods returning self instance - (#739) [Mykola Mokhnach] -- Refactor: Remove previously deprecated methods and mark - reset/close/launch APIs as deprecated (#738) [Mykola Mokhnach] -- Docs: Update changelog for 2.5.0. [Kazuaki Matsuo] - - -v2.5.0 (2022-06-25) -------------------- - -New -~~~ -- Feat: Add xcuitest driver options (#737) [Mykola Mokhnach] -- Feat: Add Gecko driver options (#735) [Mykola Mokhnach] -- Feat: Add Windows driver options (#732) [Mykola Mokhnach] -- Feat: Add Safari driver options (#731) [Mykola Mokhnach] -- Feat: Add Mac2Driver options (#730) [Mykola Mokhnach] - -Other -~~~~~ -- Chore(deps): update selenium requirement from ~=4.2 to ~=4.3 (#736) - [dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.2.0...selenium-4.3.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.14.2 to ~=2.14.3 - (#733) [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.2...v2.14.3) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Refactor: Make system_port and system_host options common (#734) - [Mykola Mokhnach] -- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 - (#725) [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore: bump version to 2.4.0. [Kazuaki Matsuo] -- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 - (#725) [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... - - -v2.4.0 (2022-06-17) -------------------- - -New -~~~ -- Feat: Add common options (#728) [Mykola Mokhnach] - -Other -~~~~~ -- Chore: Add better error handling for session creation responses (#727) - [Mykola Mokhnach] -- Docs: Update changelog for 2.3.0. [Kazuaki Matsuo] -- Bump 2.3.0. [Kazuaki Matsuo] -- Chore: Update comments to locator patches (#724) [VladimirPodolyan] - - * Update webelement.py - - * update comment section - - * CR fixes - - -v2.3.0 (2022-06-13) -------------------- - -New -~~~ -- Feat: Add base options for all supported automation names (#721) - [Mykola Mokhnach] -- Feat: Add support for w3c options (#720) [Mykola Mokhnach] - -Test -~~~~ -- Test: Use Appium2 to run functional tests (#723) [Mykola Mokhnach] - -Other -~~~~~ -- Docs: Update README with the new options format (#722) [Mykola - Mokhnach] -- Chore(deps): update mypy requirement from ~=0.960 to ~=0.961 (#718) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.960...v0.961) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore: Disable pylint checks fail CI (#719) [Mykola Mokhnach] -- Chore(deps): update selenium requirement from ~=4.1 to ~=4.2 (#715) - [dependabot[bot]] - - Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.1.0...selenium-4.2.0) - - --- - updated-dependencies: - - dependency-name: selenium - dependency-type: direct:production - ... -- Chore(deps): update sphinx requirement from <5.0,>=4.0 to >=4.0,<6.0 - (#716) [dependabot[bot]] - - Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.0...v5.0.0) - - --- - updated-dependencies: - - dependency-name: sphinx - dependency-type: direct:production - ... -- Chore(deps): update mypy requirement from ~=0.950 to ~=0.960 (#714) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.950...v0.960) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.18 to ~=2.19 - (#713) [dependabot[bot]] - - 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/main/CHANGELOG.md) - - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.18.0...v2.19.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update mypy requirement from ~=0.942 to ~=0.950 (#712) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.942...v0.950) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update typing-extensions requirement from ~=4.1 to ~=4.2 - (#711) [dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. - - [Release notes](https://github.com/python/typing/releases) - - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) - - [Commits](https://github.com/python/typing/compare/4.1.0...4.2.0) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:production - ... -- Chore(deps): update tox requirement from ~=3.24 to ~=3.25 (#709) - [dependabot[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.24.0...3.25.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.17 to ~=2.18 - (#708) [dependabot[bot]] - - 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/main/CHANGELOG.md) - - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Update changelog for 2.2.0. [Kazuaki Matsuo] - - -v2.2.0 (2022-03-30) -------------------- - -New -~~~ -- Feat: add non-w3c but still need commands (#701) [Kazuaki Matsuo] - - * add non-w3c but still need commands - - * fix id as $ - -Other -~~~~~ -- Bump 2.2.0. [Kazuaki Matsuo] -- Chore(deps): bump black from 22.1.0 to 22.3.0 (#705) [dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 22.1.0 to 22.3.0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/compare/22.1.0...22.3.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - update-type: version-update:semver-minor - ... -- Revert: pylint (#706) [Kazuaki Matsuo] -- Chore: relax selenium version as same as before. [Kazuaki Matsuo] -- Chore(deps): update mypy requirement from ~=0.941 to ~=0.942 (#703) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.941...v0.942) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.12 to ~=2.13 (#702) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.12.0...v2.13.0) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update mypy requirement from ~=0.930 to ~=0.941 (#696) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.930...v0.941) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update typing-extensions requirement from ~=4.0 to ~=4.1 - (#684) [dependabot[bot]] - - Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. - - [Release notes](https://github.com/python/typing/releases) - - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) - - [Commits](https://github.com/python/typing/compare/4.0.0...4.1.1) - - --- - updated-dependencies: - - dependency-name: typing-extensions - dependency-type: direct:production - ... -- Chore(deps): update pytest requirement from ~=7.0 to ~=7.1 (#694) - [dependabot[bot]] - - Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - - [Release notes](https://github.com/pytest-dev/pytest/releases) - - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...7.1.0) - - --- - updated-dependencies: - - dependency-name: pytest - dependency-type: direct:production - ... -- Docs: update missing changelog. [Kazuaki Matsuo] - - -v2.1.4 (2022-02-28) -------------------- -- Bump 2.1.4. [Kazuaki Matsuo] -- Update changelog for 2.1.3. [Kazuaki Matsuo] - - -v2.1.3 (2022-02-26) -------------------- - -Test -~~~~ -- Test: update tests to use find_element(by...) (#674) [Kazuaki Matsuo] - - * test: update find element/s methods - - * fix arguments - - * fix default value - -Other -~~~~~ -- Bump 2.1.3. [Kazuaki Matsuo] -- Chore: restrict selenium client version (#686) [Kazuaki Matsuo] -- Chore(deps): bump black from 21.12b0 to 22.1.0 (#681) - [dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/commits/22.1.0) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - ... -- Chore(deps): update pytest requirement from ~=6.2 to ~=7.0 (#682) - [dependabot[bot]] - - Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - - [Release notes](https://github.com/pytest-dev/pytest/releases) - - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...7.0.0) - - --- - updated-dependencies: - - dependency-name: pytest - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.16 to ~=2.17 - (#678) [dependabot[bot]] - - 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/v2.16.0...v2.17.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Refactor: Update types descriptions for mixin classes (#677) [Mykola - Mokhnach] -- Chore: bump mypy (#675) [Kazuaki Matsuo] -- Update changelog for 2.1.2. [Kazuaki Matsuo] - - -v2.1.2 (2021-12-30) -------------------- - -Fix -~~~ -- Default duration in tap (#673) [Kazuaki Matsuo] - -Other -~~~~~ -- Bump 2.1.2. [Kazuaki Matsuo] -- Update changelog for 2.1.1. [Kazuaki Matsuo] - - -v2.1.1 (2021-12-24) -------------------- - -New -~~~ -- Feat: use 'touch' pointer action (#670) [Kazuaki Matsuo] - - * chore: specify touch - - * comment out touch in drag_and_drop - - * fix mypy - - * add desctiption of touch action - -Test -~~~~ -- Ci: remove ==2021.5.29 (#653) [Kazuaki Matsuo] - - * ci: remove ==2021.5.29 - - * bump black - -Other -~~~~~ -- Bump 2.1.1. [Kazuaki Matsuo] -- Chore(deps): bump black from 21.11b1 to 21.12b0 (#664) - [dependabot[bot]] - - Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. - - [Release notes](https://github.com/psf/black/releases) - - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - - [Commits](https://github.com/psf/black/commits) - - --- - updated-dependencies: - - dependency-name: black - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 - (#663) [dependabot[bot]] - - 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/v2.15.0...v2.16.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) - - --- - updated-dependencies: - - dependency-name: astroid - dependency-type: direct:production - ... -- Update changelog for 2.1.0. [Kazuaki Matsuo] - - -v2.1.0 (2021-11-27) -------------------- - -New -~~~ -- Feat: add AppiumBy instead of MobileBy (#659) [Kazuaki Matsuo] - - * feat: add AppiumBy instead of MobileBy - - * add class description - - * use deprecated:: - -Other -~~~~~ -- Bump 2.1.0. [Kazuaki Matsuo] -- Chore: add deprecated mark for find_element_by* (#657) [Kazuaki - Matsuo] -- Chore: relax selenium version control (#656) [Kazuaki Matsuo] -- Chore: tweak keyword in metadata. [Kazuaki Matsuo] -- Update changelog for 2.0.0. [Kazuaki Matsuo] - - -v2.0.0 (2021-11-09) -------------------- - -New -~~~ -- Feat: Change base selenium client version to selenium 4 (#636) - [Kazuaki Matsuo] - - - Changed base selenium client version to v4 - - No longer forceMjsonwp works - - Add strict_ssl option in webdriver.Remote - -Test -~~~~ -- Ci: set pipenv==2021.5.29 to prevent dependencies error (#651) - [Kazuaki Matsuo] - - * ci: add --pre - - * specify pipenv as same as the previous ok case - - * set 2021.5.29 in tox as well - -Other -~~~~~ -- Bump 2.0.0. [Kazuaki Matsuo] -- Docs: update readme. [Kazuaki Matsuo] -- Chore: add Python 3.9 as metadata. [Kazuaki Matsuo] -- Chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) - [dependabot[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) - - --- - updated-dependencies: - - dependency-name: isort - dependency-type: direct:production - ... -- Update changelog for 2.0.0.rc6. [Kazuaki Matsuo] -- Bump 2.0.0.rc6. [Kazuaki Matsuo] -- Docs: update readme. [Kazuaki Matsuo] -- Chore: adding deprecation mark in touch actions and multi touch (#648) - [Kazuaki Matsuo] - - * chore: add deprecation mark in touch actions and multi touch - - * chore: add deprecated mark in MultiAction class - - * docs: update readme -- Chore: deprecate -windows uiautomation (#649) [Kazuaki Matsuo] - - * chore: add Deprecated for -windows uiautomation - - * chore: add logger -- Update changelog for 2.0.0.rc5. [Kazuaki Matsuo] -- Bump 2.0.0.rc5. [Kazuaki Matsuo] -- Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 - (#603) [Kazuaki Matsuo, dependabot[bot]] - - Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) -- Update gitchangelog once. [Kazuaki Matsuo] -- Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 - (#637) [Kazuaki Matsuo, dependabot[bot]] - - Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. - - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) - - --- - updated-dependencies: - - dependency-name: sphinx-rtd-theme - dependency-type: direct:production - ... -- Chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) - [Kazuaki Matsuo] - - chore: cleanup no longer needed code in w3c, bump dev Pipfile -- Chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) - [dependabot[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.12.0...v3.0.0) - - --- - updated-dependencies: - - dependency-name: pytest-cov - dependency-type: direct:production - ... -- Update changelog for 1.3.0. [Kazuaki Matsuo] - - -v1.3.0 (2021-09-27) -------------------- - -New -~~~ -- Feat: do not raise an error in case method is already defined (#632) - [Kazuaki Matsuo] -- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] - - * feat: add satellites in set_location - - * fix review -- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] - - * chore: add placeholder - - * move to extention way - - * revert pytest - - * add todo - - * call method_name instead of wrapper - - * remove types - - * rename a method - - * add examples - - * add types-python-dateutil as error message - - * add example more - - * tweak naming - - * Explicit Dict - -Other -~~~~~ -- Bump 1.3.0. [Kazuaki Matsuo] -- Chore(deps): update types-python-dateutil requirement (#633) - [dependabot[bot]] - - Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. - - [Release notes](https://github.com/python/typeshed/releases) - - [Commits](https://github.com/python/typeshed/commits) - - --- - updated-dependencies: - - dependency-name: types-python-dateutil - dependency-type: direct:production - ... -- Chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 - (#634) [dependabot[bot]] - - 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/v2.13.0...v2.15.0) - - --- - updated-dependencies: - - dependency-name: pre-commit - dependency-type: direct:development - ... -- Chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) - [dependabot[bot]] - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) - - --- - updated-dependencies: - - dependency-name: mypy - dependency-type: direct:production - ... -- Chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) - - --- - updated-dependencies: - - dependency-name: astroid - dependency-type: direct:production - ... -- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) - - --- - updated-dependencies: - - dependency-name: pylint - dependency-type: direct:production - ... -- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) - [dependabot[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.23.0...3.24.0) - - --- - updated-dependencies: - - dependency-name: tox - dependency-type: direct:production - ... -- Update changelog for 1.2.0. [Kazuaki Matsuo] - - -v1.2.0 (2021-06-07) -------------------- - -New -~~~ -- Feat: allow to add a command dynamically (#608) [Kazuaki Matsuo] - - * add add_commmand in python - - * add test - - * add exceptions, tweak method - - * append docstring - - * add $id example - - * use pytest.raises - - * add examples as docstring - -Other -~~~~~ -- Bump 1.2.0. [Kazuaki Matsuo] -- Chore(deps-dev): update pre-commit requirement from ~=2.12 to ~=2.13 - (#607) [dependabot[bot]] - - 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/v2.12.0...v2.13.0) -- Chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 - (#606) [Kazuaki Matsuo, dependabot[bot]] - - * chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 - - 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.11.0...v2.12.0) -- Chore(deps): update pylint requirement from ~=2.7 to ~=2.8 (#600) - [dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.0...pylint-2.8.1) -- Chore(deps-dev): update pre-commit requirement from ~=2.11 to ~=2.12 - (#599) [dependabot[bot]] - - 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/v2.11.0...v2.12.0) -- Chore(deps): update isort requirement from ~=5.7 to ~=5.8 (#596) - [dependabot[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.7.0...5.8.0) - - -v1.1.0 (2021-03-10) -------------------- - -New -~~~ -- Feat: Add optional location speed attribute for android devices (#594) - [salabogdan] -- Feat: Added docstring for macOS screenrecord option (#580) [Mori - Atsushi] - - * Added docstring for macOS screenrecord option - - * tweak - - * review comment -- Feat: add warning to drop forceMjsonwp for W3C (#567) [Kazuaki Matsuo] - - * tweak - - * fix test - - * print warning - - * revert test - - * Update webdriver.py - - * fix autopep8 -- Feat: Added descriptions for newly added screenrecord opts (#540) - [Mori Atsushi] - - * Add description for newly added opts for screen record - - * Updates - -Test -~~~~ -- Ci: Use node v12 (#585) [Mori Atsushi] - - * Use node 12 on ci - - * Update copyright - - * Update README for doc - - * tweak - - * fix copyright - - * try py310 - - * remove py310 -- Ci: remove travis (#581) [Mori Atsushi] - - * Removed travis and run unit test on azure - - * review comment - - * run tox on azure pipelines - - * removed tox-travis from pipfile -- Ci: move azure project to Appium CI, update readme (#564) [Kazuaki - Matsuo] -- Ci: Added py39-dev for travis (#557) [Mori Atsushi] - - * ci: Added py39-dev - - * Add xv option for debug - - * [debug] pip list - - * Avoid error in py39 - - * Updated modules in pre-commit -- Ci: upgrade xcode and macos (#556) [Mori Atsushi] - - * ci: upgrade xcode ver and macos - - * Upgrade iOS ver for functional tests - - * Changed xcode to 11.6 - -Other -~~~~~ -- Chore(deps-dev): update pre-commit requirement from ~=2.10 to ~=2.11 - (#595) [dependabot[bot]] - - 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/v2.10.0...v2.11.0) -- Chore(deps): update tox requirement from ~=3.22 to ~=3.23 (#593) - [dependabot[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/3.23.0/docs/changelog.rst) - - [Commits](https://github.com/tox-dev/tox/compare/3.22.0...3.23.0) -- Chore(deps): update pylint requirement from ~=2.6 to ~=2.7 (#588) - [Mori Atsushi, dependabot[bot]] - - Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - - [Release notes](https://github.com/PyCQA/pylint/releases) - - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.7.0) -- Chore(deps): update astroid requirement from ~=2.4 to ~=2.5 (#587) - [dependabot[bot]] - - Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. - - [Release notes](https://github.com/PyCQA/astroid/releases) - - [Changelog](https://github.com/PyCQA/astroid/blob/master/ChangeLog) - - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.4.0...astroid-2.5) -- Chore(deps): update mypy requirement from ~=0.800 to ~=0.812 (#589) - [Mori Atsushi, dependabot[bot]] - - * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) - - Signed-off-by: dependabot[bot] - - * Fix mypy error with mypy v0.812 (#590) - - * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 - - Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - - [Release notes](https://github.com/python/mypy/releases) - - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) -- Chore(deps): update tox requirement from ~=3.21 to ~=3.22 (#586) - [dependabot[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.21.0...3.22.0) -- Chore: Add table for screen_record kwarg (#582) [Mori Atsushi] - - * Add table for kwarg - - * update - - * Add missing doc to stop_recording - - * Push auto-generated changes by sphinx - - * delete duplicated entry [skip ci] -- Chore(deps): update isort requirement from ~=5.0 to ~=5.7 (#578) - [dependabot-preview[bot]] - - Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. - - [Release notes](https://github.com/pycqa/isort/releases) - - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - - [Commits](https://github.com/pycqa/isort/compare/5.0.0...5.7.0) -- Create Dependabot config file (#579) [dependabot-preview[bot], - dependabot-preview[bot]] -- Chore: Update pipfile to respect isort v5 (#577) [Mori Atsushi] -- Chore: Fix iOS app management functional tests (#575) [Mori Atsushi] - - * Added sleep to wait the app has gone - - * Upgrade AndroidSDK to 30 from 27 - - * Added sleep to ios tc - - * Fix android activities test - - * Revert android sdk ver - - * Used timer instead of fixed wait time - - * Created wait_for - - * Update test/functional/test_helper.py - - * review comments - - * review comments - - * Extend callable type - - * fix - - * review comment - - * review comment - - * review comment - - * fix comment -- Chore: Fix functional keyboard tests with appium v1.21.0-beta.0 (#574) - [Mori Atsushi] - - * Fix function keyboard tests - - * Updated class name for keyboard -- Chore: Apply Black code formatter (#571) [Mori Atsushi] - - * Applied black (length: 120, String skipped) - - * Updated related to ci - - * Update README -- Chore: address selenium-4 branch in readme (#566) [Kazuaki Matsuo] -- Docs: fix wrong code example in README.md (#555) [sanlengjingvv] -- Update changelog for 1.0.2. [Kazuaki Matsuo] - - -v1.0.2 (2020-07-15) -------------------- -- Bump 1.0.2. [Kazuaki Matsuo] -- Chore: Add the workaround to avoid service freezes on Windows (#552) - [Mykola Mokhnach] -- Chore: add checking package file count comparison in release script - (#547) [Kazuaki Matsuo] - - * chore: Add file count in release script - - * use f string for Python 3 :P - - * handle exit in method -- Update changelog for 1.0.1. [Kazuaki Matsuo] - - -v1.0.1 (2020-05-18) -------------------- - -Fix -~~~ -- Broken package (#545) [Kazuaki Matsuo] - - * add appium/webdriver/py.typed in find_packages - - * fix - -Other -~~~~~ -- Bump 1.0.1. [Kazuaki Matsuo] -- Update changelog for 1.0.0. [Kazuaki Matsuo] - - -v1.0.0 (2020-05-16) -------------------- - -New -~~~ -- Feat: Added Makefile (#530) [Mori Atsushi] - - * Created setup.cfg - - * Updated lib ver for pre-commit - - * Fix ci.sh to set failure even when one command failed - - * Fix pylint error - - * Add help to Makefile - - * Update README - - * Add check-all command -- Feat: Merge python3 branch to master (#526) [Hannes Hauer, Hannes - Hauer * 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 * Revert some unexpected changes * review - comments * Changed bound for TypeVar * Fix crashing ci * - Remove beta Co-authored-by: dependabot-preview[bot] - <27856297+dependabot-preview[bot]@users.noreply.github.com>, Kazuaki - Matsuo, Kazuaki Matsuo, Mori Atsushi, Mykola Mokhnach, Mykola - Mokhnach, Nrupesh Patel, Nrupesh Patel, Venkatesh, Venkatesh] - - * Drop py2 support (#478) - - * Drop py2 support - - * Support 3.7+ - - * 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 - - * Add mypy to pre-commit (#485) - - * chore: Applied some py3 formats (#486) - - * Removed unused import - - * Removed unnecessary codes - - * Applied f'' format instead ''.format() - - * Fixes - - * tweak - - * 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 - - * 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 - - * 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] - - * Add 'from' to except (#503) - - * 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] - - * 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 - - * 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: - -Fix -~~~ -- Tune mixin types, so linters could recognize them better (#536) - [Mykola Mokhnach] - -Test -~~~~ -- Test: Add appium_service functional test (#531) [Mori Atsushi] - - * Add appium_service functional test - - * Fix expressions - -Other -~~~~~ -- Bump 1.0.0. [Kazuaki Matsuo] -- Chore: Updates docstring (#533) [Mori Atsushi] - - * Updates docstring - - * Add description to Returns field - - * Remove type from docstring - - Since type hint already added to args - - * Set default lang to en - - * Change usage style in docstring - - * Updates - - * Remove rtype - - unnecessary anymore since type hint works for auto completion - - * tweak - - * Update return type - - * Restore types for keyword args - - * Remove types from Return field - - Except for property and TypeVar -- Chore: Remove saucetestcase from the client (#539) [Mykola Mokhnach] -- Chore: add py.typed in package, add maintainers (#538) [Kazuaki - Matsuo] -- Docs: Update documentation (#527) [Kazuaki Matsuo] - - * Chore: correct license, update readme - - * cleanup - - * docs: update the url of documentation -- Chore: Update readme and gitchangelog section role (#524) [Kazuaki - Matsuo] - - * chore: tweak changelog filter - - * address stoping Python 2 support - - * 2 instead of 2.0... - - * tweak readme -- Update changelog for 0.52. [Kazuaki Matsuo] - - -v0.52 (2020-04-23) ------------------- - -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] -- Chore: Fix int - str comparison error in ios desired capabilities - (#517) [Venkatesh] - - if number >= PytestXdistWorker.COUNT: -- Update changelog for 0.51. [Kazuaki Matsuo] - - -v0.51 (2020-04-12) ------------------- - -New -~~~ -- 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 -~~~ -- 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 - -Other -~~~~~ -- Bump 0.51. [Kazuaki Matsuo] -- Update changelog for 0.50. [Kazuaki Matsuo] - - -v0.50 (2020-02-10) ------------------- - -New -~~~ -- 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 - -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 - - * 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 -- Update changelog for 0.49. [Kazuaki Matsuo] - - -v0.49 (2019-12-24) ------------------- - -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] - - * Fix get_all_sessions - - * Revert changes - - * Move execute_mobile_command codes to mixin class - - * Update docstring - - It's same to webdriver.py - - * Use /sessions as endpoint for all_sessions - - https://github.com/appium/appium-base-driver/blob/master/docs/mjsonwp/protocol-methods.md - - * Delete unnecessary codes -- Replace apk for functional test (#470) [Mori Atsushi] - - * Replace apk for functional test - - https://github.com/appium/android-apidemos/releases/tag/v3.1.0 - - * Use sdkVer 27 - - * Update app package name - - * Fix: can't find android device - - * review comments - - * tweak -- Support for log_event and get_events command (#469) [Mori Atsushi] - - * Use appium/events as endpoint to get events - - * Removed unnecessary codes - - * Update unittest along to changes - - * Update docstring - - * Created LogEvents class - - * Support log_event - - * Add unittest for log_event - - * Add functional test for log_event and get_event - - * review comments - - * Restore events API - - * Add type as arg to get_events - - * tweak - - * Removed type arg from get_events - - It isn't implemented yet for now - - * Add type arg to get_event - - The value isn't passed to the server for now. - - * Updated along to type -- Cleaned up test codes (#466) [Mori Atsushi] - - * Deleted unnecessary codes - - * Move functional tests to correct class - - * Move some tests - - * Created search_context/windows_test - - * [functional] Created search_context package - - * Remove class method decolator - - * Fix import error - - * Add BaseTestCase for ios functional testcases - - * Add test_helper for android functional test - - * Add __init__.py - - * Deleted unused imports -- Move search context methods from webdriver and webelement to - search_context (#461) [Mori Atsushi] - - * Move ios search context methods to search_context file - - * Move android search text methods - - * Move windows search context - - * Move mobile search context - - * Divided search_context into each class - - * Move custom and image methods - - * Move contents in search_context.py to __init__.py - - * Add rtype to each docstring for auto completion in IDE - - * Add comments -- [CI] Run functional tests nightly (#463) [Mori Atsushi] - - * [CI] Run functional tests nightly - - * Extend timeout to wait for 2nd session created - - * Skip flaky test_all_sessions -- Revert some changes to fix broken codes (#462) [Mori Atsushi] - - * Revert some changes - - * Fix typo -- Move commands from webdriver as mixins class (#459) [Manoj Kumar] - - * move to mixins class - - * Create common class with its tests - - * incorporating PR comments -- 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] -- Add unit tests for keyboard API (#452) [Manoj Kumar] - - * Add Unit tests for Keyboard API - - * incorporating review comments - - * change per review comment -- Feat: Adding getAllSessions (#446) [Manoj Kumar] - - * Adding getAllSessions - - * adjust per lint - - * fix comments -- Add downloads badge (#441) [Mori Atsushi] - -Fix -~~~ -- 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 -- Fix CI fails (Updated iOS ver) (#440) [Mori Atsushi] - - * Updated iOS ver to fix CI fails - - * Update capability for safari test on ios - - * Fix travis CI fails -- Fix CI fails (#436) [Mori Atsushi] - - * Skip taking the screenshot not in CI - - * Skip py38 on travis -- Fix isort behavior for mock (#432) [Mori Atsushi] - - * Fix isort behavior for mock - - * Add guide to add 3rd party modules to isort conf - - * Add guide for docstrings - - * Delete unnecessary codes -- Fix android flaky tests (#413) [Mori Atsushi] - - * Fix android flaky tests - - * Use androidSdkVer 27 for emulator - - * Skip find_by_accessibility_id, find_by_uiautomator - - * Changed from https://github.com/ki4070ma/python-client/pull/5 - - * Add save_appium_log.yml - - * 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] - - -v0.47 (2019-08-22) ------------------- - -New -~~~ -- Add events property (#429) [Dan Graham] - - * add GET_SESSION - - * add events property, this property will get the current information of the session and get the events timings - - * add method for getting session_capabilities - - * update docstring - - * apply isort -- Add screenrecord unittest (#426) [Mori Atsushi] - - * Fix wrong docstring - - * Add screen_record unittest - - * Rename class names - - * Move test files - - * Fix docstring -- Add videoFilters option documentation (#419) [Mykola Mokhnach] -- Add remote_fs unittest (#410) [Mori Atsushi] - - * Add test_push_file unittest - - * 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 - - And update docstring - - * Update docstring - - * Update docstring - - * Fix import error - - * fix - - * fix import order - - * tweak -- Update changelog for 0.46. [Kazuaki Matsuo] - - -v0.46 (2019-06-27) ------------------- -- Bump 0.46. [Kazuaki Matsuo] -- Bug fix joining path in _get_main_script (#408) [Nicholas Frederick] -- Update changelog for 0.45. [Kazuaki Matsuo] - - -v0.45 (2019-06-26) ------------------- - -New -~~~ -- Add execute driver (#406) [Kazuaki Matsuo] - - * add execute driver - - * append docstring -- Add how to solve pipenv error to readme (#403) [Mori Atsushi] - - * Add how to solve pipenv error to readme - - * review comments - - * tweak - - * review comments -- Add autocompletion for pycharm (#404) [Mori Atsushi] - - * Add autocompletion for pycharm - - * Removed flaky tests from running -- 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 - - * Add missing param - - * Add run_test template - - * Fixed: test running failed - - * Fixed - - * Fixed - - * fixed - - * Add run_android_test - - * Changed emulator to Nexus6 - - * Run all android tests - - * fixed - - * Resolve python-dateutil dependency - - * Run on 3 workers - - * Add chromedriver installation - - * Skip failed test cases on ci - - * fixed - - * Extend adbExecTimeout - - * Add script source to comment - - * Run 5 workers for android - - * Use Node11 - - * Extend wait time - - * Reduced running android functional tests - - * Revert some changes -- Use the same format for docstring (#395) [Mori Atsushi] - - * Update docstring - - * Update docstring - - * Update docstring - - * tweak - - * tweak - - * tweak - - * tweak - - * tweak - - * Update docstring - - * Update docstring - - * Update docstring - - * Update docstring - - * tweak - - * Update -- Publish functional test report (#394) [Mori Atsushi] - - * Move functional tests to template - - * Add publish_test_result - - * Fix typo -- Divide functional appium tests into each module(iOS) (#391) [Mori - Atsushi] - - * Divide ios appium_tests to each module - - * Fix test file name - - * Add CI status badge -- Run iOS functional tests on azure pipelines (#390) [Mori Atsushi] - - * Set up CI with Azure Pipelines - - * review comments - - * update README -- Update changelog for 0.44. [Kazuaki Matsuo] - - -v0.44 (2019-05-24) ------------------- - -Fix -~~~ -- Installed selenium4 when 'setup.py install' (#389) [Mori Atsushi] - - * Fix: installed selenium4 when setup.py install - - * Keep existing comparison operator -- Fix ios functional tests failed (#385) [Mori Atsushi] - - * Fix safari test(iOS) - - * Fix: find_by_ios_predicate - - * Delete find_by_uiautomation_tests - - since uiautomation is deprecated - - * Move non test files - - * Replace test app with the latest - - * 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 - - * Add set_network_speed unittest - - * Add api doc - - * revert unexpected change - - * revert change -- Update changelog for 0.43. [Kazuaki Matsuo] - - -v0.43 (2019-05-18) ------------------- - -New -~~~ -- Add assertions for w3c (#384) [Kazuaki Matsuo] -- Add isort to pre-commit (#379) [Mori Atsushi] - - * Add isort to pre-commit - - * Add isort.conf - - * Applied isort for test/unit - - * Add current dir to isrot arg - - * Add check to ci.sh - - * Use exit code for condition check in ci.sh - -Fix -~~~ -- 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 - - * Replace double quote with single quote under android dir - - * Remove creating tmp file - - * tweak -- Fix functional tests failed (android, ime/multi_action) (#372) [Mori - Atsushi] - - * Fix test failed: ime_tests, multi_action_tests - - * revert change and add impl for python3 - - * Remove py3 dependency - - * Change deepcopy to copy - - * Update ime_tests -- Fix functional tests failed (android, touch_action) (#374) [Mori - Atsushi] - - * Fix: test_drag_and_drop - - * Fix: test_long_press - - * Fix: long_press_x_y, swipe - - * Fix: press_and_wait - - * Fix: driver_drag_and_drop - - * Tweak - - * 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 - - * Move android commands to android package - - * Update setup.py to include added packages - - * Changed find_packages to whitelist style -- Update changelog for 0.42. [Kazuaki Matsuo] - - -v0.42 (2019-05-10) ------------------- - -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] - - * Fix test failed: test_send_keys, test_screen_record - - * Fix test failed: test_update_settings - - * Fix test failed: test_start_activity_other_app - - * Move and rename helper package - - * 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] - - * Support get_performance_data, get_performance_data_types - - * Add api doc - - * Add performance unittest - - * Tweak - - * Update api doc -- Support set_gsm_voice (#367) [Mori Atsushi] - - * Support set_gsm_voice - - * Add set_gsm_voice unittest - - * Fix typo -- Support get_system_bars (#363) [Mori Atsushi] - - * Support get_system_bars - - * Add api doc - - * Add get_system_bars unittest - - * Remove FIXME -- Support make_gsm_call (#360) [Mori Atsushi] - - * Move const to gsm_signal_strength - - * Support make_gsm_call - - * Add make_gsm_call unittest - - * Move const to gsm class - - * Move get_dict_const to common.helper - - * Rename func - - * Use OrderedDict to keep defined order -- Support set_gsm_signal (#357) [Mori Atsushi] - - * Support set_gsm_signal - - * Fix: NONE_OR_UNKNOWN doesn't work - - * Add set_gsm_signal unittest - - * Use int for signal strength const - - * Raise exception when signal strength is out of range - - * Fix: wrong class name - - * Removed args validation - - Since arg validation already done by server side - - * Show warning log when arg is out of range - - * Some changes for less maintenance -- Mobile:pinchOpen and mobile:pinchClose no longer implemented in appium - drivers (#358) [Jonah] -- Remove unnecessary codes. [Atsushi Mori] -- Replace 'on' with AC_ON. [Atsushi Mori] -- Update api doc. [Atsushi Mori] -- Define AC_OFF, AC_ON as const. [Atsushi Mori] -- Skip pylint warnings. [Atsushi Mori] -- Update api doc. [Atsushi Mori] -- Support set_power_ac. [Atsushi Mori] -- Support set_power_capacity. [Atsushi Mori] -- Update changelog for 0.41. [Kazuaki Matsuo] -- Bump 0.41. [Kazuaki Matsuo] - - -v0.41 (2019-04-23) ------------------- - -New -~~~ -- Add send sms support (#351) [Mori Atsushi] - - * Support sendSms function - - * Added api doc - - * Add sms unittest - - * Revert unexpected changes - - * Update api doc -- 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) ------------------- - -Fix -~~~ -- Fix RuntimeError: maximum recursion depth exceeded in cmp happened - (#343) [Kazuaki Matsuo] - - * fix maximum recursion depth exceeded in sub classes - - * add docstring - - * add comparison of a number of commands - - * 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] - - * add direct connect feature - - * rmeove todo - - * update readme, extract _update_command_executor - - * add logger - - * make log level info - - * show warning if no directConnectXxxxx in dict - - * tweak error message - - * tweak message format -- Add datamatcher (#335) [Kazuaki Matsuo] - - * add datamatcher - - * add zero case - - * defines search context for driver and element - -Other -~~~~~ -- Update changelog for 0.38. [Kazuaki Matsuo] -- Bump 0.38. [Kazuaki Matsuo] - - -v0.38 (2019-02-11) ------------------- -- Bump 0.38. [Kazuaki Matsuo] -- Remove io.open from getting version code (#334) [Kazuaki Matsuo] - - * remove io.open - - * remove appium module from release script - - -v0.37 (2019-02-10) ------------------- - -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 - (#324) [Mykola Mokhnach] -- Add pressure option (#322) [Kazuaki Matsuo] - - * add pressure option - - * add a test, tweak comment and the method - - * 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] - - -v0.36 (2019-01-18) ------------------- -- Bump 0.36. [Kazuaki MATSUO] -- Import keyboard, add tests (#319) [Kazuaki Matsuo] -- Update changelog for 0.35. [Kazuaki MATSUO] - - -v0.35 (2019-01-17) ------------------- - -New -~~~ -- Add location unittest (#317) [Mori Atsushi] - - * Add test_location - - * Add test_set_location - - * Add test_toggle_location_services -- Add settings unittest (#315) [Mori Atsushi] - - * Add settings unittest - - * Remove unused import -- Added format to device_time as argument (#312) [Mori Atsushi] -- Add devicetime unittest (#309) [Mori Atsushi] - - * Add device time test - - * Removed unnecessary check from device time test - - * Changed assertion for device time test - - Along to review comments - - * Changed quote for string from double to single -- Add activities unittest (#310) [Tadashi Nemoto] - - * Add test_start_activity - - * Add current_activity and wait_activity - - * Fix pytest 4.0.2 - - * Add test_start_activity_with_opts - - * Added options -- Add network unittest (#308) [Mori Atsushi] - - * Add network connection test - - * Added set network connection test - - * Add toggle wifi test - - * Removed unnecessary codes from toggle wifi test - - * Changed assertion for set network connection test -- Add touch action unittest (#306) [Tadashi Nemoto] - - * Add press test - - * Add test_long_press - - * Add test_wait - - * Add remaining tests - - * Add tap - - * 10 -> 9 - - * Modify based on comment -- 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] -- Update changelog for 0.34. [Kazuaki MATSUO] - - -v0.34 (2018-12-18) ------------------- - -Fix -~~~ -- Fix missing package, missing commands and a test (#296) [Kazuaki - Matsuo] - - * add extensions into package - - * 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) ------------------- - -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) ------------------- - -New -~~~ -- Add unit tests for isLocked Library (#288) [Venkatesh Singh] - - * Add unit tests for isLocked Lib - - * moved isLocked library tests in lock.py -- 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] - - * extract bytes and add a test for set clipboard -- Introduce httpretty for unittest to mock Appium server (#281) [Kazuaki - Matsuo] - - * add httpretty - - * add clipboard tests as an example - - * add test for forceMjsonwp -- Update setup elements (#280) [Kazuaki Matsuo] - - * update setup elements - - * remove docgen since we can use markdown format in pypi -- Release automation (#276) [Kazuaki Matsuo] -- Updated requirements.txt file with version (#275) - [RajeshkumarAyyadurai] - - * updated required dependecies with version number as a best practice - - * updated required dependencies with version - - * updated pylint library version to support for python 2.7 -- Append document for recording screen (#271) [Kazuaki Matsuo] - - * append document for recording screen - - * add since appium 1.10.0 - - * remove Only works for real devices since the feature can work on both -- Update changelog for 0.31. [Kazuaki MATSUO] - - -v0.31 (2018-11-21) ------------------- -- V0.31. [Kazuaki MATSUO] -- Driver.push_file(destination_path, source_path) feature (#270) [Javon - Davis] - - * used base64 library for conversion - - * remove unnecessary library use - - * changed text in test file - - * * Using context when reading file - * changed docstring format - * Catch error thrown if file not present and present user with a better message - - * fixed incorrect file path in test - - * removed change in pul_file that broke backwards compat and updated docstring description for `destination_path` - - -v0.30 (2018-10-31) ------------------- - -New -~~~ -- Add release section in readme. [Kazuaki MATSUO] - -Fix -~~~ -- Fix python3 set_clipboard error (#267) [Kazuaki Matsuo] - - * fix python3 set_clipboard error - - * apply formatter - -Other -~~~~~ -- V0.30. [Kazuaki MATSUO] - - -v0.29 (2018-10-30) ------------------- - -New -~~~ -- Add an endpoint for pressing buttons (#262) [Alex] -- Add custom locator strategy (#260) [Jonathan Lipps] -- Add a duration for scroll for ios (#256) [Kazuaki Matsuo] - - * add a duration for scroll for ios - - * tweak default duration - - * apply autoformat - - * set 600 duration by default if it's w3c spec - - * skip wait if duration is none - - * add comment -- Add finger print (#252) [Kazuaki Matsuo] - - * add fingre print - - * apply auto format -- Add find_elements w3c for webelement (#251) [Kazuaki Matsuo] - - * add find_elements w3c for webelement - - * add tests for child elements - - * add todo for future work -- Add a github issue template (#250) [Kazuaki Matsuo] -- Add xdist port handling (#248) [Kazuaki Matsuo] - - * add handling port number to run ios tests in parallel - - * define PytestXdistWorker - - * use gw0 if the number of worker is over the count of workers -- Add autopep8 (#243) [Kazuaki Matsuo] - - * apply autopep8 - - * add development section as the first draft - - * relax max-line-length - - * add global-config -- Add toggle wifi command (#241) [joshuazhusince1986] - - * add toggle_wifi command - - * update comment to indicate toggle_wifi is only for Android -- Add selenium into ci-requirements (#240) [Kazuaki Matsuo] - - fix pylint - - add --py3k -- Add travis to run pylint and unit tests (#239) [Kazuaki Matsuo] - - * add pylint - - * add rcfile - - * tweak pylint - - * fix lint - - * add running pytest - - * tweak indentations -- Add tag view for android (#238) [Kazuaki Matsuo] - - * add tag view for android - - * fix typo... and tweak names of arguments - - * 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) ------------------- - -Fix -~~~ -- Fix base64 encoded string (#231) [Kazuaki Matsuo] - -Other -~~~~~ -- V0.28. [Isaac Murchie] - - -v0.27 (2018-07-10) ------------------- - -New -~~~ -- Add support for is keyboard shown command. [Jonathan Lipps] -- Add find by image commands and tests (#224) [Jonathan Lipps] - - * add find by image commands and tests - - * remove and ignore pytest cache files - - * address review comments - - * fix docstrings -- Add flags argument to press_keycode (#222) [Mykola Mokhnach] - - * Add flags argument to press_keycode - - * Add flags to long press as well -- Add an endpoint for getting battery info (#217) [Mykola Mokhnach] -- Add wrappers for OpenCV-based image comparison (#216) [Mykola - Mokhnach] - - * Add wrappers for OpenCV-based image comparison - - * Tune some docs -- Add clipboard handlers (#209) [Mykola Mokhnach] - - * Add clipboard handlers - - * Fix documentation - - * fix options notation -- Add applications management endpoint handlers (#204) [Mykola Mokhnach] -- Add methods for start/stop screen record API endpoints (#201) [Mykola - Mokhnach] - - * Add methods for start/stop screen record API endpoints - - * Fix typo - - * Add a separate test for Android and get rid of redundant stuff - - * Tune documentation - - * Add videoSize arg description - - * Fix arg name -- Add appium prefix in create session and fix find_elements for W3C - (#196) [Kazuaki Matsuo] - - * add appium prefix in create session - - * fix find_elements by w3c for Appium - - * introduce forceMjsonwp - - * refine a bit - - * fix some tests - - * 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) ------------------- -- V0.26. [Isaac Murchie] - - -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] -- Correct a wording. [Kazuaki MATSUO] -- Create README.md. [Kazuaki Matsuo] -- Append class chain related descriptions. [Kazuaki MATSUO] -- Update README to include instructions for using iOS predicates. [Emil - Petersen] -- Update docs for UIAutomation selector to include version requirement. - [Emil Petersen] - - -v0.24 (2016-12-20) ------------------- - -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) ------------------- - -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) ------------------- -- V0.22. [Isaac Murchie] -- Use id instead of elementId. [Isaac Murchie] - - -v0.21 (2016-01-20) ------------------- - -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 - - -v0.20 (2015-10-12) ------------------- -- V0.20. [Isaac Murchie] -- Revert actions change. [Isaac Murchie] - - -v0.19 (2015-10-09) ------------------- -- V0.19. [Isaac Murchie] -- Change 'actions' to 'gestures' in single action. [Isaac Murchie] - - -v0.18 (2015-10-07) ------------------- - -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] -- Use WebDriverWait to implement wait_activity. [zhaoqifa] -- Make tap duration be handled as ms, not s. [Isaac Murchie] -- Bump version. [Isaac Murchie] -- Bump version. [Isaac Murchie] -- Move monkeypatched set_value into WebElement. [Isaac Murchie] - - -v0.14 (2015-03-06) ------------------- - -Fix -~~~ -- Fix issue with single tap. [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) ------------------- - -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. -- Update webdriver.py. [urtow] - - -v0.11 (2014-11-14) ------------------- - -New -~~~ -- Add toggle_location_services. [Isaac Murchie] - -Other -~~~~~ -- Bump version. [Isaac Murchie] -- Update webdriver.py. [urtow] - - Start_y - y-coordinate for start, not end - - -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] -- Make long_press works with 'duration' parameter. [ianxiaohanxu] - - Add a new parameter 'duration = None' to _get_opts -- Typo fix! [Cass] -- Update README.md. [Johan Lundstroem] - - Verison -> Version -- Revert "Fix for #23: Re-add 'keyevent' temporarily." [Payman Delshad] - - This reverts commit ccbcaf809704bf1ac752d1b4446d1175b7434c36. - - -v0.9 (2014-07-07) ------------------ - -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] -- 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] -- Renamed keyevent to press_keycode and added long_press_keycode. - [Payman Delshad] -- Bump version. [Isaac Murchie] -- Numerous fixes. [Alexander Bayandin] - - 1. fix comparation with None - 2. remove unused imports - 3. fix imports order (according to pep8) - 4. style fixes (according to pep8) - 5. another minor fixes -- Update zoom/pinch signatures. [Isaac Murchie] -- Remove tag name, use class. [Isaac Murchie] -- Don't send multitouch for single finger tap. [Isaac Murchie] -- Miscellaneous fixes. [Isaac Murchie] -- Update desired caps. [Isaac Murchie] -- Basic module structure. [Isaac Murchie] - - From 1a811534c92427885dfc1954deef4c2976d1c5b3 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 13 Oct 2023 09:58:26 -0700 Subject: [PATCH 233/548] docs: update changelog --- CHANGELOG.rst | 4475 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4475 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e69de29bb..4a3bc9453 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -0,0 +1,4475 @@ +Changelog +========= + + +(unreleased) +------------ +- Update changelog for 3.1.0. [Kazuaki Matsuo] + + +v3.1.0 (2023-10-13) +------------------- + +New +~~~ +- Feat: Add missing platformVersion and browserName options (#925) + [Mykola Mokhnach] + +Test +~~~~ +- Ci: Use appium from the release branch. [Mykola Mokhnach] + +Other +~~~~~ +- Bump 3.1.0. [Kazuaki Matsuo] +- Chore(deps): update selenium requirement from ~=4.13 to ~=4.14 (#923) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.13.0...selenium-4.14.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pylint requirement from ~=2.17.5 to ~=3.0.1 + (#922) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. + - [Release notes](https://github.com/pylint-dev/pylint/releases) + - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.5...v3.0.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.12 to ~=4.13 (#915) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.12.0...selenium-4.13.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Docs: update README.md for v3 (#912) [Kazuaki Matsuo] + + * docs: update README.md + + * Update README.md +- Update changelog for 3.0.0. [Kazuaki Matsuo] + + +v3.0.0 (2023-09-08) +------------------- + +New +~~~ +- Feat!: Update selenium dependency to 4.12 (#908) [Mykola Mokhnach] + + BREAKING CHANGE: The minimum supported Python version set to 3.8 + BREAKING CHANGE: The minimum supported selenium version set to 4.12 + +Fix +~~~ +- Handle the situation where payload is already a dictionary (#892) + [Mykola Mokhnach] +- Add missing dependencies for types-python-dateutil (#891) [Dor + Blayzer] + + * fix: Add missing dependencies for types-python-dateutil + + * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py + + * Revert "test: replace usage of selendroid app from 'test_install_app' in applications_tests.py" + + This reverts commit dcdf08b67d24257700923c89ae2643f26af2892f. + +Test +~~~~ +- Test: selendroid cleanup (#895) [Dor Blayzer] + + * test: replace usage of selendroid app from 'test_install_app' in applications_tests.py + + * test: remove unused import pytest from applications_tests.py + + * test: fix broken TestContextSwitching by replacing selendroid with ApiDemos + + * test: remove selendroid-test-app.apk from apps folder +- Ci: add pylint_quotes for pylint to use single quote as primary method + (#886) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 3.0.0. [Kazuaki Matsuo] +- Chore(deps-dev): update tox requirement from ~=4.8 to ~=4.11 (#906) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.8.0...4.11.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Refactor!: remove several previously deprecated APIs (#909) [Mykola + Mokhnach] + + BREAKING CHANGE: Removed obsolete all_sessions and session properties + BREAKING CHANGE: Removed the obsolete start_activity method + BREAKING CHANGE: Removed the obsolete end_test_coverage method + BREAKING CHANGE: Removed the following obsolete arguments from the driver constructor: desired_capabilities, browser_profile, proxy + BREAKING CHANGE: Removed obsolete set_value and set_text methods + BREAKING CHANGE: Removed the obsolete MobileBy class + BREAKING CHANGE: Removed obsolete application management methods: launch_app, close_app, reset + BREAKING CHANGE: Removed obsolete IME methods: available_ime_engines, is_ime_active, activate_ime_engine, deactivate_ime_engine, active_ime_engine +- Chore(deps-dev): update tox requirement from ~=4.6 to ~=4.8 (#902) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.8.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.10 to ~=4.11 (#899) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/commits) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pylint requirement from ~=2.17.3 to ~=2.17.5 + (#897) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. + - [Release notes](https://github.com/pylint-dev/pylint/releases) + - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.3...v2.17.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Docs: Update README.md (#898) [Dor Blayzer] + + Some typos and grammar fixes +- Chore(deps-dev): update mock requirement from ~=5.0 to ~=5.1 (#893) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mock](https://github.com/testing-cabal/mock) to permit the latest version. + - [Changelog](https://github.com/testing-cabal/mock/blob/master/CHANGELOG.rst) + - [Commits](https://github.com/testing-cabal/mock/compare/5.0.0...5.1.0) + + --- + updated-dependencies: + - dependency-name: mock + dependency-type: direct:development + ... +- Chore: run pre-commit autoupdate (#890) [Kazuaki Matsuo] +- Chore: update isort revision to 5.12.0 (#889) [Dor Blayzer] +- Chore(deps-dev): update pytest requirement from ~=7.2 to ~=7.4 (#884) + [dependabot[bot]] +- Chore(deps-dev): update typing-extensions requirement (#885) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.6.0...4.7.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Docs: update changelogs and version. [Kazuaki Matsuo] + + +v2.11.1 (2023-06-13) +-------------------- +- Revert "chore: remove duplicated clean command" (#881) [Kazuaki + Matsuo] + + * Revert "chore: remove duplicated clean command (#809)" + + This reverts commit 2f45ef935c12dec2ab8de044ce6a1c1e0b9aa46f. + + * chore: left a comment + + * trim spaces +- Update changelog. [Kazuaki Matsuo] + + +v2.11.0 (2023-06-09) +-------------------- + +New +~~~ +- Feat: make the UA format with same as other clients (#793) [Kazuaki + Matsuo] + + * chore: set version with / + + * chore: update comment + + * update test + + * update tests + +Other +~~~~~ +- Update changelog for 2.10.2. [Kazuaki Matsuo] + + +v2.10.2 (2023-06-08) +-------------------- + +Fix +~~~ +- Update the constructor for compatibility with python client 4.10 + (#879) [Mykola Mokhnach] + +Test +~~~~ +- Ci: add py11 for the unit test (#875) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.10.2. [Kazuaki Matsuo] +- Chore: remove duplicated clean command (#809) [Kazuaki Matsuo] + + * remove clear + + * remove clear more +- Chore(deps-dev): update tox requirement from ~=4.5 to ~=4.6 (#877) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.5.0...4.6.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Docs: address version management recommendation in the readme (#874) + [Kazuaki Matsuo] + + * chore: set the max selenium deps version + + * ci: add python 11 + + * Update unit-test.yml + + * add note in the readme + + * Update README.md + + * docs: merge the matrix pr into README.md +- Docs: Improve usage examples (#873) [Mykola Mokhnach] +- Chore(deps-dev): update pytest-cov requirement from ~=4.0 to ~=4.1 + (#872) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. + - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:development + ... +- Chore(deps-dev): update typing-extensions requirement (#871) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.5.0...4.6.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore: nump the version. [Kazuaki Matsuo] +- Update changelog for 2.10.1. [Kazuaki Matsuo] + + +v2.10.1 (2023-05-20) +-------------------- + +Fix +~~~ +- W3C errors to exception classes mapping (#869) [Mykola Mokhnach] + + * fix: W3C errors to exception classes mapping + + * Imports + + * Tune + +Other +~~~~~ +- Chrom: bump the version. [Kazuaki Matsuo] +- Update changelog for 2.10.0. [Kazuaki Matsuo] + + +v2.10.0 (2023-05-11) +-------------------- + +Fix +~~~ +- Update connection manager creation (#864) [Mykola Mokhnach] + +Other +~~~~~ +- Refactor: Move driver-specific commands to use extensions (part2) + (#859) [Mykola Mokhnach] +- Chore(deps): update selenium requirement from ~=4.7 to ~=4.9 (#852) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.7.0...selenium-4.9.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Refactor: Move driver-specific commands to use extensions (part1) + (#856) [Mykola Mokhnach] +- Chore(deps-dev): update pylint requirement from ~=2.17.2 to ~=2.17.3 + (#853) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.2...v2.17.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.4 to ~=4.5 (#854) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.4.0...4.5.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=1.1 to ~=1.2 (#848) + [dependabot[bot]] +- Chore(deps-dev): update pylint requirement from ~=2.17.1 to ~=2.17.2 + (#847) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.1...v2.17.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: bump and correct version. [Kazuaki Matsuo] +- Update changelog for 2.9.0. [Kazuaki Matsuo] + + +v2.9.0 (2023-04-02) +------------------- + +New +~~~ +- Feat: respect the given executor (#844) [Kazuaki Matsuo] + + * feat: can provide a custom connection + + * add tests + + * tweak tests + + * lint + + * tweak + + * add comment + + * fix lint + + * tweak + + * add test + + * tweak review + +Fix +~~~ +- Set_value and set_text sent incorrect data (#831) [eyJhb] + +Other +~~~~~ +- Update changelog for 2.8.0. [Kazuaki Matsuo] +- Chore(deps-dev): update pylint requirement from ~=2.17.0 to ~=2.17.1 + (#843) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.17.0...v2.17.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.3 to ~=2.17.0 + (#838) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.3...v2.17.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=1.0 to ~=1.1 (#836) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.1.1) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.2 to ~=2.16.3 + (#834) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.2...v2.16.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update typing-extensions requirement (#830) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.4.0...4.5.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.1 to ~=2.16.2 + (#829) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.1...v2.16.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=0.991 to ~=1.0 (#828) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.16.0 to ~=2.16.1 + (#827) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.0...v2.16.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.10 to ~=2.16.0 + (#826) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.3 to ~=4.4 (#823) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.3.0...4.4.2) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Update changelog for 2.8.1. [Kazuaki Matsuo] + + +v2.8.1 (2023-01-20) +------------------- + +New +~~~ +- Feat: add status tentatively (#820) [Kazuaki Matsuo] + + * feat: add status tentatively + + * update test + + * fix docstring + + * fix typo + + * fix lint + + +v2.8.0 (2023-01-20) +------------------- + +New +~~~ +- Feat: add status tentatively. [Kazuaki Matsuo] + +Fix +~~~ +- Fix lint. [Kazuaki Matsuo] +- Fix typo. [Kazuaki Matsuo] +- Fix docstring. [Kazuaki Matsuo] + +Other +~~~~~ +- Update test. [Kazuaki Matsuo] +- Chore(deps-dev): update tox requirement from ~=4.2 to ~=4.3 (#817) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.2.0...4.3.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps): update sphinx requirement from <6.0,>=4.0 to >=4.0,<7.0 + (#814) [dependabot[bot]] +- Chore(deps-dev): update pylint requirement from ~=2.15.9 to ~=2.15.10 + (#816) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.9...v2.15.10) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.1 to ~=4.2 (#815) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.1.0...4.2.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.0 to ~=4.1 (#813) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.0.0...4.1.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update mock requirement from ~=4.0 to ~=5.0 (#812) + [dependabot[bot], dependabot[bot]] + + 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/4.0.0...5.0.0) + + --- + updated-dependencies: + - dependency-name: mock + dependency-type: direct:development + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.20 to ~=2.21 + (#811) [dependabot[bot], dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.20.0...v2.21.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.8 to ~=2.15.9 + (#810) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.8...v2.15.9) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update isort requirement from ~=5.10 to ~=5.11 (#808) + [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.10.0...5.11.1) + + --- + updated-dependencies: + - dependency-name: isort + dependency-type: direct:development + ... +- Chore(deps-dev): update black requirement from ~=22.10.0 to ~=22.12.0 + (#807) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.10.0...22.12.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 (#806) + [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + * chore(deps-dev): update tox requirement from ~=3.27 to ~=4.0 + + 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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/3.27.0...4.0.2) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.7 to ~=2.15.8 + (#804) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.5 to ~=4.7 (#801) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.5.0...selenium-4.7.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.6 to ~=2.15.7 + (#800) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.6...v2.15.7) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.5 to ~=2.15.6 + (#799) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.5...v2.15.6) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: update docstring in touch_action.py (#797) [Kazuaki Matsuo, + wojciodataist] + + add detailed information to long_press duration param +- Chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 + (#798) [Kazuaki Matsuo, dependabot[bot], dependabot[bot]] + + * chore(deps-dev): update mypy requirement from ~=0.982 to ~=0.991 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.982...v0.991) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=3.26 to ~=3.27 (#792) + [dependabot[bot], dependabot[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.26.0...3.27.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update pytest requirement from ~=7.1 to ~=7.2 (#791) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.2.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.4 to ~=2.15.5 + (#790) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.4...v2.15.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.3 to ~=2.15.4 + (#788) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.3...v2.15.4) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore: update precommit (#787) [Kazuaki Matsuo] +- Update changelog for 2.7.1. [Kazuaki Matsuo] +- Bump 2.7.1. [Kazuaki Matsuo] + + +v2.7.1 (2022-10-11) +------------------- + +Test +~~~~ +- Ci: run unit tests on actions (#773) [Kazuaki Matsuo] + + * ci: run unit tests on actions + + * ci: remove unit test section + + * ci: comment out win for now + + * ci: tweak trigger + +Other +~~~~~ +- Refactor: Make service startup failures more helpful (#786) [Mykola + Mokhnach] +- Chore(deps-dev): update typing-extensions requirement (#783) [Kazuaki + Matsuo, dependabot[bot], dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.3.0...4.4.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:development + ... +- Chore(deps-dev): update black requirement from ~=22.8.0 to ~=22.10.0 + (#784) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [black](https://github.com/psf/black) to permit the latest version. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.8.0...22.10.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:development + ... +- Chore(deps-dev): update mypy requirement from ~=0.981 to ~=0.982 + (#782) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.981...v0.982) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pytest-cov requirement from ~=3.0 to ~=4.0 + (#779) [dependabot[bot], dependabot[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/v3.0.0...v4.0.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.4 to ~=4.5 (#780) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.4.0...selenium-4.5.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update mypy requirement from ~=0.971 to ~=0.981 + (#777) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.971...v0.981) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=2.15.2 to ~=2.15.3 + (#774) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Bump 2.7.0. [Kazuaki Matsuo] +- Update changelog for 2.6.3. [Kazuaki Matsuo] + + +v2.7.0 (2022-09-22) +------------------- + +New +~~~ +- Feat: Add appArguments option to WindowsOptions (#768) [Mykola + Mokhnach] + +Fix +~~~ +- Move dev-only dependencies to [dev-packages] section (#772) [Mykola + Mokhnach] + +Test +~~~~ +- Ci: Fix runner name. [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update pylint requirement from ~=2.15.2 to ~=2.15.3 + (#770) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.2. [Kazuaki Matsuo] + + +v2.6.2 (2022-09-16) +------------------- + +Fix +~~~ +- Use total_seconds property of timedelta (#767) [Mykola Mokhnach] + +Test +~~~~ +- Ci: Update Conventional Commits config preset. [Mykola Mokhnach] +- Ci: Add Conventional commit format validation (#764) [Mykola Mokhnach] + + * ci: Add Conventional commit format validation + + * Rename + +Other +~~~~~ +- Chore(deps): update tox requirement from ~=3.25 to ~=3.26 (#766) + [dependabot[bot], dependabot[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.25.0...3.26.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.15.0 to ~=2.15.2 + (#765) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.15.0...v2.15.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.9 to ~=2.12 (#762) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/v2.9.0...v2.12.5) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Chore(deps): bump black from 22.6.0 to 22.8.0 (#763) [dependabot[bot], + dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Chore(deps): update pylint requirement from ~=2.14.5 to ~=2.15.0 + (#761) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.5...v2.15.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.1. [Kazuaki Matsuo] + + +v2.6.1 (2022-08-11) +------------------- + +Fix +~~~ +- Fix options in mac2 (#759) [Kazuaki Matsuo] +- Backwards compatible behaviour of swipe and scroll in action_helpers + (#744) [jatalahd] + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 + + * Backwards compatible behaviour of swipe and scroll in action_helpers + + - Fixed handling the duration argument in swipe() and scroll() helpers + - Functionality is now the same as in older versions using TouchActions + + Fixes #743 +- Move py.typed to the hierarchy root (#751) [Mykola Mokhnach] +- Typos/copypaste in various options (#750) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update selenium requirement from ~=4.3 to ~=4.4 (#757) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.3.0...selenium-4.4.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.961 to ~=0.971 (#749) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.961...v0.971) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.4 to ~=2.14.5 + (#747) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.4...v2.14.5) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.19 to ~=2.20 + (#746) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.19.0...v2.20.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update typing-extensions requirement from ~=4.2 to ~=4.3 + (#745) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing_extensions) to permit the latest version. + - [Release notes](https://github.com/python/typing_extensions/releases) + - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) + - [Commits](https://github.com/python/typing_extensions/compare/4.2.0...4.3.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.3 to ~=2.14.4 + (#742) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.3...v2.14.4) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Docs: Update changelog for 2.6.0. [Kazuaki Matsuo] + + +v2.6.0 (2022-06-28) +------------------- + +New +~~~ +- Feat: Add Android drivers options (#740) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): bump black from 22.3.0 to 22.6.0 (#741) [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.3.0...22.6.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Chore: Improve autocompletion for methods returning self instance + (#739) [Mykola Mokhnach] +- Refactor: Remove previously deprecated methods and mark + reset/close/launch APIs as deprecated (#738) [Mykola Mokhnach] +- Docs: Update changelog for 2.5.0. [Kazuaki Matsuo] + + +v2.5.0 (2022-06-25) +------------------- + +New +~~~ +- Feat: Add xcuitest driver options (#737) [Mykola Mokhnach] +- Feat: Add Gecko driver options (#735) [Mykola Mokhnach] +- Feat: Add Windows driver options (#732) [Mykola Mokhnach] +- Feat: Add Safari driver options (#731) [Mykola Mokhnach] +- Feat: Add Mac2Driver options (#730) [Mykola Mokhnach] + +Other +~~~~~ +- Chore(deps): update selenium requirement from ~=4.2 to ~=4.3 (#736) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.2.0...selenium-4.3.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.14.2 to ~=2.14.3 + (#733) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.2...v2.14.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Refactor: Make system_port and system_host options common (#734) + [Mykola Mokhnach] +- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 + (#725) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore: bump version to 2.4.0. [Kazuaki Matsuo] +- Chore(deps): update pylint requirement from ~=2.14.1 to ~=2.14.2 + (#725) [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.14.1...v2.14.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... + + +v2.4.0 (2022-06-17) +------------------- + +New +~~~ +- Feat: Add common options (#728) [Mykola Mokhnach] + +Other +~~~~~ +- Chore: Add better error handling for session creation responses (#727) + [Mykola Mokhnach] +- Docs: Update changelog for 2.3.0. [Kazuaki Matsuo] +- Bump 2.3.0. [Kazuaki Matsuo] +- Chore: Update comments to locator patches (#724) [VladimirPodolyan] + + * Update webelement.py + + * update comment section + + * CR fixes + + +v2.3.0 (2022-06-13) +------------------- + +New +~~~ +- Feat: Add base options for all supported automation names (#721) + [Mykola Mokhnach] +- Feat: Add support for w3c options (#720) [Mykola Mokhnach] + +Test +~~~~ +- Test: Use Appium2 to run functional tests (#723) [Mykola Mokhnach] + +Other +~~~~~ +- Docs: Update README with the new options format (#722) [Mykola + Mokhnach] +- Chore(deps): update mypy requirement from ~=0.960 to ~=0.961 (#718) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.960...v0.961) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore: Disable pylint checks fail CI (#719) [Mykola Mokhnach] +- Chore(deps): update selenium requirement from ~=4.1 to ~=4.2 (#715) + [dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.1.0...selenium-4.2.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps): update sphinx requirement from <5.0,>=4.0 to >=4.0,<6.0 + (#716) [dependabot[bot]] + + Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. + - [Release notes](https://github.com/sphinx-doc/sphinx/releases) + - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) + - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.0...v5.0.0) + + --- + updated-dependencies: + - dependency-name: sphinx + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.950 to ~=0.960 (#714) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.950...v0.960) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.18 to ~=2.19 + (#713) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.18.0...v2.19.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update mypy requirement from ~=0.942 to ~=0.950 (#712) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.942...v0.950) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update typing-extensions requirement from ~=4.1 to ~=4.2 + (#711) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. + - [Release notes](https://github.com/python/typing/releases) + - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) + - [Commits](https://github.com/python/typing/compare/4.1.0...4.2.0) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.24 to ~=3.25 (#709) + [dependabot[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.24.0...3.25.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.17 to ~=2.18 + (#708) [dependabot[bot]] + + 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/main/CHANGELOG.md) + - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Update changelog for 2.2.0. [Kazuaki Matsuo] + + +v2.2.0 (2022-03-30) +------------------- + +New +~~~ +- Feat: add non-w3c but still need commands (#701) [Kazuaki Matsuo] + + * add non-w3c but still need commands + + * fix id as $ + +Other +~~~~~ +- Bump 2.2.0. [Kazuaki Matsuo] +- Chore(deps): bump black from 22.1.0 to 22.3.0 (#705) [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 22.1.0 to 22.3.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/compare/22.1.0...22.3.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + update-type: version-update:semver-minor + ... +- Revert: pylint (#706) [Kazuaki Matsuo] +- Chore: relax selenium version as same as before. [Kazuaki Matsuo] +- Chore(deps): update mypy requirement from ~=0.941 to ~=0.942 (#703) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.941...v0.942) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.12 to ~=2.13 (#702) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.12.0...v2.13.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update mypy requirement from ~=0.930 to ~=0.941 (#696) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.930...v0.941) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update typing-extensions requirement from ~=4.0 to ~=4.1 + (#684) [dependabot[bot]] + + Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. + - [Release notes](https://github.com/python/typing/releases) + - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) + - [Commits](https://github.com/python/typing/compare/4.0.0...4.1.1) + + --- + updated-dependencies: + - dependency-name: typing-extensions + dependency-type: direct:production + ... +- Chore(deps): update pytest requirement from ~=7.0 to ~=7.1 (#694) + [dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...7.1.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:production + ... +- Docs: update missing changelog. [Kazuaki Matsuo] + + +v2.1.4 (2022-02-28) +------------------- +- Bump 2.1.4. [Kazuaki Matsuo] +- Update changelog for 2.1.3. [Kazuaki Matsuo] + + +v2.1.3 (2022-02-26) +------------------- + +Test +~~~~ +- Test: update tests to use find_element(by...) (#674) [Kazuaki Matsuo] + + * test: update find element/s methods + + * fix arguments + + * fix default value + +Other +~~~~~ +- Bump 2.1.3. [Kazuaki Matsuo] +- Chore: restrict selenium client version (#686) [Kazuaki Matsuo] +- Chore(deps): bump black from 21.12b0 to 22.1.0 (#681) + [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/commits/22.1.0) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + ... +- Chore(deps): update pytest requirement from ~=6.2 to ~=7.0 (#682) + [dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...7.0.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.16 to ~=2.17 + (#678) [dependabot[bot]] + + 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/v2.16.0...v2.17.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Refactor: Update types descriptions for mixin classes (#677) [Mykola + Mokhnach] +- Chore: bump mypy (#675) [Kazuaki Matsuo] +- Update changelog for 2.1.2. [Kazuaki Matsuo] + + +v2.1.2 (2021-12-30) +------------------- + +Fix +~~~ +- Default duration in tap (#673) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 2.1.2. [Kazuaki Matsuo] +- Update changelog for 2.1.1. [Kazuaki Matsuo] + + +v2.1.1 (2021-12-24) +------------------- + +New +~~~ +- Feat: use 'touch' pointer action (#670) [Kazuaki Matsuo] + + * chore: specify touch + + * comment out touch in drag_and_drop + + * fix mypy + + * add desctiption of touch action + +Test +~~~~ +- Ci: remove ==2021.5.29 (#653) [Kazuaki Matsuo] + + * ci: remove ==2021.5.29 + + * bump black + +Other +~~~~~ +- Bump 2.1.1. [Kazuaki Matsuo] +- Chore(deps): bump black from 21.11b1 to 21.12b0 (#664) + [dependabot[bot]] + + Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. + - [Release notes](https://github.com/psf/black/releases) + - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) + - [Commits](https://github.com/psf/black/commits) + + --- + updated-dependencies: + - dependency-name: black + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.15 to ~=2.16 + (#663) [dependabot[bot]] + + 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/v2.15.0...v2.16.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update pylint requirement from ~=2.11 to ~=2.12 (#662) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.11.0...v2.12.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.8 to ~=2.9 (#661) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/v2.8.0...v2.9.0) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Update changelog for 2.1.0. [Kazuaki Matsuo] + + +v2.1.0 (2021-11-27) +------------------- + +New +~~~ +- Feat: add AppiumBy instead of MobileBy (#659) [Kazuaki Matsuo] + + * feat: add AppiumBy instead of MobileBy + + * add class description + + * use deprecated:: + +Other +~~~~~ +- Bump 2.1.0. [Kazuaki Matsuo] +- Chore: add deprecated mark for find_element_by* (#657) [Kazuaki + Matsuo] +- Chore: relax selenium version control (#656) [Kazuaki Matsuo] +- Chore: tweak keyword in metadata. [Kazuaki Matsuo] +- Update changelog for 2.0.0. [Kazuaki Matsuo] + + +v2.0.0 (2021-11-09) +------------------- + +New +~~~ +- Feat: Change base selenium client version to selenium 4 (#636) + [Kazuaki Matsuo] + + - Changed base selenium client version to v4 + - No longer forceMjsonwp works + - Add strict_ssl option in webdriver.Remote + +Test +~~~~ +- Ci: set pipenv==2021.5.29 to prevent dependencies error (#651) + [Kazuaki Matsuo] + + * ci: add --pre + + * specify pipenv as same as the previous ok case + + * set 2021.5.29 in tox as well + +Other +~~~~~ +- Bump 2.0.0. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: add Python 3.9 as metadata. [Kazuaki Matsuo] +- Chore(deps): update isort requirement from ~=5.9 to ~=5.10 (#650) + [dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.9.0...5.10.0) + + --- + updated-dependencies: + - dependency-name: isort + dependency-type: direct:production + ... +- Update changelog for 2.0.0.rc6. [Kazuaki Matsuo] +- Bump 2.0.0.rc6. [Kazuaki Matsuo] +- Docs: update readme. [Kazuaki Matsuo] +- Chore: adding deprecation mark in touch actions and multi touch (#648) + [Kazuaki Matsuo] + + * chore: add deprecation mark in touch actions and multi touch + + * chore: add deprecated mark in MultiAction class + + * docs: update readme +- Chore: deprecate -windows uiautomation (#649) [Kazuaki Matsuo] + + * chore: add Deprecated for -windows uiautomation + + * chore: add logger +- Update changelog for 2.0.0.rc5. [Kazuaki Matsuo] +- Bump 2.0.0.rc5. [Kazuaki Matsuo] +- Chore(deps): update sphinx requirement from <4.0,>=3.0 to >=3.0,<5.0 + (#603) [Kazuaki Matsuo, dependabot[bot]] + + Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. + - [Release notes](https://github.com/sphinx-doc/sphinx/releases) + - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) + - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.0...v4.0.0) +- Update gitchangelog once. [Kazuaki Matsuo] +- Chore(deps): update sphinx-rtd-theme requirement from <1.0 to <2.0 + (#637) [Kazuaki Matsuo, dependabot[bot]] + + Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. + - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) + - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) + - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.1.8...1.0.0) + + --- + updated-dependencies: + - dependency-name: sphinx-rtd-theme + dependency-type: direct:production + ... +- Chore: cleanup no longer needed code in w3c, bump dev Pipfile (#646) + [Kazuaki Matsuo] + + chore: cleanup no longer needed code in w3c, bump dev Pipfile +- Chore(deps): update pylint requirement from ~=2.10 to ~=2.11 (#638) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/v2.10.0...v2.11.1) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update pytest-cov requirement from ~=2.12 to ~=3.0 (#641) + [dependabot[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.12.0...v3.0.0) + + --- + updated-dependencies: + - dependency-name: pytest-cov + dependency-type: direct:production + ... +- Update changelog for 1.3.0. [Kazuaki Matsuo] + + +v1.3.0 (2021-09-27) +------------------- + +New +~~~ +- Feat: do not raise an error in case method is already defined (#632) + [Kazuaki Matsuo] +- Feat: add satellites in set_location (#620) [Kazuaki Matsuo] + + * feat: add satellites in set_location + + * fix review +- Feat: Add command with `setattr` (#615) [Kazuaki Matsuo] + + * chore: add placeholder + + * move to extention way + + * revert pytest + + * add todo + + * call method_name instead of wrapper + + * remove types + + * rename a method + + * add examples + + * add types-python-dateutil as error message + + * add example more + + * tweak naming + + * Explicit Dict + +Other +~~~~~ +- Bump 1.3.0. [Kazuaki Matsuo] +- Chore(deps): update types-python-dateutil requirement (#633) + [dependabot[bot]] + + Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. + - [Release notes](https://github.com/python/typeshed/releases) + - [Commits](https://github.com/python/typeshed/commits) + + --- + updated-dependencies: + - dependency-name: types-python-dateutil + dependency-type: direct:production + ... +- Chore(deps-dev): update pre-commit requirement from ~=2.13 to ~=2.15 + (#634) [dependabot[bot]] + + 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/v2.13.0...v2.15.0) + + --- + updated-dependencies: + - dependency-name: pre-commit + dependency-type: direct:development + ... +- Chore(deps): update mypy requirement from ~=0.812 to ~=0.910 (#616) + [dependabot[bot]] + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) + + --- + updated-dependencies: + - dependency-name: mypy + dependency-type: direct:production + ... +- Chore(deps): update astroid requirement from ~=2.5 to ~=2.7 (#629) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.5...v2.7.2) + + --- + updated-dependencies: + - dependency-name: astroid + dependency-type: direct:production + ... +- Chore(deps): update pylint requirement from ~=2.8 to ~=2.10 (#628) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.0...v2.10.2) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:production + ... +- Chore(deps): update tox requirement from ~=3.23 to ~=3.24 (#619) + [dependabot[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.23.0...3.24.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:production + ... +- Update changelog for 1.2.0. [Kazuaki Matsuo] + + +v1.2.0 (2021-06-07) +------------------- + +New +~~~ +- Feat: allow to add a command dynamically (#608) [Kazuaki Matsuo] + + * add add_commmand in python + + * add test + + * add exceptions, tweak method + + * append docstring + + * add $id example + + * use pytest.raises + + * add examples as docstring + +Other +~~~~~ +- Bump 1.2.0. [Kazuaki Matsuo] +- Chore(deps-dev): update pre-commit requirement from ~=2.12 to ~=2.13 + (#607) [dependabot[bot]] + + 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/v2.12.0...v2.13.0) +- Chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 + (#606) [Kazuaki Matsuo, dependabot[bot]] + + * chore(deps): update pytest-cov requirement from ~=2.11 to ~=2.12 + + 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.11.0...v2.12.0) +- Chore(deps): update pylint requirement from ~=2.7 to ~=2.8 (#600) + [dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.0...pylint-2.8.1) +- Chore(deps-dev): update pre-commit requirement from ~=2.11 to ~=2.12 + (#599) [dependabot[bot]] + + 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/v2.11.0...v2.12.0) +- Chore(deps): update isort requirement from ~=5.7 to ~=5.8 (#596) + [dependabot[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.7.0...5.8.0) + + +v1.1.0 (2021-03-10) +------------------- + +New +~~~ +- Feat: Add optional location speed attribute for android devices (#594) + [salabogdan] +- Feat: Added docstring for macOS screenrecord option (#580) [Mori + Atsushi] + + * Added docstring for macOS screenrecord option + + * tweak + + * review comment +- Feat: add warning to drop forceMjsonwp for W3C (#567) [Kazuaki Matsuo] + + * tweak + + * fix test + + * print warning + + * revert test + + * Update webdriver.py + + * fix autopep8 +- Feat: Added descriptions for newly added screenrecord opts (#540) + [Mori Atsushi] + + * Add description for newly added opts for screen record + + * Updates + +Test +~~~~ +- Ci: Use node v12 (#585) [Mori Atsushi] + + * Use node 12 on ci + + * Update copyright + + * Update README for doc + + * tweak + + * fix copyright + + * try py310 + + * remove py310 +- Ci: remove travis (#581) [Mori Atsushi] + + * Removed travis and run unit test on azure + + * review comment + + * run tox on azure pipelines + + * removed tox-travis from pipfile +- Ci: move azure project to Appium CI, update readme (#564) [Kazuaki + Matsuo] +- Ci: Added py39-dev for travis (#557) [Mori Atsushi] + + * ci: Added py39-dev + + * Add xv option for debug + + * [debug] pip list + + * Avoid error in py39 + + * Updated modules in pre-commit +- Ci: upgrade xcode and macos (#556) [Mori Atsushi] + + * ci: upgrade xcode ver and macos + + * Upgrade iOS ver for functional tests + + * Changed xcode to 11.6 + +Other +~~~~~ +- Chore(deps-dev): update pre-commit requirement from ~=2.10 to ~=2.11 + (#595) [dependabot[bot]] + + 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/v2.10.0...v2.11.0) +- Chore(deps): update tox requirement from ~=3.22 to ~=3.23 (#593) + [dependabot[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/3.23.0/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/3.22.0...3.23.0) +- Chore(deps): update pylint requirement from ~=2.6 to ~=2.7 (#588) + [Mori Atsushi, dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. + - [Release notes](https://github.com/PyCQA/pylint/releases) + - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.7.0) +- Chore(deps): update astroid requirement from ~=2.4 to ~=2.5 (#587) + [dependabot[bot]] + + Updates the requirements on [astroid](https://github.com/PyCQA/astroid) to permit the latest version. + - [Release notes](https://github.com/PyCQA/astroid/releases) + - [Changelog](https://github.com/PyCQA/astroid/blob/master/ChangeLog) + - [Commits](https://github.com/PyCQA/astroid/compare/astroid-2.4.0...astroid-2.5) +- Chore(deps): update mypy requirement from ~=0.800 to ~=0.812 (#589) + [Mori Atsushi, dependabot[bot]] + + * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) + + Signed-off-by: dependabot[bot] + + * Fix mypy error with mypy v0.812 (#590) + + * chore(deps): update mypy requirement from ~=0.800 to ~=0.812 + + Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. + - [Release notes](https://github.com/python/mypy/releases) + - [Commits](https://github.com/python/mypy/compare/v0.800...v0.812) +- Chore(deps): update tox requirement from ~=3.21 to ~=3.22 (#586) + [dependabot[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.21.0...3.22.0) +- Chore: Add table for screen_record kwarg (#582) [Mori Atsushi] + + * Add table for kwarg + + * update + + * Add missing doc to stop_recording + + * Push auto-generated changes by sphinx + + * delete duplicated entry [skip ci] +- Chore(deps): update isort requirement from ~=5.0 to ~=5.7 (#578) + [dependabot-preview[bot]] + + Updates the requirements on [isort](https://github.com/pycqa/isort) to permit the latest version. + - [Release notes](https://github.com/pycqa/isort/releases) + - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) + - [Commits](https://github.com/pycqa/isort/compare/5.0.0...5.7.0) +- Create Dependabot config file (#579) [dependabot-preview[bot], + dependabot-preview[bot]] +- Chore: Update pipfile to respect isort v5 (#577) [Mori Atsushi] +- Chore: Fix iOS app management functional tests (#575) [Mori Atsushi] + + * Added sleep to wait the app has gone + + * Upgrade AndroidSDK to 30 from 27 + + * Added sleep to ios tc + + * Fix android activities test + + * Revert android sdk ver + + * Used timer instead of fixed wait time + + * Created wait_for + + * Update test/functional/test_helper.py + + * review comments + + * review comments + + * Extend callable type + + * fix + + * review comment + + * review comment + + * review comment + + * fix comment +- Chore: Fix functional keyboard tests with appium v1.21.0-beta.0 (#574) + [Mori Atsushi] + + * Fix function keyboard tests + + * Updated class name for keyboard +- Chore: Apply Black code formatter (#571) [Mori Atsushi] + + * Applied black (length: 120, String skipped) + + * Updated related to ci + + * Update README +- Chore: address selenium-4 branch in readme (#566) [Kazuaki Matsuo] +- Docs: fix wrong code example in README.md (#555) [sanlengjingvv] +- Update changelog for 1.0.2. [Kazuaki Matsuo] + + +v1.0.2 (2020-07-15) +------------------- +- Bump 1.0.2. [Kazuaki Matsuo] +- Chore: Add the workaround to avoid service freezes on Windows (#552) + [Mykola Mokhnach] +- Chore: add checking package file count comparison in release script + (#547) [Kazuaki Matsuo] + + * chore: Add file count in release script + + * use f string for Python 3 :P + + * handle exit in method +- Update changelog for 1.0.1. [Kazuaki Matsuo] + + +v1.0.1 (2020-05-18) +------------------- + +Fix +~~~ +- Broken package (#545) [Kazuaki Matsuo] + + * add appium/webdriver/py.typed in find_packages + + * fix + +Other +~~~~~ +- Bump 1.0.1. [Kazuaki Matsuo] +- Update changelog for 1.0.0. [Kazuaki Matsuo] + + +v1.0.0 (2020-05-16) +------------------- + +New +~~~ +- Feat: Added Makefile (#530) [Mori Atsushi] + + * Created setup.cfg + + * Updated lib ver for pre-commit + + * Fix ci.sh to set failure even when one command failed + + * Fix pylint error + + * Add help to Makefile + + * Update README + + * Add check-all command +- Feat: Merge python3 branch to master (#526) [Hannes Hauer, Hannes + Hauer * 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 * Revert some unexpected changes * review + comments * Changed bound for TypeVar * Fix crashing ci * + Remove beta Co-authored-by: dependabot-preview[bot] + <27856297+dependabot-preview[bot]@users.noreply.github.com>, Kazuaki + Matsuo, Kazuaki Matsuo, Mori Atsushi, Mykola Mokhnach, Mykola + Mokhnach, Nrupesh Patel, Nrupesh Patel, Venkatesh, Venkatesh] + + * Drop py2 support (#478) + + * Drop py2 support + + * Support 3.7+ + + * 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 + + * Add mypy to pre-commit (#485) + + * chore: Applied some py3 formats (#486) + + * Removed unused import + + * Removed unnecessary codes + + * Applied f'' format instead ''.format() + + * Fixes + + * tweak + + * 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 + + * 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 + + * 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] + + * Add 'from' to except (#503) + + * 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] + + * 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 + + * 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: + +Fix +~~~ +- Tune mixin types, so linters could recognize them better (#536) + [Mykola Mokhnach] + +Test +~~~~ +- Test: Add appium_service functional test (#531) [Mori Atsushi] + + * Add appium_service functional test + + * Fix expressions + +Other +~~~~~ +- Bump 1.0.0. [Kazuaki Matsuo] +- Chore: Updates docstring (#533) [Mori Atsushi] + + * Updates docstring + + * Add description to Returns field + + * Remove type from docstring + + Since type hint already added to args + + * Set default lang to en + + * Change usage style in docstring + + * Updates + + * Remove rtype + + unnecessary anymore since type hint works for auto completion + + * tweak + + * Update return type + + * Restore types for keyword args + + * Remove types from Return field + + Except for property and TypeVar +- Chore: Remove saucetestcase from the client (#539) [Mykola Mokhnach] +- Chore: add py.typed in package, add maintainers (#538) [Kazuaki + Matsuo] +- Docs: Update documentation (#527) [Kazuaki Matsuo] + + * Chore: correct license, update readme + + * cleanup + + * docs: update the url of documentation +- Chore: Update readme and gitchangelog section role (#524) [Kazuaki + Matsuo] + + * chore: tweak changelog filter + + * address stoping Python 2 support + + * 2 instead of 2.0... + + * tweak readme +- Update changelog for 0.52. [Kazuaki Matsuo] + + +v0.52 (2020-04-23) +------------------ + +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] +- Chore: Fix int - str comparison error in ios desired capabilities + (#517) [Venkatesh] + + if number >= PytestXdistWorker.COUNT: +- Update changelog for 0.51. [Kazuaki Matsuo] + + +v0.51 (2020-04-12) +------------------ + +New +~~~ +- 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 +~~~ +- 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 + +Other +~~~~~ +- Bump 0.51. [Kazuaki Matsuo] +- Update changelog for 0.50. [Kazuaki Matsuo] + + +v0.50 (2020-02-10) +------------------ + +New +~~~ +- 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 + +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 + + * 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 +- Update changelog for 0.49. [Kazuaki Matsuo] + + +v0.49 (2019-12-24) +------------------ + +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] + + * Fix get_all_sessions + + * Revert changes + + * Move execute_mobile_command codes to mixin class + + * Update docstring + + It's same to webdriver.py + + * Use /sessions as endpoint for all_sessions + + https://github.com/appium/appium-base-driver/blob/master/docs/mjsonwp/protocol-methods.md + + * Delete unnecessary codes +- Replace apk for functional test (#470) [Mori Atsushi] + + * Replace apk for functional test + + https://github.com/appium/android-apidemos/releases/tag/v3.1.0 + + * Use sdkVer 27 + + * Update app package name + + * Fix: can't find android device + + * review comments + + * tweak +- Support for log_event and get_events command (#469) [Mori Atsushi] + + * Use appium/events as endpoint to get events + + * Removed unnecessary codes + + * Update unittest along to changes + + * Update docstring + + * Created LogEvents class + + * Support log_event + + * Add unittest for log_event + + * Add functional test for log_event and get_event + + * review comments + + * Restore events API + + * Add type as arg to get_events + + * tweak + + * Removed type arg from get_events + + It isn't implemented yet for now + + * Add type arg to get_event + + The value isn't passed to the server for now. + + * Updated along to type +- Cleaned up test codes (#466) [Mori Atsushi] + + * Deleted unnecessary codes + + * Move functional tests to correct class + + * Move some tests + + * Created search_context/windows_test + + * [functional] Created search_context package + + * Remove class method decolator + + * Fix import error + + * Add BaseTestCase for ios functional testcases + + * Add test_helper for android functional test + + * Add __init__.py + + * Deleted unused imports +- Move search context methods from webdriver and webelement to + search_context (#461) [Mori Atsushi] + + * Move ios search context methods to search_context file + + * Move android search text methods + + * Move windows search context + + * Move mobile search context + + * Divided search_context into each class + + * Move custom and image methods + + * Move contents in search_context.py to __init__.py + + * Add rtype to each docstring for auto completion in IDE + + * Add comments +- [CI] Run functional tests nightly (#463) [Mori Atsushi] + + * [CI] Run functional tests nightly + + * Extend timeout to wait for 2nd session created + + * Skip flaky test_all_sessions +- Revert some changes to fix broken codes (#462) [Mori Atsushi] + + * Revert some changes + + * Fix typo +- Move commands from webdriver as mixins class (#459) [Manoj Kumar] + + * move to mixins class + + * Create common class with its tests + + * incorporating PR comments +- 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] +- Add unit tests for keyboard API (#452) [Manoj Kumar] + + * Add Unit tests for Keyboard API + + * incorporating review comments + + * change per review comment +- Feat: Adding getAllSessions (#446) [Manoj Kumar] + + * Adding getAllSessions + + * adjust per lint + + * fix comments +- Add downloads badge (#441) [Mori Atsushi] + +Fix +~~~ +- 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 +- Fix CI fails (Updated iOS ver) (#440) [Mori Atsushi] + + * Updated iOS ver to fix CI fails + + * Update capability for safari test on ios + + * Fix travis CI fails +- Fix CI fails (#436) [Mori Atsushi] + + * Skip taking the screenshot not in CI + + * Skip py38 on travis +- Fix isort behavior for mock (#432) [Mori Atsushi] + + * Fix isort behavior for mock + + * Add guide to add 3rd party modules to isort conf + + * Add guide for docstrings + + * Delete unnecessary codes +- Fix android flaky tests (#413) [Mori Atsushi] + + * Fix android flaky tests + + * Use androidSdkVer 27 for emulator + + * Skip find_by_accessibility_id, find_by_uiautomator + + * Changed from https://github.com/ki4070ma/python-client/pull/5 + + * Add save_appium_log.yml + + * 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] + + +v0.47 (2019-08-22) +------------------ + +New +~~~ +- Add events property (#429) [Dan Graham] + + * add GET_SESSION + + * add events property, this property will get the current information of the session and get the events timings + + * add method for getting session_capabilities + + * update docstring + + * apply isort +- Add screenrecord unittest (#426) [Mori Atsushi] + + * Fix wrong docstring + + * Add screen_record unittest + + * Rename class names + + * Move test files + + * Fix docstring +- Add videoFilters option documentation (#419) [Mykola Mokhnach] +- Add remote_fs unittest (#410) [Mori Atsushi] + + * Add test_push_file unittest + + * 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 + + And update docstring + + * Update docstring + + * Update docstring + + * Fix import error + + * fix + + * fix import order + + * tweak +- Update changelog for 0.46. [Kazuaki Matsuo] + + +v0.46 (2019-06-27) +------------------ +- Bump 0.46. [Kazuaki Matsuo] +- Bug fix joining path in _get_main_script (#408) [Nicholas Frederick] +- Update changelog for 0.45. [Kazuaki Matsuo] + + +v0.45 (2019-06-26) +------------------ + +New +~~~ +- Add execute driver (#406) [Kazuaki Matsuo] + + * add execute driver + + * append docstring +- Add how to solve pipenv error to readme (#403) [Mori Atsushi] + + * Add how to solve pipenv error to readme + + * review comments + + * tweak + + * review comments +- Add autocompletion for pycharm (#404) [Mori Atsushi] + + * Add autocompletion for pycharm + + * Removed flaky tests from running +- 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 + + * Add missing param + + * Add run_test template + + * Fixed: test running failed + + * Fixed + + * Fixed + + * fixed + + * Add run_android_test + + * Changed emulator to Nexus6 + + * Run all android tests + + * fixed + + * Resolve python-dateutil dependency + + * Run on 3 workers + + * Add chromedriver installation + + * Skip failed test cases on ci + + * fixed + + * Extend adbExecTimeout + + * Add script source to comment + + * Run 5 workers for android + + * Use Node11 + + * Extend wait time + + * Reduced running android functional tests + + * Revert some changes +- Use the same format for docstring (#395) [Mori Atsushi] + + * Update docstring + + * Update docstring + + * Update docstring + + * tweak + + * tweak + + * tweak + + * tweak + + * tweak + + * Update docstring + + * Update docstring + + * Update docstring + + * Update docstring + + * tweak + + * Update +- Publish functional test report (#394) [Mori Atsushi] + + * Move functional tests to template + + * Add publish_test_result + + * Fix typo +- Divide functional appium tests into each module(iOS) (#391) [Mori + Atsushi] + + * Divide ios appium_tests to each module + + * Fix test file name + + * Add CI status badge +- Run iOS functional tests on azure pipelines (#390) [Mori Atsushi] + + * Set up CI with Azure Pipelines + + * review comments + + * update README +- Update changelog for 0.44. [Kazuaki Matsuo] + + +v0.44 (2019-05-24) +------------------ + +Fix +~~~ +- Installed selenium4 when 'setup.py install' (#389) [Mori Atsushi] + + * Fix: installed selenium4 when setup.py install + + * Keep existing comparison operator +- Fix ios functional tests failed (#385) [Mori Atsushi] + + * Fix safari test(iOS) + + * Fix: find_by_ios_predicate + + * Delete find_by_uiautomation_tests + + since uiautomation is deprecated + + * Move non test files + + * Replace test app with the latest + + * 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 + + * Add set_network_speed unittest + + * Add api doc + + * revert unexpected change + + * revert change +- Update changelog for 0.43. [Kazuaki Matsuo] + + +v0.43 (2019-05-18) +------------------ + +New +~~~ +- Add assertions for w3c (#384) [Kazuaki Matsuo] +- Add isort to pre-commit (#379) [Mori Atsushi] + + * Add isort to pre-commit + + * Add isort.conf + + * Applied isort for test/unit + + * Add current dir to isrot arg + + * Add check to ci.sh + + * Use exit code for condition check in ci.sh + +Fix +~~~ +- 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 + + * Replace double quote with single quote under android dir + + * Remove creating tmp file + + * tweak +- Fix functional tests failed (android, ime/multi_action) (#372) [Mori + Atsushi] + + * Fix test failed: ime_tests, multi_action_tests + + * revert change and add impl for python3 + + * Remove py3 dependency + + * Change deepcopy to copy + + * Update ime_tests +- Fix functional tests failed (android, touch_action) (#374) [Mori + Atsushi] + + * Fix: test_drag_and_drop + + * Fix: test_long_press + + * Fix: long_press_x_y, swipe + + * Fix: press_and_wait + + * Fix: driver_drag_and_drop + + * Tweak + + * 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 + + * Move android commands to android package + + * Update setup.py to include added packages + + * Changed find_packages to whitelist style +- Update changelog for 0.42. [Kazuaki Matsuo] + + +v0.42 (2019-05-10) +------------------ + +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] + + * Fix test failed: test_send_keys, test_screen_record + + * Fix test failed: test_update_settings + + * Fix test failed: test_start_activity_other_app + + * Move and rename helper package + + * 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] + + * Support get_performance_data, get_performance_data_types + + * Add api doc + + * Add performance unittest + + * Tweak + + * Update api doc +- Support set_gsm_voice (#367) [Mori Atsushi] + + * Support set_gsm_voice + + * Add set_gsm_voice unittest + + * Fix typo +- Support get_system_bars (#363) [Mori Atsushi] + + * Support get_system_bars + + * Add api doc + + * Add get_system_bars unittest + + * Remove FIXME +- Support make_gsm_call (#360) [Mori Atsushi] + + * Move const to gsm_signal_strength + + * Support make_gsm_call + + * Add make_gsm_call unittest + + * Move const to gsm class + + * Move get_dict_const to common.helper + + * Rename func + + * Use OrderedDict to keep defined order +- Support set_gsm_signal (#357) [Mori Atsushi] + + * Support set_gsm_signal + + * Fix: NONE_OR_UNKNOWN doesn't work + + * Add set_gsm_signal unittest + + * Use int for signal strength const + + * Raise exception when signal strength is out of range + + * Fix: wrong class name + + * Removed args validation + + Since arg validation already done by server side + + * Show warning log when arg is out of range + + * Some changes for less maintenance +- Mobile:pinchOpen and mobile:pinchClose no longer implemented in appium + drivers (#358) [Jonah] +- Remove unnecessary codes. [Atsushi Mori] +- Replace 'on' with AC_ON. [Atsushi Mori] +- Update api doc. [Atsushi Mori] +- Define AC_OFF, AC_ON as const. [Atsushi Mori] +- Skip pylint warnings. [Atsushi Mori] +- Update api doc. [Atsushi Mori] +- Support set_power_ac. [Atsushi Mori] +- Support set_power_capacity. [Atsushi Mori] +- Update changelog for 0.41. [Kazuaki Matsuo] +- Bump 0.41. [Kazuaki Matsuo] + + +v0.41 (2019-04-23) +------------------ + +New +~~~ +- Add send sms support (#351) [Mori Atsushi] + + * Support sendSms function + + * Added api doc + + * Add sms unittest + + * Revert unexpected changes + + * Update api doc +- 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) +------------------ + +Fix +~~~ +- Fix RuntimeError: maximum recursion depth exceeded in cmp happened + (#343) [Kazuaki Matsuo] + + * fix maximum recursion depth exceeded in sub classes + + * add docstring + + * add comparison of a number of commands + + * 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] + + * add direct connect feature + + * rmeove todo + + * update readme, extract _update_command_executor + + * add logger + + * make log level info + + * show warning if no directConnectXxxxx in dict + + * tweak error message + + * tweak message format +- Add datamatcher (#335) [Kazuaki Matsuo] + + * add datamatcher + + * add zero case + + * defines search context for driver and element + +Other +~~~~~ +- Update changelog for 0.38. [Kazuaki Matsuo] +- Bump 0.38. [Kazuaki Matsuo] + + +v0.38 (2019-02-11) +------------------ +- Bump 0.38. [Kazuaki Matsuo] +- Remove io.open from getting version code (#334) [Kazuaki Matsuo] + + * remove io.open + + * remove appium module from release script + + +v0.37 (2019-02-10) +------------------ + +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 + (#324) [Mykola Mokhnach] +- Add pressure option (#322) [Kazuaki Matsuo] + + * add pressure option + + * add a test, tweak comment and the method + + * 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] + + +v0.36 (2019-01-18) +------------------ +- Bump 0.36. [Kazuaki MATSUO] +- Import keyboard, add tests (#319) [Kazuaki Matsuo] +- Update changelog for 0.35. [Kazuaki MATSUO] + + +v0.35 (2019-01-17) +------------------ + +New +~~~ +- Add location unittest (#317) [Mori Atsushi] + + * Add test_location + + * Add test_set_location + + * Add test_toggle_location_services +- Add settings unittest (#315) [Mori Atsushi] + + * Add settings unittest + + * Remove unused import +- Added format to device_time as argument (#312) [Mori Atsushi] +- Add devicetime unittest (#309) [Mori Atsushi] + + * Add device time test + + * Removed unnecessary check from device time test + + * Changed assertion for device time test + + Along to review comments + + * Changed quote for string from double to single +- Add activities unittest (#310) [Tadashi Nemoto] + + * Add test_start_activity + + * Add current_activity and wait_activity + + * Fix pytest 4.0.2 + + * Add test_start_activity_with_opts + + * Added options +- Add network unittest (#308) [Mori Atsushi] + + * Add network connection test + + * Added set network connection test + + * Add toggle wifi test + + * Removed unnecessary codes from toggle wifi test + + * Changed assertion for set network connection test +- Add touch action unittest (#306) [Tadashi Nemoto] + + * Add press test + + * Add test_long_press + + * Add test_wait + + * Add remaining tests + + * Add tap + + * 10 -> 9 + + * Modify based on comment +- 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] +- Update changelog for 0.34. [Kazuaki MATSUO] + + +v0.34 (2018-12-18) +------------------ + +Fix +~~~ +- Fix missing package, missing commands and a test (#296) [Kazuaki + Matsuo] + + * add extensions into package + + * 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) +------------------ + +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) +------------------ + +New +~~~ +- Add unit tests for isLocked Library (#288) [Venkatesh Singh] + + * Add unit tests for isLocked Lib + + * moved isLocked library tests in lock.py +- 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] + + * extract bytes and add a test for set clipboard +- Introduce httpretty for unittest to mock Appium server (#281) [Kazuaki + Matsuo] + + * add httpretty + + * add clipboard tests as an example + + * add test for forceMjsonwp +- Update setup elements (#280) [Kazuaki Matsuo] + + * update setup elements + + * remove docgen since we can use markdown format in pypi +- Release automation (#276) [Kazuaki Matsuo] +- Updated requirements.txt file with version (#275) + [RajeshkumarAyyadurai] + + * updated required dependecies with version number as a best practice + + * updated required dependencies with version + + * updated pylint library version to support for python 2.7 +- Append document for recording screen (#271) [Kazuaki Matsuo] + + * append document for recording screen + + * add since appium 1.10.0 + + * remove Only works for real devices since the feature can work on both +- Update changelog for 0.31. [Kazuaki MATSUO] + + +v0.31 (2018-11-21) +------------------ +- V0.31. [Kazuaki MATSUO] +- Driver.push_file(destination_path, source_path) feature (#270) [Javon + Davis] + + * used base64 library for conversion + + * remove unnecessary library use + + * changed text in test file + + * * Using context when reading file + * changed docstring format + * Catch error thrown if file not present and present user with a better message + + * fixed incorrect file path in test + + * removed change in pul_file that broke backwards compat and updated docstring description for `destination_path` + + +v0.30 (2018-10-31) +------------------ + +New +~~~ +- Add release section in readme. [Kazuaki MATSUO] + +Fix +~~~ +- Fix python3 set_clipboard error (#267) [Kazuaki Matsuo] + + * fix python3 set_clipboard error + + * apply formatter + +Other +~~~~~ +- V0.30. [Kazuaki MATSUO] + + +v0.29 (2018-10-30) +------------------ + +New +~~~ +- Add an endpoint for pressing buttons (#262) [Alex] +- Add custom locator strategy (#260) [Jonathan Lipps] +- Add a duration for scroll for ios (#256) [Kazuaki Matsuo] + + * add a duration for scroll for ios + + * tweak default duration + + * apply autoformat + + * set 600 duration by default if it's w3c spec + + * skip wait if duration is none + + * add comment +- Add finger print (#252) [Kazuaki Matsuo] + + * add fingre print + + * apply auto format +- Add find_elements w3c for webelement (#251) [Kazuaki Matsuo] + + * add find_elements w3c for webelement + + * add tests for child elements + + * add todo for future work +- Add a github issue template (#250) [Kazuaki Matsuo] +- Add xdist port handling (#248) [Kazuaki Matsuo] + + * add handling port number to run ios tests in parallel + + * define PytestXdistWorker + + * use gw0 if the number of worker is over the count of workers +- Add autopep8 (#243) [Kazuaki Matsuo] + + * apply autopep8 + + * add development section as the first draft + + * relax max-line-length + + * add global-config +- Add toggle wifi command (#241) [joshuazhusince1986] + + * add toggle_wifi command + + * update comment to indicate toggle_wifi is only for Android +- Add selenium into ci-requirements (#240) [Kazuaki Matsuo] + + fix pylint + + add --py3k +- Add travis to run pylint and unit tests (#239) [Kazuaki Matsuo] + + * add pylint + + * add rcfile + + * tweak pylint + + * fix lint + + * add running pytest + + * tweak indentations +- Add tag view for android (#238) [Kazuaki Matsuo] + + * add tag view for android + + * fix typo... and tweak names of arguments + + * 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) +------------------ + +Fix +~~~ +- Fix base64 encoded string (#231) [Kazuaki Matsuo] + +Other +~~~~~ +- V0.28. [Isaac Murchie] + + +v0.27 (2018-07-10) +------------------ + +New +~~~ +- Add support for is keyboard shown command. [Jonathan Lipps] +- Add find by image commands and tests (#224) [Jonathan Lipps] + + * add find by image commands and tests + + * remove and ignore pytest cache files + + * address review comments + + * fix docstrings +- Add flags argument to press_keycode (#222) [Mykola Mokhnach] + + * Add flags argument to press_keycode + + * Add flags to long press as well +- Add an endpoint for getting battery info (#217) [Mykola Mokhnach] +- Add wrappers for OpenCV-based image comparison (#216) [Mykola + Mokhnach] + + * Add wrappers for OpenCV-based image comparison + + * Tune some docs +- Add clipboard handlers (#209) [Mykola Mokhnach] + + * Add clipboard handlers + + * Fix documentation + + * fix options notation +- Add applications management endpoint handlers (#204) [Mykola Mokhnach] +- Add methods for start/stop screen record API endpoints (#201) [Mykola + Mokhnach] + + * Add methods for start/stop screen record API endpoints + + * Fix typo + + * Add a separate test for Android and get rid of redundant stuff + + * Tune documentation + + * Add videoSize arg description + + * Fix arg name +- Add appium prefix in create session and fix find_elements for W3C + (#196) [Kazuaki Matsuo] + + * add appium prefix in create session + + * fix find_elements by w3c for Appium + + * introduce forceMjsonwp + + * refine a bit + + * fix some tests + + * 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) +------------------ +- V0.26. [Isaac Murchie] + + +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] +- Correct a wording. [Kazuaki MATSUO] +- Create README.md. [Kazuaki Matsuo] +- Append class chain related descriptions. [Kazuaki MATSUO] +- Update README to include instructions for using iOS predicates. [Emil + Petersen] +- Update docs for UIAutomation selector to include version requirement. + [Emil Petersen] + + +v0.24 (2016-12-20) +------------------ + +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) +------------------ + +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) +------------------ +- V0.22. [Isaac Murchie] +- Use id instead of elementId. [Isaac Murchie] + + +v0.21 (2016-01-20) +------------------ + +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 + + +v0.20 (2015-10-12) +------------------ +- V0.20. [Isaac Murchie] +- Revert actions change. [Isaac Murchie] + + +v0.19 (2015-10-09) +------------------ +- V0.19. [Isaac Murchie] +- Change 'actions' to 'gestures' in single action. [Isaac Murchie] + + +v0.18 (2015-10-07) +------------------ + +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] +- Use WebDriverWait to implement wait_activity. [zhaoqifa] +- Make tap duration be handled as ms, not s. [Isaac Murchie] +- Bump version. [Isaac Murchie] +- Bump version. [Isaac Murchie] +- Move monkeypatched set_value into WebElement. [Isaac Murchie] + + +v0.14 (2015-03-06) +------------------ + +Fix +~~~ +- Fix issue with single tap. [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) +------------------ + +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. +- Update webdriver.py. [urtow] + + +v0.11 (2014-11-14) +------------------ + +New +~~~ +- Add toggle_location_services. [Isaac Murchie] + +Other +~~~~~ +- Bump version. [Isaac Murchie] +- Update webdriver.py. [urtow] + + Start_y - y-coordinate for start, not end + + +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] +- Make long_press works with 'duration' parameter. [ianxiaohanxu] + + Add a new parameter 'duration = None' to _get_opts +- Typo fix! [Cass] +- Update README.md. [Johan Lundstroem] + + Verison -> Version +- Revert "Fix for #23: Re-add 'keyevent' temporarily." [Payman Delshad] + + This reverts commit ccbcaf809704bf1ac752d1b4446d1175b7434c36. + + +v0.9 (2014-07-07) +----------------- + +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] +- 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] +- Renamed keyevent to press_keycode and added long_press_keycode. + [Payman Delshad] +- Bump version. [Isaac Murchie] +- Numerous fixes. [Alexander Bayandin] + + 1. fix comparation with None + 2. remove unused imports + 3. fix imports order (according to pep8) + 4. style fixes (according to pep8) + 5. another minor fixes +- Update zoom/pinch signatures. [Isaac Murchie] +- Remove tag name, use class. [Isaac Murchie] +- Don't send multitouch for single finger tap. [Isaac Murchie] +- Miscellaneous fixes. [Isaac Murchie] +- Update desired caps. [Isaac Murchie] +- Basic module structure. [Isaac Murchie] + + From 1e281bf085138ee85187770c263ffd91e5a83e58 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 18 Oct 2023 14:11:28 -0700 Subject: [PATCH 234/548] docs: address options in the migration guide (#929) * docs: adress options in the migration guide * Update README.md * Update README.md --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cdcde3b4e..dacad8393 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,10 @@ For example, some changes in the Selenium binding could break the Appium client. ### Quick migration guide from v2 to v3 +- `options` keyword argument in the `webdriver.Remote` constructor such as `XCUITestOptions` instead of `desired_capabilities` + - Available options are https://github.com/appium/python-client/tree/master/appium/options + - Please check the [Usage](#usage) below as an exampple. + - Not a "new" change, but the `desired_capabilities` argument has been removed since v3. - Replacement - `start_activity` method: Please use [`mobile: startActivity`](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-startactivity) - `launch_app`, `close_app` and `reset` methods: Please refer to https://github.com/appium/appium/issues/15807 @@ -67,8 +71,8 @@ For example, some changes in the Selenium binding could break the Appium client. - `set_value` and `set_text` methods: Please use `element.send_keys` or `send_keys` by W3C Actions - Removal - `end_test_coverage` method is no longer available - - `session` properly is no longer available - - `all_sessions` properly is no longer available + - `session` property is no longer available + - `all_sessions` property is no longer available ### Quick migration guide from v1 to v2 - Enhancement @@ -148,7 +152,7 @@ APPIUM_HOST = '127.0.0.1' def appium_service(): service = AppiumService() service.start( - # Check the output of `appium server --help` for the complete list of + # Check the output of `appium server --help` for the complete list of # server command line arguments args=['--address', APPIUM_HOST, '-p', str(APPIUM_PORT)], timeout_ms=20000, @@ -157,7 +161,7 @@ def appium_service(): service.stop() -def create_ios_driver(custom_opts = None): +def create_ios_driver(custom_opts = None): options = XCUITestOptions() options.platformVersion = '13.4' options.udid = '123456789ABC' @@ -165,9 +169,9 @@ def create_ios_driver(custom_opts = None): options.load_capabilities(custom_opts) # Appium1 points to http://127.0.0.1:4723/wd/hub by default return webdriver.Remote(f'http://{APPIUM_HOST}:{APPIUM_PORT}', options=options) - -def create_android_driver(custom_opts = None): + +def create_android_driver(custom_opts = None): options = UiAutomator2Options() options.platformVersion = '10' options.udid = '123456789ABC' @@ -180,16 +184,16 @@ def create_android_driver(custom_opts = None): @pytest.fixture def ios_driver_factory(): return create_ios_driver - + @pytest.fixture def ios_driver(): - # prefer this fixture if there is no need to customize driver options in tests + # prefer this fixture if there is no need to customize driver options in tests driver = create_ios_driver() yield driver driver.quit() - + @pytest.fixture def android_driver_factory(): return create_android_driver @@ -197,12 +201,12 @@ def android_driver_factory(): @pytest.fixture def android_driver(): - # prefer this fixture if there is no need to customize driver options in tests + # prefer this fixture if there is no need to customize driver options in tests driver = create_android_driver() yield driver driver.quit() - + def test_ios_click(appium_service, ios_driver_factory): # Usage of the context manager ensures the driver session is closed properly # after the test completes. Otherwise, make sure to call `driver.quit()` on teardown. From 876233e115ef68839f614e89f3dd7bd523222d36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:51:09 -0700 Subject: [PATCH 235/548] chore(deps): update selenium requirement from ~=4.14 to ~=4.15 (#933) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.14.0...selenium-4.15.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 215173bcf..83da79bdb 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.11" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.14" +selenium = "~=4.15" From 17639ea682c06fe5ea23fb5999dcf009b7baa36c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 13 Dec 2023 22:42:02 -0800 Subject: [PATCH 236/548] fix: self.command_executor instance in _update_command_executor (#940) --- appium/webdriver/webdriver.py | 7 +++++-- test/unit/webdriver/webdriver_test.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 86ca1d1a2..bf8f0a010 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -295,8 +295,11 @@ def _update_command_executor(self, keep_alive: bool) -> None: executor = f'{protocol}://{hostname}:{port}{path}' logger.debug('Updated request endpoint to %s', executor) - # Override command executor - self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) + # Override command executor. + if isinstance(self.command_executor, AppiumConnection): # type: ignore + self.command_executor = AppiumConnection(executor, keep_alive=keep_alive) + else: + self.command_executor = RemoteConnection(executor, keep_alive=keep_alive) self._add_commands() # https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277 diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py index 8872a46c7..39792dcc4 100644 --- a/test/unit/webdriver/webdriver_test.py +++ b/test/unit/webdriver/webdriver_test.py @@ -134,6 +134,7 @@ def test_create_session_register_uridirect(self): assert 'http://localhost2:4800/special/path/wd/hub' == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts + assert isinstance(driver.command_executor, AppiumConnection) @httpretty.activate def test_create_session_register_uridirect_no_direct_connect_path(self): @@ -173,6 +174,7 @@ def test_create_session_register_uridirect_no_direct_connect_path(self): assert SERVER_URL_BASE == driver.command_executor._url assert ['NATIVE_APP', 'CHROMIUM'] == driver.contexts + assert isinstance(driver.command_executor, AppiumConnection) @httpretty.activate def test_get_events(self): From 63770e8cba8c1d9d4bb3324b621bc96b037d242f Mon Sep 17 00:00:00 2001 From: kkb912002 <57607392+kkb912002@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:21:43 +0900 Subject: [PATCH 237/548] fix: typo in ActionHelpers (#937) touc -> touch --- appium/webdriver/extensions/action_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 6a458b396..191a546d9 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -96,7 +96,7 @@ def tap(self, positions: List[Tuple[int, int]], duration: Optional[int] = None) """ if len(positions) == 1: actions = ActionChains(self) - actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, 'touc')) + actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, 'touch')) x = positions[0][0] y = positions[0][1] actions.w3c_actions.pointer_action.move_to_location(x, y) From 69ca0595727043645d5c0d9488e2b51e77784075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:22:03 -0800 Subject: [PATCH 238/548] chore(deps-dev): update pylint requirement from ~=3.0.1 to ~=3.0.3 (#939) Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.1...v3.0.3) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 83da79bdb..c0a390289 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "<6.0" mypy = "<2.0" mock = "~=5.1" pre-commit = "~=2.21" -pylint = "~=3.0.1" +pylint = "~=3.0.3" pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" From 5200a9b7a1ce41eba7d6c623c347ee674463bae3 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 14 Dec 2023 22:23:34 -0800 Subject: [PATCH 239/548] Bump 3.1.1 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 64236b03e..c2061e2a0 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '3.1.0' +version = '3.1.1' From 40ac0127353ebe6dd0b235e6e1916f53d862d52e Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 14 Dec 2023 22:23:49 -0800 Subject: [PATCH 240/548] Update changelog for 3.1.1 --- CHANGELOG.rst | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a3bc9453..19b82e5bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,52 @@ Changelog ========= -(unreleased) ------------- +v3.1.1 (2023-12-15) +------------------- + +Fix +~~~ +- Typo in ActionHelpers (#937) [kkb912002] + + touc -> touch +- Self.command_executor instance in _update_command_executor (#940) + [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 3.1.1. [Kazuaki Matsuo] +- Chore(deps-dev): update pylint requirement from ~=3.0.1 to ~=3.0.3 + (#939) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. + - [Release notes](https://github.com/pylint-dev/pylint/releases) + - [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.1...v3.0.3) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.14 to ~=4.15 (#933) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.14.0...selenium-4.15.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Docs: address options in the migration guide (#929) [Kazuaki Matsuo] + + * docs: adress options in the migration guide + + * Update README.md + + * Update README.md +- Docs: update changelog. [Kazuaki Matsuo] - Update changelog for 3.1.0. [Kazuaki Matsuo] From 39a89c86357d00ee12db2af74ea3906c118fdec0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 25 Dec 2023 00:05:43 -0800 Subject: [PATCH 241/548] docs: update links (#944) --- README.md | 7 +++++-- appium/webdriver/common/multi_action.py | 2 +- appium/webdriver/common/touch_action.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dacad8393..3424cee37 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,10 @@ For example, some changes in the Selenium binding could break the Appium client. - Please check each behavior. Their behaviors could slightly differ. - Added `strict_ssl` to relax SSL errors such as self-signed ones - Deprecated - - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions. + - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions or `mobile:` extensions - e.g. - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) - - https://www.selenium.dev/documentation/support_packages/mouse_and_keyboard_actions_in_detail/ + - https://www.selenium.dev/documentation/webdriver/actions_api/ - https://www.youtube.com/watch?v=oAJ7jwMNFVU - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api @@ -111,6 +111,9 @@ actions.w3c_actions.pointer_action.release() actions.perform() ``` +- [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) +- https://www.selenium.dev/documentation/webdriver/actions_api/ + ## Usage The Appium Python Client is fully compliant with the WebDriver Protocol diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 6800546ba..918b89382 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -33,7 +33,7 @@ class MultiAction: """ deprecated:: 2.0.0 - Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + Please use W3C actions instead: https://github.com/appium/python-client?tab=readme-ov-file#multiactiontouchaction-to-w3c-actions """ def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py index aedda606b..c81190b0c 100644 --- a/appium/webdriver/common/touch_action.py +++ b/appium/webdriver/common/touch_action.py @@ -37,7 +37,7 @@ class TouchAction: """ deprecated:: 2.0.0 - Please use W3C actions instead: http://appium.io/docs/en/commands/interactions/actions/ + Please use W3C actions instead: https://github.com/appium/python-client?tab=readme-ov-file#multiactiontouchaction-to-w3c-actions """ def __init__(self, driver: Optional['WebDriver'] = None): From e2d238e10a526ab041e9fed428d996e14adde1ce Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 8 Jan 2024 17:48:48 -0800 Subject: [PATCH 242/548] docs: update example in readme (#945) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3424cee37..884db4f33 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ On UIA2, some elements can be handled with `touch` pointer action instead of the For example, the below action builder is to replace the default one with the `touch` pointer action. ```python +from selenium.webdriver import ActionChains from selenium.webdriver.common.actions import interaction from selenium.webdriver.common.actions.action_builder import ActionBuilder From 5be0ea08ef291cff4651b23fb820812562e8cf0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jan 2024 00:36:24 -0800 Subject: [PATCH 243/548] chore(deps-dev): update tox requirement from ~=4.11 to ~=4.12 (#947) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.11.0...4.12.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c0a390289..8c69de6c8 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pylint-quotes = "~=0.2.3" pytest = "~=7.4" pytest-cov = "~=4.1" python-dateutil = "~=2.8" -tox = "~=4.11" +tox = "~=4.12" types-python-dateutil = "~=2.8" [packages] From ea9e09e45c0650a39b7861b15ece85f8a77fdbc9 Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Sun, 14 Jan 2024 10:36:45 +0200 Subject: [PATCH 244/548] docs: update W3C actions example in readme (#946) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 884db4f33..9cad05507 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ For example, the below action builder is to replace the default one with the `to from selenium.webdriver import ActionChains from selenium.webdriver.common.actions import interaction from selenium.webdriver.common.actions.action_builder import ActionBuilder +from selenium.webdriver.common.actions.pointer_input import PointerInput actions = ActionChains(driver) # override as 'touch' pointer action From bdac0b89b51d70ffea12df15adaec86ba4a986a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 00:01:29 -0800 Subject: [PATCH 245/548] chore(deps): update selenium requirement from ~=4.15 to ~=4.17 (#948) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.15.0...selenium-4.17.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8c69de6c8..bbc38ffa2 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.12" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.15" +selenium = "~=4.17" From 92583ce39003b740bb5e57bbf2114d283d884d22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:18:34 -0800 Subject: [PATCH 246/548] chore(deps-dev): update pytest requirement from ~=7.4 to ~=8.0 (#953) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...8.0.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index bbc38ffa2..0bfe01303 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ mock = "~=5.1" pre-commit = "~=2.21" pylint = "~=3.0.3" pylint-quotes = "~=0.2.3" -pytest = "~=7.4" +pytest = "~=8.0" pytest-cov = "~=4.1" python-dateutil = "~=2.8" tox = "~=4.12" From 1c6dcdf642aaaa46facc6174d80a339fa49684f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:27:05 -0800 Subject: [PATCH 247/548] chore(deps): update selenium requirement from ~=4.17 to ~=4.18 (#958) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.17.0...selenium-4.18.1) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 0bfe01303..c631e48e9 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.12" types-python-dateutil = "~=2.8" [packages] -selenium = "~=4.17" +selenium = "~=4.18" From 2e0ff4e043eb8efd114d0f9f1f9f5c99a7d08d96 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 23 Feb 2024 10:47:40 -0800 Subject: [PATCH 248/548] fix: add return self in MultiAction#add (#964) --- appium/webdriver/common/multi_action.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py index 918b89382..3ab826a1c 100644 --- a/appium/webdriver/common/multi_action.py +++ b/appium/webdriver/common/multi_action.py @@ -45,7 +45,7 @@ def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) self._element = element self._touch_actions: List['TouchAction'] = [] - def add(self, *touch_actions: 'TouchAction') -> None: + def add(self, *touch_actions: 'TouchAction') -> 'MultiAction': """Add TouchAction objects to the MultiAction, to be performed later. Args: @@ -67,6 +67,8 @@ def add(self, *touch_actions: 'TouchAction') -> None: self._touch_actions.append(copy.copy(touch_action)) + return self + def perform(self) -> 'MultiAction': """Perform the actions stored in the object. From 686d4864185b86bc61038099be116ec68fe3c0c9 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 23 Feb 2024 19:57:23 -0800 Subject: [PATCH 249/548] feat: add pause in drag_and_drop (#961) * feat: add pause in drag_and_drop * docs: tweak docstring * Update action_helpers.py * apply pause only when the value is not negative --- appium/webdriver/extensions/action_helpers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/appium/webdriver/extensions/action_helpers.py b/appium/webdriver/extensions/action_helpers.py index 191a546d9..a0ff5a2a9 100644 --- a/appium/webdriver/extensions/action_helpers.py +++ b/appium/webdriver/extensions/action_helpers.py @@ -61,12 +61,15 @@ def scroll(self, origin_el: WebElement, destination_el: WebElement, duration: Op actions.perform() return cast('WebDriver', self) - def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': + def drag_and_drop( + self, origin_el: WebElement, destination_el: WebElement, pause: Optional[float] = None + ) -> 'WebDriver': """Drag the origin element to the destination element Args: origin_el: the element to drag destination_el: the element to drag to + pause: how long the action pauses before moving after the tap and hold, in float seconds. Returns: Union['WebDriver', 'ActionHelpers']: Self instance @@ -74,6 +77,8 @@ def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'W actions = ActionChains(self) # 'mouse' pointer action actions.w3c_actions.pointer_action.click_and_hold(origin_el) + if pause is not None and pause > 0: + actions.w3c_actions.pointer_action.pause(pause) actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() From 8b4b290acf39d70d2c89e22025cd686a617f871a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 23 Feb 2024 20:01:20 -0800 Subject: [PATCH 250/548] Bump 3.2.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index c2061e2a0..42b6ccbe8 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '3.1.1' +version = '3.2.0' From 6f4889874c280ca4926b2e3a89b556ac97d961b4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Fri, 23 Feb 2024 20:01:49 -0800 Subject: [PATCH 251/548] Update changelog for 3.2.0 --- CHANGELOG.rst | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 19b82e5bc..2f520bde4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,84 @@ Changelog ========= +v3.2.0 (2024-02-24) +------------------- + +New +~~~ +- Feat: add pause in drag_and_drop (#961) [Kazuaki Matsuo] + + * feat: add pause in drag_and_drop + + * docs: tweak docstring + + * Update action_helpers.py + + * apply pause only when the value is not negative + +Fix +~~~ +- Add return self in MultiAction#add (#964) [Kazuaki Matsuo] + +Other +~~~~~ +- Bump 3.2.0. [Kazuaki Matsuo] +- Chore(deps): update selenium requirement from ~=4.17 to ~=4.18 (#958) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.17.0...selenium-4.18.1) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Chore(deps-dev): update pytest requirement from ~=7.4 to ~=8.0 (#953) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...8.0.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:development + ... +- Chore(deps): update selenium requirement from ~=4.15 to ~=4.17 (#948) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. + - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) + - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.15.0...selenium-4.17.0) + + --- + updated-dependencies: + - dependency-name: selenium + dependency-type: direct:production + ... +- Docs: update W3C actions example in readme (#946) [Dor Blayzer] +- Chore(deps-dev): update tox requirement from ~=4.11 to ~=4.12 (#947) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.11.0...4.12.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Docs: update example in readme (#945) [Kazuaki Matsuo] +- Docs: update links (#944) [Kazuaki Matsuo] +- Update changelog for 3.1.1. [Kazuaki Matsuo] + + v3.1.1 (2023-12-15) ------------------- From ac9965da3839d4709625d2912abc577f52bc2dc1 Mon Sep 17 00:00:00 2001 From: WrrngnCode <53365172+WrrngnCode@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:29:55 +0100 Subject: [PATCH 252/548] fix: unclosed file <_io.BufferedReader name error by proper cleanup of subprocess.Popen process (#965) https://github.com/appium/python-client/issues/962 add a Popen.communicate call after terminate to ensure that file stdout and sdterr file descriptors are closed properly. --- appium/webdriver/appium_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/appium/webdriver/appium_service.py b/appium/webdriver/appium_service.py index c3541fed0..37e2ddab9 100644 --- a/appium/webdriver/appium_service.py +++ b/appium/webdriver/appium_service.py @@ -235,6 +235,7 @@ def stop(self) -> bool: if self.is_running: assert self._process self._process.terminate() + self._process.communicate(timeout=5) is_terminated = True self._process = None self._cmd = None From 9b6aa58038a7daeb41ecfc8728d88d1388e61163 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 25 Feb 2024 19:20:51 -0800 Subject: [PATCH 253/548] Bump 3.2.1 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 42b6ccbe8..1f70aab66 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '3.2.0' +version = '3.2.1' From b30e0b0bc72729958b47564640b0309aa77f9190 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 25 Feb 2024 19:21:04 -0800 Subject: [PATCH 254/548] Update changelog for 3.2.1 --- CHANGELOG.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f520bde4..4a371bb07 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,23 @@ Changelog ========= +v3.2.1 (2024-02-26) +------------------- + +Fix +~~~ +- Unclosed file <_io.BufferedReader name error by proper cleanup of + subprocess.Popen process (#965) [WrrngnCode] + + https://github.com/appium/python-client/issues/962 + add a Popen.communicate call after terminate to ensure that file stdout and sdterr file descriptors are closed properly. + +Other +~~~~~ +- Bump 3.2.1. [Kazuaki Matsuo] +- Update changelog for 3.2.0. [Kazuaki Matsuo] + + v3.2.0 (2024-02-24) ------------------- From 4d8db65bfb672180a9bd0a52a3254ddd1f4c5eb0 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 25 Feb 2024 19:22:13 -0800 Subject: [PATCH 255/548] feat: remove MultiAction and TouchAction (#960) BREAKING CHANGE: Remove MultiAction and TouchAction as non-w3c WebDriver-defined methods. Please use w3c actions instead. --- README.md | 15 +- appium/webdriver/common/multi_action.py | 99 -------- appium/webdriver/common/touch_action.py | 218 ------------------ ci-jobs/functional_test.yml | 2 +- docs/webdriver.common.rst | 16 -- test/functional/android/multi_action_tests.py | 98 -------- test/functional/android/touch_action_tests.py | 187 --------------- test/unit/webdriver/multi_action_test.py | 58 ----- test/unit/webdriver/touch_action_test.py | 99 -------- 9 files changed, 10 insertions(+), 782 deletions(-) delete mode 100644 appium/webdriver/common/multi_action.py delete mode 100644 appium/webdriver/common/touch_action.py delete mode 100644 test/functional/android/multi_action_tests.py delete mode 100644 test/functional/android/touch_action_tests.py delete mode 100644 test/unit/webdriver/multi_action_test.py delete mode 100644 test/unit/webdriver/touch_action_test.py diff --git a/README.md b/README.md index 9cad05507..f15dd4fe7 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,15 @@ For example, some changes in the Selenium binding could break the Appium client. > to keep compatible version combinations. +### Quick migration guide from v3 to v4 +- Removal + - `MultiAction` and `TouchAction` are removed. Please use W3C WebDriver actions or `mobile:` extensions + - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) + - https://www.selenium.dev/documentation/webdriver/actions_api/ + - https://www.youtube.com/watch?v=oAJ7jwMNFVU + - https://appiumpro.com/editions/30-ios-specific-touch-action-methods + - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api + ### Quick migration guide from v2 to v3 - `options` keyword argument in the `webdriver.Remote` constructor such as `XCUITestOptions` instead of `desired_capabilities` - Available options are https://github.com/appium/python-client/tree/master/appium/options @@ -83,12 +92,6 @@ For example, some changes in the Selenium binding could break the Appium client. - Added `strict_ssl` to relax SSL errors such as self-signed ones - Deprecated - `MultiAction` and `TouchAction` are deprecated. Please use W3C WebDriver actions or `mobile:` extensions - - e.g. - - [appium/webdriver/extensions/action_helpers.py](appium/webdriver/extensions/action_helpers.py) - - https://www.selenium.dev/documentation/webdriver/actions_api/ - - https://www.youtube.com/watch?v=oAJ7jwMNFVU - - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api - `launch_app`, `close_app`, and `reset` are deprecated. Please read [issues#15807](https://github.com/appium/appium/issues/15807) for more details #### MultiAction/TouchAction to W3C actions diff --git a/appium/webdriver/common/multi_action.py b/appium/webdriver/common/multi_action.py deleted file mode 100644 index 3ab826a1c..000000000 --- a/appium/webdriver/common/multi_action.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/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. - - -# The Selenium team implemented something like the Multi Action API in the form of -# "action chains" (https://code.google.com/p/selenium/source/browse/py/selenium/webdriver/common/action_chains.py). -# These do not quite work for this situation, and do not allow for ad hoc action -# chaining as the spec requires. - -import copy -import warnings -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from appium.webdriver.mobilecommand import MobileCommand as Command - -if TYPE_CHECKING: - from appium.webdriver.common.touch_action import TouchAction - from appium.webdriver.webdriver import WebDriver - from appium.webdriver.webelement import WebElement - - -class MultiAction: - """ - deprecated:: 2.0.0 - Please use W3C actions instead: https://github.com/appium/python-client?tab=readme-ov-file#multiactiontouchaction-to-w3c-actions - """ - - def __init__(self, driver: 'WebDriver', element: Optional['WebElement'] = None) -> None: - warnings.warn( - '[Deprecated] \'MultiAction\' action is deprecated. Please use W3C actions instead.', DeprecationWarning - ) - - self._driver = driver - self._element = element - self._touch_actions: List['TouchAction'] = [] - - def add(self, *touch_actions: 'TouchAction') -> 'MultiAction': - """Add TouchAction objects to the MultiAction, to be performed later. - - Args: - touch_actions: one or more TouchAction objects describing a chain of actions to be performed by one finger - - 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) - - Returns: - `MultiAction`: Self instance - """ - for touch_action in touch_actions: - if self._touch_actions is None: - self._touch_actions = [] - - self._touch_actions.append(copy.copy(touch_action)) - - return self - - def perform(self) -> 'MultiAction': - """Perform the actions stored in the object. - - 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() - - Returns: - `MultiAction`: Self instance - """ - self._driver.execute(Command.MULTI_ACTION, self.json_wire_gestures) - - # clean up and be ready for the next batch - self._touch_actions = [] - - return self - - @property - def json_wire_gestures(self) -> Dict[str, Union[List, str]]: - actions = [] - for action in self._touch_actions: - actions.append(action.json_wire_gestures) - if self._element is not None: - return {'actions': actions, 'elementId': self._element.id} - return {'actions': actions} diff --git a/appium/webdriver/common/touch_action.py b/appium/webdriver/common/touch_action.py deleted file mode 100644 index c81190b0c..000000000 --- a/appium/webdriver/common/touch_action.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/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. - - -# The Selenium team implemented a version of the Touch Action API in their code -# (https://code.google.com/p/selenium/source/browse/py/selenium/webdriver/common/touch_actions.py) -# but it is deficient in many ways, and does not work in such a way as to be -# amenable to Appium's use of iOS UIAutomation and Android UIAutomator -# So it is reimplemented here. -# -# Theirs is `TouchActions`. Appium's is `TouchAction`. - -# pylint: disable=no-self-use - -import copy -import warnings -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from appium.webdriver.mobilecommand import MobileCommand as Command - -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - from appium.webdriver.webelement import WebElement - - -class TouchAction: - """ - deprecated:: 2.0.0 - Please use W3C actions instead: https://github.com/appium/python-client?tab=readme-ov-file#multiactiontouchaction-to-w3c-actions - """ - - def __init__(self, driver: Optional['WebDriver'] = None): - warnings.warn( - '[Deprecated] \'TouchAction\' action is deprecated. Please use W3C actions instead.', DeprecationWarning - ) - - self._driver = driver - self._actions: List = [] - - def tap( - self, - element: Optional['WebElement'] = None, - x: Optional[int] = None, - y: Optional[int] = None, - count: int = 1, - ) -> 'TouchAction': - """Perform a tap action on the element - - Args: - element: the element to tap - x : x coordinate to tap, relative to the top left corner of the element. - y : y coordinate. If y is used, x must also be set, and vice versa - - Returns: - `TouchAction`: Self instance - """ - opts = self._get_opts(element, x, y) - opts['count'] = count - self._add_action('tap', opts) - - return self - - def press( - self, - el: Optional['WebElement'] = None, - x: Optional[int] = None, - y: Optional[int] = None, - pressure: Optional[float] = None, - ) -> 'TouchAction': - """Begin a chain with a press down action at a particular element or point - - Args: - el: the element to press - x: x coordiate to press. If y is used, x must also be set - y: y coordiate to press. If x is used, y must also be set - pressure: [iOS Only] press as force touch. Read the description of `force` property on Apple's UITouch class - (https://developer.apple.com/documentation/uikit/uitouch?language=objc) for - more details on possible value ranges. - - Returns: - `TouchAction`: Self instance - """ - self._add_action('press', self._get_opts(el, x, y, pressure=pressure)) - - return self - - def long_press( - self, - el: Optional['WebElement'] = None, - x: Optional[int] = None, - y: Optional[int] = None, - duration: int = 1000, - ) -> 'TouchAction': - """Begin a chain with a press down that lasts `duration` milliseconds - - Args: - el: the element to press - x: x coordiate to press. If y is used, x must also be set - y: y coordiate to press. If x is used, y must also be set - duration: Duration to press, expressed in milliseconds - - Returns: - `TouchAction`: Self instance - """ - self._add_action('longPress', self._get_opts(el, x, y, duration)) - - return self - - def wait(self, ms: int = 0) -> 'TouchAction': - """Pause for `ms` milliseconds. - - Args: - ms: The time to pause - - Returns: - `TouchAction`: Self instance - """ - if ms is None: - ms = 0 - - opts = {'ms': ms} - - self._add_action('wait', opts) - - return self - - def move_to( - self, el: Optional['WebElement'] = None, x: Optional[int] = None, y: Optional[int] = None - ) -> 'TouchAction': - """Move the pointer from the previous point to the element or point specified - - Args: - el: the element to be moved to - x: x coordiate to be moved to. If y is used, x must also be set - y: y coordiate to be moved to. If x is used, y must also be set - - Returns: - `TouchAction`: Self instance - """ - self._add_action('moveTo', self._get_opts(el, x, y)) - - return self - - def release(self) -> 'TouchAction': - """End the action by lifting the pointer off the screen - - Returns: - `TouchAction`: Self instance - """ - self._add_action('release', {}) - - return self - - def perform(self) -> 'TouchAction': - """Perform the action by sending the commands to the server to be operated upon - - Returns: - `TouchAction`: Self instance - """ - if self._driver is None: - raise ValueError('Set driver to constructor as a argument when to create the instance.') - params = {'actions': self._actions} - self._driver.execute(Command.TOUCH_ACTION, params) - - # get rid of actions so the object can be reused - self._actions = [] - - return self - - @property - 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: str, options: Dict) -> None: - gesture = { - 'action': action, - 'options': options, - } - self._actions.append(gesture) - - def _get_opts( - self, - el: Optional['WebElement'] = None, - x: Optional[int] = None, - y: Optional[int] = None, - duration: Optional[int] = None, - pressure: Optional[float] = None, - ) -> Dict[str, Union[int, float]]: - opts = {} - if el is not None: - opts['element'] = el.id - - # it makes no sense to have x but no y, or vice versa. - if x is not None and y is not None: - opts['x'] = x - opts['y'] = y - - if duration is not None: - opts['duration'] = duration - - if pressure is not None: - opts['pressure'] = pressure - - return opts diff --git a/ci-jobs/functional_test.yml b/ci-jobs/functional_test.yml index 7efab21dd..b64a0b8e8 100644 --- a/ci-jobs/functional_test.yml +++ b/ci-jobs/functional_test.yml @@ -84,6 +84,6 @@ jobs: name: 'func_test_android8' vmImage: ${{ parameters.vmImage }} pytestOpt: ${{ parameters.pytestOpt }} - testFiles: 'network_connection_tests.py log_event_tests.py activities_tests.py hw_actions_tests.py touch_action_tests.py' + testFiles: 'network_connection_tests.py log_event_tests.py activities_tests.py hw_actions_tests.py' sdkVer: ${{ parameters.androidSdkVer }} CI: ${{ parameters.ci }} diff --git a/docs/webdriver.common.rst b/docs/webdriver.common.rst index 2814929da..c7e07fe54 100644 --- a/docs/webdriver.common.rst +++ b/docs/webdriver.common.rst @@ -12,22 +12,6 @@ webdriver.common.mobileby module :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 --------------- diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py deleted file mode 100644 index e2946c15e..000000000 --- a/test/functional/android/multi_action_tests.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/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. - -from time import sleep - -import pytest - -from appium.webdriver.common.appiumby import AppiumBy -from appium.webdriver.common.multi_action import MultiAction -from appium.webdriver.common.touch_action import TouchAction -from test.functional.test_helper import wait_for_element - -from .helper.test_helper import BaseTestCase, is_ci - - -class TestMultiAction(BaseTestCase): - def test_parallel_actions(self) -> None: - self._move_to_splitting_touches_accros_views() - - els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') - a1 = TouchAction() - a1.press(els[0]).move_to(x=10, y=0).move_to(x=10, y=-75).move_to(x=10, y=-600).release() - - a2 = TouchAction() - a2.press(els[1]).move_to(x=10, y=10).move_to(x=10, y=-300).move_to(x=10, y=-600).release() - - ma = MultiAction(self.driver, els[0]) - ma.add(a1, a2) - ma.perform() - - def test_actions_with_waits(self) -> None: - self._move_to_splitting_touches_accros_views() - - els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.ListView') - a1 = TouchAction() - a1.press(els[0]).move_to(x=10, y=0).move_to(x=10, y=-75).wait(1000).move_to(x=10, y=-600).release() - - a2 = TouchAction() - a2.press(els[1]).move_to(x=10, y=10).move_to(x=10, y=-300).wait(500).move_to(x=10, y=-600).release() - - ma = MultiAction(self.driver, els[0]) - ma.add(a1, a2) - ma.perform() - - def _move_to_splitting_touches_accros_views(self) -> None: - el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') - el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - self.driver.scroll(el1, el2) - - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Views') - action = TouchAction(self.driver) - action.tap(el).perform() - - # simulate a swipe/scroll - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.press(el).move_to(x=100, y=-1000).release().perform() - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Layouts') - action.press(el).move_to(x=100, y=-1000).release().perform() - - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Splitting Touches across Views') - action.tap(el).perform() - - wait_for_element(self.driver, AppiumBy.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=AppiumBy.ACCESSIBILITY_ID, value='Graphics') - action = TouchAction(self.driver) - action.tap(el).perform() - - wait_for_element(self.driver, AppiumBy.CLASS_NAME, 'android.widget.TextView') - els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') - self.driver.scroll(els[len(els) - 1], els[0]) - - els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') - if els[len(els) - 1].get_attribute('name') != 'Xfermodes': - self.driver.scroll(els[len(els) - 1], els[0]) - - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Touch Paint') - action.tap(el).perform() - - positions = [(100, 200), (100, 400)] - - # makes two dots in the paint program - # THE TEST MUST BE WATCHED TO CHECK IF IT WORKS - self.driver.tap(positions) - sleep(10) diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py deleted file mode 100644 index a10a4b202..000000000 --- a/test/functional/android/touch_action_tests.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/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 pytest -from selenium.common.exceptions import NoSuchElementException - -from appium.webdriver.common.appiumby import AppiumBy -from appium.webdriver.common.touch_action import TouchAction -from test.functional.test_helper import wait_for_element - -from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase, is_ci - - -class TestTouchAction(BaseTestCase): - def test_tap(self) -> None: - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - action = TouchAction(self.driver) - action.tap(el).perform() - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') - assert el is not None - - def test_tap_x_y(self) -> None: - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - action = TouchAction(self.driver) - action.tap(el, 100, 10).perform() - - el = wait_for_element(self.driver, AppiumBy.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=AppiumBy.ACCESSIBILITY_ID, value='Text') - action = TouchAction(self.driver) - action.tap(el).perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'LogTextBox') - action.tap(el).perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Add') - action.tap(el, count=2).perform() - - els = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value='android.widget.TextView') - 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=AppiumBy.ACCESSIBILITY_ID, value='Animation') - action = TouchAction(self.driver) - action.press(el).release().perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') - assert el is not None - - def test_press_and_immediately_release_x_y(self) -> None: - el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - action = TouchAction(self.driver) - action.press(el, 100, 10).release().perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Bouncing Balls') - assert el is not None - - def test_press_and_wait(self) -> None: - self._move_to_custom_adapter() - action = TouchAction(self.driver) - - el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') - action.press(el).wait(2000).perform() - - # 'Sample menu' only comes up with a long press, not a press - el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') - assert el is not None - - def test_press_and_moveto(self) -> None: - el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') - el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - - action = TouchAction(self.driver) - action.press(el1).move_to(el2).release().perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') - assert el is not None - - def test_press_and_moveto_x_y(self) -> None: - el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') - el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='App') - - action = TouchAction(self.driver) - action.press(el1).move_to(el2, 100, 100).release().perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Views') - assert el is not None - - def test_long_press(self) -> None: - self._move_to_custom_adapter() - action = TouchAction(self.driver) - - el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("People Names")') - action.long_press(el).perform() - - # 'Sample menu' only comes up with a long press, not a tap - el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, '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: - self._move_to_custom_adapter() - action = TouchAction(self.driver) - - # the element "People Names" is located at 430:310 (top left corner) - # location can be changed by phone resolusion, OS version - action.long_press(x=430, y=310).perform() - - # 'Sample menu' only comes up with a long press, not a tap - el = wait_for_element(self.driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Sample menu")') - assert el is not None - - def test_drag_and_drop(self) -> None: - self._move_to_views() - action = TouchAction(self.driver) - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Drag and Drop') - action.tap(el).perform() - - dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) - dd2 = self.driver.find_element(by=AppiumBy.ID, value='{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) - - # dnd is stimulated by longpress-move_to-release - action.long_press(dd3).move_to(dd2).release().perform() - - el = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_result_text'.format(APIDEMO_PKG_NAME)) - assert 'Dropped!' in el.text - - def test_driver_drag_and_drop(self) -> None: - self._move_to_views() - action = TouchAction(self.driver) - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'Drag and Drop') - action.tap(el).perform() - - dd3 = wait_for_element(self.driver, AppiumBy.ID, '{}:id/drag_dot_3'.format(APIDEMO_PKG_NAME)) - dd2 = self.driver.find_element(by=AppiumBy.ID, value='{}:id/drag_dot_2'.format(APIDEMO_PKG_NAME)) - - self.driver.drag_and_drop(dd3, dd2) - - el = wait_for_element(self.driver, AppiumBy.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=AppiumBy.ACCESSIBILITY_ID, value='Views') - action = TouchAction(self.driver) - action.tap(el).perform() - - with pytest.raises(NoSuchElementException): - self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='ImageView') - - self.driver.swipe(100, 1000, 100, 100, 800) - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, 'ImageView') - assert el is not None - - def _move_to_views(self) -> None: - el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Content') - el2 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Animation') - self.driver.scroll(el1, el2) - - el = wait_for_element(self.driver, AppiumBy.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, AppiumBy.ACCESSIBILITY_ID, 'Expandable Lists') - action.tap(el).perform() - - el = wait_for_element(self.driver, AppiumBy.ACCESSIBILITY_ID, '1. Custom Adapter') - action.tap(el).perform() diff --git a/test/unit/webdriver/multi_action_test.py b/test/unit/webdriver/multi_action_test.py deleted file mode 100644 index 4f956eaa8..000000000 --- a/test/unit/webdriver/multi_action_test.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/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 pytest - -from appium.webdriver.common.multi_action import MultiAction -from appium.webdriver.common.touch_action import TouchAction - - -class TestMultiAction(object): - @pytest.fixture - def multi_action(self): - return MultiAction(DriverStub()) - - def test_json(self, multi_action): - json = { - 'actions': [ - [ - {'action': 'press', 'options': {'element': 1}}, - {'action': 'moveTo', 'options': {'x': 10, 'y': 20}}, - {'action': 'release', 'options': {}}, - ], - [ - {'action': 'press', 'options': {'x': 11, 'y': 30, 'element': 5}}, - {'action': 'moveTo', 'options': {'x': 12, 'y': -300}}, - {'action': 'release', 'options': {}}, - ], - ] - } - t1 = TouchAction(DriverStub()).press(ElementStub(1)).move_to(x=10, y=20).release() - t2 = TouchAction(DriverStub()).press(ElementStub(5), 11, 30).move_to(x=12, y=-300).release() - multi_action.add(t1, t2) - assert json == multi_action.json_wire_gestures - - -class DriverStub(object): - def execute(self, _action, _params): - print("driver.execute called") - - -class ElementStub(object): - def __init__(self, e_id): - self._id = e_id - - @property - def id(self): - return self._id diff --git a/test/unit/webdriver/touch_action_test.py b/test/unit/webdriver/touch_action_test.py deleted file mode 100644 index 9bdb77848..000000000 --- a/test/unit/webdriver/touch_action_test.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/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 pytest - -from appium.webdriver.common.touch_action import TouchAction - - -class TestTouchAction(object): - @pytest.fixture - def touch_action(self): - return TouchAction(DriverStub()) - - def test_tap_json(self, touch_action): - json = [{'action': 'tap', 'options': {'count': 1, 'element': 1}}] - touch_action.tap(ElementStub(1)) - assert json == touch_action.json_wire_gestures - - def test_tap_x_y_json(self, touch_action): - json = [{'action': 'tap', 'options': {'x': 3, 'y': 4, 'count': 1, 'element': 1}}] - touch_action.tap(ElementStub(1), 3, 4) - assert json == touch_action.json_wire_gestures - - def test_press_json(self, touch_action): - json = [{'action': 'press', 'options': {'element': 1}}] - touch_action.press(ElementStub(1)) - assert json == touch_action.json_wire_gestures - - def test_press_pressure_json(self, touch_action): - json = [{'action': 'press', 'options': {'element': 1, 'pressure': 1.0}}] - touch_action.press(ElementStub(1), pressure=1.0) - assert json == touch_action.json_wire_gestures - - def test_press_x_y_json(self, touch_action): - json = [{'action': 'press', 'options': {'element': 1, 'x': 3, 'y': 4}}] - touch_action.press(ElementStub(1), 3, 4) - assert json == touch_action.json_wire_gestures - - def test_long_press_json(self, touch_action): - json = [{'action': 'longPress', 'options': {'element': 1, 'duration': 2000}}] - touch_action.long_press(ElementStub(1), duration=2000) - assert json == touch_action.json_wire_gestures - - def test_long_press_x_y_json(self, touch_action): - json = [{'action': 'longPress', 'options': {'element': 1, 'x': 3, 'y': 4, 'duration': 1000}}] - touch_action.long_press(ElementStub(1), 3, 4) - assert json == touch_action.json_wire_gestures - - def test_wait_json(self, touch_action): - json = [{'action': 'wait', 'options': {'ms': 10}}] - touch_action.wait(10) - assert json == touch_action.json_wire_gestures - - def test_wait_without_ms_json(self, touch_action): - json = [{'action': 'wait', 'options': {'ms': 0}}] - touch_action.wait() - assert json == touch_action.json_wire_gestures - - def test_move_to_json(self, touch_action): - json = [{'action': 'moveTo', 'options': {'element': 1, 'x': 3, 'y': 4}}] - touch_action.move_to(ElementStub(1), 3, 4) - assert json == touch_action.json_wire_gestures - - def test_release_json(self, touch_action): - json = [{'action': 'release', 'options': {}}] - touch_action.release() - assert json == touch_action.json_wire_gestures - - def test_perform_json(self, touch_action): - json_tap = [{'action': 'tap', 'options': {'element': 1, 'count': 1}}] - touch_action.tap(ElementStub(1)) - assert json_tap == touch_action.json_wire_gestures - touch_action.perform() - assert [] == touch_action.json_wire_gestures - - -class DriverStub(object): - def execute(self, _action, _params): - print("driver.execute called") - - -class ElementStub(object): - def __init__(self, e_id, _x=None, _y=None, _count=None): - self._id = e_id - - @property - def id(self): - return self._id From 3330b9ae75f67b8c1b572f837d7f3f1e2cfe7a82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:15:47 -0800 Subject: [PATCH 256/548] chore(deps-dev): update pylint requirement from ~=3.0.3 to ~=3.1.0 (#966) Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.3...v3.1.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c631e48e9..236a1ab2a 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ isort = "<6.0" mypy = "<2.0" mock = "~=5.1" pre-commit = "~=2.21" -pylint = "~=3.0.3" +pylint = "~=3.1.0" pylint-quotes = "~=0.2.3" pytest = "~=8.0" pytest-cov = "~=4.1" From 08d7fbb4c0346744d34c383ec849fcbcfedb0c09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:50:18 -0800 Subject: [PATCH 257/548] chore(deps-dev): update python-dateutil requirement from ~=2.8 to ~=2.9 (#967) Updates the requirements on [python-dateutil](https://github.com/dateutil/dateutil) to permit the latest version. - [Release notes](https://github.com/dateutil/dateutil/releases) - [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS) - [Commits](https://github.com/dateutil/dateutil/compare/2.8.0...2.9.0) --- updated-dependencies: - dependency-name: python-dateutil dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 236a1ab2a..ef405c1ba 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pylint = "~=3.1.0" pylint-quotes = "~=0.2.3" pytest = "~=8.0" pytest-cov = "~=4.1" -python-dateutil = "~=2.8" +python-dateutil = "~=2.9" tox = "~=4.12" types-python-dateutil = "~=2.8" From 706f3f5e91b666f91f197e9149d959d6126b8b44 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 2 Mar 2024 16:52:49 -0800 Subject: [PATCH 258/548] chore: remove deprecated AppiumBy.WINDOWS_UI_AUTOMATION (#968) --- appium/webdriver/common/appiumby.py | 2 - appium/webdriver/webdriver.py | 1 - .../webdriver/search_context/windows_test.py | 37 ------------------- 3 files changed, 40 deletions(-) delete mode 100644 test/unit/webdriver/search_context/windows_test.py diff --git a/appium/webdriver/common/appiumby.py b/appium/webdriver/common/appiumby.py index c4f4e3f3d..0836006d6 100644 --- a/appium/webdriver/common/appiumby.py +++ b/appium/webdriver/common/appiumby.py @@ -23,8 +23,6 @@ class AppiumBy(By): ANDROID_VIEWTAG = '-android viewtag' ANDROID_DATA_MATCHER = '-android datamatcher' ANDROID_VIEW_MATCHER = '-android viewmatcher' - # Deprecated - WINDOWS_UI_AUTOMATION = '-windows uiautomation' ACCESSIBILITY_ID = 'accessibility id' IMAGE = '-image' CUSTOM = '-custom' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index bf8f0a010..ed8acf455 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -245,7 +245,6 @@ def __init__( By.IOS_CLASS_CHAIN = AppiumBy.IOS_CLASS_CHAIN By.ANDROID_UIAUTOMATOR = AppiumBy.ANDROID_UIAUTOMATOR By.ANDROID_VIEWTAG = AppiumBy.ANDROID_VIEWTAG - By.WINDOWS_UI_AUTOMATION = AppiumBy.WINDOWS_UI_AUTOMATION By.ACCESSIBILITY_ID = AppiumBy.ACCESSIBILITY_ID By.IMAGE = AppiumBy.IMAGE By.CUSTOM = AppiumBy.CUSTOM diff --git a/test/unit/webdriver/search_context/windows_test.py b/test/unit/webdriver/search_context/windows_test.py deleted file mode 100644 index 2fcc099ef..000000000 --- a/test/unit/webdriver/search_context/windows_test.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/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 httpretty -from selenium.webdriver.common import by - -from appium.webdriver.common.appiumby import AppiumBy -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 TestWebDriverWindowsSearchContext(object): - @httpretty.activate - def test_find_element_by_windows_uiautomation(self): - driver = android_w3c_driver() - element = MobileWebElement(driver, 'element_id') - httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/element/element_id/element'), - body='{"value": {"element-6066-11e4-a52e-4f735466cecf": "win-element-id"}}', - ) - el = element.find_element(by=AppiumBy.WINDOWS_UI_AUTOMATION, value='win_element') - - d = get_httpretty_request_body(httpretty.last_request()) - assert d['using'] == '-windows uiautomation' - assert el.id == 'win-element-id' From aca359309c6e16c0848ca27489458035e72f0c4a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 3 Mar 2024 00:03:22 -0800 Subject: [PATCH 259/548] docs: update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f15dd4fe7..95b03e1f1 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ For example, some changes in the Selenium binding could break the Appium client. - https://www.youtube.com/watch?v=oAJ7jwMNFVU - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api + - Deprecated `AppiumBy.WINDOWS_UI_AUTOMATION`, which has no usage right now. ### Quick migration guide from v2 to v3 - `options` keyword argument in the `webdriver.Remote` constructor such as `XCUITestOptions` instead of `desired_capabilities` From 9136957c42190da413aa8ebf5173cbfe9b5fb39b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:45:16 -0800 Subject: [PATCH 260/548] chore(deps-dev): update pytest requirement from ~=8.0 to ~=8.1 (#969) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.1.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index ef405c1ba..5f4575cf1 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ mock = "~=5.1" pre-commit = "~=2.21" pylint = "~=3.1.0" pylint-quotes = "~=0.2.3" -pytest = "~=8.0" +pytest = "~=8.1" pytest-cov = "~=4.1" python-dateutil = "~=2.9" tox = "~=4.12" From 12200e7adf18cfe145a735de7f083974a19d902f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:02:31 -0800 Subject: [PATCH 261/548] chore(deps-dev): update tox requirement from ~=4.12 to ~=4.13 (#957) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.12.0...4.13.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 5f4575cf1..c13a05a8d 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pylint-quotes = "~=0.2.3" pytest = "~=8.1" pytest-cov = "~=4.1" python-dateutil = "~=2.9" -tox = "~=4.12" +tox = "~=4.13" types-python-dateutil = "~=2.8" [packages] From 6492c27339b661b4fcd518a92c1c45d288d9de88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:19:18 -0700 Subject: [PATCH 262/548] chore(deps-dev): update tox requirement from ~=4.13 to ~=4.14 (#972) 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/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.13.0...4.14.1) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c13a05a8d..1b9d61cc6 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pylint-quotes = "~=0.2.3" pytest = "~=8.1" pytest-cov = "~=4.1" python-dateutil = "~=2.9" -tox = "~=4.13" +tox = "~=4.14" types-python-dateutil = "~=2.8" [packages] From 7b3f0b6c5db311a2a5fbe1c6dba0ceb63e7972bd Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 11 Mar 2024 21:55:43 -0700 Subject: [PATCH 263/548] Bump 4.0.0 --- appium/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/version.py b/appium/version.py index 1f70aab66..7893f2490 100644 --- a/appium/version.py +++ b/appium/version.py @@ -1 +1 @@ -version = '3.2.1' +version = '4.0.0' From dd82fde1279302cc67e7bc70b23e29d51791e486 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 11 Mar 2024 21:56:24 -0700 Subject: [PATCH 264/548] Update changelog for 4.0.0 --- CHANGELOG.rst | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a371bb07..1d19212e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,88 @@ Changelog ========= +v4.0.0 (2024-03-12) +------------------- + +New +~~~ +- Feat: remove MultiAction and TouchAction (#960) [Kazuaki Matsuo] + + BREAKING CHANGE: Remove MultiAction and TouchAction as non-w3c WebDriver-defined methods. Please use w3c actions instead. + +Other +~~~~~ +- Bump 4.0.0. [Kazuaki Matsuo] +- Chore(deps-dev): update tox requirement from ~=4.13 to ~=4.14 (#972) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.13.0...4.14.1) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update tox requirement from ~=4.12 to ~=4.13 (#957) + [dependabot[bot], dependabot[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/main/docs/changelog.rst) + - [Commits](https://github.com/tox-dev/tox/compare/4.12.0...4.13.0) + + --- + updated-dependencies: + - dependency-name: tox + dependency-type: direct:development + ... +- Chore(deps-dev): update pytest requirement from ~=8.0 to ~=8.1 (#969) + [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. + - [Release notes](https://github.com/pytest-dev/pytest/releases) + - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) + - [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.1.0) + + --- + updated-dependencies: + - dependency-name: pytest + dependency-type: direct:development + ... +- Docs: update readme. [Kazuaki Matsuo] +- Chore: remove deprecated AppiumBy.WINDOWS_UI_AUTOMATION (#968) + [Kazuaki Matsuo] +- Chore(deps-dev): update python-dateutil requirement from ~=2.8 to + ~=2.9 (#967) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [python-dateutil](https://github.com/dateutil/dateutil) to permit the latest version. + - [Release notes](https://github.com/dateutil/dateutil/releases) + - [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS) + - [Commits](https://github.com/dateutil/dateutil/compare/2.8.0...2.9.0) + + --- + updated-dependencies: + - dependency-name: python-dateutil + dependency-type: direct:development + ... +- Chore(deps-dev): update pylint requirement from ~=3.0.3 to ~=3.1.0 + (#966) [dependabot[bot], dependabot[bot]] + + Updates the requirements on [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. + - [Release notes](https://github.com/pylint-dev/pylint/releases) + - [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.3...v3.1.0) + + --- + updated-dependencies: + - dependency-name: pylint + dependency-type: direct:development + ... +- Update changelog for 3.2.1. [Kazuaki Matsuo] + + v3.2.1 (2024-02-26) ------------------- From 1871e4af09dd54e66f4483d0951d1df09b60faa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:26:07 -0700 Subject: [PATCH 265/548] chore(deps-dev): update types-python-dateutil requirement (#973) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 1b9d61cc6..23bb604c4 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,7 @@ pytest = "~=8.1" pytest-cov = "~=4.1" python-dateutil = "~=2.9" tox = "~=4.14" -types-python-dateutil = "~=2.8" +types-python-dateutil = "~=2.9" [packages] selenium = "~=4.18" From 7bd1b0665028771d06036b2b31fe76d9d32490a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:00:25 -0700 Subject: [PATCH 266/548] chore(deps): update selenium requirement from ~=4.18 to ~=4.19 (#976) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.18.0...selenium-4.19.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 23bb604c4..c545aad5a 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.14" types-python-dateutil = "~=2.9" [packages] -selenium = "~=4.18" +selenium = "~=4.19" From 9e63569b570d7a897264110a18c621c7a25f72ae Mon Sep 17 00:00:00 2001 From: zeufack Date: Thu, 11 Apr 2024 20:44:56 +0200 Subject: [PATCH 267/548] chore: remove IOS_UIAUTOMATION (#979) BREAKING: remove obsolete IOS_UIAUTOMATION --- appium/webdriver/common/appiumby.py | 1 - appium/webdriver/webdriver.py | 1 - 2 files changed, 2 deletions(-) diff --git a/appium/webdriver/common/appiumby.py b/appium/webdriver/common/appiumby.py index 0836006d6..7632ce356 100644 --- a/appium/webdriver/common/appiumby.py +++ b/appium/webdriver/common/appiumby.py @@ -17,7 +17,6 @@ class AppiumBy(By): IOS_PREDICATE = '-ios predicate string' - IOS_UIAUTOMATION = '-ios uiautomation' IOS_CLASS_CHAIN = '-ios class chain' ANDROID_UIAUTOMATOR = '-android uiautomator' ANDROID_VIEWTAG = '-android viewtag' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index ed8acf455..92d61a44f 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -240,7 +240,6 @@ def __init__( self._update_command_executor(keep_alive=keep_alive) # add new method to the `find_by_*` pantheon - By.IOS_UIAUTOMATION = AppiumBy.IOS_UIAUTOMATION By.IOS_PREDICATE = AppiumBy.IOS_PREDICATE By.IOS_CLASS_CHAIN = AppiumBy.IOS_CLASS_CHAIN By.ANDROID_UIAUTOMATOR = AppiumBy.ANDROID_UIAUTOMATOR From cdc715b686e41ab39c61e37448b934f32aa498af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:39:58 -0700 Subject: [PATCH 268/548] chore(deps): update selenium requirement from ~=4.19 to ~=4.20 (#981) Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium) to permit the latest version. - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.19.0...selenium-4.20.0) --- updated-dependencies: - dependency-name: selenium dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index c545aad5a..0b43de18e 100644 --- a/Pipfile +++ b/Pipfile @@ -19,4 +19,4 @@ tox = "~=4.14" types-python-dateutil = "~=2.9" [packages] -selenium = "~=4.19" +selenium = "~=4.20" From 9c142b8916269e420e5feb11b869feabf3eb583b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:53:11 -0700 Subject: [PATCH 269/548] chore(deps-dev): update pytest requirement from ~=8.1 to ~=8.2 (#983) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.1.0...8.2.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 0b43de18e..e88000529 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ mock = "~=5.1" pre-commit = "~=2.21" pylint = "~=3.1.0" pylint-quotes = "~=0.2.3" -pytest = "~=8.1" +pytest = "~=8.2" pytest-cov = "~=4.1" python-dateutil = "~=2.9" tox = "~=4.14" From 328c8d3d941352503d9ff69baa9dddea40eddfe4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 2 May 2024 00:16:26 -0700 Subject: [PATCH 270/548] ci: add initial gha to run by manual (#984) --- .github/functional-test.yml | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/functional-test.yml diff --git a/.github/functional-test.yml b/.github/functional-test.yml new file mode 100644 index 000000000..034fe5f1a --- /dev/null +++ b/.github/functional-test.yml @@ -0,0 +1,67 @@ +name: Functional Tests + +on: + # Run by manual at this time + workflow_dispatch: + # push: + # branches: [ master ] + # pull_request: + # branches: [ master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + ios_test: + strategy: + fail-fast: false + matrix: + test_targets: + - target: search_context/find_by_*.py remote_fs_tests.py safari_tests.py execute_driver_tests.py + name: func_test_ios1 + + runs-on: macos-14 + + steps: + - uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 'lts/*' + + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 15.3 + - run: defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false + + - uses: futureware-tech/simulator-action@v3 + with: + # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md + model: 'iPhone 15 Plus' + os_version: '17.4' + + # Start Appium + - run: npm install -g appium + - run: | + appium driver install xcuitest + appium plugin install images + appium plugin install execute-driver + nohup appium --use-plugins=images,execute-driver --relaxed-security --log-timestamp --log-no-colors --base-path=/wd/hub > appium.log & + + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - run: python -m pytest ${{ test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + working-directory: test/functional/ios + + - name: Save server output + if: ${{ always() }} + uses: actions/upload-artifact@master + with: + name: appium-ios-${{matrix.test_targets.name}}.log + path: appium.log From 85e921c8a1149d27f5136f723140f9770ee692dd Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 2 May 2024 00:18:01 -0700 Subject: [PATCH 271/548] ci: move the file --- .github/{ => workflows}/functional-test.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/functional-test.yml (100%) diff --git a/.github/functional-test.yml b/.github/workflows/functional-test.yml similarity index 100% rename from .github/functional-test.yml rename to .github/workflows/functional-test.yml From 67a561d40b814b68b76381731a4da99805d79b3f Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 4 May 2024 11:38:13 -0700 Subject: [PATCH 272/548] docs: update docstring (#986) --- appium/webdriver/webdriver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 92d61a44f..b588f779d 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -118,7 +118,7 @@ def test_command(self, argument): def add_command(self): return ('post', 'session/$sessionId/path/to/your/custom/url') - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, + driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options, extensions=[YourCustomCommand]) # Then, the driver sends a post request to `session/$sessionId/path/to/your/custom/url` @@ -138,7 +138,7 @@ def custom_method_name(self, element_id): def add_command(self): return ('GET', 'session/$sessionId/path/to/your/custom/$id/url') - driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps, + driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options, extensions=[YourCustomCommand]) element = driver.find_elemeent(by=AppiumBy.ACCESSIBILITY_ID, value='id') From 5442e60b6c219cfb539d73c3d2277148b9f8311c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 7 May 2024 01:00:00 -0700 Subject: [PATCH 273/548] ci: use gha instead of Azure for iOS in Azure (#987) ci: use gha instead of Azure for iOS in Azure --- .github/workflows/functional-test.yml | 20 ++++++++--- ci-jobs/functional_test.yml | 16 --------- test/apps/UICatalog.app.zip | Bin 19414556 -> 1832361 bytes .../ios/helper/desired_capabilities.py | 4 +-- test/functional/ios/helper/test_helper.py | 2 ++ test/functional/ios/safari_tests.py | 32 ++++++++++++++++-- .../find_by_element_webelement_tests.py | 14 ++++---- .../find_by_ios_class_chain_tests.py | 14 ++++---- 8 files changed, 62 insertions(+), 40 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 034fe5f1a..bf8ac1238 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -18,8 +18,12 @@ jobs: fail-fast: false matrix: test_targets: - - target: search_context/find_by_*.py remote_fs_tests.py safari_tests.py execute_driver_tests.py + - target: test/functional/ios/search_context/find_by_*.py test/functional/ios/remote_fs_tests.py test/functional/ios/safari_tests.py test/functional/ios/execute_driver_tests.py name: func_test_ios1 + - target: test/functional/ios/applications_tests.py test/functional/ios/hw_actions_tests.py test/functional/ios/keyboard_tests.py + name: func_test_ios2 + - target: test/functional/ios/screen_record_tests.py test/functional/ios/webdriver_tests.py + name: func_test_ios3 runs-on: macos-14 @@ -43,21 +47,29 @@ jobs: model: 'iPhone 15 Plus' os_version: '17.4' + # needed? + - run: brew install ffmpeg + # Start Appium - run: npm install -g appium - run: | appium driver install xcuitest appium plugin install images appium plugin install execute-driver - nohup appium --use-plugins=images,execute-driver --relaxed-security --log-timestamp --log-no-colors --base-path=/wd/hub > appium.log & + nohup appium --use-plugins=images,execute-driver --relaxed-security --log-timestamp --log-no-colors > appium.log & - name: Set up Python 3.9 uses: actions/setup-python@v3 with: python-version: 3.9 - - run: python -m pytest ${{ test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html - working-directory: test/functional/ios + - run: | + # Separate 'run' creates differnet pipenv env. Does them in one run for now. + pip install --upgrade pip + pip install --upgrade pipenv + pipenv lock --clear + pipenv install -d --system + pytest ${{ matrix.test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html - name: Save server output if: ${{ always() }} diff --git a/ci-jobs/functional_test.yml b/ci-jobs/functional_test.yml index b64a0b8e8..e4cdf3cb1 100644 --- a/ci-jobs/functional_test.yml +++ b/ci-jobs/functional_test.yml @@ -6,22 +6,6 @@ parameters: CI: true jobs: - - template: ./functional/run_ios_test.yml - parameters: - name: 'func_test_ios1' - vmImage: ${{ parameters.vmImage }} - pytestOpt: ${{ parameters.pytestOpt }} - testFiles: 'search_context/find_by_*.py remote_fs_tests.py safari_tests.py execute_driver_tests.py' - CI: ${{ parameters.ci }} - xcodeForIOS: ${{ parameters.xcodeForIOS }} - - template: ./functional/run_ios_test.yml - parameters: - name: 'func_test_ios2' - vmImage: ${{ parameters.vmImage }} - pytestOpt: ${{ parameters.pytestOpt }} - testFiles: 'applications_tests.py hw_actions_tests.py keyboard_tests.py screen_record_tests.py webdriver_tests.py' - CI: ${{ parameters.ci }} - xcodeForIOS: ${{ parameters.xcodeForIOS }} - template: ./functional/run_android_test.yml parameters: name: 'func_test_android1' diff --git a/test/apps/UICatalog.app.zip b/test/apps/UICatalog.app.zip index 0b112d2eb87fc11629449246f1623d8d4b5da414..479d92e50238b615305a3b1d85f24eadc8ce0d5e 100644 GIT binary patch literal 1832361 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 z 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

    lfV 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

    Qh( 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