taler-monitoring v1.0: standalone suite
This commit is contained in:
commit
a2afe690b7
73 changed files with 18268 additions and 0 deletions
308
android-test/run-android-pay-smoke.sh
Executable file
308
android-test/run-android-pay-smoke.sh
Executable file
|
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/env bash
|
||||
# Android wallet smoke: install APK, then **withdraw (Abheben)** and **pay** via
|
||||
# deep-link + best-effort UI taps. Both legs are first-class (not pay-only).
|
||||
#
|
||||
# Flow (default both on):
|
||||
# 1) WITHDRAW taler://withdraw/… from bank demo-withdraw.json + confirm taps
|
||||
# 2) PAY taler://pay… / pay-template + optional taps
|
||||
#
|
||||
# Env:
|
||||
# DO_WITHDRAW=1 (default) DO_PAY=1 (default)
|
||||
# SMOKE_STRICT=1 → require wallet-core withdraw (+ pay if DO_PAY) + no ANR
|
||||
# WITHDRAW_GUI_ROUNDS multi-round Abheben taps (default 6)
|
||||
#
|
||||
# Usage:
|
||||
# ./android-test/run-android-pay-smoke.sh
|
||||
# STACK=goa ./android-test/run-android-pay-smoke.sh
|
||||
# DO_PAY=0 ./android-test/run-android-pay-smoke.sh # withdraw-only
|
||||
#
|
||||
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 <<EOF
|
||||
No adb device. Options:
|
||||
1) Plug phone with USB debugging
|
||||
2) Headless AVD (default): $ROOT/start-android-emulator.sh --wait
|
||||
3) Windowed AVD: WINDOWED=1 $ROOT/start-android-emulator.sh --wait
|
||||
4) AUTO_START_EMULATOR=0 disables auto-start from this smoke
|
||||
EOF
|
||||
exit 3
|
||||
fi
|
||||
ADB=(adb -s "$SERIAL")
|
||||
echo "device: $SERIAL emulator_headless=${EMULATOR_HEADLESS} gpu=${EMULATOR_GPU}"
|
||||
|
||||
# Resolve stack endpoints
|
||||
case "$STACK" in
|
||||
auto)
|
||||
if curl -sfS -m 5 -o /dev/null https://bank.hacktivism.ch/config 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"
|
||||
|
||||
: "${DO_WITHDRAW:=1}"
|
||||
: "${DO_PAY:=1}"
|
||||
: "${WITHDRAW_GUI_ROUNDS:=6}"
|
||||
: "${PAY_GUI_ROUNDS:=4}"
|
||||
: "${SMOKE_STRICT:=0}"
|
||||
|
||||
"${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
|
||||
|
||||
"${ADB[@]}" shell am force-stop "$PKG" 2>/dev/null || true
|
||||
"${ADB[@]}" logcat -c 2>/dev/null || true
|
||||
|
||||
WURI=""
|
||||
PAY_URI=""
|
||||
WITHDRAW_OK=0
|
||||
PAY_OK=0
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 1) WITHDRAW / Abheben (required by default — not pay-only)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
if [ "$DO_WITHDRAW" = "1" ]; then
|
||||
echo
|
||||
echo "======== 1 WITHDRAW (Abheben) ========"
|
||||
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 URI: $WURI"
|
||||
echo "$WURI" >"$OUT_DIR/last-withdraw.uri"
|
||||
|
||||
"${ADB[@]}" shell am start -a android.intent.action.VIEW -d "$WURI" 2>&1 | tee "$OUT_DIR/start-withdraw.txt"
|
||||
sleep 6
|
||||
"${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
|
||||
|
||||
# Multi-round confirm / Abheben / ToS (same idea as GUI smoke)
|
||||
if [ -f "$ROOT/lib_ui.py" ]; then
|
||||
python3 - "$ROOT/lib_ui.py" "$SERIAL" "$OUT_DIR" "$WITHDRAW_GUI_ROUNDS" 2 <<'PY' || true
|
||||
import json, sys, importlib.util
|
||||
from pathlib import Path
|
||||
lib_path, serial, out, rounds, sleep_s = (
|
||||
Path(sys.argv[1]), sys.argv[2], Path(sys.argv[3]), int(sys.argv[4]), float(sys.argv[5])
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("lib_ui", lib_path)
|
||||
lib = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(lib)
|
||||
st = lib.gui_drive(serial, out / "gui-withdraw", rounds=rounds, sleep_s=sleep_s, phase="wd")
|
||||
(out / "gui-withdraw-status.json").write_text(json.dumps(st, indent=2))
|
||||
print("withdraw GUI taps:", st.get("taps"), "anr:", st.get("anr_dismissals"))
|
||||
PY
|
||||
else
|
||||
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", "Confirm withdrawal", "Withdraw", "Accept", "Continue", "OK",
|
||||
"I accept", "Agree", "Bestätigen", "Abheben", "Akzeptieren", "Retirer",
|
||||
]
|
||||
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, flags=re.I):
|
||||
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
|
||||
fi
|
||||
"${ADB[@]}" exec-out screencap -p >"$OUT_DIR/02-after-withdraw-taps.png" || true
|
||||
# Mid-flow logcat: withdraw must register before pay
|
||||
"${ADB[@]}" logcat -d -t 400 >"$OUT_DIR/logcat-after-withdraw.txt" || true
|
||||
if grep -qE 'prepareBankIntegratedWithdrawal|acceptWithdrawal|confirmWithdrawal' \
|
||||
"$OUT_DIR/logcat-after-withdraw.txt" 2>/dev/null; then
|
||||
WITHDRAW_OK=1
|
||||
echo "withdraw: wallet-core activity YES"
|
||||
else
|
||||
echo "withdraw: wallet-core activity NOT seen yet (onboarding/ANR/slow?)"
|
||||
fi
|
||||
else
|
||||
echo "======== 1 WITHDRAW skipped (DO_WITHDRAW=0) ========"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 2) PAY (after withdraw; still first-class, but second leg)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
if [ "$DO_PAY" = "1" ]; then
|
||||
echo
|
||||
echo "======== 2 PAY (Bezahlen) ========"
|
||||
if [ "$STACK" = "stage" ]; then
|
||||
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
|
||||
PAY_URI="taler://pay-template/taler.hacktivism.ch/instances/goa-shop/paivana"
|
||||
fi
|
||||
|
||||
if [ -n "$PAY_URI" ]; then
|
||||
echo "pay URI: $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 6
|
||||
if [ -f "$ROOT/lib_ui.py" ]; then
|
||||
python3 - "$ROOT/lib_ui.py" "$SERIAL" "$OUT_DIR" "$PAY_GUI_ROUNDS" 2 <<'PY' || true
|
||||
import json, sys, importlib.util
|
||||
from pathlib import Path
|
||||
lib_path, serial, out, rounds, sleep_s = (
|
||||
Path(sys.argv[1]), sys.argv[2], Path(sys.argv[3]), int(sys.argv[4]), float(sys.argv[5])
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("lib_ui", lib_path)
|
||||
lib = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(lib)
|
||||
st = lib.gui_drive(serial, out / "gui-pay", rounds=rounds, sleep_s=sleep_s, phase="pay")
|
||||
(out / "gui-pay-status.json").write_text(json.dumps(st, indent=2))
|
||||
print("pay GUI taps:", st.get("taps"), "anr:", st.get("anr_dismissals"))
|
||||
PY
|
||||
fi
|
||||
"${ADB[@]}" exec-out screencap -p >"$OUT_DIR/03-after-pay-uri.png" || true
|
||||
else
|
||||
echo "pay: (no public template URI for stack=$STACK)"
|
||||
fi
|
||||
else
|
||||
echo "======== 2 PAY skipped (DO_PAY=0) ========"
|
||||
fi
|
||||
|
||||
# Final evidence
|
||||
"${ADB[@]}" logcat -d -t 500 >"$OUT_DIR/logcat.txt" || true
|
||||
grep -iE 'taler-wallet|prepareBank|acceptWithdrawal|confirmWithdrawal|preparePay|confirmPay|error|Error|success|withdraw|pay' \
|
||||
"$OUT_DIR/logcat.txt" | tail -100 >"$OUT_DIR/logcat-taler.txt" || true
|
||||
|
||||
# Re-evaluate withdraw/pay from full logcat
|
||||
if [ "$DO_WITHDRAW" = "1" ]; then
|
||||
if grep -qE 'prepareBankIntegratedWithdrawal|acceptWithdrawal|confirmWithdrawal' \
|
||||
"$OUT_DIR/logcat.txt" 2>/dev/null; then
|
||||
WITHDRAW_OK=1
|
||||
fi
|
||||
fi
|
||||
if [ "$DO_PAY" = "1" ] && [ -n "$PAY_URI" ]; then
|
||||
if grep -qiE 'preparePay|confirmPay|preparePurchase|checkPay|pay-template|PayForTemplate' \
|
||||
"$OUT_DIR/logcat.txt" 2>/dev/null; then
|
||||
PAY_OK=1
|
||||
fi
|
||||
fi
|
||||
|
||||
rc=0
|
||||
echo
|
||||
echo "=== summary (withdraw + pay) ==="
|
||||
echo "package: $PKG stack: $STACK"
|
||||
echo "DO_WITHDRAW=$DO_WITHDRAW DO_PAY=$DO_PAY SMOKE_STRICT=$SMOKE_STRICT"
|
||||
if [ "$DO_WITHDRAW" = "1" ]; then
|
||||
echo "withdraw URI: ${WURI:-(none)}"
|
||||
if [ "$WITHDRAW_OK" = "1" ]; then
|
||||
echo "withdraw wallet-core: YES"
|
||||
else
|
||||
echo "withdraw wallet-core: NO"
|
||||
if [ "$SMOKE_STRICT" = "1" ]; then
|
||||
echo "FAIL: SMOKE_STRICT requires withdraw (prepareBank/accept/confirm) in logcat"
|
||||
rc=11
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$DO_PAY" = "1" ]; then
|
||||
echo "pay URI: ${PAY_URI:-(none)}"
|
||||
if [ -z "$PAY_URI" ]; then
|
||||
echo "pay: no URI (template/API)"
|
||||
if [ "$SMOKE_STRICT" = "1" ]; then
|
||||
echo "FAIL: SMOKE_STRICT requires a pay URI when DO_PAY=1"
|
||||
rc=12
|
||||
fi
|
||||
elif [ "$PAY_OK" = "1" ]; then
|
||||
echo "pay wallet-core: YES"
|
||||
else
|
||||
echo "pay wallet-core: NO (intent may still have been delivered)"
|
||||
if [ "$SMOKE_STRICT" = "1" ]; then
|
||||
echo "FAIL: SMOKE_STRICT requires pay path activity in logcat"
|
||||
[ "$rc" -eq 0 ] && rc=13
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
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 \
|
||||
|| grep -rqiE "isn't responding" "$OUT_DIR/gui-withdraw" 2>/dev/null; then
|
||||
echo "FAIL: System UI / app ANR"
|
||||
rc=10
|
||||
fi
|
||||
echo "artifacts: $OUT_DIR/"
|
||||
ls -la "$OUT_DIR" | sed 's/^/ /'
|
||||
echo
|
||||
echo "NOTE: Full unattended Abheben+Bezahlen needs responsive device (≥6–8 GiB host RAM)."
|
||||
echo "Smoke legs: withdraw=$( [ "$DO_WITHDRAW" = 1 ] && echo on || echo off) pay=$( [ "$DO_PAY" = 1 ] && echo on || echo off) rc=$rc"
|
||||
exit "$rc"
|
||||
Loading…
Add table
Add a link
Reference in a new issue