Use
Examples
Look, then act
looktap ls
@1 Button 16, 285 358x54 General
@2 Button 16, 339 358x54 Accessibility
@3 TextField 33, 773 324x38 Search = Search
Each row shows a ref (@1), the element type, its x,y position and width×height (iOS points /
Android dp, the units tapxy and swipe take), its label, and = value for fields that hold text. Unlabelled
buttons are listed too, as (no label), so you can tap them by ref. The on-screen keyboard is a single row.
Tap by label, not by coordinates
looktap tap "Open menu" # exact label (or identifier on iOS)
# tapped "Open menu" at 30,62
looktap tap "Messages" # no exact match → the one label starting with it
# tapped "Messages, 2 unread" at 342,62
looktap tap "Wi" # matches two different labels → refuses
# looktap: "Wi" is ambiguous — matches 2 labels: "Wi-Fi", "Wi-Fi hotspot"
looktap tap --exact "Messages" # exact only
looktap tap @7 # row @7 of the last ls
looktap tapxy 205 874 # when ls doesn't list it at all
- A ref works only while its element is still where
lssaw it. If the screen changed,tap @7fails ("no longer on screen — run: looktap ls") rather than tapping something else. - If another element covers the tap point (a toast, a dev-warning banner, a floating bar),
tapprints a warning, because the tap probably landed on the cover instead.
Long press and pinch-zoom
looktap longpress "Photo" # context menus, drag handles, "edit" modes (default 800 ms)
looktap longpress @4 1500 # by ref, held 1.5 s
looktap longpress 120 470 # a point, e.g. to drop a pin on a map
looktap zoom in # two fingers spreading at the screen centre (maps, photos, PDFs)
looktap zoom out 200 400 --scale 4 # pinch around a point; --scale is how far the fingers travel
zoom is a real two-finger gesture on both platforms. On Android it needs looktap's helper, because
adb input has no multi-touch.
Fill a form and capture its validation errors
looktap fill "Email" not-an-email
looktap fill "Name" "" # empty: just clears the field
looktap hidekb
looktap shot profile__validation
fill finds the field by its label (on iOS also its identifier), or else the first field at or below a matching
label. It clears the field and types. On Android it also presses Enter, because React Native fields only show
validation on blur.
hidekb tries, in order:
- the keyboard's Done/Return key;
- on iOS, the ✓ on a web form's Previous/Next bar;
- a tap on plain text above the keyboard.
It checks after each. If the keyboard is still up, it exits 2 rather than reporting success.
Wait instead of sleeping, and scroll
looktap wait "Welcome" # until it's on screen (default up to 10 s; --wait 30)
looktap wait "Loading" --gone # until it's gone
looktap scrollto "Privacy & Security" # swipe until fully on screen; fails at the end of the list
looktap scrollto "General" --up
looktap swipe 200 700 200 250 # scroll down (finger moves up); optional 5th arg: ms
looktap back # Android: system Back · iOS: the navigation bar's back button
Debug: logs, crashes, "where did my app go?"
looktap tap "Pay" # something goes wrong…
looktap logs --errors # the app's errors since the last `logs`
# 15:41:06.700 E [com.example:api] POST /payments failed: 422 amount_too_small
looktap logs --grep payment # everything it logged that matches
looktap app --app com.example # is it still running?
# front: com.android.launcher3 pid 1205
# com.example: not running — if it was, check: looktap crashes
looktap crashes
# com.example (pid 14001) exited at 14:56:06.724 while in the background: LOW_MEMORY — killed by the system to free memory, not a crash
When the app dies, looktap says so. After an action, and at the next ls, looktap checks whether the
app that was in front still has its process. If not, it prints:
looktap: warning: com.example is no longer running — it crashed or was killed. See: looktap crashes com.example
- It also catches an Android app that crashed and was restarted at once, because the check is by process id.
- Switching to another app, going home, or uninstalling doesn't trigger it.
- On iOS the crash report is written up to a minute after the crash. Until then,
crashesalready shows the signal (e.g.SIGTRAP) and says the full report is coming.
Each logs call starts where the previous one ended, so a loop of act → logs only ever shows what's new.
--since 5m sets a fixed window instead.
What's in the log:
- iOS: what the app's own code logged, plus errors and faults from anything in its process. The simulator's
own noise (low-level networking, the keyboard) is hidden.
--allshows everything, Apple frameworks included. A read takes about 1–2 s. - Android: everything logcat has for the app's uid, so restarts are covered. A read takes about 0.1 s.
What crashes reports:
- iOS: each crash report as exception, reason (such as an uncaught
NSException), the crashed thread's top frames, and the path to the full.ipsreport. - Android: Java crashes, native tombstones, ANRs, and exits that leave no crash report, low-memory kills above all.
React Native: in development builds, JS console.* output and uncaught JS errors are in logs on both
platforms:
- iOS: as
[com.facebook.react.log:javascript]lines. - Android: as
ReactNativeJSlines.
A red box is also on screen, so ls reads its message and call stack.
Swift: print() writes to stdout, not to the log. Use os.Logger or NSLog for messages logs should see.
Catch a spinner, toast, or transition (burst)
Start the burst in the background, then trigger the action:
looktap burst ./frames 5 & # 5 s: a frame each time the screen changes
looktap swipe 200 250 200 700 # pull to refresh
wait
# 61 frames, 23 distinct, in ./frames
For a video of a whole flow, record it. The recording runs in the background between the two commands:
looktap record start checkout # → ./shots/checkout.mp4
looktap tap "Add to cart" --ls
looktap tap "Checkout" --ls
looktap record stop # prints the path and the length
- Android records with the device's
screenrecord: at most 3 minutes, andFLAG_SECUREscreens come out black. - iOS records with
simctl io recordVideo. A recording started in landscape is marked to play turned.
Start from a clean state: install, reset, permissions
looktap install build/MyApp.app # iOS: a simulator build · Android: an .apk (keeps the app's data)
# installed com.example.myapp
looktap reset com.example.myapp # as if freshly installed: data and permissions cleared
looktap permission grant location com.example.myapp # no prompt
looktap permission revoke photos com.example.myapp # test the "denied" path
looktap permission reset camera com.example.myapp # it asks again on next use
looktap uninstall com.example.myapp
reset:- iOS: looktap copies the app's own bundle aside, uninstalls it and installs the copy, so documents, caches, app groups and permissions all go. The keychain survives, just as it does on a real iPhone, so an app that keeps its sign-in token there may still be signed in.
- Android:
pm clear.
- Permission names are the same on both platforms:
camera,microphone,location,location-always,photos,contacts,calendar,notifications,motion,all.- Android also takes any
android.permission.*, and acts only on permissions the app declares. Aresetalso clears "don't ask again". - The iOS simulator can't set
cameraornotifications; looktap says so.
- Android also takes any
- These commands never guess the app from the screen.
uninstall,resetandpermissiontake the app id as an argument, or from--app/looktap.config.json.
Simulated conditions: location, dark mode, text size, orientation, network, push
looktap location 51.5072 -0.1276 # GPS (Android: emulators) · `location clear` on iOS
looktap appearance dark # or light; no argument prints the current one
looktap textsize ax2 # Dynamic Type / font scale: xs s m l xl xxl xxxl ax1…ax5
looktap statusbar clean # 9:41, full battery and Wi-Fi, for screenshots · `statusbar live`
looktap rotate landscape --ls # or left, right, upsidedown, portrait; no argument prints it
looktap network offline # Android: airplane mode · `network online`
looktap push com.example.myapp "Your order has shipped" # iOS: an alert
looktap push com.example.myapp '{"aps":{"alert":"Hi"},"orderId":7}' # or any payload / a .json file
textsizeuses the iOS Dynamic Type sizes. On Android each size maps to the nearest font scale, from 0.8 to 2.0, the default beingl/largeon both.statusbar cleanuses iOS status-bar overrides, and Android's System UI demo mode.rotateturns the device the way Simulator's Rotate Left/Right does, or locks Android's rotation.landscapeisleft: the device turned counter-clockwise.lscoordinates, taps and screenshots follow the turned screen, so read the screen again afterwards. An app that doesn't support the orientation stays as it is, androtatesays so. On Android it leaves auto-rotate off.network offlineturns on Android's airplane mode and waits until the device has no route out. The iOS simulator shares the Mac's network, so it can't go offline or slow by itself.pushdelivers a remote notification the way APNs would. The app needs notification permission, as on a device.- A payload without
"aps"is sent as a silent (background) push carrying that data. - Android pushes can't be simulated locally. They come through Firebase (FCM), so send them from your backend or the Firebase console.
- A payload without
Biometrics
looktap bio match # iOS: Face ID success · Android: enrolled finger 1
looktap bio nomatch # iOS: Face ID failure · Android: unknown finger
On Android, enrol a fingerprint once first: Settings → Security → Fingerprint, using a screen-lock PIN, and touch
with looktap bio match when it asks.
Same screen on several devices
looktap devices
# ios 42E41FA7-… iPhone 14
# ios F9213EDC-… iPhone 16 Pro
# android emulator-5554
for d in 42E41FA7-EA40-45C1-8C6E-13A70BAF1FD6 F9213EDC-180D-412B-B24C-2FD69A37D3AC; do
looktap -d $d open myapp://home
looktap -d $d wait "Home"
looktap -d $d shot -o shots/$d home
done
Passing both -p and -d (or exporting LOOKTAP_PLATFORM and LOOKTAP_DEVICE) makes every command about
0.2 s faster, because looktap then skips listing devices.
A repeatable screenshot sweep
#!/bin/sh
set -e
looktap launch
for route in home search messages profile settings; do
looktap open "myapp://$route" --ls > /dev/null
looktap shot "$route"
looktap swipe 200 700 200 250
looktap shot "${route}__scrolled"
done
Android screenshots of secure screens
Apps that set FLAG_SECURE (common in banking and payment apps) make normal Android screenshots come out black. On
emulators, looktap shot takes the frame from the emulator itself, which gets past that. --plain takes a normal
on-device screenshot, for example to check that a screen is secure.
Generated from README.md. Edit it there.