---
title: "Testing iOS and Android apps with Claude Code"
description: "Test iOS and Android apps with Claude Code: drive simctl and adb, write XCUITest and Espresso tests, add a mobile MCP server, and run repeatable tests."
canonical: "https://momentic.ai/blog/claude-code-mobile-app-testing"
last-updated: "2026-10-31T09:50:51Z"
---

# Testing iOS and Android apps with Claude Code

URL: https://momentic.ai/blog/claude-code-mobile-app-testing

[blog](/blog) [/ resources](/blog/category/resources)  / claude-code-mobile-app-testing

Resources

Four ways to test iOS and Android apps with Claude Code, from simctl and adb to XCUITest, Espresso, mobile MCP servers and Momentic mobile tests.

Wei-Wei Wu

CEO, Momentic

## TL;DR

- Claude Code can run `xcrun simctl` and `adb` to boot a simulator or emulator, install an app, and capture output. Those commands alone do not provide reliable in-app assertions.
- Claude Code can write XCUITest and Espresso tests, but generated code still needs to compile against your project. Missing accessibility identifiers, waits, and device state can make a passing test flaky.
- A mobile MCP server can expose screenshots, element trees, and taps to Claude Code. The agent still needs a test definition and a way to reproduce failures.
- The Momentic mobile skill starts with `npx momentic-mobile skills --yes` and lets the agent work with mobile tests. Momentic runs on local or hosted iOS simulators and Android emulators, not physical devices.

## Why mobile testing is a harder target for a coding agent than web

Claude Code can run commands against a mobile simulator or emulator, but it cannot inspect and control an app through a browser DOM. In a [browser test](/blog/claude-code-for-end-to-end-testing), Playwright can locate elements and act on them through a browser connection. Mobile testing requires Claude Code to work through an app build, a device runtime, and tools that expose what appears on screen.

On iOS, Claude Code can call `xcrun simctl` to boot a simulator, install an app, and capture a screenshot. On Android, it can use `adb` to install an APK, start an activity, and inspect device output. Those commands give the agent useful access, but a successful install or launch does not verify that an in-app flow works. For repeatable interactions, Claude Code must use another interface or generate XCUITest or Espresso code that compiles in the app’s test target.

Each layer adds a failure mode you need to separate from an app regression. A changed Xcode scheme or Android build configuration can stop the test before launch. A simulator that has not booted can reject an otherwise valid command. Once a test runs, a fixed wait may pass on one device and fail when rendering takes longer on another.

## Driving xcrun simctl from Claude Code

Claude Code can script an iOS simulator’s boot, app installation, launch, and evidence capture through `xcrun simctl`. Before it runs those commands, confirm the simulator UDID and Xcode scheme. An agent can list both, but it cannot know which device or scheme you intend to test when a repo contains several.

For an Xcode project named `Demo.xcodeproj` with a shared `Demo` scheme, the following sequence builds for the selected simulator and captures a screenshot and recent app logs.

`xcrun simctl list devices available xcodebuild -list -project Demo.xcodeproj  UDID="PASTE-CONFIRMED-SIMULATOR-UDID" xcrun simctl boot "$UDID" xcrun simctl bootstatus "$UDID" -b  xcodebuild -project Demo.xcodeproj -scheme Demo \  -configuration Debug -sdk iphonesimulator \  -destination "platform=iOS Simulator,id=$UDID" \  -derivedDataPath "$PWD/build" build  APP="$PWD/build/Build/Products/Debug-iphonesimulator/Demo.app" BUNDLE_ID=$(/usr/libexec/PlistBuddy -c \  'Print CFBundleIdentifier' "$APP/Info.plist")  xcrun simctl install "$UDID" "$APP" xcrun simctl launch "$UDID" "$BUNDLE_ID" mkdir -p artifacts xcrun simctl io "$UDID" screenshot artifacts/launch.png xcrun simctl spawn "$UDID" log show --last 2m \  --style compact --predicate "process == \"Demo\"" \  > artifacts/app.log`

