Skip to content

How to Automate Android Apps Without Root

The best Android automation script is usually not clever.

It opens the app, taps the thing a human would tap, types the thing a human would type, and waits for the screen a human would recognize.

You can do that without rooting the phone.

For many app workflows, root is a distraction. You do not need filesystem access to the app sandbox. You do not need to patch the OS. You need a reliable way to say:

tap "Sign in"
fill "Email"
wait "Dashboard"

That is the shape Handsets is built for.

A real login flow

Here is a complete no-root login script:

#!/usr/bin/env bash
set -euo pipefail

hs use
hs wait idle 200ms

hs tap "Sign in" --visible --unique --timeout 5s
hs fill "Email" "$APP_EMAIL"
hs fill "Password" "$APP_PASSWORD"
hs tap "Continue" --visible --unique
hs wait "Dashboard" --timeout 15s

That script assumes three things:

  • The device is visible to adb.
  • USB debugging is enabled.
  • The app exposes normal Android UI nodes.

None of those require root.

Why text beats coordinates

Coordinate scripts look easy until the first device changes density.

adb shell input tap 540 860

That works on one screen. It is brittle everywhere else.

The better target is the thing the user sees:

hs tap "Continue"

Handsets resolves the label against the live Android UI tree, finds the matching node, and taps its center. If you want the script to fail when more than one thing matches, add --unique:

hs tap "Continue" --visible --unique

That is the kind of failure you can fix. A silent coordinate miss is much worse.

Waiting is part of automation

Most flaky Android scripts are not flaky because tapping is hard. They are flaky because waiting is sloppy.

This is fragile:

hs tap "Continue"
sleep 5
hs tap "Start"

This is better:

hs tap "Continue"
hs wait "Start" --timeout 15s
hs tap "Start"

The script waits for the state it actually needs, not for a guessed number of seconds. Fast devices move on quickly. Slow devices get the full timeout.

Use selectors when labels are not enough

Some screens have repeated labels: two "Continue" buttons, three "Edit" actions, a list of identical rows.

In that case, use a selector:

hs tap 'Button:has-text("Continue")' --visible --unique
hs fill 'EditText:below(TextView[text=Email])' "you@example.com"

This keeps the script readable while giving it enough structure to survive real layouts.

What this is good for

No-root app automation is a good fit for:

  • Login and onboarding smoke tests.
  • Mobile RPA flows.
  • Repetitive app setup on test devices.
  • LLM agents that need to drive a phone.
  • Internal tools that need a small Android control surface.

It is not a replacement for every test framework. Appium has a huge ecosystem. Espresso is excellent for in-app tests. Raw adb is always available.

Handsets sits in a smaller lane: fast shell-first control of a real Android UI, with no root and no app installed on the phone.

The useful minimum

If you remember only one pattern, use this:

hs use
hs ui
hs tap "Thing the user sees"
hs wait "Thing that should appear next"

That is enough to automate a surprising amount of Android work.