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
+11 -5
View File
@@ -24,20 +24,27 @@ This is exactly the case the project's Hexagonal + DDD governance decision calls
- **Registry key is `"uiautomator2"`, not `"android"`.** The existing key `"wda"` names the automation *backend* (WebDriverAgent), not the platform (`"ios"`). For the two supported driver types to stay consistent, Android's key should likewise name its backend — Appium's UiAutomator2 driver — not its platform. Rejected alternative: `"android"`, which would break that symmetry and would also be ambiguous if Espresso support is ever added later (both would be "android").
- **`AndroidDriverConfig` mirrors `WDADriverConfig` field-for-field where an Android equivalent exists**: `server_url` (same default `http://127.0.0.1:4723` — one Appium server can host sessions for both platforms), `platform_name="Android"`, `automation_name="UiAutomator2"`, `device_name`, `udid` (adb serial, selects among multiple connected devices), `no_reset`, `extra_capabilities`. `wda_local_port` (WDA's per-session port-isolation capability for parallel devices) has a direct UiAutomator2 analog — a system-port capability serving the same purpose — carried over as `system_port`. Rejected alternative: a from-scratch config shape — rejected because the parity makes both drivers predictable to configure from the same `connection_info` dict shape the registry already handles generically.
- **Same error-wrapping pattern as `WDADriver`**: every method's Appium/network exception is caught and re-raised as the existing `DriverError`, with `DeviceOfflineError` reserved for connect failures and pre-connect calls (via the same `_require_client()` guard pattern). Rejected alternative: introducing Android-specific error types — rejected because `core/errors.py`'s existing hierarchy is already driver-agnostic and callers above the driver layer must not need to know which concrete driver raised.
- **Exact Appium UiAutomator2 mobile-command names/parameters for gesture-based methods (`tap`, `swipe`, `home`) are confirmed against the installed `Appium-Python-Client` source/docs at implementation time, not guessed here.** WDA's `tap`/`swipe`/`home` use WDA-specific `mobile:` command names (`mobile: tap`, `mobile: dragFromToForDuration`, `mobile: pressButton`) that do not carry over verbatim to UiAutomator2, which exposes its own gesture command set (e.g. click/drag/swipe gesture commands) with different parameter shapes. Implementation must read the installed driver's actual capability before writing each method body — this is called out as its own task rather than assumed. `screenshot`/`tree`/`input`/`launch`/`terminate`/`lock`/`unlock` map to the same cross-platform Selenium/Appium client methods WDA already uses (`get_screenshot_as_png`, `page_source`, `switch_to.active_element.send_keys`, `activate_app`, `terminate_app`, `lock`, `unlock`) and need no research.
- **Appium UiAutomator2 mobile-command names/parameters for gesture-based methods (`tap`, `swipe`, `home`), confirmed via research against the official `appium-uiautomator2-driver` docs (2026-07)**: WDA's `tap`/`swipe`/`home` use WDA-specific `mobile:` command names (`mobile: tap`, `mobile: dragFromToForDuration`, `mobile: pressButton`) that do not carry over verbatim to UiAutomator2. The confirmed Android equivalents:
- `tap(x, y)``execute_script("mobile: clickGesture", {"x": x, "y": y})`. The driver's own docs recommend this over any legacy tap call as a workaround for native-tap failures, so it is also the more robust choice, not just the closest analog.
- `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})`. Unlike WDA's `dragFromToForDuration`, `dragGesture` takes a `speed` in pixels/second instead of a duration, so the implementation must convert: `speed = distance / (duration_ms / 1000)`, guarding against a zero/near-zero distance (fall back to the driver's default speed rather than dividing by zero). `mobile: swipeGesture` was considered and rejected — it takes a bounding-area + direction + percent shape, not a coordinate pair, so it does not match `Driver.swipe`'s signature.
- `home()``execute_script("mobile: pressKey", {"keycode": 3})` (Android `KeyEvent.KEYCODE_HOME`).
- The parallel-session port-isolation capability is confirmed as `appium:systemPort` (maps directly to `AndroidDriverConfig.system_port`), documented by the driver as "recommended for parallel tests" — the direct analog of WDA's `wdaLocalPort`.
- `screenshot`/`tree`/`input`/`launch`/`terminate`/`lock`/`unlock` map to the same cross-platform Selenium/Appium client methods WDA already uses (`get_screenshot_as_png`, `page_source`, `switch_to.active_element.send_keys`, `activate_app`, `terminate_app`, `lock`, `unlock`) and needed no research.
- Implementation should still sanity-check these against whatever `appium-uiautomator2-driver` version is actually resolved in `.venv` at coding time (docs reflect the driver's current released behavior as of this research, not a pinned version in this repo).
- **Appium 3 compatibility, confirmed via research (released 2025-08-07, latest 3.5.2 as of this research)**: Appium 3 is a deliberately small breaking-change release (Node.js/npm minimum version bump, mandatory feature-flag scope prefixes for `--allow-insecure`, `GET /sessions` moved to `GET /appium/sessions` behind a feature flag, full JSONWP removal in favor of W3C-only parameters, driver-owned file upload handling). None of these affect this change: `AndroidDriver` (like `WDADriver`) builds sessions purely through W3C `Options` classes, never relies on session discovery, requests no insecure feature flags, and does no file upload. `Appium-Python-Client>=5.1.1` (already pinned in root `pyproject.toml`) has no reported incompatibility with Appium 3 servers. No version pin changes are needed.
- **`driver-registry` spec gets a new scenario, not a new capability file.** No `wda-driver` capability spec exists today — the registry mechanism is spec'd once, generically, and individual driver behavior is not separately spec'd. Adding an `android-driver` capability would break that precedent for no benefit; instead, `driver-registry`'s existing "Building a factory for a known driver type" requirement gets a second scenario for `driver_type="uiautomator2"`, mirroring the existing `"wda"` scenario, so the spec's example coverage stays symmetric across both real driver types.
- **Test coverage exceeds current WDA parity on purpose.** `WDADriver` today has zero mocked unit tests — only `tests/test_wda_integration.py`, hardware-gated and skipped without `APEX_WDA_*` env vars. For `AndroidDriver`, add mocked unit tests (mock `appium.webdriver.Remote`) covering connect failure → `DeviceOfflineError`, pre-connect calls → `DeviceOfflineError`, and operation exceptions → `DriverError`, in addition to an equivalent hardware-gated `tests/test_android_integration.py`. This is a deliberate quality bar increase, not scope creep — it costs nothing extra in production code and closes a gap the WDA driver has always had.
- **No new setup documentation in this change.** Writing an accurate, detailed Android SDK/adb/real-device guide (parallel to `docs/MACOS_IPHONE_SETUP.md`'s 12 sections) without access to real hardware to validate each step would mean inventing untested instructions — the existing iOS doc reads as having been validated against a real device. `docs/MACOS_IPHONE_SETUP.md` §1 gets only a factual correction (Android driver is now registered); a full setup guide is explicit follow-up work once real-device validation is possible.
## Risks / Trade-offs
- [Appium UiAutomator2 mobile-command names for tap/swipe/home are not yet confirmed] → Mitigation: dedicated implementation task to read the installed `Appium-Python-Client` UiAutomator2 driver source/docs before writing these three method bodies; not treated as settled by this design doc.
- [`dragGesture`'s `speed` (px/s) is a different shape from `Driver.swipe`'s `duration_ms`] → Mitigation: convert explicitly (`speed = distance / (duration_ms / 1000)`) with a guard for zero/near-zero distance; cover this conversion with a unit test (start==end and a normal case) rather than trusting it silently.
- [No real Android device or emulator available during this change to exercise `connect()`/`screenshot()` end-to-end] → Mitigation: mocked unit tests cover the error-handling contract; the integration test is env-var gated and simply skips until real hardware is available, exactly mirroring `WDADriver`'s current state — no regression versus today's validation depth.
- [`driver-registry` spec now carries two platform-specific example scenarios under one requirement] → Mitigation: accepted; this is the intended pattern for a third driver type in the future, not a maintenance burden.
## Migration Plan
1. Add `driver/android_driver.py` (`AndroidDriverConfig`, `AndroidDriver(Driver)`), verifying exact UiAutomator2 mobile-command names/parameters against the installed Appium client before implementing `tap`/`swipe`/`home`.
1. Add `driver/android_driver.py` (`AndroidDriverConfig`, `AndroidDriver(Driver)`), using the confirmed UiAutomator2 mobile commands for `tap`/`swipe`/`home` (see Decisions), with a quick sanity check against whatever driver version is actually resolved in `.venv`.
2. Add `build_android_driver_factory` and register `SUPPORTED_DRIVER_TYPES["uiautomator2"]` in `driver/registry.py`.
3. Add mocked unit tests for `AndroidDriver` (connect failure, pre-connect calls, per-method exception wrapping).
4. Add `tests/test_android_integration.py`, mirroring `tests/test_wda_integration.py`'s env-var-gated structure.
@@ -49,5 +56,4 @@ Rollback: everything is additive (one new driver module, one new registry entry,
## Open Questions
- Exact UiAutomator2 mobile-command names/parameters for `tap`/`swipe`/`home` — resolved during implementation (task-level, not a proposal-level blocker).
- Exact capability key name for the system-port equivalent (`systemPort` or similar) — confirmed against `UiAutomator2Options` during implementation.
None outstanding. Both items originally listed here (exact UiAutomator2 mobile-command names/parameters for `tap`/`swipe`/`home`, and the system-port capability's exact key name) were resolved via research against the official `appium-uiautomator2-driver` docs — see the Decisions section.