
Taking App Store screenshots by directly manipulating the simulator with Claude Code was inefficient, so I solved it by scripting the process.
This page has been translated by machine translation. View original
About a month ago, an iOS simulator panel was added to Claude Desktop, allowing Claude to operate and check iOS apps as if using a real device. This was already covered in the following article. This feature is available in Claude Desktop v1.24012.0 and later.
I thought this looked useful and decided to try using it to capture App Store Connect screenshots for my own app. In this article, I'll introduce the high-cost problem I discovered through actually trying it, and how I ended up scripting the process and creating a dedicated sub-agent as a workaround.
Test Environment
- MacBook Pro 2021 (Apple M1 Pro)
- macOS 26.5.2 (25F84)
- Xcode 26.6
- iPhone 17 Pro Max / iOS 26.5 (Simulator)
- Claude 1.40609.0 (f65e38)
Screenshots That Had Been Left Neglected
Currently, the screenshots for my app are just the ones I manually captured right before release, left completely unmaintained.
App Store screenshots commonly include descriptive text and catch copy overlaid on them. I had assumed this was a gray-area practice under the guidelines, but when I looked into it, I found it was explicitly permitted under section 2.3.3 of the App Review Guidelines. It states that "Screenshots must show the app in use, and not merely title art, login pages, or splash screens," while also allowing the inclusion of text and image overlays. In other words, adding catch copy was actually an expected practice. Note that exaggerated presentations that diverge from the actual UI are prohibited under a different section, not 2.3.3, and these need to be understood as distinct. For example, misleading marketing that shows non-existent features falls under 2.3.1, and using screenshots from other platforms falls under 2.3.10. Once I confirmed there were no guideline issues, I took another look at my app's screenshots and found they were missing that catch copy entirely.
So I decided to have Claude Code handle the entire screenshot capture process. I'm very busy these days and rarely have time to sit in front of a PC, so having an AI that can just get work done from instructions alone is a great help.

Direct Simulator Operation Was Costly
I personally use Claude Pro. I asked it to capture screenshots for 9 locales: Japanese, English, Korean, Chinese (Simplified/Traditional), German, French, and Portuguese (Portugal/Brazil). It was taking a long time, so I took a nap, and when I woke up, I could see that it had exhausted the 5-hour limit allocation and additionally consumed $44.71 worth of usage credits beyond that.[1]
When I had it create screenshots for one language again, I found it consumed approximately 38% of the Claude Pro 5-hour limit allocation.

Having Claude operate the simulator with taps and swipes means making Claude infer "what to press next" each time while looking at screenshots. With 9 locales and multiple screens, this requires calling the model for every single inference, and costs balloon quickly. Using this approach for routine work like capturing App Store screenshots was clearly uneconomical.
Solved with Scripting + Dedicated Sub-Agent
In the end, I stopped having the simulator operated manually and switched to the following setup.
- Capture is done via XCUITest. Since it's a UI test, there's no need for the AI to guess simulator coordinates, and it's reproducible.
- The actual capture uses fastlane's
snapshot. By listing locales and target devices inSnapfile, it relaunches the app for each locale, runs UI tests, and automatically collects screenshots underscreenshots/<locale>/. - For device frame compositing and catch copy overlaying after capture, I use fastlane's
frameit. It overlays device frames on the raw screenshots and applies catch copy specified inFramefile.jsonas overlays, producing images ready to submit directly to App Store Connect.
Framefile.json
{
"default": {
"background": "../_frameit_assets/background.png",
"show_complete_frame": false,
"title": {
"color": "#FFFFFF",
"font": "../_frameit_assets/SFNS.ttf",
"font_size": 110
},
"padding": 40,
"title_min_height": 420
},
"data": [
{
"filter": "/en-US/01_home",
"title": {
"text": "Import Switch\nScreenshots in Seconds"
}
},
{
"filter": "/en-US/02_detail",
"title": {
"text": "Auto-Detects\n19,000+ Game Titles"
}
},
{
"filter": "/en-US/03_share",
"title": {
"text": "Share to SNS with\nSmart Hashtags"
}
},
{
"filter": "/de-DE/03_share",
"title": {
"text": "Mit smarten Hashtags\nauf Social Media teilen"
}
},
{
"filter": "/ja/03_share",
"title": {
"text": "ゲームタグを自動追加\nSNSへ賢くシェア",
"font": "../_frameit_assets/HiraginoKakuGothic.ttc"
}
}
]
}
data elements for other locales continue similarly (full content omitted).
- This entire workflow (UI test execution → frame compositing) was scripted as
fastlane/process_screenshots.sh, and I created a dedicated sub-agent just for this task. The model was specified as Haiku.
The sub-agent is explicitly prohibited from using tools to directly tap or swipe the simulator and is instructed to always capture via UI tests. To prevent ad banners or database update banners from appearing in the screenshots, a launch argument called -uitest-screenshot-mode is passed from the UI test side, and the app uses UITestMode.isScreenshotCapture to forcibly enable the paid state and suppress banner display. If capture fails, the sub-agent is also set up not to try fixing the code itself, but instead to report only the failure location and error message, deferring judgment to the caller (me).
The sub-agent definition is as follows.
---
name: screenshot_capture
description: >
A sub-agent that inexpensively captures and processes App Store marketing
screenshots for NSEasyConnect. Used when asked to "retake screenshots,"
"update screenshots," or similar requests involving screenshot capture and
device frame compositing via UI tests.
Rather than interactively operating the iOS simulator, it simply runs
`NSEasyConnectUITests` and `fastlane/process_screenshots.sh`. It does not
modify copy text, make design decisions, or fix code (reports results only).
tools: Bash, Read, Grep, Glob
model: haiku
color: purple
---
When I actually asked this sub-agent to retake 3 Japanese screenshots, the cost was $0.95 and the API call time was 2 minutes and 8 seconds (wall time was 33 minutes and 45 seconds).
With direct simulator operation, just one locale's worth of work consumed approximately 38% of the Pro plan's 5-hour limit, whereas after scripting, retaking 3 Japanese screenshots cost only $0.95 in API terms. These are numbers in different units, but the gap speaks for itself.
Rather than having the AI manually operate the simulator, shifting to a model of "encoding the capture and processing steps into a script and having an inexpensive model call that script" dramatically reduced costs. Even as the number of locales and screens increases, since the AI doesn't need to view the screen and infer operations each time, costs increase only roughly linearly.
Summary
The fact that Claude Code can now directly operate the simulator is an interesting advancement, but it wasn't suited for "routine work that just repeats the same steps every time," like capturing App Store screenshots. On the positive side, by encoding the simulator operations into a script, costs now increase only roughly linearly even as the number of locales and screens grows. On the other hand, consuming a large portion of the Claude Pro allocation before reaching that point was a drawback.
For this kind of work, it's far better to simply script it with UI tests and fastlane, giving the AI only the thin role of "run that script and report the results." I came to realize that it's not a matter of having Claude Code directly operate everything—you need to understand its strengths and weaknesses and use it accordingly. I hope this is useful for anyone else struggling with the costs of simulator operations.
In hindsight, I should have been checking the actual costs more frequently. At a minimum, running
/usagewould have allowed me to compare costs, which is unfortunate. ↩︎
