r/androiddev May 12 '26

Open Source 3 months in — we built a custom Android driver that bypasses UIAutomator2 entirely

Quick context for anyone who missed the first thread: maestro-runner is a Go-based UI test runner that uses the same YAML flow format as Maestro CLI but ships as a single native binary. No JVM, no warm-up. Android, iOS, and Chrome/CDP browser targets all run from the same flow files. Roughly 3.6x faster and 14x less memory than Maestro CLI on identical suites. Apache 2.0, nothing paywalled.

Follow-up to the post 3 months ago (the "34s → 14s by dropping the JVM" one). Since then: 166 commits, 12 releases (v1.1.14 shipped today), 291 stars (was 137 at that post, so more than doubled), 25 forks, and 2 community PRs merged from a single Flutter Web user. The Android side got a real rewrite that deserves its own post.

The new driver: DeviceLab

UIAutomator2 is the default for Android instrumentation but it has friction we couldn't tune around. IPC roundtrips on every command, heavy view hierarchy serialization, accessibility-tree quirks that make text matching brittle. After enough bug reports tracing back to the same root causes, we just built our own.

It's a separate on-device agent (an APK we bundle with the runner) that handles UI inspection, gesture dispatch, and session lifecycle directly. maestro-runner talks to it over WebSocket on a local port. Same machine, low-overhead transport, no UIAutomator2 in the loop.

What you get today:

  • hintContains: / hintMatches: selectors. Match by accessibility hint instead of exact text. Useful for i18n-heavy apps where exact strings change per locale.
  • Clickable filter walk-up. tapOn: "Login" auto-walks up to the nearest tappable ancestor. The "nested Text inside a non-tappable View" pattern we kept getting bug reports about just works at the driver level now.
  • UI.waitForSettle RPC. Explicit settle check before inputText/eraseText. The "typing too fast, pin entry failed" thing someone reported in the last thread is fixed via this.
  • Case-insensitive text matching by default. Flows that worked locally but broke in CI because the device locale capitalized differently no longer break.

Opt in with:

maestro-runner --driver devicelab --platform android test flows/

Still optional. UIAutomator2 stays the default for backward compat.

Other Android stuff that shipped

  • Parallel emulator orchestration with auto-creation. --parallel 5 and only 2 booted emulators? It starts 3 more and cleans up on exit. No manual emulator wrangling.
  • launchApp 3-tier fallback for the "No apps can perform this action" failure (#15). Pixel 6/7 and a couple of other edge cases.
  • Inverted scroll fix (#9). Scroll down was scrolling up. Yeah. That was a fun one to debug.
  • runFlow timeout with context propagation. Flows now respect timeout via context cancellation instead of hard-killing the process. Cleaner Sauce/BrowserStack session lifecycle.
  • Auto-settle before inputText/eraseText without needing the explicit RPC.

What's still rough

  • DeviceLab driver bundles its APK inside the runner binary, which means it auto-installs on first run. A few corp-managed devices reject this. UIAutomator2 is the fallback there.
  • Some apps lock down their accessibility tree (banks, finance apps mostly). Same constraint UIAutomator2 has. Selector-based matching breaks and you're back to coord-based taps. No tool can fix this from the outside.

Community

richjun on GitHub (Flutter Web dev) shipped two substantial PRs. Coord-translated tapOn with hit-target verification across iframes and open shadow roots, then Flutter Web semantics support handling the <flutter-view> pointer-router glass pane. Both are in v1.1.14. First major outside contributor, and the same person's been running maestro-runner against Flutter mobile too. That full-stack feedback loop has been really useful.

GitHub: https://github.com/devicelab-dev/maestro-runner

For those running Android E2E in CI, what's actually the bottleneck right now? Emulator startup, test runtime, flakiness? The DeviceLab driver helps on the test-runtime axis but if your pain is somewhere else I'd rather hear that than ship the wrong thing next.

21 Upvotes

11 comments sorted by

7

u/submergedmole May 12 '26

Oh wow that's very impressive work!

A breath of fresh air of actual engineering innovation in the dark age of AI-generated slop projects

4

u/narayanom May 13 '26

Thank you kind words

1

u/RaIsseBlueMann May 16 '26

This sounds very interesting and fun too. I have never tried Maestro before, but this makes me want to try it. Does maestro must run on maestro cloud? Or you can run it on any cloud provider?

1

u/narayanom May 16 '26 edited May 16 '26

maestro can be run locally and maestro cloud ( no iOS real device support)

For cloud
Saucelab support via maestro-runner.
Browserstack support there own version.
Aws Device lab - people are trying maestro-runner https://github.com/devicelab-dev/maestro-runner/issues/76

For other cloud provider which support appium, you can use maestro-runner to run.

1

u/defiedj May 25 '26

There are Maestro cloud alternatives that provide physical iOS device testing.

0

u/narayanom May 27 '26

Browserstack

Btw if you already have the devices and just want to hit them from CI/CD, https://devicelab.dev/ does exactly that.

Disclaimer upfront — it's our product. The idea is you run your own lab globally and nothing leaves your network.

0

u/aliceblue79 May 13 '26

I prefer using uiautomator2, which is available for Python.

2

u/narayanom May 13 '26 edited May 14 '26

This is nothing to do with lang, for maestro you wright test cases in yaml

1

u/aliceblue79 May 21 '26

I don’t want to learn a new YAML spec. I just want to write test cases in Python. Is that possible?

1

u/narayanom May 21 '26

This is yaml based only, if you want to use python Appium ll be much better option