Replace the project and scheme names with those in your repo. If the simulator is already booted, skip `simctl boot`. The screenshot shows what appeared after launch, and the log file gives Claude Code output to inspect when the app fails to start.

`simctl` controls the simulator and the app lifecycle, but these commands do not tap an in-app button or assert that a screen works. For repeatable interaction with iOS UI elements, Claude Code needs another layer, such as XCUITest.

## Driving adb from Claude Code

Claude Code can use `adb` to install, launch, and inspect an Android app when an emulator is already running. The commands below assume you have built a debug APK. Replace the serial with one shown by `adb devices`, and use your app’s package name and launcher activity.

`adb devices SERIAL=emulator-5554 APK=app/build/outputs/apk/debug/app-debug.apk  adb -s "$SERIAL" install -r "$APK" adb -s "$SERIAL" shell am start -n com.example.app/.MainActivity adb -s "$SERIAL" shell uiautomator dump /sdcard/window.xml adb -s "$SERIAL" exec-out cat /sdcard/window.xml > window.xml adb -s "$SERIAL" logcat -d -s AndroidRuntime:E '*:S' > crashes.log`

Claude Code can read the UI hierarchy in `window.xml` and check `crashes.log` after launch. You need to confirm the APK path and activity name against your build and manifest. If `adb devices` shows no emulator, `emulator -list-avds` can identify installed Android Virtual Devices, but a missing system image or unavailable hardware virtualization needs a functioning local Android setup before the test can run.

`adb` controls the app process and exposes device state, but shell commands alone do not make a reliable in-app test. Claude Code can send coordinate taps with `adb shell input tap`, yet a different screen size, keyboard state, or layout can move the target. Use UI-aware test tooling when the assertion depends on what a user can actually tap.

## Writing XCUITest with Claude Code: what compiles and what flakes

Claude Code can generate a useful first XCUITest when you give it the app’s UI test target and the accessibility identifiers the app exposes. For an app that opens on an onboarding screen, a test can tap Continue and check that the home screen appears.

`import XCTest  final class OnboardingUITests: XCTestCase {  func testContinueOpensHome() {  let app = XCUIApplication()  app.launchArguments = ["-ui-testing", "-reset-onboarding"]  app.launch()   let continueButton = app.buttons["onboarding.continue"]  XCTAssertTrue(continueButton.waitForExistence(timeout: 5))  continueButton.tap()   let homeTitle = app.staticTexts["home.title"]  XCTAssertTrue(homeTitle.waitForExistence(timeout: 5))  } }`

The app must assign those identifiers to the button and title, and it must handle the launch arguments that reset onboarding state. Claude Code can put the file in the UI test target and run it against a booted simulator. Replace the scheme and simulator ID with values from your project.

`xcodebuild test \  -scheme MyApp \  -destination 'platform=iOS Simulator,id=YOUR_SIMULATOR_UDID' \  -only-testing:MyAppUITests/OnboardingUITests/testContinueOpensHome`

The standard `XCUIApplication` queries and assertions are straightforward to generate, but compilation depends on the project’s target membership and test scheme. A compiling test can still fail intermittently. `waitForExistence` does not guarantee that a button is ready to receive a tap after an animation or overlay, and a previous run may leave onboarding marked complete. Claude Code may also query an identifier that exists in its proposed test but not in the app.

Give the agent the relevant view code and a failing test log so it can correct the first pass. As the UI changes, you still need someone or a separate testing system to keep the test’s identifiers, state reset, and wait conditions accurate.

## Writing Espresso tests with Claude Code: what compiles and what flakes

Claude Code can generate an Espresso test that compiles, provided it uses the app’s actual resource IDs and the Android test dependencies already exist. For an app with a `continue_button` that reveals a `welcome_message`, a first pass could look like this.

`package com.example.app  import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith  @RunWith(AndroidJUnit4::class) class WelcomeTest {  @Test  fun continueShowsWelcomeMessage() {  onView(withId(R.id.continue_button)).perform(click())  onView(withId(R.id.welcome_message))  .check(matches(withText("Welcome")))  } }`

