Platforms
iOS Simulator
Troupe runs your app on iOS Simulators that it creates, boots and deletes itself. This page covers what your Mac needs, how Troupe drives a simulator, and what the platform can and cannot do.
The platform's id is ios-simulator. A troupe.config.ts may write ios.
Requirements
- macOS on Apple silicon. Intel Macs are not supported.
- A full Xcode, not only the Command Line Tools, with at least one iOS simulator runtime. Xcode 27.1 and 27.2 with the iOS 27.0 runtime are verified. Xcode 26 is not verified yet.
- Bun 1.4.3 or newer.
- Memory for each simulator: Troupe budgets 3,194 MB for a session and starts one only when that fits.
Prepare the platform
Run this once in your project:
bunx troupe install iosIt records your Xcode in ~/.troupe/host.json (the one DEVELOPER_DIR names, else
/Applications/Xcode.app/Contents/Developer), runs the environment checks and prints a fix for each failure,
builds Troupe's XCUITest runner and a simulator template, and starts one simulator to prove the path.
Install describes the checks.
A project then names the device and the app:
import { defineConfig } from '@alivelabs/troupe'
export default defineConfig({
projects: [
{
name: 'ios',
use: { platform: 'ios', deviceType: 'iPhone 18 Pro', os: '27.0', app: './build/Shop.app' },
},
],
})How Troupe drives a simulator
Three parts do the work. Troupe owns all three: it does not depend on Appium, WebDriverAgent or another driver.
| Part | What it does |
|---|---|
simctl | Creates, boots and deletes the simulator; installs and launches apps; sets permissions, location and appearance; follows the logs |
| The XCUITest runner | Reads the accessibility tree, resolves locators and performs test actions. Troupe builds it on your Mac with your Xcode and keeps it in $TROUPE_HOME/cache/ios-runner/ |
| simcap | Captures the screen as H.264 for the live stream and delivers live input from a viewer. It ships with the platform package |
Test actions and live input take different paths. A test's tap() or fill() goes through the XCUITest
runner. A click in Troupe Studio goes through simcap. The capabilities below list
both.
Devices are Troupe's own
Troupe creates simulators named troupe-… and acts only on those, by UDID. It never boots, erases or deletes
a simulator you created.
A session's simulator is a clone of a template that Troupe booted once (troupe-tpl-…), so it boots without
the data migration a new simulator runs on its first boot. The template never runs a session, and a clone
is deleted when its device shuts down, so a new device has no app, setting or keychain item from an earlier
one. troupe install ios builds the template ahead of time.
A released device stays warm for a few minutes and may serve the next session with the same spec, with its apps still installed. See Release.
Device Hub and Simulator.app
Quit Xcode's Device Hub and Simulator.app while Troupe runs devices. Either can attach to Troupe's
simulators, and live input may then be unavailable: the stream says so with the reason
device-hub-attached, and Troupe Studio shows Input unavailable. troupe doctor reports them as the
ios.deviceHub check (ENVIRONMENT_DEVICE_HUB_ACTIVE). By default that check warns; test actions are not
affected.
Apps
Troupe installs iOS Simulator builds:
- a
.appdirectory, such as Xcode'sDebug-iphonesimulator/Shop.app; - an archive that holds one
.app:.ipa,.zip,.tar.gzor.tgz.
A build for a physical device is refused with APP_INSTALL_FAILED and the reason wrong-platform. Real
devices are not supported.
Launch arguments, launch environment variables, clearing an app's data and deep links all work. A deep link's scheme is approved ahead, so iOS shows no "Open in" prompt.
Capabilities
The runtime reports these through runtime.capabilities,
and refuses a call the platform cannot serve with CAPABILITY_UNSUPPORTED before it touches the device.
| Area | iOS Simulator |
|---|---|
| Locators | Role, text, label, placeholder and test id, with regular expressions; system UI and alerts through screen.system |
| Test id | accessibilityIdentifier (React Native's testID) |
| Touch in tests | One contact: tap, double tap, long press, swipe, drag, scroll |
| Keyboard in tests | Text. There are no key events |
| Hardware buttons in tests | home |
| Rotation | Yes |
| Permissions | calendar, contacts, location, locationAlways, microphone, motion, photos, reminders |
| System settings | Location, appearance and the clipboard |
| Screenshots | Yes |
| Live stream | H.264, with frame rate and bitrate changed while streaming |
| Live input | Up to five contacts, keys and text, the buttons home, power, volumeUp and volumeDown, rotation |
| Logs | System log, app log and crash reports |
| Screen recording | No |
Limits
- Test actions use one finger.
input.pinchand aninput.gesturewith more than one contact are refused. input.pressKeyneeds key events, soscreen.pressKey()is refused. Type text withfill()ortype()instead.input.hideKeyboardis not available.- The permissions
cameraandnotificationscannot be set:simctlhas no service for them. - Live text is typed key by key on a US keyboard layout. Text with a character that layout cannot type is refused.
- Live rotation is offered once the session is
ready.
Device types and OS versions
The device types and OS versions are those your Xcode has. Troupe reads them from simctl, so a device type
is offered only for a runtime that hosts it.
deviceTypeis a simulator device type by name ('iPhone 18 Pro') or by its identifier. Without one, Troupe takes the newest numbered iPhone Pro that an installed runtime hosts.osis an iOS version such as'27.0'. Without one, or with'latest', Troupe takes the newest installed runtime that runs the device type.
List what a runtime offers with catalog.deviceTypes and
catalog.osVersions:
const { deviceTypes } = await troupe.call('catalog.deviceTypes', { platform: 'ios-simulator' })
const { osVersions } = await troupe.call('catalog.osVersions', { platform: 'ios-simulator' })A version that is not installed fails with ENVIRONMENT_IOS_RUNTIME_MISSING. Its fix is the xcodebuild
command that downloads the runtime. An unknown device type is DEVICE_SPEC_INVALID, with the available names
in its data.
A spec may also set locale, timeZone, orientation and appearance. See
the device spec.
Host settings
troupe install ios writes platforms.ios-simulator in $TROUPE_HOME/host.json. You can edit these keys:
| Key | Default | What it does |
|---|---|---|
developerDir | /Applications/Xcode.app/Contents/Developer | The Xcode Troupe uses. The daemon reads only this, never your shell's DEVELOPER_DIR |
maxDevices | 2 | The most simulators running at once |
enabled | true | false switches the platform off: a request for it fails with ENVIRONMENT_PLATFORM_DISABLED |
templates | true | false creates every simulator from scratch instead of cloning a template |
deviceHub | warn | refuse stops Troupe from booting a simulator while Device Hub or Simulator.app runs |
runnerReadyTimeoutMs | 180000 | How long the XCUITest runner may take to start |
Next
- Android Emulator
- Fixtures and options: launch arguments, permissions and app isolation in tests
- Sessions: how a device is acquired and released