Today I published version 2.0.0. It has been about two months since 1.5.0, and most of that time went into using Swipium on real apps, on real emulators and simulators, and fixing what broke along the way.

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

This is a major version. It removes and renames some tools and tightens several defaults, so upgrading takes a few steps, which I cover at the end. If you are new to the project, the 1.0.0 launch post explains what Swipium is and why it exists, and the 1.5.0 post explains the last big cleanup. The docs cover setup.

It types and swipes correctly now

Before: the basics could fail quietly. On Android, a password like P@ss&w0rd! was cut at the &, so the login failed and Swipium reported an app bug that did not exist. For a QA tool that is the worst kind of failure: one false bug makes you doubt every real one. Swipes used fixed coordinates tuned for one Android screen size, which landed off-screen on iPhones. And tapping a button hidden behind the soft keyboard could type into the field instead of pressing the button.

Now: text is typed exactly, special characters included, and text that the device cannot receive fails with a clear TEXT_INPUT_UNSUPPORTED error instead of being mistyped. Swipes and scrolls use the live screen size and rotation, and start inside the list instead of the middle of the screen. When the keyboard covers the target, Swipium hides it, finds the target again, and only then taps. Switches and checkboxes are no longer toggled back by a retry, and deep links with & or spaces reach the app intact.

Why: everything else, flows, reports, generated suites, sits on top of typing and swiping. If those are wrong, the whole tool looks flaky, and the agent blames the app.

iOS without WebDriverAgent: qa_visual

On iOS, taps, typing, and reading the UI tree need WebDriverAgent, the on-simulator automation server that Appium also uses. It has to be built with Xcode, and that is often the step where people give up.

Before: without WebDriverAgent, a simulator session could take screenshots and not much more, and qa_test_this stopped with a blocker. Visual assertions lived in a separate tool.

Now: one tool, qa_visual, works only from screenshots and has five modes: assert, baseline, diff, find_text, and find_image. The two find modes return tappable coordinates and can tap them directly:

qa_visual {
  "sessionId": "...",
  "mode": "find_text",
  "query": "General",
  "tap": true
}

find_text needs a local OCR command, and Swipium gives you a ready tesseract script the first time you call it without one. Tapping on a simulator without WebDriverAgent uses idb. qa_test_this no longer blocks either: without WebDriverAgent it runs a visual-only smoke check.

When WebDriverAgent is there, it is much more stable. A resumed session goes back to its own simulator, a WebDriverAgent started by a previous server run is reused instead of rebuilt, and an expired WebDriverAgent session recovers without relaunching your app.

Why: it makes iOS useful before anyone touches Xcode, and it follows the rule from 1.5: one tool with modes, not five tools.

60 tools down to 55, and agents get lost less

In 1.5.0 I went from 95 tools to 60. This time the change is smaller in numbers but bigger in how the agent behaves.

Before: five different tools answered some version of "where am I and what should I do next?". Some sessions went in circles, with the status tool suggesting the same smoke test again after it had already run. Worse, unknown arguments were ignored silently. During device testing, an agent called qa_app_control with an appId of another app, the argument was dropped, and Swipium force-stopped the app under test instead.

Now: qa_status is the one place to ask, and it follows the last run, so after a finished job it points to the report or the blocker instead of repeating advice. The operating guide is sent to the client when it connects. Unknown arguments are rejected with INVALID_ARGUMENT and the list of accepted ones, and calling a removed tool returns STALE_CLIENT with the name of its replacement. Every tool is labeled read-only or not, so clients can auto-approve the safe ones, and responses are smaller: a 32-element screen snapshot is less than half its old size.

Setup is simpler too. swipium init now also covers Cursor and VS Code, and writes a portable config you can commit for the team. A server started inside an app folder finds the project on its own, and Android tools are found even when the client was not launched from a terminal.

Why: same lesson as 1.5. The agent does better with fewer, clearer choices and with answers that tell it exactly what to do next.

Secrets stay secret, and a person approves risky actions

Swipium types real credentials, builds your app, and installs things on your machine. People need to trust what it does and what it writes into their repository.

