summaryrefslogtreecommitdiff
path: root/bin/android-ui
diff options
context:
space:
mode:
Diffstat (limited to 'bin/android-ui')
-rwxr-xr-xbin/android-ui98
1 files changed, 98 insertions, 0 deletions
diff --git a/bin/android-ui b/bin/android-ui
new file mode 100755
index 0000000..25257f1
--- /dev/null
+++ b/bin/android-ui
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# android-ui <package> <command> [args]
+#
+# dump list the on-screen text and content-descriptions
+# tap <description> tap the element whose content-desc or text matches, exactly once
+# shot <file> screenshot, but only after confirming <package> is foreground
+# foreground print the foreground package and exit non-zero if it isn't <package>
+#
+# Exists because a blind `adb shell input tap X Y` during this project landed on a completely
+# different app's settings screen, and the screenshot taken straight afterwards was reported as
+# the app under test. Coordinates are guesses about a layout; element descriptions are what the
+# app actually published. Every command here verifies the foreground package first, so a tap can
+# never silently drive something else.
+set -uo pipefail
+
+ADB=${ADB:-/opt/android-sdk/platform-tools/adb}
+LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)"
+die() { echo "android-ui: $1" >&2; exit 2; }
+
+[ -x "$ADB" ] || die "adb not found at $ADB (set ADB=...)"
+[ -x "$LIB/uinodes.py" ] || die "missing $LIB/uinodes.py"
+[ $# -ge 2 ] || die "usage: android-ui <package> <dump|tap|shot|foreground> [args]"
+
+PKG="$1"; CMD="$2"; shift 2
+
+foreground_pkg() {
+ "$ADB" shell dumpsys activity activities 2>/dev/null \
+ | grep -oE 'topResumedActivity=ActivityRecord\{[^}]*' \
+ | head -1 \
+ | grep -oE '[a-zA-Z0-9_.]+/[a-zA-Z0-9_.]+' \
+ | head -1 \
+ | cut -d/ -f1
+}
+
+require_foreground() {
+ local actual
+ actual=$(foreground_pkg)
+ if [ "$actual" != "$PKG" ]; then
+ echo "android-ui: foreground is '${actual:-unknown}', expected '$PKG'" >&2
+ echo " Nothing was done. Bring the app forward first; a tap here would have driven" >&2
+ echo " whatever else is on screen." >&2
+ exit 1
+ fi
+}
+
+dump_xml() {
+ "$ADB" shell uiautomator dump /sdcard/android-ui.xml >/dev/null 2>&1 \
+ || die "uiautomator dump failed"
+ "$ADB" shell cat /sdcard/android-ui.xml 2>/dev/null
+}
+
+case "$CMD" in
+ foreground)
+ actual=$(foreground_pkg)
+ echo "foreground: ${actual:-unknown}"
+ echo "expected : $PKG"
+ [ "$actual" = "$PKG" ] && { echo "result : MATCH"; exit 0; }
+ echo "result : MISMATCH"; exit 1
+ ;;
+
+ dump)
+ require_foreground
+ dump_xml | "$LIB/uinodes.py" list
+ ;;
+
+ tap)
+ [ $# -ge 1 ] || die "tap needs a label"
+ require_foreground
+ TARGET="$1"
+ COORDS=$(dump_xml | "$LIB/uinodes.py" find "$TARGET")
+
+ case "$COORDS" in
+ NONE)
+ echo "android-ui: nothing on screen is labelled '$TARGET'" >&2
+ echo " Run: android-ui $PKG dump" >&2
+ exit 1 ;;
+ AMBIGUOUS*)
+ # Tapping the first of several is how you act on the wrong row.
+ echo "android-ui: '$TARGET' matches ${COORDS#AMBIGUOUS } places; refusing to guess" >&2
+ echo " Run: android-ui $PKG dump" >&2
+ exit 1 ;;
+ esac
+
+ "$ADB" shell input tap $COORDS >/dev/null 2>&1 || die "tap failed"
+ echo "tapped '$TARGET' at ($COORDS)"
+ ;;
+
+ shot)
+ [ $# -ge 1 ] || die "shot needs an output path"
+ require_foreground
+ "$ADB" exec-out screencap -p > "$1" || die "screencap failed"
+ SIZE=$(stat -c%s "$1" 2>/dev/null || echo 0)
+ [ "$SIZE" -gt 1000 ] || die "screenshot is $SIZE bytes; capture failed"
+ echo "captured $1 ($SIZE bytes) with $PKG in the foreground"
+ ;;
+
+ *) die "unknown command '$CMD'" ;;
+esac