From 71a53ff97cc6dbad07d4d95589e72c50d68cdc0b Mon Sep 17 00:00:00 2001 From: Dor-bl <59066376+Dor-bl@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:31:35 +0000 Subject: [PATCH 1/3] refactor: remove legacy fallbacks for hw_actions extension Removed the legacy Appium extension presence checks and try-except fallbacks for `lock`, `unlock`, `is_locked`, `shake`, and `finger_print` in `HardwareActions`. The commands now exclusively rely on executing scripts with the `mobile:` prefix. Removed inheritance from `CanRememberExtensionPresence` and cleaned up tests to assert on the script execution endpoints. --- appium/webdriver/extensions/hw_actions.py | 55 +++---------------- .../unit/webdriver/device/fingerprint_test.py | 8 +-- test/unit/webdriver/device/lock_test.py | 36 ++++-------- test/unit/webdriver/device/shake_test.py | 4 -- 4 files changed, 23 insertions(+), 80 deletions(-) diff --git a/appium/webdriver/extensions/hw_actions.py b/appium/webdriver/extensions/hw_actions.py index b6bbb468b..54ccc6c42 100644 --- a/appium/webdriver/extensions/hw_actions.py +++ b/appium/webdriver/extensions/hw_actions.py @@ -14,17 +14,13 @@ from typing import Optional -from selenium.common.exceptions import UnknownMethodException from typing_extensions import Self 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 HardwareActions(CanExecuteCommands, CanExecuteScripts, CanRememberExtensionPresence): +class HardwareActions(CanExecuteCommands, CanExecuteScripts): def lock(self, seconds: Optional[int] = None) -> Self: """Lock the device. No changes are made if the device is already unlocked. @@ -39,11 +35,7 @@ def lock(self, seconds: Optional[int] = None) -> Self: """ 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) + self.execute_script(ext_name, args) return self def unlock(self) -> Self: @@ -53,13 +45,9 @@ def unlock(self) -> Self: Union['WebDriver', 'HardwareActions']: Self instance """ ext_name = 'mobile: unlock' - try: - if not self.assert_extension_exists(ext_name).execute_script('mobile: isLocked'): - return self - self.execute_script(ext_name) - except UnknownMethodException: - # TODO: Remove the fallback - self.mark_extension_absence(ext_name).execute(Command.UNLOCK) + if not self.execute_script('mobile: isLocked'): + return self + self.execute_script(ext_name) return self def is_locked(self) -> bool: @@ -69,11 +57,7 @@ def is_locked(self) -> bool: `True` if the device is locked """ 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'] + return self.execute_script(ext_name) def shake(self) -> Self: """Shake the device. @@ -82,11 +66,7 @@ def shake(self) -> Self: Union['WebDriver', 'HardwareActions']: Self instance """ 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) + self.execute_script(ext_name) return self def touch_id(self, match: bool) -> Self: @@ -125,25 +105,8 @@ def finger_print(self, finger_id: int) -> Self: """ 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) + self.execute_script(ext_name, args) return self def _add_commands(self) -> None: - self.command_executor.add_command(Command.LOCK, 'POST', '/session/$sessionId/appium/device/lock') - self.command_executor.add_command(Command.UNLOCK, 'POST', '/session/$sessionId/appium/device/unlock') - self.command_executor.add_command(Command.IS_LOCKED, 'POST', '/session/$sessionId/appium/device/is_locked') - self.command_executor.add_command(Command.SHAKE, 'POST', '/session/$sessionId/appium/device/shake') - self.command_executor.add_command(Command.TOUCH_ID, 'POST', '/session/$sessionId/appium/simulator/touch_id') - self.command_executor.add_command( - Command.TOGGLE_TOUCH_ID_ENROLLMENT, - 'POST', - '/session/$sessionId/appium/simulator/toggle_touch_id_enrollment', - ) - self.command_executor.add_command( - Command.FINGER_PRINT, - 'POST', - '/session/$sessionId/appium/device/finger_print', - ) + pass diff --git a/test/unit/webdriver/device/fingerprint_test.py b/test/unit/webdriver/device/fingerprint_test.py index 83a992ef8..93b027842 100644 --- a/test/unit/webdriver/device/fingerprint_test.py +++ b/test/unit/webdriver/device/fingerprint_test.py @@ -22,11 +22,6 @@ class TestWebDriverFingerprint: @httpretty.activate def test_finger_print(self): driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/appium/device/finger_print'), - # body is None - ) httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/execute/sync'), @@ -36,4 +31,5 @@ def test_finger_print(self): assert isinstance(driver.finger_print(1), WebDriver) d = get_httpretty_request_body(httpretty.last_request()) - assert d.get('fingerprintId', d['args'][0]['fingerprintId']) == 1 + assert d['script'] == 'mobile: fingerprint' + assert d['args'][0]['fingerprintId'] == 1 diff --git a/test/unit/webdriver/device/lock_test.py b/test/unit/webdriver/device/lock_test.py index 4c959dae9..80da467d8 100644 --- a/test/unit/webdriver/device/lock_test.py +++ b/test/unit/webdriver/device/lock_test.py @@ -22,35 +22,28 @@ class TestWebDriverLockAndroid: @httpretty.activate def test_lock(self): driver = android_w3c_driver() - 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.get('seconds', d['args'][0]['seconds']) == 1 + assert d['script'] == 'mobile: lock' + assert d['args'][0]['seconds'] == 1 @httpretty.activate def test_lock_no_args(self): driver = android_w3c_driver() - 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() @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 def test_islocked_true(self): driver = android_w3c_driver() - 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 @@ -58,10 +51,11 @@ def test_islocked_true(self): def test_unlock(self): driver = android_w3c_driver() httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/appium/device/unlock'), + httpretty.POST, appium_command('/session/1234567890/execute/sync'), responses=[ + httpretty.Response(body='{"value": true}'), + httpretty.Response(body='{"value": ""}'), + ] ) - httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.unlock(), WebDriver) @@ -69,35 +63,28 @@ class TestWebDriverLockIOS: @httpretty.activate def test_lock(self): driver = ios_w3c_driver() - 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.get('seconds', d['args'][0]['seconds']) == 1 + assert d['script'] == 'mobile: lock' + assert d['args'][0]['seconds'] == 1 @httpretty.activate def test_lock_no_args(self): driver = ios_w3c_driver() - 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() @httpretty.activate def test_islocked_false(self): driver = ios_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 def test_islocked_true(self): driver = ios_w3c_driver() - 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 @@ -105,10 +92,11 @@ def test_islocked_true(self): def test_unlock(self): driver = ios_w3c_driver() httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/appium/device/unlock'), + httpretty.POST, appium_command('/session/1234567890/execute/sync'), responses=[ + httpretty.Response(body='{"value": true}'), + httpretty.Response(body='{"value": ""}'), + ] ) - httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/execute/sync')) assert isinstance(driver.unlock(), WebDriver) @httpretty.activate diff --git a/test/unit/webdriver/device/shake_test.py b/test/unit/webdriver/device/shake_test.py index b12d791a7..8c796255a 100644 --- a/test/unit/webdriver/device/shake_test.py +++ b/test/unit/webdriver/device/shake_test.py @@ -23,10 +23,6 @@ class TestWebDriverShake: @httpretty.activate def test_shake(self): driver = android_w3c_driver() - httpretty.register_uri( - httpretty.POST, - appium_command('/session/1234567890/appium/device/shake'), - ) httpretty.register_uri( httpretty.POST, appium_command('/session/1234567890/execute/sync'), From 8440da7d58098bb3d2bb657d308b8e5fef81e41d Mon Sep 17 00:00:00 2001 From: Dor-bl <59066376+Dor-bl@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:33:53 +0000 Subject: [PATCH 2/3] refactor: remove legacy fallbacks for hw_actions extension Removed the legacy Appium extension presence checks and try-except fallbacks for `lock`, `unlock`, `is_locked`, `shake`, `finger_print`, `touch_id`, and `toggle_touch_id_enrollment` in `HardwareActions`. The commands now exclusively rely on executing scripts with the `mobile:` prefix. Removed inheritance from `CanRememberExtensionPresence` and cleaned up tests to assert on the script execution endpoints. --- test/unit/webdriver/device/lock_test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/webdriver/device/lock_test.py b/test/unit/webdriver/device/lock_test.py index 80da467d8..1294f5051 100644 --- a/test/unit/webdriver/device/lock_test.py +++ b/test/unit/webdriver/device/lock_test.py @@ -51,10 +51,12 @@ def test_islocked_true(self): def test_unlock(self): driver = android_w3c_driver() httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/execute/sync'), responses=[ + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + responses=[ httpretty.Response(body='{"value": true}'), httpretty.Response(body='{"value": ""}'), - ] + ], ) assert isinstance(driver.unlock(), WebDriver) @@ -92,10 +94,12 @@ def test_islocked_true(self): def test_unlock(self): driver = ios_w3c_driver() httpretty.register_uri( - httpretty.POST, appium_command('/session/1234567890/execute/sync'), responses=[ + httpretty.POST, + appium_command('/session/1234567890/execute/sync'), + responses=[ httpretty.Response(body='{"value": true}'), httpretty.Response(body='{"value": ""}'), - ] + ], ) assert isinstance(driver.unlock(), WebDriver) From 813c6651fa1443fc6487b21956cc5935dc58ccc5 Mon Sep 17 00:00:00 2001 From: Dor-bl <59066376+Dor-bl@users.noreply.github.com> Date: Mon, 10 Aug 2026 07:39:09 +0000 Subject: [PATCH 3/3] ci: fix missing flutter driver assets on latest release Update the functional-test.yml workflow to point to the specific 0.0.33 release instead of 'latest' because the latter is returning a 404 for the test applications. --- .github/workflows/functional-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index fecd4b1b1..c4d81cd1c 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -245,8 +245,8 @@ jobs: XCODE_VERSION: 16.4 IOS_VERSION: 18.5 IPHONE_MODEL: iPhone 16 - FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk" - FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/ios.zip" + FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/download/0.0.33/app-debug.apk" + FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/download/0.0.33/ios.zip" PREBUILT_WDA_PATH: ${{ github.workspace }}/wda/WebDriverAgentRunner-Runner.app steps: