There is a phone on a desk here, plugged into a USB port. Nobody touches it. An AI agent installs our apps on it, drives the screens, takes screenshots, looks at them, and says what is broken.
This started as a shortcut for producing store screenshots and turned into something more useful. Here is what works, what it found, and the part that went wrong.
Why real hardware and not an emulator
An emulator would have been easier. We tried it and moved away from it, for three reasons.
Emulators are slow unless you give them hardware acceleration you may not have spare. A real phone on the end of a cable answers immediately, and when the loop is capture, look, decide, tap, repeated a few hundred times, latency is the whole budget.
More importantly, the phone runs the manufacturer’s real operating system. Phone makers modify Android, and those modifications are exactly the kind of thing that breaks an install or a permission flow in the field. One of our test devices refuses a normal automated install and puts up its own confirmation dialog first. That is a genuine condition for a large share of real users, and no emulator would ever have shown it to us.
And an emulator cannot tell you that an app looks wrong. It renders pixels. The valuable part of our loop is a model looking at a screenshot and saying “the progress bar is squashed,” which needs a screenshot worth looking at.
The loop
It is unglamorous. Three steps, repeated.
Capture the screen as an image. Read the interface: on most apps the system can hand back a machine-readable list of every visible element with its label and position. Act by tapping a coordinate, then start again.
That middle step is where apps differ, and the difference matters more than it sounds. A normal app built from standard interface components describes itself fully, so navigation is exact. A game does not. Anything that paints to a single drawing surface exposes no structure at all, so the agent has nothing to read and must work purely from the picture, judging coordinates by eye. Two completely different approaches, and you want to know which one you are in before planning a test run.
Around that core we ended up with a handful of small scripts: unlock the device, install a build through the manufacturer’s dialog, find a settings row by its label, list the state of every toggle on a screen, and enter a credential without it ever appearing in a log. Each one exists because the agent kept needing it and kept doing it slightly differently.
What it actually caught
Two bugs that matter, both of which survived ordinary testing.
A proportion bug invisible on phones. One of our apps draws a row of small shapes as a progress indicator. The row sized itself as a percentage of screen width, but with a fixed height in code. On a phone the two numbers happen to produce the right shape. On a tablet-sized screen the row grows wider while the height stays put, and the shapes stretch into flattened ovals. Nobody had noticed, because nobody had looked at the app on a large screen. The agent was capturing screenshots at tablet size, looked at one, and said the shapes were wrong. The fix was to derive the height from the width. One commit.
A failure that only exists in release builds. A debug build started fine. The release build hung on launch, because the build step that strips unused resources removed an image that was only referenced from code it could not see, so initialisation waited for something that was no longer there.
This class of bug cannot be found in a debug build, by definition. It is also exactly the class that reaches customers, because customers get the release build. That produced a standing rule: test the release artifact whenever the project can produce one.
Screenshots for hardware you do not own
App stores want screenshots at tablet sizes. We do not own tablets.
You do not need them. Android lets you override the resolution and pixel density a device reports to apps. The app then lays itself out as though it were running on a larger screen, and the captured image comes out at the size the store asks for. Only the physical panel is unchanged.
Two things we got wrong before it worked. Density is not a cosmetic setting: pick the wrong value and the app reports a screen so large that a list page renders half empty, so the numbers need a sweep with something judging each result. And the app has to be fully restarted after the override, because a running app does not re-lay-out. Skip that and you capture the old layout scaled up, which looks convincing and is wrong.
Reset the override when finished. It survives reboots, and every later step that depends on screen coordinates will quietly misbehave until you do.
Guardrails matter more than you expect
An agent that can tap a real screen needs limits that are enforced, not assumed. Ours check the state of the device before every action: the right app has to be in the foreground before anything is captured, and returning to a known screen means closing and reopening the app rather than guessing at navigation.
That sounds fussy for a QA tool. It is the whole difference between automation you can leave running and automation you have to supervise. When you give an agent a tool that acts on the real world, the dangerous moments are not the ones where a command fails and something obviously breaks. They are the ones where every command succeeds and the context quietly stopped being what you assumed.
Is it worth it
For interface regressions and layout bugs on real hardware, yes. It found two genuine bugs that manual testing missed, and it produces every store screenshot we ship, including sizes for hardware we do not own.
It is not a replacement for a test suite. It is slow, every action is a round trip, and giving it something repetitive wastes time badly. We learned not to have it play a game to generate score data, for instance. A person does in ten seconds what costs the agent several minutes.
Treat it as an extra pair of eyes that never gets bored of looking at the same screen on the tenth run, which is the same argument we make for AI-assisted development generally. It works for the same reason: not because the machine is cleverer, but because it does the boring check every single time.
The apps it tests are on the apps page, and they are all built the same way: no tracking, no ads, nothing leaving the device.