With a running emulator and the app’s debug test configuration in place, Claude Code can run the test through Gradle.

`./gradlew connectedDebugAndroidTest`

The generated test depends on details Claude Code must check in the repository. Both IDs must exist, and `welcome_message` must display the asserted text. An unresolved `R.id` fails compilation. A valid ID paired with the wrong screen or text compiles but fails at runtime.

Espresso waits for the main thread to become idle, but it cannot infer when every background operation finishes. If the welcome message depends on an untracked network request, the assertion may run before the response updates the view. The app needs an appropriate `IdlingResource` or another test-controlled way to wait for that work. RecyclerView tests need similar care because rows may be off-screen or recycled, and matching repeated row text can select the wrong view. Animations left enabled in test builds can also change when a view becomes available for an action.

Claude Code can revise a failing assertion or add a missing dependency. A successful compile does not establish that the test will pass repeatedly across emulator runs and UI changes.

## Giving Claude Code a mobile MCP server

Claude Code needs observable app state to make an in-app interaction repeatable. The earlier `xcrun simctl` and `adb` commands can install and launch an app, but the agent still has to decide how to inspect the screen, locate a control, and confirm that a tap worked.

A mobile MCP server can expose those operations as tool calls. If the server provides screenshots, accessibility element trees, and taps, Claude Code can inspect the current screen, choose an element, interact with it, and inspect the result. The element tree can provide a more stable target than screenshot coordinates, but the server’s actual tools and supported runtimes depend on its implementation. Check the tool definitions when configuring the server.

Tool access still leaves test logic with the agent. Claude Code must decide what to assert, wait for the right state, and handle app data that changes between runs. An MCP connection makes simulator interaction easier to structure, but a repeatable test also needs explicit assertions and a controlled starting state.

## Running a Momentic mobile test from Claude Code

Claude Code can write a mobile test without generating XCUITest or Espresso code. Install the Momentic mobile skill in the repository, then ask Claude Code to create a plain-English YAML test and run it against an available simulator or emulator. Unlike raw `simctl` or `adb` commands, the test records the behavior you want to verify in a file you can review and commit.

`npx momentic-mobile skills --yes`

For example, give Claude Code a task tied to a build it can access.

`Use the installed Momentic mobile skill to test the login flow on the available iOS simulator. Create a YAML test in this repository that opens the app, signs in with the configured test account, and checks that the home screen appears. Run the test and report the result and any failure evidence. Do not report a pass unless the test ran.`

Claude Code should use the installed skill’s instructions to author and run the test. The prompt does not substitute for a run result. Before committing the YAML, check the test’s account configuration, the simulator target, and the reported failure evidence. To run it again outside the agent, use `npx momentic-mobile run`. The [iOS quickstart](/docs/quickstart/ios) covers the test format.

Momentic mobile tests run on local or hosted iOS simulators and Android emulators, not physical devices. A simulator or emulator gives CI a device runtime it can provision for repeatable runs, but you still need to control the app build and test data. Unlike a generated XCUITest or Espresso file, the Momentic test does not depend on Claude Code producing Swift or Kotlin test code that compiles against your current project.

## Local simulators vs. hosted ones

A local iOS simulator or Android emulator gives Claude Code a device it can inspect during development. In CI, that same approach requires a runner with the right Xcode or Android toolchain, a booted device, and a built app. You also need to reset app state between runs and collect logs when a test fails.

Hosted infrastructure moves device provisioning out of your CI runners and gives you capacity for parallel runs. Momentic can run the same tests on hosted iOS simulators and Android emulators, so CI does not need a Mac with Xcode or an Android SDK. Hosted capacity does not make a flaky test repeatable by itself. Your suite still needs controlled app state, reliable waits, and useful failure evidence.

Choose local devices when you need fast feedback against a build on your machine. Consider hosted devices when CI needs parallel capacity without managing simulator and emulator runtimes on every runner. Momentic runs mobile tests on local or hosted simulators and emulators, not physical devices.