Before: consent could be approved without a person ever seeing a prompt, and a password typed into a normal text field could end up in plain text in a generated flow or in saved session state.

Now: on clients that support it, risky actions like builds, installs, and data wipes are approved in a real prompt shown to you, and dismissing the prompt counts as no. Every app install asks for consent. Passwords, PINs, and codes are kept out of errors, reports, generated tests, and saved state, and if a generator would write a secret, it stops and writes nothing. A cloned repository is treated as untrusted: its flows can only read environment variables that start with SWIPIUM_, and the consent prompt shows the exact commands it wants to run.

One honest limit: screenshots are not redacted. When that matters, start the session with sensitive: true and Swipium refuses screen captures.

Why: a tool that runs on your machine with your credentials should never surprise you.

From my machine to the pipeline

In the launch post I wrote about error and fix tracking, and reports that grow as Swipium learns about the app. 2.0.0 takes two real steps there.

Issue memory: in 1.5, qa_issue_log could only show history. Now it covers the full lifecycle: log an issue, mark it fixed with the commit and version, verify the fix with evidence, suppress known noise with an expiry date, and get metrics. If an issue marked as fixed shows up again, it is flagged as a regression.

Swipium CI Pipelines: reports now export as JUnit, SARIF (which shows up in GitHub code scanning), and a GitHub job summary. A new swipium report command renders the last report and can fail the job when the release gate says block:

npx swipium report --latest --format junit --out swipium.xml --fail-on-gate

To be clear about what this is: the CLI does not drive devices. CI still needs an agent, for example headless Claude Code, calling the Swipium tools, and since nobody can approve consent in CI, you install the app before the agent step. The CI reports guide has a complete GitHub Actions recipe with an Android emulator, headless Claude Code, and the report upload.

Why: it moves Swipium from "QA on my machine" toward "QA in my pipeline".

A more reliable release

As in 1.5, the other half of this version is how it was tested.

Tests. The suite went from 87 tests in 1.5.0 to 899, many of them driving the full server with simulated devices.

Real devices. Before publishing, I ran Swipium on an Android 16 emulator and an iOS 18 Simulator. That found bugs no unit test had caught: a cancelled exploration was recorded as a release-blocking finding, a WebDriverAgent from a previous run was never reused because Xcode relaunches it under a different path, and the appId bug above. Every failure was fixed and checked again on the devices.

Release check. The publish pipeline caught a Linux-only bug, where a Metro process started through npx was not always cleaned up after a crash. It was fixed before anything reached npm.

Docs. The documentation was rewritten against the code, with new pages for concepts, flows, CI reports, and physical devices, and an upgrade checklist.

Upgrading from 1.5

2.0.0 is not a drop-in upgrade. The short version: restart your MCP client after upgrading, rename the environment variables your flows read so they start with SWIPIUM_, install the app yourself before the agent step in CI, and replace the six removed or merged tools. If an old tool name is still in a saved prompt, the STALE_CLIENT error tells you what to use instead.

The full upgrade checklist and migration table are in the changelog.

Thoughts

The purpose of this version was to fix everything that kept breaking with the use I gave it, and to simplify the things I started noticing were redundant. Fixing the basics is less fun than adding features, but I think it matters more.

In 1.5.0 I explained that having close to 100 tools does not give you 100 capabilities. Many of them were redundant, several shared the same functionality, and above all, the agent is not going to read through 100 tools before deciding what to do. In this version I narrowed the work down to 55 tools, and they will probably keep going down as I polish them further.

The one big new addition is Swipium CI Pipelines: the report exports and the swipium report command described above, which let a Swipium run gate a build in your pipeline. It is the part I most want to keep building on.

There are still limits. Physical devices are out of scope and Swipium refuses them on purpose for now, and on slow emulators a tap that opens a new screen can take several seconds. The ideas from the launch post are still on the list: real devices, security testing, performance testing, and UX suggestions.

As always, I will write another post when there is a new version. If you try 2.0.0 and something breaks, or works better than you expected, I would like to hear about it.