Today I published version 2.3.0, a week after 2.2.0. This one is about the core loop: the agent's mobile QA loop is now faster, more precise and cheaper in tokens, and its verdicts are harder to fool.

It is live on GitHub and on npm as swipium@2.3.0.

If you are new to the project, the 1.0.0 launch post explains what Swipium is, and the 2.2.0 post covers Codex and headless runs. The docs cover setup.

Three calls to a tested app

Before: calling qa_test_this without a mode only returned a plan. The agent had to call it again with mode: "execute", poll for the result, and then read a long report.

Now: qa_test_this runs by default. A typical session is three calls: start the run and wait for it (in Claude Code one call waits up to about 105 seconds), poll once if it is still going, then read report.md, a short summary written for the agent.

qa_test_this { "waitForCompletion": true }

The plan is still there when you want it: pass mode: "plan" for a preview with no side effects.

Why: most of the time the agent wants the app tested, not a description of how it would be tested.

Faster everywhere the agent waits

Measured on an API 36 Android emulator and an iOS 26.5 simulator on an Apple Silicon Mac:

What2.2.02.3.0
Android tap to settled screen4.2 to 4.5 s2.2 to 2.3 s
Android app launch (qa_app_control)about 2.8 s0.3 to 0.5 s
Emulator boot to launcher21 to 25 s (cold)about 3.5 s (quick boot)
iOS screen read (page source)1.4 to 1.6 sabout 0.1 s (about 14x)
Android screen read, opt-in on-device dumper2.0 s0.28 s
qa_report result size11 KB data + 14 KB text4.8 KB + 4.7 KB

Where it comes from: an action now settles one screen read sooner, because the first screen that changed and shows nothing loading is accepted. App launches return as soon as the app is in front, instead of sleeping a fixed 2.5 seconds. Emulators quick-boot from their snapshot. And on iOS, Swipium asks WebDriverAgent to skip two expensive attributes, so reading the screen takes a tenth of a second.

The on-device Android dumper is off by default (SWIPIUM_ANDROID_DUMPER=agent turns it on). It is the fastest option, but it has trade-offs I list in the changelog, so it stays opt-in for now.

Taps land on the right thing

Before: element ids like @e3 could change between screen reads, and a tap based on an older read could hit the spot where an element used to be. Flows failed if their target was not on screen at the first look. And typed text that came out wrong was reported as typed, with a warning.

Now: element ids stay stable across screens. A target that moved or vanished is re-checked before tapping: a moved one is tapped at its new position, and a vanished one returns up to three look-alike candidates instead of hitting a stale spot. Flows wait up to 5 seconds for their targets. Typed text is read back, and if the field does not show what was typed, the call fails with TEXT_INPUT_MISMATCH.

Why: a wrong tap in QA either creates a bug that does not exist or hides one that does.

Cheaper in tokens

qa_report now returns a lean view: about 4.8 KB of data and 4.7 KB of text in a small session, down from 11 KB and 14 KB. The full report is still on disk, and format: "json" returns all of it. Errors lead with the failure code and are about half their old size. A snapshot of the same screen with diff: true returns only what changed.

Overall, results, errors and reports are about half their 2.2.0 size. The surface did not grow: still 55 tools, and now 7 prompts.

Verdicts you can trust

Before: a run could look green without really testing the app. In one pre-release test, an app launched onto a React Native RedBox error screen, the agent noted "pass", and the report said SHIP.

Now: a run that did not test the app is never SHIP. Only checks Swipium ran itself, that looked at the app's screen and found it healthy, count as testing. An agent saying "it passed" does not, so it is reported as an unverified claim, and CI gates using swipium report --fail-on-gate fail on untested runs.

Why: the worst thing a QA tool can say is SHIP when nothing was tested.

Safe on shared machines

SWIPIUM_DEVICE pins a server to one device: an adb serial, an AVD name, or a simulator UDID or name. Requests for any other device are refused before a consent prompt is even shown, so even a pre-approved destructive action like erasing a simulator cannot reach another one. Each iOS Simulator also gets its own WebDriverAgent port, so two simulators no longer fight over the same one.

One thing to know: WebDriverAgent's screen stream listens on every network interface, and WebDriverAgent has no setting to change that. Use a firewall on untrusted networks.

React Native aware

Swipium now catches three React Native problems instead of passing them: Metro serving a different project (METRO_WRONG_PROJECT), RedBox error screens, and apps stuck on their own loading screen (BLANK_SCREEN, "launch not verified").

Easier to start

There are now more ways in:

npx -y swipium init claude --apply

or, as a Claude Code plugin:

/plugin marketplace add GeroPalombo/swipium
/plugin install swipium@swipium

Setup also installs a swipium-qa agent skill that teaches the agent the loop. Swipium is listed in the MCP Registry, WebDriverAgent setup for iOS is one call (qa_wda setup), and swipium init claude --trust-local pre-approves the safe actions for fewer approval prompts on your own machine. Builds, Metro, WebDriverAgent and destructive taps still ask.

Tested like users use it

Before release, AI agents drove real Android emulators and iOS Simulators end to end, the way your agent would, in five rounds of testing. Every issue they found was fixed or listed under Known issues in the changelog. The RedBox example above is one of them.

Upgrading from 2.2

Swipium now needs Node.js 22.12 or newer. Restart your MCP client after upgrading. A few behaviors changed: qa_test_this runs by default, qa_report returns the lean view, CI gates fail on runs that tested nothing, and emulators Swipium boots now quick-boot from their snapshot (set SWIPIUM_EMULATOR_COLD_BOOT=1 for a clean cold boot). The full upgrade checklist is in the changelog.

Thoughts

In the 2.2.0 post I said I wanted to work more on the MCP side and on how Swipium communicates with agents. This release is mostly that: fewer calls, smaller answers, and verdicts the agent cannot talk its way around.

It is not perfect yet, but a lot of improvements went in. Something I still find hard to measure is whether a delay comes from the agent's processing, the simulator, or the hardware, and that is something I am working on. Even so, this version brings big improvements in timing, and it reaches the point of "ok, I'm happy with this", because the timing makes sense.

There are more things I would like to work on in the future. The next versions should bring some fixes and improvements, and maybe more progress on API analysis.