#!/usr/bin/env bash # Device/emulator smoke — catches the bugs that unit tests + "any extract" miss. # Cases: # 1) Tight circle on "How to circle" must NOT steal Ship demo block above # 2) Circle email region → Copy chip works set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" ADB="${ADB:-adb}" COMP='com.example.crkl/com.example.crkl.accessibility.CrklAccessibilityService' PKG=com.example.crkl die() { echo "FAIL: $*" >&2; exit 1; } pass() { echo "OK: $*"; } # adb devices -l uses spaces, not tabs, between serial and state "$ADB" devices | awk '/device( |$)/ && $1 !~ /List/{found=1} END{exit !found}' \ || die "no adb device" echo "== smoke Circle on $($ADB get-serialno) ==" "$ADB" shell settings put secure accessibility_enabled 1 "$ADB" shell settings put secure enabled_accessibility_services "$COMP" "$ADB" shell am force-stop "$PKG" >/dev/null 2>&1 || true sleep 1 "$ADB" shell settings put secure enabled_accessibility_services "$COMP" "$ADB" shell settings put secure accessibility_enabled 1 sleep 1.5 "$ADB" shell am start -n "$PKG/.fixtures.TestFixturesActivity" >/dev/null sleep 1.5 enter_circle() { "$ADB" logcat -c "$ADB" shell input tap 964 2200 sleep 0.4 if "$ADB" logcat -d -s OverlayView:D | grep -q 'floating button clicked'; then return 0 fi for y in $(seq 2140 20 2280); do for x in $(seq 900 20 1030); do "$ADB" shell input tap "$x" "$y" sleep 0.05 if "$ADB" logcat -d -s OverlayView:D | grep -q 'floating button clicked'; then return 0 fi done done return 1 } draw_loop() { local cx="$1" cy="$2" r="$3" python3 - "$cx" "$cy" "$r" <<'PY' import math, subprocess, sys, time cx, cy, r = map(int, sys.argv[1:4]) adb = lambda *a: subprocess.check_call(["adb", *a]) n = 28 pts = [ ( int(cx + r * math.cos(2 * math.pi * i / n - math.pi / 2)), int(cy + r * math.sin(2 * math.pi * i / n - math.pi / 2)), ) for i in range(n + 1) ] adb("shell", "input", "motionevent", "DOWN", str(pts[0][0]), str(pts[0][1])) for x, y in pts[1:]: adb("shell", "input", "motionevent", "MOVE", str(x), str(y)) adb("shell", "input", "motionevent", "UP", str(pts[-1][0]), str(pts[-1][1])) for _ in range(25): time.sleep(0.3) out = subprocess.check_output(["adb", "logcat", "-d"], text=True) if "extract:" in out or "Selection ready" in out: break PY } close_panel() { "$ADB" shell input tap 980 1300 || true sleep 0.3 "$ADB" shell input keyevent KEYCODE_BACK || true sleep 0.3 } # Resolve live UI bounds so layout/scroll changes do not false-fail smoke. "$ADB" shell uiautomator dump /sdcard/crkl-smoke.xml >/dev/null "$ADB" pull /sdcard/crkl-smoke.xml /tmp/crkl-smoke.xml >/dev/null eval "$(python3 <<'PY' import re, sys xml = open("/tmp/crkl-smoke.xml").read() m = re.search(r'text="How to circle"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml) if not m: sys.exit("How to circle not on screen — open fixtures / scroll to top") x1, y1, x2, y2 = map(int, m.groups()) cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 r = max(60, min(100, (x2 - x1) // 2 + 30, (y2 - y1) // 2 + 40)) print(f"HEAD_CX={cx}; HEAD_CY={cy}; HEAD_R={r}") # Email fixture / subject area — prefer Subject line if present m2 = re.search(r'text="[^"]*Subject: Q2[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml) if not m2: m2 = re.search(r'text="[^"]*Q2 planning moved[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml) if m2: a, b, c, d = map(int, m2.groups()) print(f"MAIL_CX={(a+c)//2}; MAIL_CY={(b+d)//2}; MAIL_R=160") else: print("MAIL_CX=540; MAIL_CY=1720; MAIL_R=160") PY )" # --- Case 1: heading must not steal Ship demo --- enter_circle || die "FAB not clickable — is Circle Overlay ON?" pass "enter circle mode" draw_loop "$HEAD_CX" "$HEAD_CY" "$HEAD_R" logs="$("$ADB" logcat -d)" echo "$logs" | grep -q 'Selection ready' || die "case1: no selection" echo "$logs" | grep -E 'extract:.*preview=' | grep -qi 'How to circle' \ || die "case1: extract missing 'How to circle' (got wrong node?)" if echo "$logs" | grep -E 'extract:.*preview=' | grep -qi 'English sentence'; then die "case1: Ship demo block leaked into heading circle" fi pass "tight heading extract (no Ship demo leak)" close_panel # --- Case 2: email region → Copy --- "$ADB" shell am start -n "$PKG/.fixtures.TestFixturesActivity" >/dev/null sleep 1 # Re-dump in case scroll changed "$ADB" shell uiautomator dump /sdcard/crkl-smoke.xml >/dev/null "$ADB" pull /sdcard/crkl-smoke.xml /tmp/crkl-smoke.xml >/dev/null eval "$(python3 <<'PY' import re xml = open("/tmp/crkl-smoke.xml").read() m2 = re.search(r'text="[^"]*Subject: Q2[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml) if not m2: m2 = re.search(r'text="[^"]*Q2 planning moved[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml) if m2: a, b, c, d = map(int, m2.groups()) print(f"MAIL_CX={(a+c)//2}; MAIL_CY={(b+d)//2}; MAIL_R=160") else: print("MAIL_CX=540; MAIL_CY=1720; MAIL_R=160") PY )" enter_circle || die "FAB not clickable (case2)" draw_loop "$MAIL_CX" "$MAIL_CY" "$MAIL_R" logs="$("$ADB" logcat -d)" echo "$logs" | grep -qE 'extract:.*chars=[1-9]' || die "case2: no text extracted" pass "email region extract" "$ADB" logcat -c # Icon-only chips: Translate, Copy, Explain, Share, Todo — ~44dp, y≈2100. copied=0 for y in 2090 2100 2110; do for x in 200 240 280 160 320; do "$ADB" shell input tap "$x" "$y" sleep 0.3 if "$ADB" logcat -d | grep -q 'action kind=COPY'; then copied=1 break 2 fi done done [[ "$copied" = 1 ]] || die "Copy chip not triggered" "$ADB" logcat -d | grep -q 'action result ok=true' || die "Copy failed" pass "Copy chip" echo "== smoke PASSED ==" echo "Tip: full dogfood is docs/dogfood.md"