Finding iOS simulators identifiers for CI
GitHub Actions are powerful tools and a great complement to Xcode Cloud for Swift projects’ continuous integration.
However, I encountered an issue: when using xcodebuild test
for iOS, a
concrete device is required:
Cannot test target “FooTests” on “Any iOS Device”: Tests must be run on a concrete device
The challenge then arose: how to identify the simulator identifiers suitable for the intended device?
Of course, Apple provides a command for this:
xcrun simctl list devices available
But then CI requires parsing and filtering on this command’s output. Here are 3 solutions to do so.
Fastlane era
Fastlane provides a FastlaneCore::DeviceManager
that greatly assists in
inspecting available devices.
require 'fastlane_core/device_manager'
# Usage: latest_simulator_with_name('iPhone Xʀ').udid
def latest_simulator_with_name(device_name)
result =
FastlaneCore::DeviceManager.simulators
.select { |s| s.name == device_name }
.max_by { |s| Gem::Version.create(s.os_version) }
return result unless result.nil?
FastlaneCore::UI.error "#{device_name} missing. Please select one of the following:"
FastlaneCore::DeviceManager.simulators
.each { |s| FastlaneCore::UI.message "#{s.name} (#{s.os_version})" }
raise "Device with name #{device_name} cannot be found. Please install it."
end
Despite my extensive use of Fastlane, I’m starting to perceive a decline in its relevance with the emergence of Xcode Cloud.
Exploring the PointFreeCo Approach
As an admirer of the PointFreeCo team’s work, I investigated their methods and discovered a useful yet succinct shell command.
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS 17.2,iPhone \d\+ Pro [^M])
…
define udid_for
$(shell xcrun simctl list devices available '$(1)' | grep '$(2)' | sort -r | head -1 | awk -F '[()]' '{ print $$(NF-3) }')
endef
While efficient, it lacks readability.
Implementing a Swift Solution
To address this challenge, I developed a Swift script to obtain the necessary information. You can find the script on the Scripts section of my Blocks project or as a command of the CLI that I provide with this tool.
To use it in continuous integration (CI), follow these steps:
# Download the script and make it executable
curl -sSL https://raw.githubusercontent.com/dirtyhenry/swift-blocks/main/Scripts/ListDevices.swift \
-o findDevice
chmod +x findDevice
# Find the device identifier
DEVICE_ID=$(./findDevice "$TEST_IOS_VERSION" "$TEST_IOS_SIMULATOR_MODEL")
# Run the test
set -o pipefail && xcodebuild test \
-skipMacroValidation -skipPackagePluginValidation \
-workspace foo.xcworkspace \
-scheme "bar" \
-destination platform="iOS Simulator,id=$DEVICE_ID" \
| xcpretty
💡 I recommend setting TEST_IOS_VERSION
and TEST_IOS_SIMULATOR_MODEL
as
environment variables so that failures after Xcode updates can be resolved
without changing the code.