monitoring: port GOA GUI chain from taler-android inference branches
Document dev/hernani-inference/gui-workflows and related fix branches; add Linux-capable run-goa-gui-chain.sh (mint + deep-link + UI taps).
This commit is contained in:
parent
f1b6eb8b45
commit
4d49657a63
3 changed files with 413 additions and 2 deletions
|
|
@ -100,11 +100,61 @@ balance/settlement is not asserted (settlement still better covered by
|
||||||
| `run-android-gui-smoke.sh` | **Android app UI** surface for the same stacks |
|
| `run-android-gui-smoke.sh` | **Android app UI** surface for the same stacks |
|
||||||
| Combined | CLI = money path green; GUI = app UX green as far as taps go |
|
| Combined | CLI = money path green; GUI = app UX green as far as taps go |
|
||||||
|
|
||||||
|
## Upstream: `taler-android` `dev/hernani-inference/*`
|
||||||
|
|
||||||
|
Branch name is **`hernani-inference`** (not “inferenced”). Remote:
|
||||||
|
`git@git.taler.net:taler-android.git`.
|
||||||
|
|
||||||
|
| Branch | Relevance for automation |
|
||||||
|
|--------|---------------------------|
|
||||||
|
| **`dev/hernani-inference/gui-workflows`** | **Primary:** macOS emulator helpers + **GOA GUI chain** (`scripts/goa-chain-emu.sh`, `docs/gui-workflows.md`, `setup-avds.sh`, `start-emulators.sh`). Deep-link + `uiautomator` text taps + bank API mint + shop templates. |
|
||||||
|
| **`fix/bank-withdraw-auto-exchange`** | App fix: resolve exchange when bank omits it (needed for GOA withdraw UI). |
|
||||||
|
| **`fix/withdraw-spinner-fallback`** | App fix: stop endless withdraw spinner (+ OIM UI work). |
|
||||||
|
| **`experimental-oim` / `experimental-oim-pos`** | Experimental cash denomination UI; optional for demos, not required for chain. |
|
||||||
|
|
||||||
|
### Port into this tree (Linux-capable)
|
||||||
|
|
||||||
|
| Upstream (gui-workflows) | Here (`android-test/`) |
|
||||||
|
|--------------------------|-------------------------|
|
||||||
|
| `goa-chain-emu.sh` | **`run-goa-gui-chain.sh`** (goa + stage, Linux paths, explorer secrets as monitoring ladder) |
|
||||||
|
| `tap_text` / `click_through` | same idea in chain + `lib_ui.py` |
|
||||||
|
| `mint_withdraw` explorer API | same (Basic → token → POST withdrawals) |
|
||||||
|
| `mint_pay` public templates | same (goa-shop products / stage fermes templates) |
|
||||||
|
| `android-env.sh` Homebrew | use `$HOME/Android/Sdk` + system `adb` (see GIT-AUTOMATION-NOTES) |
|
||||||
|
| macOS-only docs | **Linux supported** for chain/smoke; AVD setup still DIY on Linux |
|
||||||
|
|
||||||
|
Open issues mirrored from upstream `ISSUES.md` (gui-workflows):
|
||||||
|
|
||||||
|
- **I-and-gui-001** — taps still best-effort; ToS/confirm may need human
|
||||||
|
- **I-and-gui-003** — `EXP_PW_FILE` required for mint
|
||||||
|
- **I-and-gui-004** — emulator RAM cost (this host ~3.7 GiB)
|
||||||
|
- **I-and-gui-007** — for reliable withdraw GUI, build/install app from **fix/bank-withdraw-auto-exchange** or **fix/withdraw-spinner-fallback**, not plain master alone
|
||||||
|
|
||||||
|
Recommended demo stack:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1) Checkout taler-android: fix/withdraw-spinner-fallback (or merge fixes)
|
||||||
|
2) ./gradlew :wallet:installFdroidDebug
|
||||||
|
3) Boot emulator / phone
|
||||||
|
4) android-test/run-goa-gui-chain.sh # STACK=goa|stage
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# GOA chain (explorer mint + GUI taps)
|
||||||
|
EXP_PW_FILE=$HOME/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank/bank-explorer-password.txt \
|
||||||
|
STACK=goa PKG=net.taler.wallet.fdroid.debug \
|
||||||
|
./run-goa-gui-chain.sh
|
||||||
|
|
||||||
|
# Stage TESTPAYSAN (explorer via stagepaysan SSH if EXP_PW_FILE unset)
|
||||||
|
STACK=stage ./run-goa-gui-chain.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Future upgrades (not required for vanilla)
|
## Future upgrades (not required for vanilla)
|
||||||
|
|
||||||
- Maestro / Appium flows with stable resource-ids
|
- Maestro / Appium flows with stable resource-ids
|
||||||
- Explicit wait for text “Withdrawal complete” / balance
|
- Explicit wait for text “Withdrawal complete” / balance
|
||||||
- Separate jobs: F-Droid vs nightly source vs Play flavor
|
- Port Paivana unlock scripts (upstream I-and-gui-002)
|
||||||
|
- Dual AVD wallet+POS (upstream I-and-gui-008)
|
||||||
- Record video (`adb shell screenrecord`) on failure
|
- Record video (`adb shell screenrecord`) on failure
|
||||||
|
|
||||||
## Checklist — “vanille erreicht?”
|
## Checklist — “vanille erreicht?”
|
||||||
|
|
@ -114,6 +164,9 @@ balance/settlement is not asserted (settlement still better covered by
|
||||||
- [x] Source-build path (same smoke after build)
|
- [x] Source-build path (same smoke after build)
|
||||||
- [x] Deep-link shortcuts documented
|
- [x] Deep-link shortcuts documented
|
||||||
- [x] Multi-round **graphical** taps (uiautomator)
|
- [x] Multi-round **graphical** taps (uiautomator)
|
||||||
|
- [x] GOA-style **chain** (multi withdraw + multi pay) from inference branch
|
||||||
- [x] Screenshots + UI XML + logcat artifacts
|
- [x] Screenshots + UI XML + logcat artifacts
|
||||||
|
- [x] Inference branch mapping documented
|
||||||
- [ ] Fully reliable unattended Confirm→settled on low-RAM emulator (host-limited)
|
- [ ] Fully reliable unattended Confirm→settled on low-RAM emulator (host-limited)
|
||||||
|
- [ ] App built from withdraw-fix branch by default in build script (optional)
|
||||||
- [ ] Assert final balance in app UI (optional next step)
|
- [ ] Assert final balance in app UI (optional next step)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,19 @@ STACK=stage ./run-android-gui-smoke.sh
|
||||||
GUI=1 ./run-android-build-and-smoke.sh
|
GUI=1 ./run-android-build-and-smoke.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Documented shortcuts and limits: **[AUTOMATED-GUI-NOTES.md](./AUTOMATED-GUI-NOTES.md)**.
|
## 4) Full GOA/stage chain (from `dev/hernani-inference/gui-workflows`)
|
||||||
|
|
||||||
|
Bank API mint → withdraw deep-link → taps → template pay → taps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# needs explorer password + device
|
||||||
|
EXP_PW_FILE=$HOME/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank/bank-explorer-password.txt \
|
||||||
|
STACK=goa PKG=net.taler.wallet.fdroid.debug \
|
||||||
|
./run-goa-gui-chain.sh
|
||||||
|
|
||||||
|
STACK=stage ./run-goa-gui-chain.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Documented shortcuts, inference branches, limits: **[AUTOMATED-GUI-NOTES.md](./AUTOMATED-GUI-NOTES.md)**.
|
||||||
|
|
||||||
See also parent [GIT-AUTOMATION-NOTES.md](../GIT-AUTOMATION-NOTES.md).
|
See also parent [GIT-AUTOMATION-NOTES.md](../GIT-AUTOMATION-NOTES.md).
|
||||||
|
|
|
||||||
345
scripts/taler-monitoring/android-test/run-goa-gui-chain.sh
Executable file
345
scripts/taler-monitoring/android-test/run-goa-gui-chain.sh
Executable file
|
|
@ -0,0 +1,345 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# GOA (and optionally stage) GUI chain on Android — adapted from
|
||||||
|
# taler-android branch **dev/hernani-inference/gui-workflows**
|
||||||
|
# (`scripts/goa-chain-emu.sh`, docs/gui-workflows.md).
|
||||||
|
#
|
||||||
|
# Linux + macOS: uses adb + uiautomator only (no Homebrew hard dependency).
|
||||||
|
# Upstream scripts are documented as macOS-only for SDK install paths.
|
||||||
|
#
|
||||||
|
# Flow:
|
||||||
|
# 1) API mint withdraw (explorer) → VIEW taler://withdraw → GUI taps
|
||||||
|
# 2) Merchant template POST → VIEW taler://pay → GUI taps
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./run-goa-gui-chain.sh
|
||||||
|
# STACK=stage ./run-goa-gui-chain.sh
|
||||||
|
# WITHDRAW_AMOUNTS='GOA:10' PAY_LIMIT=2 ./run-goa-gui-chain.sh
|
||||||
|
# PKG=net.taler.wallet.fdroid.debug ./run-goa-gui-chain.sh --no-clear
|
||||||
|
#
|
||||||
|
# Env: ANDROID_SERIAL, PKG, EXP_PW_FILE, BANK, MERCHANT, INSTANCE,
|
||||||
|
# WITHDRAW_AMOUNTS, PRODUCTS, PAY_LIMIT, SHOTDIR, PAUSE
|
||||||
|
#
|
||||||
|
# See AUTOMATED-GUI-NOTES.md § "hernani-inference branches"
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
|
||||||
|
CLEAR=1
|
||||||
|
for a in "$@"; do
|
||||||
|
case "$a" in
|
||||||
|
--no-clear) CLEAR=0 ;;
|
||||||
|
-h|--help) sed -n '2,28p' "$0"; exit 0 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
|
step() { echo; echo "======== $* ========"; }
|
||||||
|
info() { echo " $*"; }
|
||||||
|
pause() { sleep "${1:-${PAUSE:-0.8}}"; }
|
||||||
|
|
||||||
|
if ! command -v adb >/dev/null 2>&1; then
|
||||||
|
[ -x "$HOME/Android/Sdk/platform-tools/adb" ] && export PATH="$HOME/Android/Sdk/platform-tools:$PATH"
|
||||||
|
fi
|
||||||
|
command -v adb >/dev/null || die "adb not found"
|
||||||
|
command -v python3 >/dev/null || die "python3 required"
|
||||||
|
|
||||||
|
STACK="${STACK:-goa}"
|
||||||
|
case "$STACK" in
|
||||||
|
goa|hacktivism)
|
||||||
|
PKG="${PKG:-net.taler.wallet.fdroid.debug}"
|
||||||
|
BANK="${BANK:-https://bank.hacktivism.ch}"
|
||||||
|
MERCHANT="${MERCHANT:-https://taler.hacktivism.ch}"
|
||||||
|
INSTANCE="${INSTANCE:-goa-shop}"
|
||||||
|
DONATE_INSTANCE="${DONATE_INSTANCE:-goa-demo-cp4zqk}"
|
||||||
|
DONATE_TEMPLATE="${DONATE_TEMPLATE:-goa-free}"
|
||||||
|
DEFAULT_AMOUNTS=("GOA:10" "GOA:20")
|
||||||
|
DEFAULT_PAYS=("product:orbit-sticker" "product:nebula-coffee" "product:voidwave-playlist" "donate:GOA:12")
|
||||||
|
CUR_HINT=GOA
|
||||||
|
;;
|
||||||
|
stage|testpaysan)
|
||||||
|
PKG="${PKG:-net.taler.wallet.fdroid.debug}"
|
||||||
|
BANK="${BANK:-https://stage.bank.lefrancpaysan.ch}"
|
||||||
|
MERCHANT="${MERCHANT:-https://stage.monnaie.lefrancpaysan.ch}"
|
||||||
|
INSTANCE="${INSTANCE:-fermes-des-collines}"
|
||||||
|
DONATE_INSTANCE="${DONATE_INSTANCE:-fermes-des-collines}"
|
||||||
|
DONATE_TEMPLATE="${DONATE_TEMPLATE:-don-panier-libre}"
|
||||||
|
DEFAULT_AMOUNTS=("TESTPAYSAN:10" "TESTPAYSAN:20")
|
||||||
|
DEFAULT_PAYS=("product:panier-legumes" "product:fromage-chevre" "product:oeufs-6")
|
||||||
|
CUR_HINT=TESTPAYSAN
|
||||||
|
;;
|
||||||
|
*) die "STACK must be goa|stage (got $STACK)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ACT="${ACT:-$PKG/net.taler.wallet.main.MainActivity}"
|
||||||
|
EXP_USER="${EXP_USER:-explorer}"
|
||||||
|
PAY_LIMIT="${PAY_LIMIT:-4}"
|
||||||
|
PAUSE="${PAUSE:-0.8}"
|
||||||
|
SHOTDIR="${SHOTDIR:-$ROOT/out-gui-chain/$(date +%Y%m%d-%H%M%S)-$STACK}"
|
||||||
|
mkdir -p "$SHOTDIR"
|
||||||
|
|
||||||
|
# Explorer password (same search as taler-monitoring ladder)
|
||||||
|
if [[ -z "${EXP_PW_FILE:-}" ]]; then
|
||||||
|
for cand in \
|
||||||
|
"${HOME}/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank/bank-explorer-password.txt" \
|
||||||
|
"${HOME}/.config/taler-landing/bank-explorer-password.txt"
|
||||||
|
do
|
||||||
|
[[ -f "$cand" ]] && EXP_PW_FILE="$cand" && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
# stage: try stagepaysan secret via ssh if missing
|
||||||
|
if [[ ! -f "${EXP_PW_FILE:-}" && "$STACK" = "stage" ]]; then
|
||||||
|
for host in francpaysan-stage-user francpaysan-host; do
|
||||||
|
tmp=$(mktemp)
|
||||||
|
if ssh -o BatchMode=yes -o ConnectTimeout=10 "$host" \
|
||||||
|
'tr -d "\n\r" </mnt/data/stagepaysan/bank/secrets/bank-explorer-password.txt' \
|
||||||
|
>"$tmp" 2>/dev/null && [[ -s "$tmp" ]]; then
|
||||||
|
EXP_PW_FILE="$tmp"
|
||||||
|
info "explorer password via $host"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[[ -n "${EXP_PW_FILE:-}" && -f "$EXP_PW_FILE" ]] || die "set EXP_PW_FILE (explorer password)"
|
||||||
|
|
||||||
|
if [[ -z "${ANDROID_SERIAL:-}" ]]; then
|
||||||
|
n=$(adb devices | awk 'NR>1 && $2=="device"{c++} END{print c+0}')
|
||||||
|
[[ "$n" -ge 1 ]] || die "no adb device (start emulator or plug phone)"
|
||||||
|
export ANDROID_SERIAL=$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}')
|
||||||
|
fi
|
||||||
|
info "ANDROID_SERIAL=$ANDROID_SERIAL STACK=$STACK PKG=$PKG SHOTDIR=$SHOTDIR"
|
||||||
|
ADB=(adb -s "$ANDROID_SERIAL")
|
||||||
|
|
||||||
|
adb_sh() { "${ADB[@]}" shell "$@"; }
|
||||||
|
|
||||||
|
shot() {
|
||||||
|
local n="$1"
|
||||||
|
adb_sh screencap -p "/sdcard/demo-$n.png" 2>/dev/null || true
|
||||||
|
"${ADB[@]}" pull "/sdcard/demo-$n.png" "$SHOTDIR/$n.png" >/dev/null 2>&1 || true
|
||||||
|
info "[shot] $SHOTDIR/$n.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
tap_text() {
|
||||||
|
local label="$1"
|
||||||
|
adb_sh uiautomator dump /sdcard/ui.xml >/dev/null 2>&1 || return 1
|
||||||
|
"${ADB[@]}" pull /sdcard/ui.xml "$SHOTDIR/ui.xml" >/dev/null 2>&1 || return 1
|
||||||
|
SERIAL="$ANDROID_SERIAL" python3 - "$label" "$SHOTDIR/ui.xml" <<'PY'
|
||||||
|
import os, re, subprocess, sys
|
||||||
|
from pathlib import Path
|
||||||
|
label, path = sys.argv[1], sys.argv[2]
|
||||||
|
serial = os.environ.get("SERIAL") or os.environ.get("ANDROID_SERIAL") or ""
|
||||||
|
xml = Path(path).read_text(errors="replace")
|
||||||
|
# ANR dismiss
|
||||||
|
if "isn't responding" in xml or "reagiert nicht" in xml.lower():
|
||||||
|
for lab in ("Wait", "Warten"):
|
||||||
|
for m in re.finditer(rf'text="{lab}"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', xml):
|
||||||
|
x = (int(m.group(1))+int(m.group(3)))//2
|
||||||
|
y = (int(m.group(2))+int(m.group(4)))//2
|
||||||
|
subprocess.run(["adb","-s",serial,"shell","input","tap",str(x),str(y)], check=False)
|
||||||
|
print(f"anr-wait @{x},{y}")
|
||||||
|
sys.exit(0)
|
||||||
|
for pat in [
|
||||||
|
rf'text="{re.escape(label)}"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"',
|
||||||
|
rf'text="[^"]*{re.escape(label)}[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"',
|
||||||
|
rf'content-desc="[^"]*{re.escape(label)}[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"',
|
||||||
|
]:
|
||||||
|
for m in re.finditer(pat, xml, re.I):
|
||||||
|
chunk = xml[max(0, m.start()-120):m.end()]
|
||||||
|
if 'enabled="false"' in chunk:
|
||||||
|
continue
|
||||||
|
x = (int(m.group(1))+int(m.group(3)))//2
|
||||||
|
y = (int(m.group(2))+int(m.group(4)))//2
|
||||||
|
print(f"tap {label!r} @{x},{y}")
|
||||||
|
cmd = ["adb","shell","input","tap",str(x),str(y)]
|
||||||
|
if serial:
|
||||||
|
cmd = ["adb","-s",serial,"shell","input","tap",str(x),str(y)]
|
||||||
|
subprocess.run(cmd, check=False)
|
||||||
|
sys.exit(0)
|
||||||
|
print("miss", label)
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_withdraw() {
|
||||||
|
local amount="$1"
|
||||||
|
EXP_USER="$EXP_USER" EXP_PW_FILE="$EXP_PW_FILE" BANK="$BANK" \
|
||||||
|
python3 - "$amount" <<'PY'
|
||||||
|
import base64, json, os, ssl, sys, urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
amount = sys.argv[1]
|
||||||
|
bank = os.environ["BANK"].rstrip("/")
|
||||||
|
user = os.environ["EXP_USER"]
|
||||||
|
pw = Path(os.environ["EXP_PW_FILE"]).read_text().strip()
|
||||||
|
auth = "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode()
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
|
||||||
|
def req(method, url, data=None, headers=None):
|
||||||
|
h = dict(headers or {})
|
||||||
|
body = None
|
||||||
|
if data is not None:
|
||||||
|
body = json.dumps(data).encode()
|
||||||
|
h["Content-Type"] = "application/json"
|
||||||
|
r = urllib.request.Request(url, data=body, headers=h, method=method)
|
||||||
|
with urllib.request.urlopen(r, context=ctx, timeout=25) as resp:
|
||||||
|
return json.load(resp)
|
||||||
|
|
||||||
|
tok = req(
|
||||||
|
"POST",
|
||||||
|
f"{bank}/accounts/{user}/token",
|
||||||
|
{"scope": "readwrite", "duration": {"d_us": 3600_000_000}},
|
||||||
|
{"Authorization": auth},
|
||||||
|
)["access_token"]
|
||||||
|
wd = req(
|
||||||
|
"POST",
|
||||||
|
f"{bank}/accounts/{user}/withdrawals",
|
||||||
|
{"amount": amount},
|
||||||
|
{"Authorization": f"Bearer {tok}"},
|
||||||
|
)
|
||||||
|
print(wd.get("taler_withdraw_uri") or wd.get("talerWithdrawUri") or "")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_pay() {
|
||||||
|
local mode="$1"
|
||||||
|
MERCHANT="$MERCHANT" INSTANCE="$INSTANCE" \
|
||||||
|
DONATE_INSTANCE="$DONATE_INSTANCE" DONATE_TEMPLATE="$DONATE_TEMPLATE" \
|
||||||
|
python3 - "$mode" <<'PY'
|
||||||
|
import json, os, ssl, sys, urllib.error, urllib.request
|
||||||
|
mode = sys.argv[1]
|
||||||
|
merchant = os.environ["MERCHANT"].rstrip("/")
|
||||||
|
inst = os.environ["INSTANCE"]
|
||||||
|
d_inst = os.environ["DONATE_INSTANCE"]
|
||||||
|
d_tmpl = os.environ["DONATE_TEMPLATE"]
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
|
||||||
|
def post(url, data):
|
||||||
|
body = json.dumps(data).encode()
|
||||||
|
r = urllib.request.Request(url, data=body, method="POST",
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(r, context=ctx, timeout=25) as resp:
|
||||||
|
return resp.status, json.load(resp)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
raw = e.read() if hasattr(e, "read") else b""
|
||||||
|
try:
|
||||||
|
return getattr(e, "code", None), json.loads(raw)
|
||||||
|
except Exception as err:
|
||||||
|
raise SystemExit(f"POST {url} failed: {e} body={raw[:200]!r}") from err
|
||||||
|
|
||||||
|
if mode.startswith("product:"):
|
||||||
|
pid = mode.split(":", 1)[1]
|
||||||
|
st, d = post(f"{merchant}/instances/{inst}/templates/{pid}", {})
|
||||||
|
use_inst = inst
|
||||||
|
elif mode.startswith("donate:"):
|
||||||
|
amt = mode.split(":", 1)[1]
|
||||||
|
st, d = post(f"{merchant}/instances/{d_inst}/templates/{d_tmpl}", {"amount": amt})
|
||||||
|
use_inst = d_inst
|
||||||
|
else:
|
||||||
|
raise SystemExit(f"bad mode {mode!r}")
|
||||||
|
|
||||||
|
if "taler_pay_uri" in d:
|
||||||
|
print(d["taler_pay_uri"]); raise SystemExit(0)
|
||||||
|
oid, tok = d.get("order_id"), d.get("token")
|
||||||
|
if not oid or not tok:
|
||||||
|
raise SystemExit(f"no order: {d!r}"[:200])
|
||||||
|
host = merchant.replace("https://","").replace("http://","").split("/")[0]
|
||||||
|
print(f"taler://pay/{host}/instances/{use_inst}/{oid}/?c={tok}")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
click_through() {
|
||||||
|
local labels=("$@")
|
||||||
|
local i lab
|
||||||
|
for i in 1 2 3 4 5 6 7 8; do
|
||||||
|
for lab in "${labels[@]}"; do
|
||||||
|
if tap_text "$lab" 2>/dev/null; then
|
||||||
|
pause 0.9
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
pause 0.4
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "${WITHDRAW_AMOUNTS:-}" ]]; then
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
AMOUNTS=($WITHDRAW_AMOUNTS)
|
||||||
|
else
|
||||||
|
AMOUNTS=("${DEFAULT_AMOUNTS[@]}")
|
||||||
|
fi
|
||||||
|
if [[ -n "${PRODUCTS:-}" ]]; then
|
||||||
|
PAYS=()
|
||||||
|
for p in $PRODUCTS; do PAYS+=("product:$p"); done
|
||||||
|
else
|
||||||
|
PAYS=("${DEFAULT_PAYS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
############################
|
||||||
|
step "0 device + wallet ($STACK / $CUR_HINT)"
|
||||||
|
adb_sh input keyevent KEYCODE_WAKEUP 2>/dev/null || true
|
||||||
|
adb_sh wm dismiss-keyguard 2>/dev/null || true
|
||||||
|
if [[ "$CLEAR" -eq 1 ]]; then
|
||||||
|
info "pm clear $PKG"
|
||||||
|
adb_sh pm clear "$PKG" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
adb_sh am force-stop "$PKG" 2>/dev/null || true
|
||||||
|
pause 0.4
|
||||||
|
# Prefer explicit MainActivity (from gui-workflows); fall back to VIEW launcher
|
||||||
|
if ! adb_sh am start -n "$ACT" >/dev/null 2>&1; then
|
||||||
|
info "MainActivity path failed — monkey launch"
|
||||||
|
adb_sh monkey -p "$PKG" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
pause 2.0
|
||||||
|
shot "00-start"
|
||||||
|
|
||||||
|
############################
|
||||||
|
step "2 withdraw chain"
|
||||||
|
idx=0
|
||||||
|
for AMT in "${AMOUNTS[@]}"; do
|
||||||
|
idx=$((idx + 1))
|
||||||
|
step "2.$idx withdraw $AMT"
|
||||||
|
URI="$(mint_withdraw "$AMT" | head -1)"
|
||||||
|
[[ -n "$URI" ]] || die "mint failed for $AMT"
|
||||||
|
info "URI=$URI"
|
||||||
|
echo "$URI" >>"$SHOTDIR/withdraw-uris.txt"
|
||||||
|
adb_sh am start -a android.intent.action.VIEW -d "$URI" "$PKG" >/dev/null
|
||||||
|
pause 1.5
|
||||||
|
shot "w${idx}-open"
|
||||||
|
click_through "Accept" "I accept" "Accept terms" "Confirm" "Continue" "Withdraw" "Next" "OK" "Agree" "Bestätigen" "Abheben" "Akzeptieren"
|
||||||
|
pause 3.0
|
||||||
|
shot "w${idx}-after"
|
||||||
|
adb_sh am start -n "$ACT" >/dev/null 2>&1 || true
|
||||||
|
pause 0.6
|
||||||
|
tap_text "Assets" 2>/dev/null || true
|
||||||
|
pause 0.4
|
||||||
|
shot "w${idx}-assets"
|
||||||
|
done
|
||||||
|
|
||||||
|
############################
|
||||||
|
step "3 pay chain (limit=$PAY_LIMIT)"
|
||||||
|
pidx=0
|
||||||
|
for PAY in "${PAYS[@]}"; do
|
||||||
|
pidx=$((pidx + 1))
|
||||||
|
[[ "$pidx" -gt "$PAY_LIMIT" ]] && break
|
||||||
|
step "3.$pidx pay $PAY"
|
||||||
|
if ! PAYURI="$(mint_pay "$PAY" | head -1)"; then
|
||||||
|
info "mint_pay failed for $PAY — skip"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ -n "$PAYURI" ]] || { info "empty pay uri — skip"; continue; }
|
||||||
|
info "PAYURI=$PAYURI"
|
||||||
|
echo "$PAYURI" >>"$SHOTDIR/pay-uris.txt"
|
||||||
|
adb_sh am start -a android.intent.action.VIEW -d "$PAYURI" "$PKG" >/dev/null
|
||||||
|
pause 1.5
|
||||||
|
shot "p${pidx}-open"
|
||||||
|
click_through "Pay" "Confirm" "Accept" "Continue" "OK" "Next" "Bezahlen" "Bestätigen" "Payer"
|
||||||
|
pause 1.5
|
||||||
|
shot "p${pidx}-after"
|
||||||
|
adb_sh am start -n "$ACT" >/dev/null 2>&1 || true
|
||||||
|
pause 0.5
|
||||||
|
done
|
||||||
|
|
||||||
|
step "4 done → $SHOTDIR"
|
||||||
|
ls -la "$SHOTDIR" | tail -40
|
||||||
|
adb_sh dumpsys window 2>/dev/null | grep mCurrentFocus | head -1 || true
|
||||||
|
info "PKG=$PKG amounts=${AMOUNTS[*]} pays(limit)=$PAY_LIMIT"
|
||||||
|
echo "See AUTOMATED-GUI-NOTES.md (ported from taler-android dev/hernani-inference/gui-workflows)"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue