Skip to content

How to Run Mobile QA Tests Without Rooted Phones

A QA phone should look like a user's phone.

That sounds obvious, but test labs drift. Devices get rooted. System images get patched. Helper apps pile up. The lab becomes easier to automate and harder to trust.

For many mobile QA checks, you can keep the phones normal.

Use adb. Drive the UI. Wait for real screens. Save artifacts when something breaks.

No root required.

The QA check

Here is a small release check for a shopping app:

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

hs use
hs go com.example.shop

hs wait "Search" --timeout 15s
hs fill "Search" "coffee grinder"
hs submit

hs wait "coffee grinder" --timeout 10s
hs tap "Add to cart" --visible --unique
hs wait "Cart" --timeout 10s

It is not trying to inspect app internals. It is checking whether the user path works:

  1. Open the app.
  2. Search for a product.
  3. Add it to the cart.
  4. Verify the cart state appears.

That is a good no-root QA test.

Why this works without root

Android exposes enough control to the shell user for ordinary UI automation. A tool can read the visible UI tree, tap coordinates, type text, launch packages, and capture screenshots without changing the device.

Handsets wraps those pieces in commands that read like the test:

hs tap "Add to cart"
hs wait "Cart"

The phone remains a normal phone. The app remains a normal app.

Make failures useful

QA tests should fail with evidence.

Add a trap that captures the current state:

ARTIFACTS="/tmp/mobile-qa-$$"
mkdir -p "$ARTIFACTS"

trap 'hs ui > "$ARTIFACTS/ui.txt"; hs logs --tail 200 > "$ARTIFACTS/logs.txt"; hs see --size 768 "$ARTIFACTS/screen.jpg"; echo "$ARTIFACTS"' ERR

Now a failure leaves behind:

  • The visible UI text.
  • A recent log tail.
  • A screenshot.

That is enough for a human to understand most smoke-test failures.

Keep the test human-shaped

The strongest no-root tests are written in the language of the interface.

Prefer this:

hs tap "Add to cart" --visible --unique
hs wait "Cart"

Over this:

adb shell input tap 983 1842
sleep 5

The first version says what the test means. The second version says where one button happened to be on one device.

When labels are repeated, make the selector more specific:

hs tap 'Button:has-text("Add to cart")' --visible --unique

The test stays readable, but it is less likely to hit the wrong node.

Where this fits

No-root mobile QA is a good fit for:

  • Release smoke tests.
  • Signup and login checks.
  • Search and checkout paths.
  • Push notification flows.
  • Basic app-health monitoring on real devices.

It is not a replacement for every layer of testing. Unit tests, integration tests, Espresso tests, and Appium suites all have their place.

This layer answers a simpler question:

Can a normal phone still complete the path a user cares about?

That question is worth asking on every release.

The short script

For a lot of teams, the starting point can be this small:

hs use
hs go com.example.app
hs wait "Home" --timeout 15s
hs tap "Primary action" --visible --unique
hs wait "Success" --timeout 15s

That is mobile QA without rooted phones: ordinary devices, ordinary UI, explicit waits, and useful artifacts when reality disagrees.