#!/usr/bin/env bash # Android wallet smoke: install APK (published F-Droid **or** from-source), open # withdraw/pay URIs via adb. # # No root required if: # - adb is available (package android-tools-adb / platform-tools) # - a device or emulator is connected (`adb devices` shows "device") # - optional user-local SDK under $HOME/Android/Sdk (emulator) # # Usage: # ./android-test/run-android-pay-smoke.sh # published F-Droid APK # STACK=goa ./android-test/run-android-pay-smoke.sh # STACK=stage ./android-test/run-android-pay-smoke.sh # # from-source (built first): # ./android-test/run-android-build-and-smoke.sh # APK_PATH=/path/to.apk PKG=net.taler.wallet.fdroid.debug ./run-android-pay-smoke.sh # # Published APK (default): F-Droid Taler Wallet 1.6.1 (854) # https://f-droid.org/packages/net.taler.wallet.fdroid/ # set -euo pipefail ROOT=$(cd "$(dirname "$0")" && pwd) # shellcheck source=lib_android_env.sh . "$ROOT/lib_android_env.sh" if [ "${AUTO_ANDROID}" != "1" ]; then echo "skipped: AUTO_ANDROID=${AUTO_ANDROID} (platform flags — see GUI-AUTOMATION-NOTES.md)" exit 0 fi APK_DIR="${APK_DIR:-$ROOT/apks}" APK_URL="${APK_URL:-https://f-droid.org/repo/net.taler.wallet.fdroid_854.apk}" APK_NAME="${APK_NAME:-net.taler.wallet.fdroid_854.apk}" # Optional override: full path to an already-built APK (from-source) APK_PATH="${APK_PATH:-}" PKG="${PKG:-net.taler.wallet.fdroid}" STACK="${STACK:-auto}" # auto | goa | stage SERIAL="${SERIAL:-}" OUT_DIR="${OUT_DIR:-$ROOT/out}" mkdir -p "$APK_DIR" "$OUT_DIR" if ! command -v adb >/dev/null 2>&1; then echo "adb missing — install android-tools-adb or user SDK platform-tools" >&2 exit 2 fi # Default: headless AVD (no host window). WINDOWED=1 / EMULATOR_HEADLESS=0 for UI. if ! android_ensure_device; then cat >&2 </dev/null; then STACK=goa else STACK=stage fi ;; esac case "$STACK" in goa) BANK=https://bank.hacktivism.ch MERCHANT=https://taler.hacktivism.ch WITHDRAW_JSON="$BANK/intro/demo-withdraw.json" PAY_HINT="paivana template / goa-shop" ;; stage) BANK=https://stage.bank.lefrancpaysan.ch MERCHANT=https://stage.monnaie.lefrancpaysan.ch WITHDRAW_JSON="$BANK/intro/demo-withdraw.json" PAY_HINT="farmer shop templates" ;; *) echo "unknown STACK=$STACK" >&2; exit 2 ;; esac echo "stack: $STACK bank: $BANK" # APK: explicit path, or download published F-Droid into APK_DIR if [ -n "$APK_PATH" ] && [ -f "$APK_PATH" ]; then : elif [ -f "$APK_DIR/$APK_NAME" ]; then APK_PATH="$APK_DIR/$APK_NAME" else APK_PATH="$APK_DIR/$APK_NAME" echo "downloading published wallet APK…" curl -fL --retry 3 -o "$APK_PATH" "$APK_URL" fi echo "apk: $APK_PATH ($(wc -c <"$APK_PATH") bytes)" echo "pkg: $PKG" "${ADB[@]}" wait-for-device "${ADB[@]}" install -r "$APK_PATH" "${ADB[@]}" shell settings put global window_animation_scale 0 || true "${ADB[@]}" shell settings put global transition_animation_scale 0 || true "${ADB[@]}" shell settings put global animator_duration_scale 0 || true # Withdraw URI from live mint WURI=$(curl -sfS -m 25 "$WITHDRAW_JSON" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("taler_withdraw_uri") or d.get("qr_payload") or "")') if [ -z "$WURI" ]; then echo "FAIL: no taler_withdraw_uri from $WITHDRAW_JSON" >&2 exit 4 fi echo "withdraw: $WURI" echo "$WURI" >"$OUT_DIR/last-withdraw.uri" "${ADB[@]}" shell am force-stop "$PKG" 2>/dev/null || true "${ADB[@]}" logcat -c 2>/dev/null || true "${ADB[@]}" shell am start -a android.intent.action.VIEW -d "$WURI" 2>&1 | tee "$OUT_DIR/start-withdraw.txt" sleep 12 "${ADB[@]}" exec-out screencap -p >"$OUT_DIR/01-after-withdraw-uri.png" || true "${ADB[@]}" shell uiautomator dump /sdcard/ui.xml 2>/dev/null || true "${ADB[@]}" pull /sdcard/ui.xml "$OUT_DIR/01-ui.xml" 2>/dev/null || true # Best-effort: tap common confirm labels python3 - "$SERIAL" "$OUT_DIR/01-ui.xml" <<'PY' || true import re, subprocess, sys serial, path = sys.argv[1], sys.argv[2] try: xml = open(path, errors="replace").read() except FileNotFoundError: raise SystemExit(0) labels = [ "Confirm", "Withdraw", "Accept", "Continue", "OK", "I accept", "Agree", "Bestätigen", "Abheben", "Akzeptieren", ] for label in labels: for pat in ( r'text="%s"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' % re.escape(label), r'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"[^>]*text="%s"' % re.escape(label), ): for m in re.finditer(pat, xml): x1, y1, x2, y2 = map(int, m.groups()) x, y = (x1 + x2) // 2, (y1 + y2) // 2 print(f"tap {label} @ {x},{y}") subprocess.run(["adb", "-s", serial, "shell", "input", "tap", str(x), str(y)], check=False) PY sleep 8 "${ADB[@]}" exec-out screencap -p >"$OUT_DIR/02-after-confirm-tap.png" || true # Optional public pay template (stage fixed product / GOA shop if amount known) # Stage: open monnaies shop is browser-side; deep link pay after template POST if available. PAY_URI="" if [ "$STACK" = "stage" ]; then # fixed template panier-legumes → taler://pay/… if RESP=$(curl -sfS -m 20 -X POST -H 'Content-Type: application/json' -d '{}' \ "$MERCHANT/instances/fermes-des-collines/templates/panier-legumes" 2>/dev/null); then PAY_URI=$(printf '%s' "$RESP" | python3 -c ' import json,sys d=json.load(sys.stdin) oid=d.get("order_id") or "" tok=d.get("token") or "" if oid and tok: print("taler://pay/stage.monnaie.lefrancpaysan.ch/instances/fermes-des-collines/%s/?c=%s"%(oid,tok)) ' 2>/dev/null || true) fi elif [ "$STACK" = "goa" ]; then # Paivana pay-template deep link (wallet prepares order) PAY_URI="taler://pay-template/taler.hacktivism.ch/instances/goa-shop/paivana" fi if [ -n "$PAY_URI" ]; then echo "pay: $PAY_URI ($PAY_HINT)" echo "$PAY_URI" >"$OUT_DIR/last-pay.uri" "${ADB[@]}" shell am start -a android.intent.action.VIEW -d "$PAY_URI" 2>&1 | tee "$OUT_DIR/start-pay.txt" sleep 12 "${ADB[@]}" exec-out screencap -p >"$OUT_DIR/03-after-pay-uri.png" || true else echo "pay: (skipped — no public template order)" fi # Evidence from logcat "${ADB[@]}" logcat -d -t 300 >"$OUT_DIR/logcat.txt" || true grep -iE 'taler-wallet|prepareBank|preparePay|confirm|error|Error|success' "$OUT_DIR/logcat.txt" \ | tail -80 >"$OUT_DIR/logcat-taler.txt" || true # SMOKE_STRICT=1 (matrix gate default): ANR or missing wallet-core withdraw = hard fail : "${SMOKE_STRICT:=0}" rc=0 echo echo "=== summary ===" echo "package installed: $PKG" echo "withdraw intent delivered: $WURI" if grep -qiE "isn't responding|isn.t responding|System UI isn" "$OUT_DIR/01-ui.xml" 2>/dev/null \ || grep -qiE "isn't responding|not responding" "$OUT_DIR/logcat.txt" 2>/dev/null; then echo "FAIL: System UI / app ANR (host too small or emulator hung)" rc=10 fi if grep -q 'prepareBankIntegratedWithdrawal' "$OUT_DIR/logcat.txt" 2>/dev/null; then echo "wallet-core: prepareBankIntegratedWithdrawal seen (wallet accepted withdraw URI)" else echo "wallet-core: prepareBankIntegratedWithdrawal NOT seen (UI may still show onboarding/ANR on small RAM)" if [ "$SMOKE_STRICT" = "1" ]; then echo "FAIL: SMOKE_STRICT=1 requires prepareBankIntegratedWithdrawal in logcat" rc=11 fi fi if [ -n "$PAY_URI" ]; then if grep -qiE 'preparePay|confirmPay|pay-template|preparePurchase' "$OUT_DIR/logcat.txt" 2>/dev/null; then echo "wallet-core: pay path activity seen" else echo "wallet-core: pay path not confirmed in logcat (check $OUT_DIR/03-after-pay-uri.png)" fi fi echo "artifacts: $OUT_DIR/" ls -la "$OUT_DIR" | sed 's/^/ /' echo echo "NOTE: Full unattended UI payment needs a responsive device/emulator (≥6–8 GiB host RAM recommended)." echo "This smoke proves: APK install + deep-link delivery on stack=$STACK (strict=$SMOKE_STRICT rc=$rc)." exit "$rc"