Test prep tools¶
Prepare a repeatable device and app state before running a flow. These tools are useful for fixtures, localization, permissions, and system state.
App files¶
Use putAppFile to write text, base64, or a host file into an app container:
{
"name": "putAppFile",
"arguments": {
"platform": "android",
"target": {
"domain": "app_containers",
"appId": "com.example.app",
"container": "documents"
},
"files": [
{
"destinationPath": "fixtures/settings.json",
"contentText": "{\"enabled\":true}"
}
]
}
}
putAppFile never terminates the app. After a successful app_containers write,
result.warning appears when the target app is known to be running (on Android,
in the written user profile): it may not see the change until it re-reads the file
or is relaunched. The warning is omitted when running state is unknown or the
app is not running, and for user_files and media_library writes.
user_files also supports bounded iOS Simulator staging through the managed
Files fixture app dev.jasonpearson.automobile.FilesFixture (not yet shipped in
this repo). It resolves the installed app’s data container at runtime and writes
only Documents/automobile/<namespace>; reset deletes only that namespace.
Missing fixture installation returns actionable guidance. Host staging and
picker visibility are separate effects; visibility is unavailable unless a
verifier observes the exact destination. Physical iOS and iOS user-files
list/read remain unsupported or unexposed. Validation here uses fakes; the
historical iOS 17.5 picker experiment is documented in the
accepted design.
On Android, putAppFile accepts optional userId (a non-negative safe integer).
An explicit ID selects that profile without discovery. Omission checks package
installation for every user: use the sole installed user; with several candidates,
prefer the user of the foreground app when it is the requested package, then the
current user if installed among the candidates. Otherwise, ambiguity lists the
candidate IDs and asks for userId; no installation names the app/device, and
failed discovery asks for an explicit ID. Batches resolve once. Private
documents/cache/tmp containers use run-as and require a debuggable app.
externalFiles uses /sdcard/Android/data/<appId>/files for user 0 and
/storage/emulated/<userId>/Android/data/<appId>/files for nonzero users.
Shared-storage domains keep their existing resolution.
The canonical list resource is
automobile:devices/{deviceId}/storage-domains/app_containers/{appId}/{container}{?userId};
its existing automobile:devices/{deviceId}/apps/{appId}/files/{container}{?userId}
alias remains supported until device verification permits retirement;
append /{path} before the query to read a file. For example:
automobile:devices/emulator-5554/storage-domains/app_containers/com.example.app/documents/settings.json?userId=10.
Both accept the same optional ?userId=N; omission auto-resolves on Android.
Returned put and list file URIs pin the resolved Android user. Explicit IDs
always round-trip, including ?userId=0, without extra discovery. Auto-resolved
nonzero users always include ?userId=N; auto-resolved user 0 includes
?userId=0 only when the app is installed for several users. A sole user-0
installation keeps the existing query-free URI. The same rule applies to
externalFiles; iOS URIs have no user query.
Unverified: run-as --user API support has no captured fixture. Unsupported
option/usage errors identify this uncertainty (unverified which API level),
and advise omitting userId only for a primary-user installation or using a
debuggable build via an adb-user-0 session. Unverified, from Android
scoped-storage documentation: on API 30+, plain shell read/list access to other
apps’ Android/data directories is not guaranteed and push may be denied;
the existing shell/push mechanism is retained. See
File containers for semantics and the Android documentation source.
Locale and device state¶
changeLocalizationsets language, region, time zone, and formatting for a device. Set the locale before launching the app.setDeviceStateconfigures supported system state such as biometric enrollment.biometricAuthsimulates a match or failure on supported emulators and iOS simulators, and every result in apps that embed the AutoMobile SDK.wakeAndUnlockwakes an Android device and unlocks it with an optional PIN.postNotificationcreates a notification for notification-flow tests.clipboardsets, reads, pastes, or clears clipboard content.
For a clean app start, use launchApp with clearAppData: true where the
platform supports it. Use observe after preparation to confirm the expected
state before continuing.
Device snapshots¶
Enable deviceSnapshot for the current connection, then capture a known-good
state before a destructive flow:
{
"name": "setToolEnabled",
"arguments": { "toolName": "deviceSnapshot", "enabled": true }
}
{
"name": "deviceSnapshot",
"arguments": {
"action": "capture",
"snapshotName": "signed-in",
"platform": "android",
"includeAppData": true,
"includeSettings": true
}
}
Android emulators can restore full VM snapshots. iOS simulators back up the specified app containers, so an iOS capture that includes app data must list the bundle IDs:
{
"name": "deviceSnapshot",
"arguments": {
"action": "capture",
"snapshotName": "signed-in",
"platform": "ios",
"appBundleIds": ["com.example.app"],
"includeAppData": true,
"includeSettings": true
}
}
For an iOS settings-only capture, set includeAppData to false. Physical
Android devices restore settings only. Restore with action: "restore" and the
same snapshotName.
An Android VM restore in daemon mode returns a new deviceSessionUuid when
the device has a live registry epoch. The owning tool sessionUuid survives.
The old device-session UUID is superseded by the restore: subscribers receive
device_session_ended with successorSessionUuid and
reason: "superseded-by-restore", followed by device_session_started for the
new epoch. Subscribe again with the returned UUID. You can also discover it
from daemon/listDeviceSessions or the device description’s
runtime.deviceSessionUuid. Direct mode, settings-only restores, and devices
without a live epoch omit this field. A definitive pre-load rejection preserves
the epoch; a VM load followed by readiness failure still retires it.
Archive size, eviction, and reclaim¶
The archive resource automobile:deviceSnapshots/archive reports what the
snapshot archive costs and what still needs cleaning up:
| Field | Meaning |
|---|---|
totalSizeBytes |
Sum of every record whose size is known. |
unsizedCount |
Records whose payload could not be measured. They are not in totalSizeBytes, so a non-zero count means the archive is larger than the total says. |
pendingReclaimCount |
Records whose in-AVD VM snapshot still needs deleting because its emulator was offline. |
orphanedAvdSnapshots |
In-AVD snapshot directories with no record behind them. |
maxVmSnapshotsPerAvd |
Maximum VM snapshots retained for each Android AVD (default 3). |
maxVmArchiveSizeMb |
Optional additional per-AVD byte ceiling for VM snapshots; null means unlimited. |
maxArchiveSizeMb |
The byte budget for non-vm (app_data, adb, simctl) archive records (default 100 MB). |
An Android emulator VM snapshot does not live in the archive store — the
emulator writes it inside the AVD, at
~/.android/avd/<avd>.avd/snapshots/<name>/ (ram.bin, textures.bin,
snapshot.pb, …), which is routinely a couple of gigabytes. That directory is
what a vm record’s size measures. VM snapshots are retained per AVD by
maxVmSnapshotsPerAvd (default 3), evicting least-recently-accessed records
first. Operators that also need a VM byte ceiling can opt into the per-AVD
maxVmArchiveSizeMb setting; it is unlimited by default because a single VM
snapshot is routinely gigabytes. maxArchiveSizeMb does not apply to vm
records; it continues to govern only archive-store app_data, adb, and
simctl records.
maxVmSnapshotsPerAvd, maxVmArchiveSizeMb, and maxArchiveSizeMb are
DeviceSnapshotConfig fields, but no CLI flag, environment variable, or MCP
tool sets them today. They take effect through the compiled-in defaults in
src/features/snapshot/DeviceSnapshotConfig.ts or the daemon’s private
device-snapshot-config Unix-socket updateConfig protocol in
src/daemon/deviceSnapshotSocketServer.ts; its only known consumer is the
desktop app’s Kotlin DeviceSnapshotSocketClient, which does not yet pass
through either VM-specific field. Exposing them through that client or a
first-class CLI/MCP surface is follow-up work, not part of this change.
Evicting a vm record deletes the in-AVD snapshot through the emulator console
(adb -s <serial> emu avd snapshot del <name>). When that emulator is not
running there is nothing to issue the delete to, so the record is kept and
flagged pendingReclaim rather than dropped — dropping it would lose the only
reference to gigabytes still on disk. The next capture on that AVD sweeps the
flagged records and finishes the job.
Cleaning up orphaned in-AVD snapshots¶
orphanedAvdSnapshots lists in-AVD snapshot directories no record accounts for.
AutoMobile never deletes them automatically: an orphan may predate AutoMobile or
be a snapshot someone made by hand. The emulator’s own default_boot quick-boot
state is excluded from the report entirely.
Each entry reports avdName, snapshotName, sizeBytes and directoryPath.
Use directoryPath for anything that touches the filesystem: it is the
directory the scan actually measured. ANDROID_AVD_HOME (and an <avd>.ini
registry file redirecting to a relocated AVD) move an AVD off the conventional
~/.android/avd/<avd>.avd path, so that path is not reliably where the bytes
are.
To remove one yourself, with the AVD’s emulator running:
adb -s <serial> emu avd snapshot del <snapshot-name>
With the emulator stopped, delete the reported directory directly:
rm -rf "<directoryPath>"
Check what is there first with du -sh "<directoryPath>", or list every in-AVD
snapshot of one AVD with du -sh "$(dirname "<directoryPath>")"/*.