# uiautomator2: a Python wrapper that talks HTTP to an on-device UiAutomator server

> The openatx/uiautomator2 package puts an HTTP service on the phone and drives it from Python, which is a different architecture from Appium's WebDriver stack. Here is what it does, how to install it, and where it stops being the right tool.

**openatx/uiautomator2** — Android Uiautomator2 Python Wrapper

- Repository: https://github.com/openatx/uiautomator2
- Stars: 8,402 · Forks: 1,602
- Language: Python
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/openatx-uiautomator2

## The problem uiautomator2 solves for Python test authors

Android's own UiAutomator is a Java API. Using it directly means writing Java instrumentation code, packaging it, and pushing it to the device before you can click a button. uiautomator2 exists to remove that step for people who write Python. The README describes it plainly as "a simple, easy-to-use, and stable Android automation library," and the repository topics are python, test and uiautomator, which matches the audience: test engineers, script authors and anyone doing device-side automation from a Python codebase. The stated dependencies are Android 4.4+ and Python 3.8+, so the floor is low enough to cover old lab devices. The unit of work is a device object. You connect to a phone, call methods on it, and read results back as Python values. There is no separate test runner imposed on you and no page-object framework to learn; the library is the transport and the element API, nothing more.

## How the device-side HTTP service and Python client fit together

The architecture is two processes. On the device, a jar file runs an HTTP service built on UiAutomator and exposes automation interfaces. On the host, the Python client speaks HTTP to that service. The README states this directly: "This framework mainly consists of two parts," device side and Python client, and that it "exposes Android automation capabilities to Python through HTTP interfaces." The jar is separately open source at openatx/android-uiautomator-server-jar, so the protocol boundary is inspectable rather than hidden inside the pip package. The practical consequence is that the Python process is thin. It does not hold a UiAutomator session in-process; it issues requests. That is why the README can claim Python-side code becomes "simpler and more intuitive," and it is also why connection setup matters more here than in an in-process binding. The default device port is 9008, and the README shows overriding it with `u2.connect('Q5S5T19611004599', port=9009)`. If that port is occupied or the service is not running on the device, the client has nothing to talk to. Element lookup happens on the device side; the Python side sends selectors and receives element data and screenshots.

## Installing uiautomator2 and running a first script

Installation is a single pip command. The README then suggests verifying it by asking the package for its version, which normally prints the library version:

```bash
pip install uiautomator2
uiautomator2 version
# or: python -m uiautomator2 version
```

Before any of that is useful, the phone must be prepared. The README says to enable Developer options, connect the device, and confirm that `adb devices` shows it. Then, from a Python interactive window, connect and print device info:

```python
import uiautomator2 as u2

d = u2.connect()  # Specify device serial number if multiple devices are connected
print(d.info)
```

The expected output is a dictionary with keys such as `currentPackageName`, `displayWidth`, `displayHeight`, `sdkInt` and `screenOn`. If you get that dictionary, the HTTP service is up and the client is talking to it. A first real script is also given in the README, and it is worth reading as a sequence rather than a snippet: start an app with `d.app_start('tv.danmaku.bili', stop=True)`, wait for an activity with `d.wait_activity('.MainActivityV2')`, sleep past a splash ad, click an element located by XPath, then read text out of another element.

```python
import uiautomator2 as u2

d = u2.connect('Q5S5T19611004599')
d.app_start('tv.danmaku.bili', stop=True)  # Start Bilibili
d.wait_activity('.MainActivityV2')
d.sleep(5)  # Wait for splash screen ad to disappear
d.xpath('//*[@text="我的"]').click()  # Click "My"
fans_count = d.xpath('//*[@resource-id="tv.danmaku.bili:id/fans_count"]').text
print(f"Fan count: {fans_count}")
```

The `d.sleep(5)` line is the honest part of this example. Waiting for a fixed number of seconds is how the README handles a splash ad, and that pattern does not scale to a suite of hundreds of tests. The XPath API offers better tools: `sl.click_exists()`, `sl.wait(timeout=15)`, `sl.wait_gone()` and `sl.get()` return or raise rather than blocking blindly, and `d.implicitly_wait(10.0)` sets the wait used by `click`, `long_click`, `drag_to`, `get_text`, `set_text` and `clear_text`. The default implicit wait is 20 seconds, so a missing element costs you twenty seconds before `UiObjectNotFoundError` surfaces. For element inspection during development, the README recommends the separate uiautodev tool, launchable with `npx uiautodev -open` or installed via `pip install uiautodev`, and lists uiautomatorviewer and Appium Inspector as alternatives.

## XPath selectors and the syntactic sugar that shortens them

XPath is the primary locating language here, and the README spends real space explaining it: `/` selects from the root, `//` selects from any position, `@` selects attributes, and `[]` is a predicate. The selector object is where the library earns its keep. `d.xpath('@personal-fm')` is documented as equivalent to `d.xpath('//*[@resource-id="personal-fm"]')`, a shorthand that removes most of the noise from resource-id lookups. The returned `XPathSelector` carries the waiting semantics: `click(timeout=10)` raises `XPathElementNotFoundError` if the element never appears, `click_exists()` returns a boolean instead, `wait()` returns an `XMLElement` or `None`, and `get()` raises. Having both raising and non-raising variants on the same object is a sensible split, because the correct behaviour depends on whether a missing element is a test failure or an expected branch. `set_text("")` clears a field and `set_text("hello world")` types into it. The README points to XPATH.md for the rest, and the repository also ships XPATH_CN.md, so the selector documentation is maintained in two languages alongside the two READMEs.

