English · 日本語
iOS on a real device and in a device cloud¶
A device cloud runs iOS on real hardware — there is no Simulator on the far side. Bajutsu's two iOS backends cannot reach that hardware, so reaching a device cloud takes real new code rather than a configuration switch. This page explains why the existing backends fall short, the one change that fixes it — making the XCUITest backend drive a real device — and the two routes that change opens: a batch route through AWS Device Farm and a live route through a reserved device behind an Appium endpoint. The same real-device work also lets Bajutsu drive a locally attached iPhone or iPad, which was effectively Simulator-only before.
Why idb and simctl do not reach a real device¶
The gap is structural, not a missing option. Bajutsu's two iOS backends each depend on something a real-device cloud does not provide.
simctldrives the Simulator only. It is the command-line control surface for Apple's iOS Simulator, so it has nothing to target on a cloud that runs physical devices — there is no simulator there to command.idbneeds a Mac-residentidb_companiondaemon. The idb backend talks to a companion process running on the Mac host. A managed cloud macOS host offers a limited shell and does not let you run an arbitrary long-lived daemon, so the companion the backend depends on cannot be started.
What the clouds do speak on iOS is Apple's own XCTest, and on AWS Device Farm also Appium's
XCUITest driver. Both build on the same XCUITest machinery that Bajutsu's
XCUITest backend (BE-0019)
already drives through xcodebuild. The path forward is therefore to generalize that backend to a
real device, not to write a third iOS backend from scratch.
The reusable core: XCUITest real-device targeting¶
The XCUITest backend targets the Simulator by default. A single config key on the target selects a real device instead:
targets:
my-app:
xcuitest:
deviceType: device # "simulator" (the default) or "device"
With deviceType: device, the backend generalizes its xcodebuild -destination from a named
Simulator to platform=iOS, so the same xcodebuild test-without-building driving layer runs
against a real device. The device the destination resolves to comes from the run's udid — a locally
attached device's udid, or the udid a device cloud hands the run.
A real device also skips the Simulator bring-up that simctl performs, which drops three
preconditions the Simulator path takes for granted: erasing the device, installing the app from a
local appPath, and granting permissions up front. A scenario that needs one of those on a real
device fails loudly rather than silently doing nothing, and the Device Farm route below installs the
app through the cloud instead. This same key drives a locally attached iPhone or iPad, so the
real-device work is useful on its own, before any cloud is involved.
Two routes to a device cloud¶
Both routes share the real-device XCUITest core above; they differ in how a device is reserved and how the run reaches it.
Batch — AWS Device Farm¶
AWS Device Farm is a batch service: it runs your commands on its host, which already has the
reserved device attached, rather than lending you a device to drive over the network. Bajutsu reaches
it through a CI-side submitter that packages the app plus your scenarios, uploads them with a
test-spec that runs bajutsu run --backend xcuitest against the reserved device's udid, and collects
the artifacts. The verdict still comes from Bajutsu's own machine-checkable assertions, never from
Device Farm's own pass/fail classification.
The submitter, the test-spec it renders, the re-signing caveats, and the manual proof-of-concept
procedure are all documented on the AWS Device Farm page — the iOS run reuses that
same batch machinery, selecting the iOS app upload and the xcuitest backend by platform.
Live — an Appium endpoint provider¶
Where a cloud reserves a single iOS device and exposes it behind an Appium / WebDriver endpoint —
a self-hosted grid, for example — Bajutsu models that reservation as a device provider on the live
seam (BE-0236).
A built-in appium provider yields the run the fixed endpoint of the reserved device instead of a
locally attached udid:
targets:
my-app:
deviceProvider:
kind: appium
endpoint: https://grid.example.com/wd/hub # the reserved device's Appium / WebDriver address
The provider treats the reservation as a device Bajutsu never boots or installs through simctl: it
reports the device ready with its build in place and has nothing to release, since the reservation
belongs to the grid. A missing endpoint fails closed when the run resolves the provider, mirroring
the guard on an unknown provider kind.
Bajutsu drives that endpoint over a live W3C WebDriver transport. The endpoint's http(s)://
scheme is the routing signal: it is exactly the value the shared device_id character set rejects
(a URL carries a /), so it can never collide with a real simctl udid, and the run routes it
around the simctl / xcodebuild udid machinery entirely — that machinery structurally cannot
carry a URL. A tap-and-assert flow works end to end. The semantic actions the local runner drives
natively map onto Appium's XCUITest mobile: commands over the endpoint: tap, query, screenshots,
condition waits, the input steps (type / delete, swipe / scroll), and the two-finger
pinch / rotate gestures. As on the local backends, an ambiguous selector still fails before any
actuation — the selector resolves in Bajutsu, not on the grid.
What the WebDriver transport cannot drive is narrowed away up front, the same way the real-device
path narrows the simctl-backed families:
- Native text selection (
select/copy) has no first-class Appium XCUITest command — the local runner does select-all and clipboard copy natively, but the endpoint exposes no faithful counterpart. simctldevice control and permission grants do not apply on the live route either, exactly as on any other real device (see the caveats below).
So on the live route the preflight (BE-0082)
advertises only what the transport actually drives and skips a scenario that needs one of the
narrowed capabilities — with a clear reason, before any device work — rather than failing late with
UnsupportedAction mid-run.
A worked example config lives at
demos/showcase/live/showcase.live.config.yaml:
it mirrors the local showcase-swiftui target but carries a deviceProvider of kind appium and no
local appPath / xcuitest.testRunner, since the reserved device already holds the build and the
live route speaks WebDriver rather than the runner channel. Point its endpoint at your own grid,
then run the shared showcase suite against it:
bajutsu run --target showcase-swiftui-live --config demos/showcase/live/showcase.live.config.yaml
Real-device caveats: re-signing and capability degradation¶
Running on a real device rather than the Simulator changes two things Bajutsu accounts for up front.
Both are properties of physical hardware, not of any one cloud, so they hold for every real device
the XCUITest backend drives — whether reached by xcuitest.deviceType: device (Device Farm or a
locally attached device) or over the live WebDriver route above.
- Re-signing strips entitlements. A device cloud re-signs the uploaded app with its own provisioning profile so it installs on the reserved device, and the re-sign drops the entitlements the new profile does not carry — commonly Push and App Groups. An app feature that depends on a dropped entitlement behaves as the re-signed build does, so a scenario asserting on such a feature should expect the re-signed behavior rather than the App Store one.
simctldevice control and permission grants do not apply. Bajutsu's iOS device control (setLocation, the clipboard steps,push,clearKeychain,background/foreground, and the status-bar overrides) and its permission grants are all backed bysimctl, which reaches only the Simulator. On a real device the XCUITest backend advertises neither, so a scenario that uses one is skipped by the preflight (BE-0082) with a clear reason, before any device work, rather than failing late with asimctlerror mid-run. The on-device capabilities the XCTest runner drives itself — query, elements, screenshots, taps, and two-finger gestures — are unaffected.
The AWS Device Farm page covers the same two caveats in the batch context, including the specific entitlement keys Device Farm's re-sign drops.
References¶
- AWS Device Farm — the batch route: the submitter, the test-spec, and the manual proof of concept.
- Drivers — the
Driverinterface and the backends behind it, including XCUITest. - Configuration — the
xcuitest.deviceTypeanddeviceProvidertarget keys. - BE-0019 — XCUITest backend
- BE-0236 — device-cloud provider abstraction
- BE-0238 — iOS device-cloud execution