Files
agentic-mobile-control/openspec/changes/android-driver/tasks.md
T
q792602257andClaude Opus 4.6 50f0b8ade9
Tests / Test No test results found
feat(driver): add Android UiAutomator2 driver
Register `SUPPORTED_DRIVER_TYPES["uiautomator2"]` backed by the new
`AndroidDriver`, mirroring `WDADriver` method-for-method. tap/swipe/home use
the confirmed UiAutomator2 mobile commands (`clickGesture`, `dragGesture`,
`pressKey`); swipe converts `duration_ms` to a drag speed with a zero-distance
guard. Adds 34 mocked unit tests, an env-var-gated integration test, and
corrects the outdated "Android not registered" note in the iPhone setup doc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-13 14:47:01 +08:00

4.5 KiB

1. Appium UiAutomator2 command surface (confirmed via research, see design.md Decisions)

  • 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 (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():
    • 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

  • 3.1 Add build_android_driver_factory to driver/registry.py, mirroring build_wda_driver_factory's logic for splitting connection_info into declared AndroidDriverConfig fields vs. extra_capabilities.
  • 3.2 Register SUPPORTED_DRIVER_TYPES["uiautomator2"] = build_android_driver_factory.

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; swipe()'s duration_msspeed 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

  • 5.1 Add tests/test_android_integration.py mirroring tests/test_wda_integration.py's structure: @pytest.mark.integration, pytest.skip when APEX_ANDROID_SERVER_URL is unset, optional APEX_ANDROID_UDID/APEX_ANDROID_DEVICE_NAME, connects and asserts a non-empty screenshot() before disconnecting.

6. Spec and docs

  • 6.1 Confirm openspec/changes/android-driver/specs/driver-registry/spec.md's driver_type="uiautomator2" scenario still matches the shipped registry key and behavior exactly; update the delta if anything changed during implementation (e.g. the system-port capability name).
  • 6.2 Correct docs/MACOS_IPHONE_SETUP.md §1: replace "Android 只是架构上的未来目标,当前 driver/registry.py 没有注册 Android Driver" with an accurate statement that the Android driver is registered, while a full real-device setup guide remains separate follow-up work.

7. Verification

  • 7.1 Run uv run --all-packages pytest -m "not integration" and confirm no regressions.
  • 7.2 Run the project's lint/format checks against the new files and fix any violations.
  • 7.3 Run openspec validate android-driver --strict and confirm it passes.