iOS Simulators — Programmatic Control from the Terminal
Master programmatic control of iOS virtual devices with this step-by-step tutorial.
simctl = simulator control
What is simulator (sim) control? How do mobile testers use it? Let’s first check how Apple defines a sim.
So we can use a sim for various purposes:
- Interact with our apps on iOS, watchOS, and tvOS using our pointer and keyboard.
- Prototype and develop our apps.
- Test and debug our apps.
- Optimize our graphics.
Now, simctl
— simulator (sim
) c
ont
rol
— is a utility from Apple that helps us manipulate our iOS virtual devices from the command line. On macOS, we can utilize our simctl
binary to prototype, develop, and test our mobile apps. The util binary is stored at the /Applications/Xcode.app/Contents/Developer/usr/bin/simctl path.
We can use our simctl
binary with the xcrun
utility . Let’s first access all the available usage options using the help
subcommand with xcrun simctl
.
% xcrun simctl help
usage: simctl [--set <path>] [--profiles <path>] <subcommand> ...
simctl help [subcommand]
Command line utility to control the Simulator
For subcommands that require a <device> argument, you may specify a device UDID
or the special "booted" string which will cause simctl to pick a booted device.
If multiple devices are booted when the "booted" device is selected, simctl
will choose one of them.
Subcommands:
addmedia Add photos, live photos, videos, or contacts to the library of a device.
boot Boot a device or device pair.
clone Clone an existing device.
...
uninstall Uninstall an app from a device.
unpair Unpair a watch and phone pair.
upgrade Upgrade a device to a newer runtime.
With the help of this simctl
functionality, we can perform various testing activities with the sim, including but not limited to the following:
- Create and launch/boot a sim ✅
- Shut down and erase data from a sim ✅
- Add media to a sim (seed input data) ✅
- Install/uninstall and launch/terminate a mobile app inside a sim ✅
- Take screen shots and videos ✅
- Collect logs ✅
- Delete one or more sims ✅
Let’s explore these functionalities in detail.
First, it would be good to know what are those devices that we want to control and manipulate. We can get the list of all of the sims printed out in the terminal output with the xcrun simctl list
command. This command lists all the available sims with runtime. It also shows any booted sims if they’re already running.
% xcrun simctl list
== Device Types ==
iPhone 6s (com.apple.CoreSimulator.SimDeviceType.iPhone-6s)
iPhone 6s Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus)
iPhone SE (1st generation) (com.apple.CoreSimulator.SimDeviceType.iPhone-SE)
iPhone 7 (com.apple.CoreSimulator.SimDeviceType.iPhone-7)
iPhone 7 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus)
...
== Runtimes ==
iOS 15.5 (15.5 - 19F70) - com.apple.CoreSimulator.SimRuntime.iOS-15-5
iOS 17.0 (17.0.1 - 21A342) - com.apple.CoreSimulator.SimRuntime.iOS-17-0
== Devices ==
-- iOS 15.5 --
iPhone 13 Pro (62F65BF7-E713-4C25-B0CA-AE8560FB2E1A) (Shutdown)
...
iPhone 13 (15D61E09-9554-458E-AEBB-2D0A1A9E811F) (Booted)
...
-- iOS 17.0 --
iPhone SE (3rd generation) (6533E766-B7D0-4DBB-B409-1E18ABD4A70C) (Shutdown)
iPhone 15 (A52CE266-B186-4610-97A1-9BD06207FCE7) (Booted)
...
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-16-4 --
iPhone SE (3rd generation) (ED03A8E4-E2F5-45F2-B853-45FF18AD85BE) (Shutdown) (unavailable, runtime profile not found using "System" match policy)
...
iPad Pro (12.9-inch) (6th generation) (253F6971-F257-42D8-B8F7-E2D67D80FBCB) (Shutdown) (unavailable, runtime profile not found using "System" match policy)
== Device Pairs ==
We can eliminate the guesswork of what sims are booted or not. Let’s save some time and run command xcrun simctl list | grep Booted
.
xcrun simctl list | grep Booted
iPhone 13 (15D61E09–9554–458E-AEBB-2D0A1A9E811F) (Booted)
iPhone 15 (A52CE266-B186–4610–97A1–9BD06207FCE7) (Booted)
Sometimes we may need to find a sim by its name in order to retrieve its UDID. We can find that UDID and proceed with the intended actions, such as booting, erasing, or deleting a sim. Here’s a sample script to erase a sim with the name “test” using RegEx.
$ xcrun simctl list | grep -w “test” | awk -F “[()]” ‘{ for (i=2; i<NF; i+=2) print $i }’ | grep ‘^[-A-Z0–9]*$’ | xargs -I uuid xcrun simctl erase uuid
Great success! We can now view the full list of our sims, as well as directly retrieve information on the ones that are already booted.
① Create and Launch/Boot a Sim
Now, let’s learn how to create and start up a new sim.
We can easily create a new device and give it some arbitrary name like test-iPhone15
, on top of the already existing iPhone 15 (iOS 17.0) simulator using command xcrun simctl create
followed by <name>
and <device type id> [<runtime id>]
.
<name>
is an arbitrary name we choose to give to the new device. Example:test-iPhone15
.<device type id>
stands for a valid available device type. Find these by runningxcrun simctl list devicetypes
. Example:iPhone 15 Pro (com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro)
.
<runtime id>
stands for a valid and available runtime. Find these by runningxcrun simctl list runtimes
. If no runtime is specified, the newest runtime compatible with the device type is chosen. Example:iOS 17.0 (17.0.1–21A342) — com.apple.CoreSimulator.SimRuntime.iOS-17–0
.
Now that we have our chosen <name>
of test-iPhone15Pro
, our <device type id>
of com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro
, and the applicable <runtime id>
of com.apple.CoreSimulator.SimRuntime.iOS-17–0
, we can construct our create
command as follows:
% xcrun simctl create
// <name>
test-iPhone15Pro
// <device type id>
com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro
// <runtime id>
com.apple.CoreSimulator.SimRuntime.iOS-17–0
In the output we receive back the udid
of the newly created sim, such as 47ED2C95–0F10–4B66–9A34-D31E685A366
.
% xcrun simctl create test-iPhone15Pro
com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro
com.apple.CoreSimulator.SimRuntime.iOS-17-0
47ED2C95-0F10-4B66-9A34-D31E685A366
Now can we can use that udid
for the freshly created iPhone 15 Pro to start/boot that sim. Let’s run command xcrun simctl boot <device_udid>
.
xcrun simctl boot 47ED2C95–0F10–4B66–9A34-D31E685A3669
Let’s verify that this new sim is actually booted with the expected device udid
of 47ED2C95-0F10-4B66-9A34-D31E685A366
. Let’s grep
information on our up and running sims with the xcrun simctl list | grep Booted
command, just as we did before.
Our new test-iPhone15Pro
is indeed booted under udid
number 47ED2C95–0F10–4B66–9A34-D31E685A3669
, as expected.
② Shut Down and Wipe/Erase a Sim
As we know, there were two other sims that had already been booted up, prior to us launching our new test-iPhone15Pro
device. Let’s shut those down and erase their content. Note that erasing content does not delete the device itself.
The order is important here. We can’t erase a sim in the booted state, hence we need to shutdown
first, then erase
.
$ xcrun simctl shutdown <device_udid>
$ xcrun simctl erase <device_udid>
Now let’s attempt to shut down and delete the sims are are up and running.
xcrun simctl shutdown A52CE266-B186-4610-97A1-9BD06207FCE7
xcrun simctl shutdown 15D61E09-9554-458E-AEBB-2D0A1A9E811F
xcrun simctl erase A52CE266-B186-4610-97A1-9BD06207FCE7
xcrun simctl erase 15D61E09-9554-458E-AEBB-2D0A1A9E811F
As a result of this cleanup we are left with the one booted test-iPhone15Pro
device which we’ve explicitly created during this tutorial.
We can wipe data from all the sims at once, using one simple command erase all
:
$ xcrun simctl erase all
It turns our devices back to the tabula rasa state in one clean sweep. This might save a decent chunk of time, when we consider how much effort goes into wiping data from each device by their unique UDIDs one-by-one. Fun fact: tabula rasa in Latin literally means a ‘scraped tablet’, denoting a tablet with the writing erased.
③ Add Media (Seed Input Data) into a Sim
We can add various formats of media files to our sim — PNG, GIF, MOV, and so on.
% xcrun simctl addmedia
Add photos, live photos, videos, or contacts to the library of a device.
Usage: simctl addmedia <device> <path> [... <path>]
You can specify multiple files including a mix of photos, videos, and contacts.
You can also specify multiple live photos by providing the photo and video files. They will automatically be discovered and imported correctly.
Contacts support the vCard format.
An iOS sim by default comes with six images. However, we can seed our test data files into the device, for example, by adding more images or videos with the simctl addmedia
functionality.
Let’s add one photo (.png
) and one video (.gif
) — that are currently stored on the desktop — inside of our sim. We run command simctl addmedia <device> <path>
on our booted
test-iPhone15Pro device.
$ xcrun simctl addmedia booted ~/Desktop/test_png.png
Now, the sim photo gallery has another photo.
Similarly, we can also add a video as the 8th file to the sim’s media gallery.
$ xcrun simctl addmedia booted ~/Desktop/test_gif.gif
Now, the media gallery contains an 8th file we’ve just added.
As we’ve practiced, we can wipe our sim clean of any seeded data with the simctl
shutdown
and erase
commands. Then we should boot up the same sim again and verify that the media gallery has been restored to its default state.
As an experiment, let’s test out additional video file formats, such as .mp3
and .mov
. I wanted to provide you with an adequate example that would demonstrate both audio and video aspects of a media file. So, I prepared a a short recording for you to see and hear what’s happening.
We end up discovering that the MP3 file format is not supported. But we do succeed adding a 9th media file to the sim’s gallery in the supported MOV format. Both the audio and the video aspects of the .mov
file correspond to what a user would experience on their device in a real-world scenario.
④ Install/Uninstall and Launch/Terminate a Mobile App inside a Sim
It’s time to exercise our app install and uninstall skills. We can easily install our app inside a sim from the command line. But first, let’s obtain the path to our .app
file.
Let’s create a new single view app in Xcode, name it test-app
and save the project on the desktop.
As a result, we get our demo.app
file generated inside theDerivedData
directory. The path to our .app
is as follows: /Users/lanabegunova/Library/Developer/Xcode/DerivedData/demo-bacucdjzvpjqgparawwbmxhcmvag/Build/Intermediates.noindex/Previews/demo/Products/Debug-iphonesimulator/demo.app. Take a note of this path, as we intend to use it for the simctl install
command shortly.
Let’s change directory to our project files created in ~/Desktop/demo
.
Now, that we are in the right location, we can build the demo
app using the xcodebuild
command.
$ xcodebuild build build -scheme “demo” CODE_SIGN_IDENTITY=”” CODE_SIGNING_REQUIRED=NO
Luckily for us, the build is successful. We can now install our demo.app
inside the sim using the simctl install
command. Remember that long path to the .app
we noted down earlier? We pass it with the command, in order to install demo
on our booted
device.
$ xcrun simctl install booted /Users/lanabegunova/Library/Developer/Xcode/DerivedData/demo-bacucdjzvpjqgparawwbmxhcmvag/Build/Intermediates.noindex/Previews/demo/Products/Debug-iphonesimulator/demo.app
Similarly we can uninstall our iOS app from a sim easily with command simctl uninstall <device> <app bundle identifier>
. Our <device>
is the one booted
sim we have running. We defined the <app bundle identifier>
earlier in the process of creating an Xcode project and called it a bundle id. That bundle id was test.demo.demo
.
$ xcrun simctl uninstall booted test.demo.demo
Similar to installation, we can launch and terminate an app inside a sim via command line using simctl
. We have to make sure the app is installed before we launch
it. So, let’s install our .app
again with the previously used command:
$ xcrun simctl install booted /Users/lanabegunova/Library/Developer/Xcode/DerivedData/demo-bacucdjzvpjqgparawwbmxhcmvag/Build/Intermediates.noindex/Previews/demo/Products/Debug-iphonesimulator/demo.app
And, open the .app
with the launch
command:
$ xcrun simctl launch booted test.demo.demo
We can see that app is launched, we can then terminate the app using the terminate
functionality.
⑤ Take Screen Shots and Videos
Moreover, Xcode has this nice feature of capturing a screenshot and recording a video of an iOS sim with the simctl io
functionality.
The general syntax for a device IO operation is simctl io screenshot simctl io <device> <operation> <arguments>
. It’s easy to capture a screenshot of the sim’s current state using the screenshot
operation and passing in screencap.png
as an argument as follows:
$ xcrun simctl io booted screenshot screencap.png
screenshot [--type=<type>] [--display=<display>] [--mask=<policy>] <file or url>
Saves a screenshot as a PNG to the specified file or url(use "-" for stdout).
--type Can be "png", "tiff", "bmp", "gif", "jpeg". Default is png.
--display iOS: supports "internal" or "external". Default is "internal".
tvOS: supports only "external"
watchOS: supports only "internal"
You may also specify a port by UUID
--mask For non-rectangular displays, handle the mask by policy:
ignored: The mask is ignored and the unmasked framebuffer is saved.
alpha: The mask is used as premultiplied alpha.
black: The mask is rendered black.
Example:
Save a screenshot of the booted device to screenshot.png:
simctl io booted screenshot screenshot.png
This will save the screenshot of the sim in our host machine’s current working directory with the file name screencap.png
.
To remove the screenshot from our Mac, we can rum the rm
command.
rm screencap.png
Similarly, we can record a video of a sim while using an app and save it on disk using the recordVideo
IO operation.
$ xcrun simctl io booted recordVideo freeform.mov
recordVideo [--codec=<codec>] [--display=<display>] [--mask=<policy>] [--force] <file or url>
Records the display to a QuickTime movie at the specified file or url.
--codec Specifies the codec type: "h264" or "hevc". Default is "hevc".
--display iOS: supports "internal" or "external". Default is "internal".
tvOS: supports only "external"
watchOS: supports only "internal"
--mask For non-rectangular displays, handle the mask by policy:
ignored: The mask is ignored and the unmasked framebuffer is saved.
alpha: Not supported, but retained for compatibility; the mask is rendered black.
black: The mask is rendered black.
--force Force the output file to be written to, even if the file already exists.
simctl writes 'Recording started' to stderr once the first video frame has been processed. Look for this
if you want to wait for the recording to start.
Send SIGINT (Control + C) to stop recording. simctl exits once the in-flight frames are processed
and the video file is finalized.
This saves a video of our sim to the freeform.mov
file which can be played using Quick Time player or similar. We can open the file through the terminal with the open freeform.mov
command.
To be consistent, we should tidy up and remove the file, once it’s no longer needed. Let’s use the same rm
command, as we did with the screenshot file screencap.png
before.
rm freeform.mov
This removes our video of the sim’s screen recording from the host machine’s current directory. This is due to the fact that the device’s IO operation outputs, such as a screen capture or a screen recording, are saved on our Mac and not on the mobile device.
In the terminal window, let’s make sure we are in the proper directory and run the ls
command to verify the files are no longer stored there.
So, upon successfully removing the undesired files from our current working directory, we revisit the directory contents to verify they have been deleted as expected.
It’s essential to support our bug reports with screenshots and recordings to provide the pertinent information to the developer and expedite the fix.
⑥ Collect Sim Logs
We can easily determine activity of the simulator by reading the logs. Logs are essential for debugging and root cause analysis. Wouldn’t it be nice to have the sim logs printed in the command line output? Xcode comes handy with its log
utility and multiple levels such debug, info, etc.
$ xcrun simctl spawn booted log stream --level=debug
We can also filter the log output if we’re looking for logs of particular app.
# filter log output
$ xcrun simctl spawn booted log stream — predicate ‘processImagePath endswith “demo”’
$ xcrun simctl spawn booted log stream — predicate ‘eventMessage contains “error” and messageType == info’
# collect log data
$ xcrun simctl spawn booted log collect
# open location
$ cd `xcrun simctl getenv booted SIMULATOR_SHARED_RESOURCES_DIRECTORY`
⑦ Delete All Sims
Deletion of virtual devices might be useful in those cases when we don’t want too many of them clogging up our host machine.
We can use the following shell script that deletes all of the sims at once. Beware of the impact and use this command at your own risk, only when you’re certain that you want to run it.
$ xcrun simctl list | awk -F “[()]” ‘{ for (i=2; i<NF; i+=2) print $i }’ | grep ‘^[-A-Z0–9]*$’ | xargs -I uuid xcrun simctl delete uuid
Conclusion
By using the simctl
utility we can programmatically interact with iOS sims and even script the setup for test execution on CI servers. I hope you get some useful take-aways from this tutorial. Share your thoughts on any interesting and modern mobile testing techniques.
I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta.