@loadmill/droid-cua
v2.47.0
Published
AI-powered Android testing agent using OpenAI's computer-use model and ADB
Readme
droid-cua
AI-powered mobile testing desktop app for Android and iOS
Create, run, and manage mobile tests with natural language. The desktop app guides setup, connects to your target device or simulator, and turns AI exploration into reusable test scripts.
https://github.com/user-attachments/assets/b9e15a1d-8072-4a2f-a4c5-db180ae38620
droid-cua is a desktop app for AI-powered mobile testing.
It helps teams create, edit, and run tests for Android devices and emulators and iOS simulators on macOS using natural language instead of traditional test code.
Under the hood, droid-cua uses an AI agent to explore your app, execute actions, and save reusable test scripts that can also be run later in headless workflows.
1. Download the desktop app
2. Launch the app
Open the desktop app and choose the platform you want to test.
3. Choose your AI provider
In Settings, choose the CUA model you want to run. Loadmill Smart is the recommended option, Pulse is fast and efficient, and Beacon is experimental. The Loadmill models use your Loadmill sign-in and usage tracking. The OpenAI models use your own OpenAI API key.
4. Connect a target device
- Android: connect a device or select an emulator
- iOS: choose a simulator on macOS
No app of your own yet? The Apps page includes a built-in Demo Shop Android app. Install it on a connected Android device or emulator, or pick it for a cloud Android session, to try the agent right away.
5. Create or run a test
Open Your QA agent to describe a scenario, choose a project, and use the currently connected target. The device control links to Devices to connect or change targets. Local devices work directly with their installed apps. App and cloud configuration stay on the Apps and Devices pages. Suggestions fill an editable prompt; sending starts the existing exploration and test-generation flow. You can also edit an existing test or run a saved script with live execution logs.
Use the Back and Forward buttons next to the sidebar toggle to revisit pages and selections. Pending editor changes are saved before Back or Forward leaves the page. Automatic batch-test transitions do not add history entries. Report summaries remain visible while switching between saved runs. Navigation history lasts for the current app session; opening a new destination after going back clears forward history.
You can also keep project run history in a results folder and review past runs from desktop app reports. New design sessions are saved locally in Recent, including their messages, screenshots, and generated tests. Open a past session to review it or view its saved test; historical activity is read-only, while unsaved suggested tests can still be edited and saved without resuming the agent.
- Android - Physical devices and emulators
- iOS - Simulators on macOS, and iPhones connected to your Mac by USB
Desktop app support:
- macOS: Android, iOS simulator, and iPhone (USB) workflows
- Windows: Android workflows only
Requirements
All platforms:
- Loadmill sign-in for the Loadmill models, or an OpenAI API key for the OpenAI models
- Node.js 20 or newer for CLI use
Android:
- Android Debug Bridge (ADB)
- Android Emulator CLI for launchable emulators
iOS (macOS only):
- Xcode with iOS Simulator
- Appium
- XCUITest driver
iPhone over USB (macOS only): see Testing on a real iPhone.
- Desktop-first workflow - Create, run, and manage tests from one app
- Setup guidance - Configure API access and platform prerequisites in the app
- Device and simulator connection - Connect Android targets and iOS simulators
- Network certificate preparation - Manage one active root CA and install it on a connected local simulator or supported emulator, or get manual setup steps for a physical iPhone or Android phone, from the desktop app's Settings → Network capture page
- Natural-language test creation - Describe flows in plain English
- Test management - Create, edit, save, and rerun reusable scripts, including tests organized in nested project folders
- Live execution logs - Watch actions and progress as tests run
- Reports and history - Review past runs from a project results folder inside the desktop app
- JUnit XML output - Write standard test reports for CI systems and external tooling
- Run video export - Generate and download a polished MP4 replay from a desktop run's screenshots and instruction timeline
- Headless support - Reuse scripts in CLI and automation workflows
Network certificate preparation: Use Settings → Network capture → Manage certificates to see the active CA, upload a single .pem containing a CA certificate and its matching unencrypted private key, generate a new Droid CA, or remove the active CA. Droid stores one active pair in ~/.droid-cua/network/; upload copies the PEM material without changing the source file, and removal does not change device trust. Connect a local iOS Simulator or a rootable Android Emulator, then choose Settings → Network capture → Install on connected device. This installs the active CA, generating a Droid CA if none exists. Devices prepared with a previous CA may need installation again. Droid may reapply Android system trust when a prepared emulator reconnects after a cold boot. Restart an already running Android app after installation so it loads the updated trust store. With a local iPhone connected by USB, Settings → Network capture → Install on connected device does not change the phone; it shows the manual steps instead. Send to iPhone opens the macOS share menu with a DER .cer copy of the active CA named after its common name (for example Droid CUA Root fbd877ab.cer, written to ~/.droid-cua/network/iphone/); choose AirDrop and your iPhone, install it from Settings → Profile Downloaded, then turn on full trust for that common name under Settings → General → About → Certificate Trust Settings. Android Emulator certificate setup supports capable rootable images through Android 16; Google Play images without root access and Android 17 are not supported. With a physical Android phone connected over ADB, Settings → Network capture → Install on connected device writes a PEM .crt copy of the active CA named after its common name (for example Droid CUA Root fbd877ab.crt, in ~/.droid-cua/network/android/), copies it to the phone's /sdcard/Download/ folder with adb push, and shows the manual steps; it changes no phone settings. Install it from Settings → Security → Encryption & credentials → Install a certificate → CA certificate (names vary by vendor), then restart the tested app. If the copy fails, Droid shows the local file path so you can copy it to the phone yourself.
Network capture: Enable Network Capture in desktop Settings or pass --capture-network for CLI tests. Local Chrome and Edge capture through Playwright with no certificate or proxy setup, using the same network queries and report cards as mobile. Capture covers the test browser context, including frames and popups, from run start. Previously loaded traffic is not recovered and existing pages are not automatically reloaded. CLI --url navigation happens after capture starts. Cloud browser capture, WebSocket messages and HAR export are not supported.
Mobile setup: Manually route a prepared local simulator or emulator to 127.0.0.1:8765 (10.0.2.2:8765 from Android Emulator). Enable Network Capture in desktop Settings to keep the proxy forwarding while Droid is open and automatically capture every local mobile test, including each project or scenario case. Existing Network Proxy settings do not turn capture on; opt in to the new setting. For CLI tests, pass --capture-network; this starts the proxy and writes one JSONL log per test. --network-proxy remains a forwarding-only CLI flag. Droid prints or logs each capture path and a best-effort health summary. Desktop artifacts are in the project's results folder; CLI artifacts are beside --report output when enabled, or under logs/. A missing CA stops mobile capture before test actions. No observed traffic or failed HTTPS decryption is reported separately from test assertions, but capture health does not prove that all traffic was observed. WebSocket messages are unsupported. Manual proxy setup is the V1 workflow. Droid does not change device or Mac proxy settings; turn off manually configured routing when finished. If Droid exits while routing remains enabled, restart Droid or clear the proxy to restore connectivity.
Physical iPhone capture: A USB-connected iPhone reaches the proxy over Wi-Fi, so Droid listens on all Mac network interfaces (0.0.0.0:8765) instead of 127.0.0.1 while an iPhone is the target: in the CLI when --avd ios-device:<udid|auto> is used with --capture-network or --network-proxy, and in the desktop app while Network Capture is on and a local iPhone is connected (the proxy switches back to 127.0.0.1 on disconnect, but not during a run). Droid prints the setting to use, for example On the iPhone, set Wi-Fi → Configure Proxy → Manual: server 192.168.1.23, port 8765. Put the Mac and iPhone on the same Wi-Fi network with device-to-device traffic allowed, install and fully trust the Droid CA manually as described above, and set the iPhone proxy by hand; Droid never changes iPhone settings. Turn the iPhone proxy off after testing. Anyone on the same network can use the proxy while it listens on 0.0.0.0, so use a trusted network. Apps that pin certificates still connect through the proxy, but their HTTPS is not decrypted.
Physical Android phone capture: A phone connected over ADB (any serial other than emulator-<port>) is handled the same way as a physical iPhone: Droid listens on 0.0.0.0:8765, in the CLI once the connected device turns out to be a phone (with --capture-network or --network-proxy) and in the desktop app while Network Capture is on and the phone is connected. Droid prints the setting to use, for example On the Android phone, set Wi-Fi → Edit network → Proxy → Manual: hostname 192.168.1.23, port 8765. Install the CA as a user certificate as described above and set the phone's Wi-Fi proxy by hand; Droid does not root the phone or change its settings, and turn the proxy off after testing. Since Android 7, apps trust user-installed CAs only when their network security config allows it, for example <certificates src="user"/> in network_security_config.xml or a <debug-overrides> block in a debuggable build. Debug and QA builds usually opt in; Play Store builds usually do not, so their HTTPS is not decrypted. Chrome trusts user CAs. Rooted-device system trust is not supported.
Captures stop recording at 100 MiB per run, retaining a partial JSONL with a CAPTURE_LIMIT error. Traffic continues forwarding; live network queries report incomplete evidence. Captured bodies are limited to 1 MiB, both on the wire and after decompression. Compressed bodies that expand beyond that limit are omitted with decoded-body-too-large; this is incomplete evidence, not a missing response. The separate query evidence limit is 1,000,000 serialized characters. A capture limit or missing traffic is not proof that an application request never occurred.
For troubleshooting, “No traffic observed” points to routing or an app that did not send traffic; it does not prove an expected request was absent. HTTPS connections with no decrypted requests point to certificate trust or interception compatibility. A port-in-use error means another Droid instance or proxy already owns the listener. Stop that listener before starting the CLI. A capture-size or write failure retains only partial evidence. Ordinary iOS runs without capture do not require proxy or certificate preparation.
Network questions and assertions: With capture enabled, tests can ask Droid to inspect HTTP traffic and compare values across requests in natural language. For example: “Assert that the POST to /pageview after opening Home contains the item_id returned by /home.” The query_network tool searches by URL substring, method, status, flow IDs, request timestamps, or a starting step. It considers all matching exchanges in time order and sends only selected headers/bodies to one side call using the selected run model. Large results require a narrower search; they are not silently truncated. Optional observation windows wait the full duration, up to 30 seconds per query. Absence means only “no matching traffic observed during this window”; unavailable capture or insufficient evidence cannot establish absence. Desktop and HTML reports show one collapsed Network query card per call; expand it to see the question and answer. The agent receives structured flow references and capture diagnostics, while model details stay out of the timeline.
To inspect a saved capture without a model, run node scripts/query-network.mjs path/to/network.jsonl '{"url_contains":"/pageview","method":"POST","sections":["request_body"]}' from the repository. Select request_headers, response_headers, request_body, or response_body; use sections: [] for metadata. Saved-log queries accept after and before timestamps, but not live step IDs or observation waits.
Developer network capture: node scripts/network-capture-smoke.mjs still offers a standalone proxy checkpoint. Leave desktop Network Capture off and omit CLI network flags while it owns 127.0.0.1:8765. Press Ctrl+C when finished; use --output path.jsonl to choose the JSONL path. Request and response headers are written immediately, body records when streams finish or abort, binary bodies are omitted, and captured bodies are limited to 1 MiB on the wire and after decompression. Filtering is partial: JSONL captures may contain credentials, tokens, cookies, or other sensitive data. Treat them as private run artifacts.
- The desktop app connects to an Android device or emulator through ADB, or to an iOS simulator through Appium + XCUITest
- Captures full-screen device screenshots
- Scales down the screenshots for OpenAI model compatibility
- Sends screenshots and user instructions to OpenAI's computer-use model
- Receives structured actions such as click, scroll, type, keypress, wait, and drag
- Rescales model outputs back to real device coordinates
- Executes the actions on the device or simulator
- Validates assertions and handles failures
- Repeats until task completion
The desktop app is the primary way to use droid-cua.
For CI, scripting, or advanced workflows, droid-cua also includes a CLI for running saved instructions headlessly.
Desktop projects can also keep run reports in a results folder, including JUnit XML output that the app can read back as project history.
When a desktop project contains nested folders, the app discovers .dcua files recursively and displays their directory structure in the project tree. You can create tests in nested folders, open those folders in Finder, drag tests between project roots and nested folders, run an individual test, run a nested folder and all its descendants, or run the entire project. Nested tests use the configured project folder's context.md, test-data.md, .secrets, results folder, and desktop execution settings.
The recommended workflow is:
- design and debug tests in the desktop app,
- commit the
.dcuafile plus a headless CLI config file, - run the same test headlessly in CI with
--configfor prompt parity.
Install:
npm install -g @loadmill/droid-cuaInstall the bundled Agent Skill for your coding agent:
In the desktop app, choose Help → Install Droid CUA Skill, then select your coding agent from the submenu. The app installs the skill globally for your user account.
Alternatively, install it from the CLI:
# Global installation (default)
droid-cua skill install cursor
droid-cua skill install claude-code
droid-cua skill install codex
# Or install in the current project
droid-cua skill install codex --localUse droid-cua skill status <agent> to see the installed and bundled skill
versions and installation path; add --local to inspect a project installation.
The skill version is stored in SKILL.md under metadata.version, independently
of the CLI package version. Older copies without version metadata show unknown.
The modified status means the files differ from the bundled copy, which can
indicate an older release or local edits even when version labels match.
Use droid-cua skill uninstall <agent> to remove an installation. Installed skill
files are never overwritten or removed after local modification unless --force
is supplied. After upgrading the npm package, update an installed skill explicitly:
droid-cua skill install <agent> --forceExamples:
# Interactive CLI
droid-cua
# Headless Android run
droid-cua run tests/login.dcua --avd adb:emulator-5554
# Headless Android folder run
droid-cua run tests --avd adb:emulator-5554
# Select tests from different folders
droid-cua run tests/auth/login.dcua tests/checkout/purchase.dcua admin/refund.dcua \
--avd adb:emulator-5554
# Headless iOS simulator run
droid-cua run tests/login.dcua --platform ios --avd "iPhone 16"
# Check an iPhone connected by USB, then run on it
droid-cua ios-device check --fix
droid-cua run tests/login.dcua --avd ios-device:auto
# List running desktop applications and their window IDs (macOS or Windows)
droid-cua --target desktop --list-desktop-apps
# Control the full primary display across applications
droid-cua --target desktop --instructions tests/launcher.dcua
# Control one already-running desktop application window
droid-cua --target desktop --desktop-app com.apple.TextEdit --desktop-window 123 --instructions tests/editor.dcua
# Explicitly use cursor-free accessibility input instead of the default physical input
droid-cua --target desktop --desktop-app com.apple.TextEdit --desktop-input accessibility --instructions tests/editor.dcua
# Headless run with prompt-parity config
droid-cua --avd adb:emulator-5554 --instructions tests/login.dcua --config ci/droid-cua.json
# Headless LambdaTest real-device run
LAMBDATEST_USERNAME=... LAMBDATEST_ACCESS_KEY=... droid-cua \
--device-source lambdatest \
--platform android \
--device-name "Galaxy S24" \
--os-version "14" \
--app ./app/build/outputs/apk/debug/app-debug.apk \
--instructions tests/login.dcua \
--config ci/droid-cua.json
# List AWS Device Farm remote-access devices
AWS_DEVICE_FARM_ACCESS_KEY_ID=... \
AWS_DEVICE_FARM_SECRET_ACCESS_KEY=... \
AWS_DEVICE_FARM_PROJECT_ARN=... \
droid-cua --device-source aws-device-farm --list-devices
# Headless AWS Device Farm real-device run
AWS_DEVICE_FARM_ACCESS_KEY_ID=... \
AWS_DEVICE_FARM_SECRET_ACCESS_KEY=... \
AWS_DEVICE_FARM_PROJECT_ARN=... \
droid-cua \
--device-source aws-device-farm \
--platform android \
--device-name "Google Pixel 10 Pro" \
--os-version "16" \
--app ./app/build/outputs/apk/debug/app-debug.apk \
--instructions tests/login.dcua
# Headless Loadmill Cloud iOS run in the French (France) locale
LOADMILL_API_TOKEN=... droid-cua run tests/login.dcua \
--device-source loadmill-cloud \
--platform ios \
--device-name "iPhone 16" \
--os-version "18" \
--app ./build/MyApp.ipa \
--locale fr_FRdroid-cua run accepts one or more .dcua files or directories. Inputs are
expanded in the order provided, files inside each directory retain their natural
name ordering, and overlapping paths are run only once. A single config, target,
context, test-data file, and secrets source applies to the entire invocation.
The run command must be the first argument. Keep all selected paths contiguous
and place options after the complete path list. Arbitrary option/path
interleaving is not supported.
The existing --instructions <file-or-directory> form remains supported for
backward compatibility.
Example headless config:
{
"cuaModel": "gpt-6-sol",
"promptCustomizations": {
"basePromptInstructions": "",
"designModeInstructions": "",
"executionModeInstructions": ""
},
"appContextEnabled": true,
"appContextPath": "../tests/context.md",
"contextOptimizationEnabled": true,
"contextOptimizationThreshold": 60000
}Typical CI-style usage:
droid-cua \
--avd adb:emulator-5554 \
--instructions tests/login.dcua \
--config ci/droid-cua.json \
--debugSupported CLI options include:
--avd(includingios-device:<udid>orios-device:autofor an iPhone connected by USB)--ios-team--platform--device-source--device-name--os-version--app--list-devices--browser--target(mobile,web, ordesktop)--desktop-app(limit desktop control to an application ID or exact name; omit for full desktop)--desktop-window(select a window within--desktop-app)--desktop-input(physicalby default; useaccessibilityfor cursor-free input)--list-desktop-apps--instructions--config--cua-model--context--no-context--context-optimization-threshold--no-context-optimization--explain--step-watchdog <off|on>--base-prompt--execution-prompt--base-prompt-file--execution-prompt-file--secrets--record--network-proxy(local mobile sessions only; starts idle forwarding at127.0.0.1:8765, or0.0.0.0:8765for--avd ios-device:<udid|auto>and physical Android phones)--capture-network(local mobile and Chrome/Edge test runs; writes a JSONL log for each test, using a proxy only for mobile)--report--report-metadata <path>--no-cloud-report--debug
--cua-model accepts loadmill-smart, loadmill-pulse, loadmill-beacon,
gpt-6-sol, and gpt-6-luna. OpenAI models use direct OpenAI mode and require
OPENAI_API_KEY. Saved direct OpenAI selections of GPT-5.6 Terra, GPT-5.6 Luna,
or GPT-5.4 move to GPT-6 Sol, GPT-6 Luna, or GPT-6 Sol, respectively.
Desktop control supports macOS and Windows. In the desktop app, enable Experimental desktop control at the bottom of Settings to show Desktop in the platform picker. Desktop runs use physical pointer and keyboard input by default. Droid shows a system notice for three seconds before taking control and a completion notice when the run finishes, fails, or is stopped. The delay is fixed; use Stop in Droid once the run starts. Avoid using the mouse or keyboard during the run. macOS requires Screen & System Audio Recording and Accessibility permissions for Droid or the terminal running the CLI. On Windows, Droid must run at the same integrity level as the target application. System notices and automatic minimization apply only to runs started in the desktop app.
CLI runs with --target desktop control the full primary display by default, with no application flag needed. In the desktop app, choose Full desktop, the first option in the Application dropdown. This mode captures and controls the primary display across applications, including apps launched during the test; no target application needs to be running when connecting. Keep all test windows on the primary display. Full desktop requires physical input. Droid minimizes itself after the three-second notice and remains minimized when the run ends; open Droid to review the result or use Stop. Existing saved application selections are preserved.
Use --desktop-app <id-or-exact-name> or select an application in Droid to limit control to that application's windows. Application mode brings the selected window forward and leaves it there when the run ends. Add --desktop-window <id> to select a particular window, or --desktop-input accessibility for cursor-free CLI input; both options require --desktop-app. Before each run and screenshot, Droid refreshes window selection within the original application so it can follow a recreated login window. If replacement is ambiguous, select a window again. If a captured window disappears or moves before input, Droid requests a fresh screenshot instead of transferring its coordinates to a replacement. Focus errors report the activation reason; they do not establish an elevation problem.
If Windows returns an all-black window capture, Droid brings the target forward and crops the visible desktop to the window's visible frame, excluding its invisible resize border. The fallback logs a warning, crops before resizing to preserve detail, and requires the target to remain unobstructed on the primary display; off-screen portions appear black. With accessibility input, bring the target forward yourself when requested. Full desktop reports capture failures directly, without this window capture fallback.
Desktop Test design uses the same notices once you submit your instructions and exploration starts. Full desktop minimizes Droid at that point. Droid sends the completion notice when the generated test is ready, exploration is stopped, or recovery ends in an error. It does not automatically bring itself forward; open Droid whenever you need to give guidance, use Stop, or review the generated test. Opening Test design alone does not take desktop control. When a controller rejects an action, Droid skips the remaining actions in that batch and asks the model to continue from a fresh screenshot with the failure reason. Rejected actions are not counted as completed.
The system notices are shown only for desktop control runs started in the desktop app. On macOS, they may not appear in the unsigned Electron dev app; verify them in a signed packaged build.
Config and precedence rules:
- Use
--config <file>to supply prompt-affecting settings for headless runs. - CLI flags override config file values.
--contextoverrides the config app-context path.--no-contextdisables app context entirely.- Context Optimization is enabled by default with a
60000input-token threshold. --context-optimization-threshold <tokens>sets the threshold and enables Context Optimization.--no-context-optimizationdisables Context Optimization.- Explain Mode is enabled by default and asks the agent for concise explanations of non-obvious decisions, skipped actions, and completion evidence; explanations appear in terminal output and reports.
- Set
"explainMode": falsein the JSON config to disable it. The--explainflag remains available to explicitly enable it for a run. - Step Watchdog is experimental and defaults to
off. Use--step-watchdog onto check for failed operations or blocked execution, or--step-watchdog offto disable checks. The JSON field is"stepWatchdogMode": "on"; the CLI flag overrides it. Desktop users can toggle Settings → Agent → Verify Step Results on or off. Successful checks run silently. Failures use an assertion-style Step Failed card with the expected instruction, actor explanation, and screenshot. Interactive runs offer Skip to continue or Stop to fail; headless runs fail automatically. Evaluator errors end execution. Enable--debugor desktop Debug Mode to inspectstep_watchdog.resultentries; probabilities and evaluator diagnostics remain out of normal output and reports. See Step Watchdog for provider requirements, logging, and calibration. --base-promptand--execution-promptlet you pass prompt customizations inline on the command line.--base-prompt-fileand--execution-prompt-fileoverride the corresponding prompt customizations from config.- If both inline and file-based prompt overrides are provided, the inline prompt flags win.
Secret typing:
- Put runtime typing secrets in a project
.secretsfile using dotenv-styleKEY=valuelines. - Desktop execution and design mode load
.secretsfrom the selected project folder. Headless CLI loads.secretsfrom the current working directory. - Headless CLI also accepts
--secrets KEY=value,OTHER=value; these values override.secretsfor that run. - The agent sees only secret keys and may call
type_sensitive_text({ key }). Logs and reports may include keys, but not resolved values. - Screenshot masking is out of scope. Secret values also necessarily pass through ADB, Appium, or browser automation while being typed.
HTML report (--report):
- Desktop, CLI, and Loadmill-downloaded HTML reports include a hidden
pre#agent-reportcontaining HTML-escaped JSON (schemaVersion: 1). Parse itstextContentfor the recorded report timeline and metadata; each event’s optionalscreenshotRefidentifies an existing lightbox whoseimgcontains the screenshot. No debug mode is required, and images are not duplicated in this section. Reports generated by older Droid versions do not contain it. - Writes a self-contained HTML report of the run — the same Run Details view the desktop app shows, with screenshots inlined — that you can share and open in any browser.
- With no value it saves
<test>--<date>--<time>.html(e.g.login--2026-06-03--16-09-20.html) in the current directory; pass--report <file>to pick a filename or--report <dir>to pick a folder. - A multi-path selection writes the individual test reports plus one aggregate
Selected testsreport with a child result and timeline for every selected test. When--report <file.html>is used, that exact path is used for the aggregate report. - Aggregate HTML reports show each test as an expandable row with its duration and status. Passed tests start collapsed; failed, errored, and stopped tests start expanded. Instruction timelines use the same rendering as single-test reports.
- In the desktop app, click an instruction title or its duration to expand its recorded actions.
- In the desktop app the same report is available from a run's Share → Download HTML report button.
- Generated HTML reports opt out of Cloudflare email obfuscation so ordinary email values remain visible in hosted reports. Secret-typing values are omitted from typing logs, but may still appear in screenshots.
- Loadmill flow entries link Test started to the flow's test-suite run results, including in reports generated by CI.
- Runs with captured screenshots also expose Share → Generate video. The desktop app renders portrait mobile runs as square replays and landscape mobile, web, and desktop runs as 16:9 replays. Landscape Android and iOS screenshots keep a device frame sized to the captured screen instead of browser chrome. Rendering happens locally, then the app prompts for a save location using
<test>--<date>--<time>--test-replay.mp4as the suggested filename. While rendering, a compact Background tasks card shows progress for Generating test replay; it can be minimized to a task-count card or canceled without opening the save dialog. - Authenticated headless runs upload their HTML report and screenshots to Loadmill by default. The Loadmill run record also includes the resolved Droid version, model, provider, Agent Pace, Aim Assist, Context Optimization, and target environment. Use
--no-cloud-reportto disable artifact upload; the Droid run record is still reported. - Use
--report-metadata <path>when automation needs the exact published Loadmill run ID and last uploaded screenshot object name. Droid clears that destination when the invocation starts and writes it only after report publication succeeds, including for published failure reports.
droid-cua run tests/login.dcua --avd adb:emulator-5554 --report ./reportsThe desktop app and the CLI can run tests on your own iPhone connected to your Mac by USB, on the apps already installed on it (TestFlight, App Store, or your team's builds). droid-cua installs a small helper app (WebDriverAgent) on the iPhone, signed with your company's Apple team; nothing is built from source at run time.
Requirements:
- macOS with an Xcode that supports the iPhone's iOS version, opened once. Its first launch installs the device components and asks for a Mac admin password.
- The iOS platform installed in that Xcode (Xcode > Settings > Components, or
xcodebuild -downloadPlatform iOS; about 10 GB). Xcode 26 doesn't include it. - Your Apple ID signed into Xcode (Xcode > Settings > Apple Accounts), as a member of your company's paid Apple Developer team, with at least the Developer role and Access to Certificates, Identifiers & Profiles. Free Apple IDs are not supported.
- The Appium XCUITest driver, as for Simulators.
- On the iPhone, once: connect it by USB, unlock it and tap Trust; turn on Developer Mode (Settings > Privacy & Security > Developer Mode, then let it restart and confirm); set Auto-Lock to Never (Settings > Display & Brightness).
- A test-only iPhone is recommended: droid-cua tests the apps and data already on the phone and never resets it.
Check the iPhone and set it up:
droid-cua ios-device check # report each check and how to fix it
droid-cua ios-device check --fix # also set up signing and install the helper appios-device check prints every check as passed (✔), failed (✖), or a
reminder (!), with the exact fix for each failure, and exits 0 only when the
iPhone is ready. Use --udid <udid> to choose an iPhone (the first one connected
by USB is used otherwise). The first --fix on a Mac runs a one-time Xcode signing
build (about a minute); installing the helper app takes a few seconds. A test run
performs the same checks and fixes automatically before it starts.
Xcode installed names what's wrong: Xcode isn't in /Applications, the
Command Line Tools are selected instead of Xcode (fix: sudo xcode-select -s
/Applications/Xcode.app), or the Xcode license isn't accepted (fix: sudo
xcodebuild -license accept). iOS platform installed checks that Xcode has the
iOS platform for its iOS SDK, without which Xcode can't build for an iPhone (fix:
install it from Xcode > Settings > Components, or run xcodebuild
-downloadPlatform iOS, about 10 GB). Apple team access reads your teams from Xcode;
on a fresh Mac, where Xcode hasn't saved them yet, droid-cua fetches them from
Apple with a short Xcode build (a few seconds, at most once a minute). It then
says whether no Apple account is signed into Xcode, or you're signed in but none
of your accounts is in a paid Apple Developer team.
Run tests on the iPhone:
droid-cua run tests/login.dcua --avd ios-device:auto
droid-cua run tests/login.dcua --avd ios-device:00008120-001C18501A81A01E --ios-team ABCDE12345--avd ios-device:<udid>selects an iPhone;ios-device:autopicks the first iPhone connected by USB. The platform is detected as iOS from the token. Runningdroid-cuawithout--avdlists connected iPhones next to Simulators.--ios-team <TEAMID>chooses the Apple team that signs the helper app when you belong to several paid teams.DROID_CUA_IOS_TEAM_IDworks too; the flag wins.--appis not used for a local iPhone: tests run against apps already installed on it, so open the app in your test instructions (for example,Open the Settings app).- After the iPhone restarts, unlock it. On the first run after a restart or a new helper install, the iPhone may ask for your passcode to allow automation; enter it and droid-cua continues. If the iPhone locks during a run, droid-cua pauses until you unlock it.
- After a major iOS update, droid-cua tells you which Xcode to install and open once.
In the desktop app:
- Run the Setup Wizard (Help > Run Setup Wizard) and choose iPhone (USB).
The Connect iPhone checklist runs the same checks as
ios-device check. Steps you do on the iPhone or in Xcode (plug in, Trust, Developer Mode, unlock, device components) re-check every few seconds and turn green on their own. Set up signing and Install helper app run the fix and stream its progress. When the Command Line Tools are selected or the Xcode license isn't accepted, Xcode installed offers Set Xcode Developer Path or Accept Xcode License (both ask for a Mac admin password). When the iOS platform is missing, iOS platform installed offers Download iOS Platform, which runsxcodebuild -downloadPlatform iOS(about 10 GB). Auto-Lock and the dedicated test iPhone are shown as reminders and don't block. - If you belong to several paid Apple teams, choose the team in the wizard. The desktop app remembers it and uses it for every iPhone run.
- Connected iPhones appear in the iOS device picker next to Simulators, with a hint when the iPhone isn't trusted yet or Developer Mode is off. droid tests the apps already installed on the iPhone and never installs any.
- The connection log and the run view highlight what needs you on the iPhone: Enter your passcode on the iPhone to allow automation, iPhone locked — unlock to continue, and Your iPhone is on iOS N. Install Xcode N and open it once.
Known limits: USB only (no Wi-Fi), one iPhone at a time, attended runs (someone
needs to unlock the phone and approve prompts), macOS only, and no .ipa
installation. Verified on iOS 26; iOS 17 to 26 are expected to work. iOS betas
and iOS 27 are not supported yet.
LambdaTest CLI runs:
- Use
--device-source lambdatestwith--platform,--device-name,--os-version,--app, and--instructions. - Set
LAMBDATEST_USERNAMEandLAMBDATEST_ACCESS_KEYin the environment. - Android LambdaTest runs require an
.apk; iOS LambdaTest runs require an.ipa. - The CLI uploads the app to LambdaTest for each run and closes the cloud session when the run finishes or fails.
- Each CLI LambdaTest run gets a unique LambdaTest build name so repeated local runs are easy to distinguish in the LambdaTest UI.
AWS Device Farm CLI runs:
- Use
--device-source aws-device-farmwith--platform,--device-name,--os-version,--app, and--instructions. - Set
AWS_DEVICE_FARM_ACCESS_KEY_ID,AWS_DEVICE_FARM_SECRET_ACCESS_KEY, andAWS_DEVICE_FARM_PROJECT_ARNin the environment (an IAM user with Device Farm access and the ARN of a Device Farm project inus-west-2). --device-source aws-device-farm --list-devicesprints the remote-access device catalog (name, platform, OS) so you can pick valid--device-name/--os-versionvalues, then exits.- Android runs require an
.apk; iOS runs require an.ipa..appbundles are not supported. - Re-running with the same app binary skips the upload: uploads are cached by checksum and reused for up to 30 days (Device Farm's upload retention).
- Device provisioning takes ~1 minute; the CLI shows live progress while it waits.
- The session is stopped when the run finishes, fails, or is interrupted with Ctrl-C — billing stops as soon as the stop is requested, so the CLI does not wait for the session to fully wind down.
AWS Device Farm in the desktop app:
- Open Integrations → AWS Device Farm and enter an AWS Access Key ID + Secret Access Key. The keys are validated on connect, a
droid-cuaDevice Farm project is created automatically (the IAM user needs the minimal Device Farm policy, includingListProjects/CreateProject), and the remote-access device catalog loads. Credentials are stored in plaintextdesktop-config.json, the same as BrowserStack/LambdaTest. - On the Devices page, pick AWS Device Farm as the source, choose a device + an uploaded
.apk/.ipa, and start a Design/Run session. Provisioning takes ~1 minute and live progress flows into the device log. - Pause caveat: a Device Farm session paused for more than 5 minutes may be ended by AWS and need reconnecting.
Loadmill Cloud locale:
- Pass
--locale <lang_REGION>(for examplefr_FR,en_NL,hi_IN) with--device-source loadmill-cloudto start a mobile run in that locale. In the desktop app, use the gear next to the connected Loadmill Cloud device in the test header to pick a locale; it applies to Run and Design sessions started afterwards. - Android changes the device language and region and relaunches the app. iOS relaunches the app with
-AppleLanguagesand-AppleLocale, so only the app under test sees the locale. - The locale is applied once, when the session starts.
--localecannot be combined with--cloud-session-id.
Minimal GitHub Actions shape:
jobs:
droid-cua:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install -g @loadmill/droid-cua
- run: |
droid-cua \
--device-source lambdatest \
--platform android \
--device-name "Galaxy S24" \
--os-version "14" \
--app ./app/build/outputs/apk/debug/app-debug.apk \
--instructions tests/login.dcua \
--config ci/droid-cua.json \
--debug
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
LAMBDATEST_USERNAME: ${{ secrets.LAMBDATEST_USERNAME }}
LAMBDATEST_ACCESS_KEY: ${{ secrets.LAMBDATEST_ACCESS_KEY }}The same shape works for AWS Device Farm — swap the run step and env:
- run: |
droid-cua \
--device-source aws-device-farm \
--platform android \
--device-name "Google Pixel 10 Pro" \
--os-version "16" \
--app ./app/build/outputs/apk/debug/app-debug.apk \
--instructions tests/login.dcua \
--config ci/droid-cua.json \
--debug
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
AWS_DEVICE_FARM_ACCESS_KEY_ID: ${{ secrets.AWS_DEVICE_FARM_ACCESS_KEY_ID }}
AWS_DEVICE_FARM_SECRET_ACCESS_KEY: ${{ secrets.AWS_DEVICE_FARM_SECRET_ACCESS_KEY }}
AWS_DEVICE_FARM_PROJECT_ARN: ${{ secrets.AWS_DEVICE_FARM_PROJECT_ARN }}Example without a config file:
droid-cua \
--avd adb:emulator-5554 \
--instructions tests/login.dcua \
--context tests/context.md \
--base-prompt "stop and look at the screen after every action you take."Headless debug artifacts:
--debugwrites desktop-style structured JSONL artifacts underlogs/.- Each run creates
logs/execution-<runId>-<timestamp>.jsonl. New run IDs are UUIDs; a batch shares one ID across its tests and aggregate report, and a rerun gets a new ID. - Each run also creates a sibling screenshot folder next to that JSONL file.
- Shared device events are written to
logs/device-events.jsonl. --debugno longer creates the legacylogs/debug-*.logfile for headless runs.- If
--debugand--recordare both used, screenshots are written to both the debug artifacts folder and the legacydroid-cua-recording-<timestamp>folder.
Current headless behavior is documented in docs/headless-cli-spec.md.
© 2026 Loadmill. All rights reserved.
Desktop browser compatibility
Droid disables password-saving prompts and password-breach warnings in every local Chrome/Edge automation profile before launch, for both fresh and persistent sessions in the CLI and desktop app. Other profile preferences are preserved, and personal browser profiles are unaffected. Reconnect an already-open session to apply these defaults. Cloud and externally attached WebDriver browsers must be configured by their provisioner.
For sites that block automated browsers, enable Settings > General > Capabilities > Reduce browser automation detection, then reconnect. This desktop preference defaults off and applies across projects to local Chrome/Edge connections. It suppresses Chromium's automation signal but does not guarantee compatibility. CLI and Cloud sessions are unaffected.
