iOS Simulators — Programmatic Control from the Terminal

Lana Begunova
14 min readNov 9, 2023

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) control — 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.

simctl is a Unix executable file is stored at /Applications/Xcode.app/Contents/Developer/usr/bin/simctl.
Detailed information about the simctl executable.

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
% 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:

  1. Create and launch/boot a sim ✅
  2. Shut down and erase data from a sim ✅
  3. Add media to a sim (seed input data) ✅
  4. Install/uninstall and launch/terminate a mobile app inside a sim ✅
  5. Take screen shots and videos ✅
  6. Collect logs ✅
  7. 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.

Command xcrun simctl list lists all the available sims with runtime. It also shows any booted sims if they are 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.

The list of all available sims we’ve previously retrieved with the xcrun simctl list command.

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 running xcrun 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 running xcrun 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
test-iPhone15Pro
// <name>
com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro
// <device type id>
com.apple.CoreSimulator.SimRuntime.iOS-17–0
// <runtime id>

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
xcrun simctl boot <device_udid> command spins up a sim

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.

The new test-iPhone15Pro is booted under udid number 47ED2C95–0F10–4B66–9A34-D31E685A3669.

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.

Shut down and erase data on undesired devices.
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.

Open Xcode and click on the Create New Project… button.
Select the iOS App template and click Next.
Give the app some product name and id, for example, demo and test.demo respectively. These values form together our bundle id of test.demo.demo. Take a note of this id and then click Next.
Select Desktop as the target location and click Create..
The new demo project folder is being created.
The folder is created and stored as directory ~Desktop/demo.

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.

Path to the built .app file: /Users/lanabegunova/Library/Developer/Xcode/DerivedData/demo-bacucdjzvpjqgparawwbmxhcmvag/Build/Intermediates.noindex/Previews/demo/Products/Debug-iphonesimulator/demo.app.

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.

simctl install
$ 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
simctl uninstall

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
simctl launch

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.

At the end remove the screenshot with rm screencap.png command

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.

We need to verify that we’re in the right working directory and then run the ls command to list the contents of that directory.
Command ls with various flags, for example, ls -ltavr outputs much richer information about the directory contents.

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.

The undesired files 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

https://developer.apple.com/documentation/os/logging/customizing_logging_behavior_while_debugging

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.

--

--

Lana Begunova

I am a QA Automation Engineer passionate about discovering new technologies and learning from it. The processes that connect people and tech spark my curiosity.