Use
Commands & options
| Command | What it does |
|---|---|
| Look | |
looktap ls [--all] [--json] | Visible elements with a label, plus unlabelled buttons, each with an @ref. --all includes everything. --json prints {screen, elements}. While the screen is still loading (nothing labelled yet), waits up to --wait |
looktap wait <label|@ref> [--gone] | Until it's on screen (with --gone, until it isn't). Default up to 10 s, or --wait. Exits 2 on timeout |
looktap expect <label|@ref> [= value] [--gone] [--exact] | Checks it's on screen, with = value that its value is exactly that, or with --gone that it isn't. Matches like wait (--exact: the whole label only). Waits up to --wait (3 s) for it to come true. Exits 1 with what's there instead: expect failed: "Email" is "[email protected]", expected "[email protected]" |
looktap scrollto <label> [--up] | Swipes until the element is fully on screen. Each swipe holds still before lifting, so the list stops where it was dragged instead of coasting. Fails at the end of the list, or after 15 swipes |
looktap shot <name> [--plain] [--scale f] | Saves <out>/<name>.png and prints its path |
looktap burst <dir> <seconds> | A frame each time the screen changes. Only distinct frames are kept |
looktap record start [name] · record stop | An MP4 of everything in between, saved to <out>/<name>.mp4; stop prints the path and the length. Android: at most 3 minutes, FLAG_SECURE screens black |
Act (add --ls to print the settled screen afterwards) | |
looktap tap <label|@ref> [--exact] | @ref from the last ls, or a label: exact, else a prefix/contains match that names a single label (ambiguous → exit 2). Prints what it tapped |
looktap longpress <label|@ref|x y> [ms] | Holds the touch for ms (default 800). Same targets as tap, or a point |
looktap zoom in|out [x y] [--scale f] | Two-finger pinch around a point (default: the screen centre). --scale sets how far apart the fingers end up (default 2.5) |
looktap tapxy <x> <y> | Taps a point (iOS points / Android dp) |
looktap type <text> | Types into the focused field. Text the keyboard can't type (accents, emoji, CJK) is pasted, which replaces the clipboard |
looktap key <name> … | return, escape, delete, tab, space, selectall, paste, home. Android also back, any KEYCODE_* |
looktap fill <label|@ref> <text> [--exact] | Focuses the field, clears it, types. Empty text just clears. iOS simulator: if the field doesn't read back the text afterwards (a web field drops keys while WebKit autocorrects), it pastes the text instead and empties the clipboard again |
looktap hidekb | Dismisses the keyboard, checking after each method. Exits 2 if it's still up |
looktap swipe <x1> <y1> <x2> <y2> [ms] | Default 150 ms on iOS, 300 ms on Android |
looktap back | Android system Back · iOS: the navigation bar's back button, else a top-left "Back" button |
looktap home | The Home button: goes to the home screen and checks it's in front; the app keeps running |
looktap open <url> | Opens a deep link |
looktap launch [app] [--time] | (Re)starts the app. --time times a cold start: until the app is in front, and until its first screen settled. It says when screen reads are too slow to time the screen precisely. Android adds the system's own TotalTime |
looktap stop [app] | Terminates the app and waits until its process is gone |
looktap bio match|nomatch | Face ID (iOS) or fingerprint (Android) |
looktap rotate [portrait|landscape|left|right|upsidedown] | Turns the device (landscape = left, turned counter-clockwise). ls, taps and screenshots follow the turned screen. Says so if the app doesn't support the orientation. Without an argument, prints the current one |
Debug ([app] defaults to --app / config, else the app in front) | |
looktap logs [app] [--errors] [--grep re] [--since 5m] [--lines n] [--all] | The app's log since the last logs (the first call covers the last minute). At most 80 lines, newest kept |
looktap crashes [app] [--since 1h] | Crashes, ANRs and low-memory kills since the last crashes (the first call covers the last hour) |
looktap app [app] | The app in front and its pid, and whether [app] is running |
looktap app [app] --paths | Where the app's files are. iOS simulator: its bundle, data folder and app groups, as paths on the Mac. Android: its APKs and data folder, which adb shell run-as reads on debuggable builds |
looktap perf [app] [--for 10s] [--json] | The app's CPU (100% is one core), memory and threads. iOS simulator: the memory footprint Xcode's gauge shows, and its peak. Android: PSS, RSS, Java and native heap. Real iPhone: CPU, footprint and threads through Instruments (xcrun xctrace, part of Xcode), 10–25 s a reading. --for takes a reading each second (a real iPhone: about every 8 s), then prints avg/max |
looktap perf [app] --frames | Frames since the last perf --frames, the janky share and p50/p90/p95/p99 frame time. Android: the app's own render times (gfxinfo) and why frames were late. iOS: frames as the screen shows them (the simulator's framebuffer, a real iPhone's USB screen feed), fps while moving and the longest stall; the first call starts counting |
looktap capabilities [--json] | What this device can't do, or can only in part, and why. --json: every command with supported, conditional or unsupported |
App state (<app> from the argument or --app / config, never the app in front) | |
looktap apps [filter] [--all] [--json] | The installed apps: id, name (iOS) and version. --all adds the system's own apps. A filter matches part of the id or name, system apps included, and exits 1 when nothing matches, so looktap apps com.example answers "is it installed?" |
looktap install <path> [--grant] | iOS: a simulator .app. Android: an .apk, replacing an installed copy and keeping its data (--grant: all runtime permissions). Prints the app id |
looktap uninstall <app> | Removes the app |
looktap reset <app> | As if freshly installed: data and permissions cleared. iOS: the keychain survives |
looktap permission grant|revoke|reset <name> [app] | camera, microphone, location, location-always, photos, contacts, calendar, notifications, motion, all, … Android also takes android.permission.* |
| Conditions | |
looktap location <lat> <lon> · location clear | Simulated GPS. Android: emulators only, and the emulator keeps its last fix (no clear) |
looktap appearance [dark|light] | Dark mode. Without an argument, prints the current mode |
looktap textsize [size] | Dynamic Type (iOS) / font scale (Android): xs s m l xl xxl xxxl ax1…ax5, or iOS names such as accessibility-large. Without an argument, prints the current size |
looktap statusbar clean|live | A screenshot-clean status bar (9:41, full battery and Wi-Fi), or back to live values |
looktap network [offline|online] | Android only: airplane mode on or off, waiting until the device has lost (or regained) its connection. Without an argument, prints the current state |
looktap push <app> <payload> | iOS: a remote notification. The payload can be a .json file, inline JSON, or plain text for an alert |
| Batch, setup & info | |
looktap mcp [--config] | Serves these commands over MCP (stdio) for clients without a shell. --config prints a client config |
looktap run '<cmd>; <cmd> …' | Several commands in one process. run - reads them from stdin. Stops at the first failure and names it |
looktap codegen <flow-file> [app] | Android: restarts the app, then saves each touch on the device as the step that replays it: tap "<label>", fill <field> <text>, scrollto, longpress, zoom, back, home. A command typed while it records (expect "Welcome") is run and saved too; undo drops the last step; Enter on an empty line or Ctrl+C ends it. See Record a flow |
looktap test <flow|dir> … [--junit file] | Runs flow files (run scripts, one step per line) as tests, folders searched, and prints a line per flow and the totals. Exits 1 if any flow fails. A failed flow prints its output and leaves error.txt, output.txt, screen.txt, screen.png, logs.txt and crashes.txt in <out>/failures/<flow>/. An app that dies during a flow fails it. --junit writes a JUnit XML report |
looktap skill [--install] | Prints the AI-agent guide, or installs it as a Claude Code skill |
looktap devices [--all] [--json] | What's running or connected, and what each is: simulator, emulator (with its AVD name) or real device (model, OS version, USB or Wi-Fi), and whether it has a window or runs headless. An Android phone on both USB and Wi-Fi is one row (USB + Wi-Fi), and commands drive it over USB; -d with its Wi-Fi id still picks Wi-Fi. A phone that's connected but not ready is listed with what to do (accept the USB debugging prompt, tap Trust, turn on Developer Mode). A simulator or emulator still starting is listed as such. --all also lists what boot can start; --json for scripts and agents |
looktap boot [name] [--window|--headless] | Boots an iOS simulator or Android emulator by name, UDID or AVD name (exact, else a unique prefix or substring; -p to choose a platform) and waits until it's ready to drive. Opens its window unless --headless (or LOOKTAP_HEADLESS=1, or "headless": true in the config file). Already running: says so, and --window opens a simulator's window. One that's still starting (adb lists an emulator offline while it boots) is waited for, not started twice. A running device can't drop its window (closing the Simulator app shuts its simulators down; an emulator starts with or without one), so --headless then says how to restart it |
looktap shutdown [name] | Shuts down a simulator or emulator (name, UDID, AVD name or serial), and waits until it's gone |
looktap doctor | Checks Node, Xcode and the simulator frameworks, adb, and booted devices |
looktap setup | Starts the helper ahead of time. Android: also adb reverse tcp:8081 for Metro |
looktap teardown | Stops the helper. Android: also removes adb reverse rules |
--ls works on tap, longpress, zoom, tapxy, fill, type, key, hidekb, swipe, back, home, open, launch, bio,
rotate and scrollto. After the action it waits for the screen to change (up to 1.5 s, or 6 s for launch/open), then for
it to stop changing, then prints it like ls.
Exit codes: 0 means OK. 2 means a usage problem: bad arguments, element not found, no device, or the
helper can't start. 1 means anything else failed, including an expect that didn't hold and a test run with a
failed flow. A script can set -e and stop at the first missing element.
Platform support
Every command works on all four kinds of device except the ones below. ⚠️ means it works with the limit shown; ❌
means it doesn't run there and exits 2 with that reason. looktap capabilities prints the same for the device in
front of you (--json for every command).
<!-- capabilities: generated by npm run docs:capabilities -->
| Command | iOS simulator | iPhone / iPad | Android emulator | Android device |
|---|---|---|---|---|
app --paths | ✅ | ❌ an app's files on a device stay on it: Xcode → Window → Devices and Simulators → the app → Download Container | ✅ | ✅ |
back | ⚠️ iOS has no system Back: it taps the navigation bar's back button, else a top-left Back button | ⚠️ iOS has no system Back: it taps the navigation bar's back button, else a top-left Back button | ✅ | ✅ |
bio | ✅ | ❌ Face ID needs a real face on a device | ✅ | ❌ fingerprints can only be simulated on an emulator |
burst | ✅ | ✅ | ✅ | ⚠️ FLAG_SECURE screens come out black (emulators bypass it) |
codegen | ❌ recording touches works on Android only, for now: write the flow by hand, or record it on an Android device | ❌ recording touches works on Android only, for now: write the flow by hand, or record it on an Android device | ✅ | ✅ |
crashes | ✅ | ❌ see Xcode → Window → Devices and Simulators → View Device Logs | ✅ | ✅ |
location | ✅ | ❌ a real device uses its own GPS; use a simulator to fake one | ✅ | ❌ a device uses its own GPS (faking it needs a mock-location app) |
location clear | ✅ | ❌ a real device uses its own GPS; use a simulator to fake one | ❌ the emulator keeps its last location: set a new one instead | ❌ a device uses its own GPS (faking it needs a mock-location app) |
logs | ✅ | ❌ use Console.app with the device selected | ✅ | ✅ |
network | ❌ the simulator uses the Mac's own network, so it can't go offline or slow by itself. Network Link Conditioner (Xcode → Open Developer Tool → More Developer Tools → Additional Tools) throttles the whole Mac | ❌ turn on Airplane Mode in Control Center | ✅ | ⚠️ airplane mode: over wireless adb, going offline also drops looktap's connection |
perf | ✅ | ⚠️ read through Instruments (xcrun xctrace): a reading takes 10–25 s and --for gives one every ~8 s; memory is the footprint, without its peak | ✅ | ✅ |
perf --frames | ⚠️ frames as the screen shows them (the simulator's framebuffer updates), not the app's own render times: the first call starts counting | ⚠️ frames as the screen shows them, from its USB screen feed (60 Hz at most, so a 120 Hz screen counts at half its rate); needs a USB cable, an unlocked phone, and QuickTime not showing it | ✅ | ✅ |
permission | ⚠️ camera and notifications can't be set: simctl privacy has no such service | ❌ change it in the Settings app | ⚠️ contacts-limited, reminders and siri are iOS-only; any android.permission.* works | ⚠️ contacts-limited, reminders and siri are iOS-only; any android.permission.* works |
push | ✅ | ❌ send a real push through APNs | ❌ Android pushes come through Firebase (FCM), so adb can't inject one: send it from your backend or the Firebase console | ❌ Android pushes come through Firebase (FCM), so adb can't inject one: send it from your backend or the Firebase console |
record | ✅ | ❌ use burst for frames, or QuickTime Player for video | ⚠️ at most 3 minutes, and FLAG_SECURE screens record black | ⚠️ at most 3 minutes, and FLAG_SECURE screens record black |
reset | ✅ | ❌ uninstall and install the app again | ✅ | ✅ |
rotate | ✅ | ✅ | ⚠️ turned while the home screen is in front, it doesn't last: the next app reopens in the orientation it was last shown in. Turn it with the app in front | ⚠️ turned while the home screen is in front, it doesn't last: the next app reopens in the orientation it was last shown in. Turn it with the app in front |
shot | ✅ | ✅ | ✅ | ⚠️ FLAG_SECURE screens come out black (emulators bypass it) |
statusbar | ✅ | ❌ only simulators can override the status bar | ✅ | ✅ |
textsize | ✅ | ❌ change it in Settings → Display & Brightness → Text Size | ✅ | ✅ |
zoom | ✅ | ⚠️ around the screen's centre only | ⚠️ only through the Android helper (the default): adb input has no multi-touch | ⚠️ only through the Android helper (the default): adb input has no multi-touch |
<!-- /capabilities -->
Options, environment variables, config file
| Option | Meaning |
|---|---|
-p, --platform ios|android | Default: whichever has a booted device. Required when both do |
-d, --device <udid|serial> | Default: the only booted one |
-o, --out <dir> | Folder for screenshots and videos (default shots), and for test's failures/ |
--junit <file> | test: also write a JUnit XML report there |
--wait <seconds> | How long tap, fill, back and expect look for their target (default 3). An ambiguous match fails at once |
--app <id> | App id for launch, stop, open, logs, crashes, app, uninstall, reset and permission |
-h, --help · -v, --version |
| Environment variable | Meaning |
|---|---|
LOOKTAP_PLATFORM, LOOKTAP_DEVICE, LOOKTAP_OUT, LOOKTAP_WAIT | Defaults for -p, -d, -o, --wait |
LOOKTAP_SCALE | Override Android's px-per-dp (normally read from wm density) |
LOOKTAP_ANDROID_HELPER=0 | Plain adb + uiautomator dump instead of the helper (slower) |
LOOKTAP_EMULATOR_GRPC=0 | Don't take emulator frames over gRPC: use the emulator console |
LOOKTAP_IOS_HID=indigo|dtuhid | Force the iOS input path. Takes effect when the helper starts |
LOOKTAP_HOME | Where sockets, logs and state live (default ~/.looktap) |
LOOKTAP_DEBUG=1 | codegen prints each touch it sees: where, on which window, and what was under it |
LOOKTAP_HEADLESS=1 | boot without a window by default (=0: with one). Overrides "headless" in the config file |
Config file. looktap uses the nearest looktap.config.json, walking up from the current folder. Put it at
the root of your app's repo:
{
"out": "shots",
"wait": 3,
"headless": false,
"ios": { "app": "com.example.myapp" },
"android": { "app": "com.example.myapp" }
}
Precedence: command-line option, then environment variable, then config file, then built-in default.
Files looktap writes
| Path | What |
|---|---|
~/.looktap/sim-<udid prefix>.sock, .log | The iOS helper's socket and log |
~/.looktap/helpers.json | Which local port reaches each Android device's helper |
~/.looktap/refs/<device>.json | What @1…@N meant in the last ls on that device |
~/.looktap/marks/<device>.json | Where the last logs / crashes stopped |
~/.looktap/recordings.json | Recordings in progress, so record stop can find them |
~/.looktap/rec-<udid prefix>.log | The iOS recorder's output |
~/.looktap/shots/ | Where the MCP server saves screenshots unless -o / LOOKTAP_OUT / config says otherwise |
/data/local/tmp/looktap-server.jar (device) | The Android helper |
/data/local/tmp/looktap-recording.mp4 (device) | An Android recording in progress, removed when it's copied off |
<out>/<name>.png, <out>/<name>.mp4 | Your screenshots and videos |
Generated from README.md. Edit it there.