## Where uiautomator2 is the wrong choice

The most obvious boundary is iOS. The library is an Android UiAutomator wrapper, the README's dependency list begins at Android 4.4+, and nothing in the repository describes an iOS backend. A team that needs one script to cover both platforms will not get it here, regardless of how the package name reads.

The second boundary is the HTTP hop itself. Because the client talks to a service on the device over a port, anything that disturbs that channel disturbs your test: the service not running, the port taken, or the device connection dropping. An in-process binding has no such failure mode. This is a real trade-off the architecture makes in exchange for keeping the Python side small.

Third, the 2.x to 3.x transition is not silent. The README warns that users still on 2.x.x should check docs/2to3.md before upgrading, and notes that the upgrade is "highly recommended." A warning of that shape usually means API changes, so pinning a version and reading that document is cheaper than discovering the differences through failing tests. Finally, the README does not document rollback or downgrade procedures, so plan the version before you deploy it to a lab.

## uiautomator2 against Appium and against plain Java UiAutomator

The comparison people search for is uiautomator2 vs Appium, and the architectural difference is the whole story. Appium is a WebDriver server: a client library sends W3C WebDriver commands to a server process, which drives a platform-specific driver. Its uiautomator2 driver is one such driver, and it is not the same thing as this Python package even though the name overlaps. Appium's value is a uniform protocol across platforms and a large ecosystem of clients in many languages; its cost is the extra server process and the driver layer between your test and the device. This library removes both. There is no WebDriver server to start and no client protocol to conform to: you import `uiautomator2` in Python and call methods. The cost is that you get Python only, Android only, and a protocol of this project's own design rather than a standard one. If you need Ruby, Java or JavaScript clients, or you need iOS in the same suite, Appium's abstraction is worth its overhead. If your suite is Python and Android, Appium's driver is an extra hop between you and the same underlying UiAutomator.

The second alternative is writing UiAutomator in Java directly. That gives you the full framework with no HTTP boundary and no Python client, at the price of the Java build and instrumentation workflow the README is designed to spare you. The device-side jar is itself open source, so the middle path exists: read the server implementation when you need to know exactly what a Python call does on the device.

## Versioning, licence and the cost of keeping up

The package is MIT licensed, which the LICENSE file and the pyproject.toml `license = "MIT"` field both confirm. MIT is permissive, so embedding the library in a commercial test stack is not the kind of decision that requires a legal review of copyleft obligations. That is a statement about the licence text, not legal advice; your own counsel decides what your distribution does.

Version numbering is dynamic rather than hand-edited. The pyproject.toml enables `poetry-dynamic-versioning` with the pattern `^((?P<epoch>\d+)!)?(?P<base>\d+(\.\d+)*)`, and the substitution writes into `uiautomator2/version.py` at build time. In practice that means the version you see from `uiautomator2 version` is derived from the git tag, not from a constant someone remembered to bump. Recent releases include 3.7.0 on 2026-06-26, 3.6.0 on 2026-06-17 and 3.5.2 on 2026-05-28, and the last push to the repository was on 2026-09-11. The upgrade cost is mostly the 2.x to 3.x boundary documented in docs/2to3.md; within 3.x the release cadence above suggests small, frequent increments rather than long-lived branches. Dependencies are pinned loosely (`requests = "*"`, `lxml = "*"`, `Pillow = "*"`) except for `adbutils`, which is constrained to `>=2.11.0,<3`. That adbutils ceiling is the one dependency worth watching, because a major adbutils release would require a coordinated bump here.

## Conclusion

Adopt uiautomator2 when your automation is Python, your targets are Android 4.4 and newer, and you want element queries and XPath without a WebDriver server. Skip it for iOS, which it does not cover, and for teams already standardised on Appium's client libraries. Before committing, run `uiautomator2 version` after install, confirm `adb devices` lists your phone, and read docs/2to3.md if you are still on 2.x, because the jump to 3.x is not a drop-in patch.

## FAQ

### What is uiautomator2?

It is a Python wrapper for Android automation from the openatx organisation. A UiAutomator-based HTTP service runs on the device, and the Python client talks to it over HTTP to invoke automation functions.

### How do I install uiautomator2?

Run `pip install uiautomator2`, then check the install with `uiautomator2 version` or `python -m uiautomator2 version`, which normally prints the library version. It requires Python 3.8+ and an Android device running 4.4 or newer.

### How do I use uiautomator2 to drive a phone?

Enable Developer options, connect the device, and confirm `adb devices` shows it. Then create a device object with `u2.connect()` and call methods on it, such as `d.app_start(...)`, `d.xpath(...).click()` or `d(text="Settings").click()`.

### Is uiautomator2 the same as the Appium uiautomator2 driver?

No. This project is a Python client that talks directly to an HTTP service on the device. Appium's uiautomator2 driver sits behind a WebDriver server, which adds a server process and a driver layer that this library does not use.

### How do I install a specific version of uiautomator2?

The repository does not document a version-pinning command. Version numbers are generated from git tags by poetry-dynamic-versioning, and the README only gives the plain `pip install uiautomator2` form.

## Sources

- [Issues](https://github.com/openatx/uiautomator2/issues)
- [License: MIT](https://github.com/openatx/uiautomator2/blob/master/LICENSE)
- [openatx/uiautomator2 on GitHub](https://github.com/openatx/uiautomator2)
- [README](https://github.com/openatx/uiautomator2/blob/master/README.md)
- [Releases](https://github.com/openatx/uiautomator2/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openatx-uiautomator2
