docs(openspec): confirm android-driver UiAutomator2 commands via research

Resolve the previously-open design questions in the android-driver
change by researching the appium-uiautomator2-driver docs and Appium 3
release notes:

- tap -> mobile: clickGesture
- swipe -> mobile: dragGesture (duration_ms converted to speed px/s)
- home -> mobile: pressKey (KEYCODE_HOME)
- port isolation -> appium:systemPort capability
- Appium 3 breaking changes confirmed to not affect this design

Updates design.md (Decisions/Risks/Open Questions/Migration Plan) and
tasks.md (section 1 and tasks 2.1/2.4/4.1) accordingly.
This commit is contained in:
2026-07-13 13:56:04 +08:00
parent e61dcca801
commit 62923b9285
2 changed files with 19 additions and 10 deletions
+8 -5
View File
@@ -1,13 +1,16 @@
## 1. Verify Appium UiAutomator2 command surface
## 1. Appium UiAutomator2 command surface (confirmed via research, see design.md Decisions)
- [ ] 1.1 Read the installed `Appium-Python-Client` UiAutomator2 driver source/docs (`appium.options.android.uiautomator2`, `appium.webdriver` extensions) to confirm the exact mobile-command names and parameters for: a tap/click gesture at a coordinate, a coordinate-to-coordinate swipe/drag with duration, and a home-button press. Confirm the exact capability key name for per-session port isolation (UiAutomator2's analog to WDA's `wdaLocalPort`).
- [ ] 1.1 Sanity-check the confirmed mobile commands against whatever `appium-uiautomator2-driver` version is actually resolved in `.venv` before coding: `mobile: clickGesture` (tap), `mobile: dragGesture` (swipe/drag), `mobile: pressKey` (home), `appium:systemPort` (port isolation capability). Docs referenced (2026-07): `github.com/appium/appium-uiautomator2-driver` README and `docs/android-mobile-gestures.md`.
## 2. Driver implementation
- [ ] 2.1 Add `driver/android_driver.py` with `AndroidDriverConfig` (frozen dataclass): `server_url` (default `http://127.0.0.1:4723`), `platform_name` (default `"Android"`), `automation_name` (default `"UiAutomator2"`), `device_name`, `udid`, `system_port`, `no_reset` (default `True`), `extra_capabilities`.
- [ ] 2.1 Add `driver/android_driver.py` with `AndroidDriverConfig` (frozen dataclass): `server_url` (default `http://127.0.0.1:4723`), `platform_name` (default `"Android"`), `automation_name` (default `"UiAutomator2"`), `device_name`, `udid`, `system_port` (maps to the `appium:systemPort` capability), `no_reset` (default `True`), `extra_capabilities`.
- [ ] 2.2 Implement `AndroidDriver(Driver).connect()`/`disconnect()` using `appium.webdriver` + `UiAutomator2Options`, building capabilities the same way `WDADriver.connect()` does, with the same `_require_client()` guard and `DeviceOfflineError` on connect failure.
- [ ] 2.3 Implement `screenshot()`, `tree()`, `input()`, `launch()`, `terminate()`, `lock()`, `unlock()` using the same cross-platform Appium client methods `WDADriver` already uses (`get_screenshot_as_png`, `page_source`, `switch_to.active_element.send_keys`, `activate_app`, `terminate_app`, `lock`, `unlock`).
- [ ] 2.4 Implement `tap()`, `swipe()`, `home()` using the mobile-command names/parameters confirmed in task 1.1.
- [ ] 2.4 Implement `tap()`, `swipe()`, `home()`:
- `tap(x, y)``execute_script("mobile: clickGesture", {"x": x, "y": y})`
- `swipe(start_x, start_y, end_x, end_y, duration_ms)``execute_script("mobile: dragGesture", {"startX": start_x, "startY": start_y, "endX": end_x, "endY": end_y, "speed": speed})` where `speed = distance / (duration_ms / 1000)`, guarded against zero/near-zero distance
- `home()``execute_script("mobile: pressKey", {"keycode": 3})` (`KeyEvent.KEYCODE_HOME`)
- [ ] 2.5 Wrap every method's underlying exception into `DriverError` (`DeviceOfflineError` for connect failure and for calls made before a client exists), matching `WDADriver`'s try/except-per-method pattern exactly.
## 3. Registry wiring
@@ -17,7 +20,7 @@
## 4. Unit tests
- [ ] 4.1 Add mocked unit tests for `AndroidDriver` (mock `appium.webdriver.Remote`, no real device/emulator) covering: connect builds a client with the expected capabilities from a given `AndroidDriverConfig`; connect failure raises `DeviceOfflineError`; calling any operation before `connect()` raises `DeviceOfflineError`; each operation's underlying exception is wrapped into `DriverError`.
- [ ] 4.1 Add mocked unit tests for `AndroidDriver` (mock `appium.webdriver.Remote`, no real device/emulator) covering: connect builds a client with the expected capabilities from a given `AndroidDriverConfig`; connect failure raises `DeviceOfflineError`; calling any operation before `connect()` raises `DeviceOfflineError`; each operation's underlying exception is wrapped into `DriverError`; `swipe()`'s `duration_ms``speed` conversion for both a normal case and a zero/near-zero-distance case (must not divide by zero).
- [ ] 4.2 Add a unit test for `build_android_driver_factory` covering `connection_info` field extraction and `extra_capabilities` merging (mirror `build_wda_driver_factory`'s existing test coverage if any exists; if none exists today, note that in the test file rather than silently skipping equivalent WDA coverage).
## 5. Integration test