taler-monitoring v1.13.12
Multi-phase global SUMMARY, warn-filter dimmed context, SUMMARY chrome as meta; FP stage host-agent via francpaysan-stage-user (stagepaysan); monpages GOA + FP stage.
This commit is contained in:
commit
5a5431bfa9
88 changed files with 21996 additions and 0 deletions
192
android-test/lib_ui.py
Executable file
192
android-test/lib_ui.py
Executable file
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Minimal adb + uiautomator helpers for Taler wallet GUI smoke (no root).
|
||||
|
||||
Does not replace Maestro/Appium; uses only adb shell + uiautomator dump.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Optional, Sequence, Tuple
|
||||
|
||||
Bounds = Tuple[int, int, int, int]
|
||||
|
||||
|
||||
def adb(serial: str, *args: str, check: bool = False, timeout: Optional[float] = 60) -> subprocess.CompletedProcess:
|
||||
cmd = ["adb", "-s", serial, *args]
|
||||
return subprocess.run(cmd, check=check, capture_output=True, text=True, timeout=timeout)
|
||||
|
||||
|
||||
def dump_ui(serial: str, dest: Path) -> str:
|
||||
adb(serial, "shell", "uiautomator", "dump", "/sdcard/ui.xml", timeout=90)
|
||||
adb(serial, "pull", "/sdcard/ui.xml", str(dest), timeout=30)
|
||||
return dest.read_text(errors="replace") if dest.is_file() else ""
|
||||
|
||||
|
||||
def screencap(serial: str, dest: Path) -> None:
|
||||
p = subprocess.run(
|
||||
["adb", "-s", serial, "exec-out", "screencap", "-p"],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
if p.returncode == 0 and p.stdout:
|
||||
dest.write_bytes(p.stdout)
|
||||
|
||||
|
||||
def texts(xml: str) -> List[str]:
|
||||
return [t for t in re.findall(r'text="([^"]*)"', xml) if t.strip()]
|
||||
|
||||
|
||||
def find_bounds(xml: str, label: str) -> List[Bounds]:
|
||||
"""Return list of (x1,y1,x2,y2) for nodes whose text or content-desc matches label (exact)."""
|
||||
out: List[Bounds] = []
|
||||
esc = re.escape(label)
|
||||
patterns = [
|
||||
rf'(?:text|content-desc)="{esc}"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"',
|
||||
rf'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"[^>]*(?:text|content-desc)="{esc}"',
|
||||
]
|
||||
for pat in patterns:
|
||||
for m in re.finditer(pat, xml, flags=re.I):
|
||||
out.append(tuple(map(int, m.groups()))) # type: ignore[arg-type]
|
||||
return out
|
||||
|
||||
|
||||
def find_bounds_re(xml: str, pattern: str) -> List[Tuple[str, Bounds]]:
|
||||
out: List[Tuple[str, Bounds]] = []
|
||||
for m in re.finditer(
|
||||
rf'(?:text|content-desc)="([^"]*{pattern}[^"]*)"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"',
|
||||
xml,
|
||||
flags=re.I,
|
||||
):
|
||||
label = m.group(1)
|
||||
b = tuple(map(int, m.group(2, 3, 4, 5)))
|
||||
out.append((label, b)) # type: ignore[arg-type]
|
||||
return out
|
||||
|
||||
|
||||
def center(b: Bounds) -> Tuple[int, int]:
|
||||
x1, y1, x2, y2 = b
|
||||
return (x1 + x2) // 2, (y1 + y2) // 2
|
||||
|
||||
|
||||
def tap(serial: str, x: int, y: int) -> None:
|
||||
adb(serial, "shell", "input", "tap", str(x), str(y))
|
||||
|
||||
|
||||
def tap_bounds(serial: str, b: Bounds) -> None:
|
||||
x, y = center(b)
|
||||
tap(serial, x, y)
|
||||
|
||||
|
||||
def dismiss_anr(serial: str, xml: str) -> bool:
|
||||
"""If System UI isn't responding, prefer Wait over Close."""
|
||||
if "isn't responding" not in xml and "reagiert nicht" not in xml.lower():
|
||||
return False
|
||||
for label in ("Wait", "Warten", "OK"):
|
||||
bs = find_bounds(xml, label)
|
||||
if bs:
|
||||
tap_bounds(serial, bs[0])
|
||||
return True
|
||||
# fallback: right-ish button often Wait
|
||||
adb(serial, "shell", "input", "keyevent", "22")
|
||||
adb(serial, "shell", "input", "keyevent", "66")
|
||||
return True
|
||||
|
||||
|
||||
# Labels seen on wallet UI (EN/DE/FR) — expand as needed
|
||||
CONFIRM_LABELS = [
|
||||
"Confirm",
|
||||
"Confirm withdrawal",
|
||||
"Withdraw",
|
||||
"Accept",
|
||||
"I accept",
|
||||
"Agree",
|
||||
"Continue",
|
||||
"Next",
|
||||
"OK",
|
||||
"Pay",
|
||||
"Pay now",
|
||||
"Bestätigen",
|
||||
"Abheben",
|
||||
"Akzeptieren",
|
||||
"Zustimmen",
|
||||
"Weiter",
|
||||
"Bezahlen",
|
||||
"Confirmer",
|
||||
"Retirer",
|
||||
"Accepter",
|
||||
"Payer",
|
||||
]
|
||||
|
||||
TOS_LABELS = [
|
||||
"I accept",
|
||||
"Accept",
|
||||
"Agree",
|
||||
"Akzeptieren",
|
||||
"Zustimmen",
|
||||
"Accepter",
|
||||
]
|
||||
|
||||
|
||||
def try_tap_any(serial: str, xml: str, labels: Sequence[str]) -> Optional[str]:
|
||||
for label in labels:
|
||||
bs = find_bounds(xml, label)
|
||||
if bs:
|
||||
tap_bounds(serial, bs[0])
|
||||
return label
|
||||
return None
|
||||
|
||||
|
||||
def gui_drive(
|
||||
serial: str,
|
||||
out_dir: Path,
|
||||
*,
|
||||
rounds: int = 8,
|
||||
sleep_s: float = 3.0,
|
||||
phase: str = "gui",
|
||||
) -> dict:
|
||||
"""Poll UI, dismiss ANR, tap confirm/ToS-like buttons. Returns status dict."""
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
seen_texts: List[str] = []
|
||||
taps: List[str] = []
|
||||
anr = 0
|
||||
for i in range(1, rounds + 1):
|
||||
xml_path = out_dir / f"{phase}-{i:02d}-ui.xml"
|
||||
png_path = out_dir / f"{phase}-{i:02d}.png"
|
||||
try:
|
||||
xml = dump_ui(serial, xml_path)
|
||||
except Exception as e:
|
||||
taps.append(f"dump-fail:{e}")
|
||||
time.sleep(sleep_s)
|
||||
continue
|
||||
try:
|
||||
screencap(serial, png_path)
|
||||
except Exception:
|
||||
pass
|
||||
ts = texts(xml)
|
||||
seen_texts.extend(ts[:20])
|
||||
if dismiss_anr(serial, xml):
|
||||
anr += 1
|
||||
time.sleep(sleep_s)
|
||||
continue
|
||||
hit = try_tap_any(serial, xml, CONFIRM_LABELS)
|
||||
if hit:
|
||||
taps.append(hit)
|
||||
time.sleep(sleep_s)
|
||||
continue
|
||||
# partial regex for amount confirm rows
|
||||
for label, b in find_bounds_re(xml, r"Confirm|Withdraw|Pay|Accept|Bestätig|Abheben|Payer"):
|
||||
tap_bounds(serial, b)
|
||||
taps.append(f"re:{label}")
|
||||
time.sleep(sleep_s)
|
||||
break
|
||||
else:
|
||||
time.sleep(sleep_s)
|
||||
return {
|
||||
"taps": taps,
|
||||
"anr_dismissals": anr,
|
||||
"sample_texts": seen_texts[-40:],
|
||||
"rounds": rounds,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue