Piloter un téléphone au texte affiché avec uiautomator
💡 Sujet
Automatiser un écran Android sans instrumenter l'application : ni testTag, ni resource-id ajouté
pour l'occasion, ni build spécial. uiautomator dump photographie la hiérarchie de vues affichée et
la rend en XML, ce qui suffit à vérifier ce qui est à l'écran et à cliquer dessus.
C'est la seule voie quand l'app n'expose rien : un receiver RECEIVER_NOT_EXPORTED ne se déclenche
pas en ADB, et les marqueurs de log n'existent que dans les builds Debug. Le texte visible, lui,
est là sur n'importe quel build.
📸 Le dump n'affiche rien, il écrit un fichier
Piège d'entrée : uiautomator dump ne sort pas le XML sur la sortie standard. Il l'écrit sur le
téléphone, et n'imprime que le chemin. Il faut donc deux commandes :
adb shell uiautomator dump /sdcard/ui.xml
adb shell cat /sdcard/ui.xmlSans argument, il choisit un emplacement par défaut sur le stockage de l'appareil — autant lui imposer un chemin connu, c'est plus simple à relire.
🔎 Chercher un texte : le XML tient sur une seule ligne
Le dump est produit sans retour à la ligne. Un grep dessus renvoie donc tout le document, ce
qui rend inutilisable tout ce qui suit. Il faut d'abord découper un nœud par ligne :
ui_dump | tr '>' '\n' | grep -iF "Démarrer la session"Pour une simple assertion de présence, la découpe est inutile :
ui_has_text() { ui_dump | grep -qiF "$1"; }👆 Cliquer : uiautomator ne clique pas, il donne des coordonnées
Le dump expose la position de chaque nœud dans un attribut bounds :
bounds="[144,1230][936,1398]"Soit [x1,y1][x2,y2]. On en extrait le centre, puis on tape avec input tap :
ui_bounds() {
ui_dump | tr '>' '\n' | grep -iF "$1" \
| grep -o 'bounds="\[[0-9]*,[0-9]*\]\[[0-9]*,[0-9]*\]"' | head -1 \
| grep -o '[0-9]\+' | tr '\n' ' '
}
ui_tap_text() {
local b; b=$(ui_bounds "$1"); [ -n "$b" ] || return 1
set -- $b; adb shell input tap $(( ($1+$3)/2 )) $(( ($2+$4)/2 ))
}Même principe pour un slide-to-confirm : au lieu de taper le centre, on balaie la mi-hauteur du nœud de gauche à droite, sur 600 ms :
set -- $b; cy=$(( ($2+$4)/2 ))
adb shell input swipe $(( $1+20 )) "$cy" $(( $3-20 )) "$cy" 600⏳ Attendre : un écran peut mettre plus de 20 s à apparaître
Un dump immédiatement après une action photographie l'écran précédent. Plutôt qu'un sleep fixe
calibré au pire cas, on boucle jusqu'à voir le texte attendu :
ui_wait_text() { # <texte> [timeout_s]
local text=$1 timeout=${2:-45} waited=0
while [ "$waited" -lt "$timeout" ]; do
ui_has_text "$text" && return 0
sleep 3; waited=$((waited + 3))
done
return 1
}🔒 Le piège : un écran verrouillé fait dumper le keyguard
Si le téléphone est verrouillé, uiautomator dump renvoie la hiérarchie du keyguard, pas celle
de l'application. Toutes les assertions échouent alors pour une raison qui n'a rien à voir avec ce
qu'on teste.
Et l'état de confiance ment : un keyguard à simple balayage s'affiche avec
strongAuthRequired=0x0, donc une app lancée « déverrouillée » reste derrière. Le seul contrôle
honnête est de regarder si le keyguard est réellement à l'écran :
keyguard_showing() { adb shell dumpsys window | grep -q "mIsShowing=true"; }🧩 Résumé
- 📸
uiautomator dump <fichier>écrit sur le téléphone : enchaîner avecadb shell cat. - 🔎 Le XML est sur une seule ligne —
tr '>' '\n'avant toutgrepciblé. - 👆 uiautomator ne clique pas : lire
bounds="[x1,y1][x2,y2]", calculer le centre,input tap;input swipesur la mi-hauteur pour un slide-to-confirm. - ⏳ Boucler sur le texte attendu plutôt que
sleepau pire cas. - 🔒 Écran verrouillé ⇒ c'est le keyguard qui est dumpé ; vérifier
mIsShowing=truedansdumpsys window, pas l'état de confiance. - ✅ Assertion par texte visible ⇒ marche sur un build release, là où les marqueurs de log
demandent un build
Debug.