looktap v0.2.1

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

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:

  1. the keyboard's Done/Return key;
  2. on iOS, the ✓ on a web form's Previous/Next bar;
  3. 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

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:

What crashes reports:

React Native: in development builds, JS console.* output and uncaught JS errors are in logs on both platforms:

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

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

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

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.