feat: Add iOS physical device support
Proposed changes
copilot:summary
This PR adds support for running Maestro tests on physical iOS devices.
I didn't want to build something that restricts users to a single device, so this works for both Android and iOS, and naturally enables concurrent test execution on multiple devices.
I've only modified the core test runner to accept a custom port. The orchestration layer (how Maestro manages building for real devices, test runner and port forwarding) is intentionally left unchanged - I have already on thin ice by my opinion to add multiple device support.
Changes are organized in separate commits for easy cherry-picking.
Why we built this
We needed this for our solution offering and wanted to give it back to the community.
Credit: Builds upon the approach proposed by @avinash-bharti in #2339.
Usage: iOS Physical Device
1. Build XCTest Runner for Device
cd maestro-ios-xctest-runner
xcodebuild clean build-for-testing \
-project maestro-driver-ios.xcodeproj \
-scheme maestro-driver-ios \
-destination 'platform=iOS,id=<DEVICE_UDID>' \
-derivedDataPath Build \
DEVELOPMENT_TEAM=<YOUR_TEAM_ID>
- Start XCTest Server on Device
xcodebuild test-without-building \
-xctestrun ./Build/Build/Products/maestro-driver-ios_iphoneos*.xctestrun \
-destination "platform=iOS,id=<DEVICE_UDID>" &
- Start Port Forwarding
The XCTest server runs on port 22087 on the device. Forward it to your machine using any of these tools:
Using iproxy (libimobiledevice):
iproxy 22087:22087 -u <DEVICE_UDID>
Using pymobiledevice3:
pymobiledevice3 usbmux forward --udid <DEVICE_UDID> 22087 22087
Using go-ios:
ios forward 22087 22087 --udid <DEVICE_UDID>
Using Xcode's devicectl:
xcrun devicectl device forward --device <DEVICE_UDID> localPort=22087 remotePort=22087
- Run Maestro Tests
maestro --driver-host-port 22087 --device <DEVICE_UDID> test flow.yaml
Tested on
-
iOS real device + iOS simulators
-
iOS real device + Android real device
-
iOS real device + Android real device + Android emulator
-
iOS real device + iOS simulator + Android real device
-
Multiple iOS real devices in parallel with different OS versions (iOS 18.x and iOS 26.x)
Issues fixed
Fixes #686
Thanks! This looks like a good step forwards.
Does this work with addMedia or setLocation? Will setting permissions work? I think there's a bunch of simctl invocations in the codebase that won't work on a real device.
addMediaorsetLocation
I haven't had a chance to test it thoroughly yet — my main focus was just getting it to work. There's still a lot more that can be done, and I'd love to improve it further when time permits (honestly, mostly driven by my own needs 😅). For now, I'll focus on making it easier for others to use. Any feedback or suggestions are welcome
To clarify (my previous comment may have been confusing): we have tested this on multiple devices with different iOS versions, but the following commands are not supported in this PR:
- addMedia
- setLocation
- clearState
- permissions
Status of each:
-
addMedia / setLocation / permissions: No solution currently.
-
clearState: We have a workaround, but it requires Maestro to be aware of the IPA file.
Still debating the best approach, adding a CLI arg feels messy since it's only needed for physical devices.
Alternative: provide a separate build along with our opinionated approach for building the Runner app for physical devices, starting it, and handling port forwarding, until Maestro has official support.
So excited for when this lands! 🙌