## Comparing the four approaches

Claude Code can use all four approaches to check a mobile app, but they differ in what survives the next UI change. A successful run on a local simulator or emulator does not, by itself, give you a test you can trust in CI.

| Approach | What Claude Code automates | What can still flake | What maintains itself as the app changes |
| --- | --- | --- | --- |
| Raw xcrun simctl or adb | Boots a simulator or emulator, installs and launches the app, and captures logs or screenshots. | Device boot state, app startup, and shell scripts that cannot assert in-app behavior. | Nothing. You update commands and checks. |
| Generated XCUITest or Espresso | Writes native tests with element queries and assertions that you can run in CI. | Missing accessibility identifiers, timing, animations, and test state left by earlier runs. | Nothing automatically. You review and revise generated code when the UI changes. |
| <a href="/blog/mobile-mcp-server-setup">Mobile MCP server</a> | Exposes device interaction through tool calls, so Claude Code can inspect and act on the app during a session. | Device state and assertions the agent has not made repeatable. | Tool access alone does not maintain a test suite. |
| Momentic mobile skill | Lets Claude Code author and run Momentic YAML tests against local or hosted simulators and emulators. Momentic does not run these tests on physical devices. | Environment failures and behavior changes that need engineering review. | Momentic can repair tests as the UI changes. You still review whether a repaired test checks the intended behavior. |

## What a repeatable mobile test setup actually needs

A test becomes repeatable when it controls app state, waits for the right UI condition, and records enough evidence to diagnose a failure. Claude Code can drive a simulator or emulator, but a successful tap in one session does not prove the same path will pass after the app changes. Keep the test in source control, run it in CI, and review failures before changing assertions to make them pass.

To try the Momentic mobile skill from Claude Code, run the setup command.

`npx momentic-mobile skills --yes`

For adjacent reading, see _Testing mobile apps in Cursor_ and _What tests look like with Claude Code_.

## Frequently asked questions

Can Claude Code test on a physical iPhone or Android device?    The methods in this guide target simulators and emulators. Momentic mobile runs on local or hosted iOS simulators and Android emulators, not physical devices.    Does Claude Code need Xcode or Android Studio installed?    For local iOS testing with xcrun simctl, you need Xcode and a simulator runtime. For local Android testing, you need the Android SDK tools and an emulator image, but you do not necessarily need the Android Studio IDE. A hosted runner provides the device runtime instead.    Can Claude Code fix a flaky XCUITest or Espresso test itself?    Claude Code can inspect a failure, edit the test, and rerun it. A passing rerun does not prove the fix is repeatable. You still need to check whether the failure came from a missing accessibility identifier, a wait condition, or device state left by an earlier run.    What does the Momentic mobile skill require?    Install the skill from your project.

``sh
npx momentic-mobile skills --yes
``

To run a mobile test, you also need an app build and a configured simulator or emulator target. Check the [Momentic documentation](/docs) for project setup.

Still have additional questions?

## Keep reading.

[Resources   UI and end-to-end testing with OpenAI Codex     How far Codex gets on UI and end-to-end tests, and how AGENTS.md instructions plus a test runner over MCP turn its output into checks you can trust.     Wei-Wei Wu     8 min read](/blog/codex-ui-testing-guide)[Resources   Migrating from Appium to Maestro     How to move an Appium suite to Maestro flows, with a worked login example, a concept map and the cases where Appium is still the better fit.     Wei-Wei Wu     6 min read](/blog/migrate-appium-to-maestro)[Resources   Migrating from Cypress to Playwright: a step-by-step guide     A practical plan for moving a Cypress suite to Playwright: command mapping, the async model change, what breaks, and running both suites during the move.     Wei-Wei Wu     10 min read](/blog/migrate-cypress-playwright)

## Close the feedback loop.

Point Momentic at your app. Free to start, no credit card.

[Try for free](https://app.momentic.ai/signup) [Contact sales](/sales)
