Skip to main content
This guide covers how to build and package your compiled XCUITest suite (*-Runner.app) so it runs correctly on the MobileBoost device grid, and how to trigger and monitor runs via the API. Follow the section that matches where you want to run: physical devices or virtual devices (simulators). The two are not interchangeable - a runner built for one will not launch on the other.
You do not need to solve code signing or provisioning for our devices. Our grid re-signs the test runner with the certificate and device profile for the specific hardware it runs on.
All API endpoints below authenticate with your mb_live_ API key passed as a Bearer token in the Authorization header.

Prerequisites

  • Xcode (matching or newer than the OS versions you target).
  • Your project has a UI Testing bundle target and a scheme that includes it in its Test action.
  • You know three bundle identifiers - you’ll need them at upload time:
    • App under test (must match the .ipa you upload)
    • Test runner (*-Runner)
    • Test bundle (*UITests)

1. Why physical vs. virtual matters

build-for-testing compiles your runner and its .xctest bundle for one platform slice at a time: A simulator build links the simulator XCTest frameworks and CPU slice; a device build links the device ones. Uploading the wrong slice is the single most common failure - it will install but the test harness won’t start. If you want to run on both, build and upload two separate zips.

2. Build the runner

Output lands in:

3. Package the runner zip

Zip from inside the Products slice folder so the .app is at the root of the archive, and use --symlinks (framework bundles contain symlinks - omitting this bloats the zip and can break the embedded frameworks).
Zip contents (required + recommended):
  • *-Runner.app - required (contains PlugIns/*.xctest, your compiled tests)
  • *.xctestrun - recommended. It preserves your test configuration: launch environment variables, launch arguments, the target-app reference, and skip/only test lists. Include it and your tests run exactly as configured in your scheme.

4. Verify the zip before uploading

You should see the *-Runner.app, a PlugIns/*UITests.xctest inside it, and the .xctestrun. To sanity-check you built the device slice (not simulator) when targeting physical hardware, confirm it came out of the Debug-iphoneos folder - that folder is the source of truth for the slice.

5. Bundle-ID matching

The app under test you upload as .ipa must have the same bundle identifier the test scheme was built against. If your tests use a bare XCUIApplication(), it launches the scheme’s target app by bundle ID; a mismatch means the tests launch nothing (or the wrong app).
  • For physical runs, upload your enterprise-distribution .ipa as the app under test. Do not upload the debug Debug-iphoneos/*.app produced by build-for-testing as the app under test - that debug app and your test runner just need to share the bundle ID, not the binary.
  • If your tests target a specific bundle explicitly, use XCUIApplication(bundleIdentifier: "<APP_UNDER_TEST_BUNDLE_ID>") and tell us that ID.

6. Code signing - what you do and don’t need to do

Physical devices: You do not need a provisioning profile that includes our devices. We re-sign the runner with our own Apple Development certificate and a profile scoped to the target hardware. To avoid any local provisioning friction, you can even build without signing:
(Building with your own team’s automatic signing is also fine - we strip and re-sign either way.) Virtual devices: Simulators don’t enforce code signing; no certificates or profiles are required. Because we re-sign the runner, please keep bundle identifiers stable and don’t hard-code entitlements that depend on your own team’s provisioning (e.g. keychain-sharing / app-group IDs tied to your Team ID) in the UI test target - those can break re-signing. Your app under test is unaffected; it keeps your enterprise signature.

7. Optional: get your test list for sharding

If you want to split tests across devices, we can shard automatically, or you can send us the test identifiers:
Identifiers use TargetName/ClassName/testMethod and can be passed as only-testing / skip-testing filters at launch time.

8. What to upload

You’ll end up with, per target type:

Common mistakes checklist

  • Built for simulator, uploaded for a physical run (or vice-versa). → Build the matching slice; upload the matching zip.
  • Zipped from the wrong directory, so the .app is nested under Build/Products/... inside the archive. → cd into the slice folder first; the .app must be at the zip root.
  • Zipped without --symlinks. → Frameworks break / signing fails.
  • App-under-test bundle ID ≠ what the tests expect. → Keep them identical; tell us the ID.
  • Uploaded the debug build-for-testing app instead of the enterprise .ipa for physical runs. → Upload your enterprise IPA as the app under test.
  • Ran test instead of build-for-testing (produces no reusable runner). → Use build-for-testing.

9. Trigger a test run

Once both files are uploaded, start a run with the IDs returned by the two upload calls: the app_id from POST /uploadBuild and the test_suite_id from POST /app-automate/xcui/test-suite. Endpoint
Minimal request
Required parameters Optional parameters Full request with GPS and sharding

GPS location

gpsLocation overrides the device’s location for the whole run; it is applied on each device before your first test starts and cleared automatically at teardown.
If you omit gpsLocation, physical devices keep their real GPS and virtual devices use our neutral default. To read or change the location from inside a test, drive your app’s own location flows as usual - gpsLocation sets the starting position for the run.

Sharding

Sharding is optional. Provide a shards object to split the suite across devices and run them in parallel. Auto-distribute - give only numberOfShards; we spread the discovered tests evenly across that many shards:
Explicit test cases per shard - provide a mapping array. Each entry names a shard and lists the tests it runs via strategy: "only-testing" and a values list of TargetName/ClassName[/testMethod] identifiers. numberOfShards must match the number of mapping entries.
A strategy of skip-testing is also accepted per shard if you prefer to express a shard as “everything except these”. Use -showTestPlans to list valid identifiers. Response
Store the build_id; it identifies the run when you poll status and download results.

10. Check run status

Poll the build with the build_id from the launch response.
The build carries an overall status plus one session per device/shard combination. Each session has its own session_id.
Status values (both build and session): queued, running, passed, failed, error (infrastructure/setup problem, e.g. install or signing failure), timedout. Poll until terminal

11. Retrieve results

Once the build reaches a terminal status, fetch the machine-readable report at the build level.
junit returns a standard JUnit XML your CI can ingest; json returns the same pass/fail data as structured JSON.