How I Let Claude Code Test My iPhone App in the Simulator

Claude Code can test an iPhone app on its own if you give it two things: the shell commands that build, install, launch and screenshot the app in the iOS Simulator, and, since Xcode 26.3, access to Xcode's own MCP server. With both, it can change a screen, run it, look at the result and fix what it sees before it reports back to you.

I build iPhone apps mostly alone, with Claude Code writing a large share of the Swift. The biggest change in my workflow came from closing the loop, so the agent could see the app it had just changed. Before that, every "done" was a guess I had to check by hand.

Why the loop matters more than the model

A coding agent that cannot run your app is reviewing its own code by reading it. That catches typos. It misses the button that renders under the keyboard, the Japanese label that wraps into three lines, the empty state that never got designed. Those are exactly the things that show up in App Review and in one star ratings.

The Simulator ships with every copy of Xcode, and almost everything it does is available from the command line through xcrun simctl. That covers most of what you need. If a person can do it by clicking around the Simulator, there is usually a command for it, and anything with a command can be handed to an agent.

The commands that close the loop

Start by finding a simulator and booting it. simctl accepts the word booted in place of a device ID, which keeps the rest of the commands short.

xcrun simctl list devices available
xcrun simctl boot "iPhone 17"

Build for the Simulator into a folder inside the project, so the agent always knows where the app ends up:

xcodebuild -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -derivedDataPath build build

Then install it, launch it and take a screenshot:

xcrun simctl install booted build/Build/Products/Debug-iphonesimulator/MyApp.app
xcrun simctl launch booted com.example.myapp
xcrun simctl io booted screenshot shot.png

Claude Code can open a PNG and describe what is on it, so that last line is what turns a build into feedback. Ask it to compare the screenshot against what the screen was supposed to show, and you get a second pass that actually looks at pixels.

Test the states you never open yourself

The real value is in the states a developer skips because they take effort to reach. simctl reaches most of them in one line.

xcrun simctl ui booted appearance dark
xcrun simctl ui booted content_size accessibility-extra-extra-extra-large
xcrun simctl launch booted com.example.myapp -AppleLanguages "(ja)" -AppleLocale ja_JP
xcrun simctl privacy booted revoke photos com.example.myapp
xcrun simctl openurl booted "myapp://settings"

Dark mode, the largest Dynamic Type size, the app in Japanese, a permission the user said no to, a deep link straight into a screen. Each is a screenshot away. Living in Japan, the language launch argument is the one I would keep if I could only keep one, because Japanese text breaks layouts that look perfect in English.

simctl can also fake a push notification with xcrun simctl push and a small JSON payload, and pin the status bar with xcrun simctl status_bar booted override --time 9:41 so App Store screenshots look clean.

Connect Xcode's MCP server

Apple announced Xcode 26.3 on February 3, 2026 with built in support for coding agents, including Anthropic's Claude Agent and OpenAI's Codex. The part that matters for Claude Code in a terminal is that Xcode now exposes its capabilities through the Model Context Protocol, so outside agents can use them too.

Apple's guide to giving external agents access to Xcode has three steps. In Xcode, open Settings, then Intelligence, and under Model Context Protocol turn on "Allow external agents to use Xcode tools". Then register the bridge with Claude Code:

claude mcp add --transport stdio xcode -- xcrun mcpbridge
claude mcp list

Last, open the project in Xcode before you start prompting. Xcode shows an alert when an external agent connects and while it is active, which is a good habit check: you always know when something else is driving.

I keep both routes. The MCP tools understand the project the way Xcode does. The shell commands are plain, scriptable and easy to read in a log. When one route gets confused, the other one usually still works.

Write the recipe down once

Claude Code loads a project's CLAUDE.md at the start of every session, according to its documentation on memory. That is where the recipe belongs: the scheme name, the simulator to use, the bundle ID, the build folder, and a short instruction that every UI change ends with a screenshot in light mode, dark mode and the largest text size.

Written down, the loop stops depending on whether you remembered to ask for it. If you want it enforced rather than suggested, a hook can run the build after edits; I wrote about that pattern in the guide to self correcting hooks.

What the Simulator cannot tell you

A simulator is a Mac pretending to be a phone. Camera input, real push delivery through Apple's servers, performance on an older device, haptics, how the app feels in one hand on a crowded train: none of that shows up in a screenshot. Purchases need a StoreKit configuration or a sandbox account before they mean anything.

So I treat the Simulator loop as the floor. It catches the broken layouts, missing strings and unhandled states early, which leaves my own time on a real device for the things only a real device can show. That split is a big part of how one person can ship an iOS app with AI, and it is a cheap way to stay out of the two million submissions Apple rejected in 2025.

← All posts