Groupe de communication QQ : 815453846 Discord : https://discord.gg/PbJhnZJKDd
Je n'ai pas maintenu ce projet depuis un certain temps (peut-être deux ans), mais récemment, j'ai besoin de refaire des recherches sur l'automatisation native d'Android. Bien sûr, j'ai également étudié Appium. Après l'avoir comparé, j'ai découvert la vitesse d'exécution du projet uiautomator2. c'est vraiment bien. C'est rapide, de la détection des éléments au clic, tout se fait en millisecondes, et le code est plus facile à comprendre. Je ne m'attendais vraiment pas à avoir écrit un projet aussi magique auparavant. Comment un si bon projet a-t-il pu laisser la poussière prendre la poussière ? Il doit être réorganisé et certains codes indésirables doivent être nettoyés. La version du projet a donc été mise à niveau de 2.xx à 3.xx
Les utilisateurs qui utilisent encore la version 2.xx peuvent jeter un œil à 2to3 avant de décider de passer à la version 3.xx (je recommande personnellement fortement la mise à niveau)
Après tout, les versions 2 à 3 sont une mise à niveau majeure de la version et de nombreuses fonctions ont été supprimées. La première chose à supprimer est atx-agent, et deuxièmement, il existe un tas de fonctions liées à atx-agent. Fonctions obsolètes telles que init.
Numéros de version de diverses bibliothèques dépendantes
UiAutomator est une bibliothèque Java fournie par Google pour les tests d'automatisation Android, basée sur le service Accessibility. Il est très puissant et peut tester des applications tierces, obtenir n'importe quel attribut de contrôle de n'importe quelle application à l'écran et effectuer n'importe quelle opération dessus, mais il présente deux inconvénients : 1. Le script de test ne peut utiliser que le langage Java 2. Le test script Il doit être emballé dans un package jar ou apk et téléchargé sur l'appareil pour être exécuté.
Nous espérons que la logique de test pourra être écrite en Python et pourra contrôler le téléphone mobile tout en s'exécutant sur l'ordinateur. Je tiens à remercier beaucoup Xiaocong He (@xiaocong) pour avoir réalisé cette idée (voir xiaocong/uiautomator). Le principe est d'exécuter un service http rpc sur le téléphone mobile, d'ouvrir les fonctions dans uiautomator, puis d'utiliser ces http. L'interface est encapsulée dans une bibliothèque Python. Parce que la bibliothèque xiaocong/uiautomator
n'a pas été mise à jour depuis longtemps. Nous avons donc directement créé une version Afin de faciliter la distinction, nous avons ajouté 2 openatx/uiautomator2 à la fin. J'ai également créé une copie du code source du package Android correspondant, openatx/android-uiautomator-server.
En plus de corriger les bugs de la bibliothèque d'origine, de nombreuses nouvelles fonctionnalités ont également été ajoutées. Il comprend principalement les parties suivantes :
Laissez-moi d'abord vous expliquer ici, car beaucoup de gens demandent souvent que openatx/uiautomator2 ne prend pas en charge les tests iOS. Si vous avez besoin de tests automatisés iOS, vous pouvez accéder à cette bibliothèque openatx/facebook-wda.
PS : Cette bibliothèque
https://github.com/NeteaseGame/ATXIl n'est plus en maintenance, veuillez le remplacer dès que possible.
Voici un GUIDE DE RÉFÉRENCE RAPIDE adapté à ceux qui ont déjà commencé. Les commentaires sont les bienvenus.
Préparez d’abord un (et non deux) téléphone Android avec开发者选项
activées, connectez-le à l’ordinateur et assurez-vous que vous pouvez voir l’appareil connecté en exécutant adb devices
.
Exécutez pip3 install -U uiautomator2
pour installer uiautomator2
Exécutez python
à partir de la ligne de commande pour ouvrir la fenêtre interactive Python. Entrez ensuite la commande suivante dans la fenêtre.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
Lorsque vous voyez un résultat similaire à celui ci-dessous, vous pouvez officiellement commencer à utiliser notre bibliothèque. Parce que cette bibliothèque a trop de fonctions et qu'il y a beaucoup de contenu derrière elle, vous devez la lire lentement....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
De plus, afin de maintenir la stabilité, vous devez activer l'autorisation de fenêtre flottante de小黄车
. L'article de référence py-uiautomator2 rend les services disponibles pendant longtemps via des fenêtres flottantes
Cela réussit généralement, mais il peut y avoir des surprises. Vous pouvez rejoindre le groupe QQ pour signaler des problèmes (le numéro du groupe est en haut). Il y a beaucoup de gros joueurs dans le groupe qui peuvent vous aider à résoudre les problèmes.
Merci à tous nos sponsors !
Vide
Excellentes recommandations d'articles (bienvenue chez moi dans le groupe QQ pour commentaires)
成都-测试只会一点点
Installation
Se connecter à un appareil
Ligne de commande
Paramètres globaux
Gestion des applications
Automatisation de l'interface utilisateur
Contributeurs
LICENCE
Installer uiautomator2
pip install -U uiautomator2
Testez si l'installation a réussi uiautomator2 --help
Inspecteur de l'interface utilisateur
pip install uiautodev
# 启动
uiauto.dev
Ouvrez https://uiauto.dev dans le navigateur pour afficher la structure de l'interface de l'appareil actuel.
uiauto.dev
uiauto.dev est un projet indépendant de uiautomator2, utilisé pour visualiser la structure des couches. Il s'agit d'une version reconstruite de l'ancien éditeur de projet, et peut être facturée ultérieurement (le prix en vaut vraiment la peine) pour prendre en charge la maintenance continue du projet en cours. Si vous êtes intéressé, vous pouvez rejoindre le groupe pour discuter (y compris faire des demandes) Groupe QQ 536481989
utilisez le numéro de série pour connecter l'appareil, par exemple 123456f
(vu depuis adb devices
)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
La série peut être transmise via env-var ANDROID_SERIAL
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
représente l'adresse IP de l'appareil
Si vous devez spécifier le périphérique, vous devez transmettre --serial
tel que python3 -m uiautomator2 --serial bff1234
, SubCommand est une sous-commande (capture d'écran, actuelle, etc.)
1.0.3 Ajouté :
python3 -m uiautomator2
est égal àuiautomator2
capture d'écran : capture d'écran
$ uiautomator2 screenshot screenshot.jpg
current : obtient le nom et l'activité actuels du package
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
désinstaller : désinstaller l'application
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
arrêter : arrêter l'application
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
médecin:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Lorsque Python se ferme, le service UiAutomation se ferme également.
Imprimez les informations de la requête HTTP derrière le code
> >> d . debug = True
> >> d . info
12 : 32 : 47.182 $ curl - X POST - d '{"jsonrpc": "2.0", "id": "b80d3a488580be1f3e9cb3e926175310", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:54179/jsonrpc/0'
12 : 32 : 47.225 Response >> >
{ "jsonrpc" : "2.0" , "id" : "b80d3a488580be1f3e9cb3e926175310" , "result" :{ "currentPackageName" : "com.android.mms" , "displayHeight" : 1920 , "displayRotation" : 0 , "displaySizeDpX" : 360 , "displaySizeDpY" : 640 , "displayWidth" : 1080 , "productName"
: "odin" , "screenOn" : true , "sdkInt" : 25 , "naturalOrientation" : true }}
< << END
Définir le temps d'attente pour la recherche d'éléments (20 s par défaut)
d . implicitly_wait ( 10.0 ) # 也可以通过d.settings['wait_timeout'] = 10.0 修改
d ( text = "Settings" ). click () # if Settings button not show in 10s, UiObjectNotFoundError will raised
print ( "wait timeout" , d . implicitly_wait ()) # get default implicit wait
Cette fonction aura une influence sur click
, long_click
, drag_to
, get_text
, set_text
, clear_text
, etc.
Cette partie montre comment effectuer la gestion des applications
Nous prenons uniquement en charge l'installation d'un APK à partir d'une URL
d . app_install ( 'http://some-domain.com/some.apk' )
# 默认的这种方法是先通过atx-agent解析apk包的mainActivity,然后调用am start -n $package/$activity启动
d . app_start ( "com.example.hello_world" )
# 使用 monkey -p com.example.hello_world -c android.intent.category.LAUNCHER 1 启动
# 这种方法有个副作用,它自动会将手机的旋转锁定给关掉
d . app_start ( "com.example.hello_world" , use_monkey = True ) # start with package name
# 通过指定main activity的方式启动应用,等价于调用am start -n com.example.hello_world/.MainActivity
d . app_start ( "com.example.hello_world" , ".MainActivity" )
# equivalent to `am force-stop`, thus you could lose data
d . app_stop ( "com.example.hello_world" )
# equivalent to `pm clear`
d . app_clear ( 'com.example.hello_world' )
# stop all
d . app_stop_all ()
# stop all app except for com.examples.demo
d . app_stop_all ( excludes = [ 'com.examples.demo' ])
d . app_info ( "com.examples.demo" )
# expect output
#{
# "mainActivity": "com.github.uiautomator.MainActivity",
# "label": "ATX",
# "versionName": "1.1.7",
# "versionCode": 1001007,
# "size":1760809
#}
# save app icon
img = d . app_icon ( "com.examples.demo" )
img . save ( "icon.png" )
d . app_list_running ()
# expect output
# ["com.xxxx.xxxx", "com.github.uiautomator", "xxxx"]
pid = d . app_wait ( "com.example.android" ) # 等待应用运行, return pid(int)
if not pid :
print ( "com.example.android is not running" )
else :
print ( "com.example.android pid is %d" % pid )
d . app_wait ( "com.example.android" , front = True ) # 等待应用前台运行
d . app_wait ( "com.example.android" , timeout = 20.0 ) # 最长等待时间20s(默认)
Ajouté dans la version 1.2.0
envoyer un fichier vers l'appareil
# push to a folder
d . push ( "foo.txt" , "/sdcard/" )
# push and rename
d . push ( "foo.txt" , "/sdcard/bar.txt" )
# push fileobj
with open ( "foo.txt" , 'rb' ) as f :
d . push ( f , "/sdcard/" )
# push and change file access mode
d . push ( "foo.sh" , "/data/local/tmp/" , mode = 0o755 )
extraire un fichier de l'appareil
d . pull ( "/sdcard/tmp.txt" , "tmp.txt" )
# FileNotFoundError will raise if the file is not found on the device
d . pull ( "/sdcard/some-file-not-exists.txt" , "tmp.txt" )
# grant all the permissions
d . app_auto_grant_permissions ( "io.appium.android.apis" )
# open scheme
d . open_url ( "appname://appnamehost" )
# same as
# adb shell am start -a android.intent.action.VIEW -d "appname://appnamehost"
Cette partie montre comment effectuer les opérations courantes sur les appareils :
Exécutez une commande shell de courte durée avec une protection contre l'expiration (délai d'expiration par défaut 60 s).
Remarque : la prise en charge du délai d'attente nécessite atx-agent >=0.3.3
La fonction adb_shell
est obsolète. Utilisez plutôt shell
.
Utilisation simple
output , exit_code = d . shell ( "pwd" , timeout = 60 ) # timeout 60s (Default)
# output: "/n", exit_code: 0
# Similar to command: adb shell pwd
# Since `shell` function return type is `namedtuple("ShellResponse", ("output", "exit_code"))`
# so we can do some tricks
output = d . shell ( "pwd" ). output
exit_code = d . shell ( "pwd" ). exit_code
Le premier argument peut être une liste par exemple.
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
Cela renvoie une chaîne pour stdout fusionné avec stderr. Si la commande est une commande bloquante, shell
bloquera également jusqu'à ce que la commande soit terminée ou que le délai d'attente soit écoulé. Aucune sortie partielle ne sera reçue pendant l'exécution de la commande. convient aux commandes de longue durée. La commande shell donnée s'exécute dans un environnement similaire à adb shell
, qui a un niveau d'autorisation Linux adb
ou shell
(supérieur à une autorisation d'application).
Exécuter une commande shell de longue durée (supprimé)
La session représente un cycle de vie d'application. Peut être utilisée pour démarrer une application et détecter un crash d'application.
Lancer et fermer l'application
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Utilisez Python with
pour lancer et fermer l'application
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
Joindre à l'application en cours d'exécution
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
Détecter le crash de l'application
# When app is still running
sess ( text = "Music" ). click () # operation goes normal
# If app crash or quit
sess ( text = "Music" ). click () # raise SessionBrokenError
# other function calls under session will raise SessionBrokenError too
# check if session is ok.
# Warning: function name may change in the future
sess . running () # True or False
Obtenir des informations de base
d . info
Voici une sortie possible :
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Obtenir la taille de la fenêtre
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Obtenir les informations actuelles sur l'application Pour certains appareils Android, la sortie peut être vide (voir Exemple de sortie 3 ).
print ( d . app_current ())
# Output example 1: {'activity': '.Client', 'package': 'com.netease.example', 'pid': 23710}
# Output example 2: {'activity': '.Client', 'package': 'com.netease.example'}
# Output example 3: {'activity': None, 'package': None}
Activité d'attente
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Obtenir le numéro de série de l'appareil
print ( d . serial )
# output example: 74aAEDR428Z9
Obtenir une adresse IP WLAN
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
Obtenez des informations détaillées sur l'appareil d.device_info
info_appareil
print ( d . device_info )
Voici une sortie possible :
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Obtenir le contenu du presse-papiers défini
Définir le contenu du presse-papier ou obtenir du contenu
presse-papiers/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
Obtenir le contenu du presse-papiers
obtenir le presse-papiers nécessite l'appel IME (com.github.uiautomator/.AdbKeyboard)
d.set_input_ime()
avant de l'utiliser.
```python
# get clipboard content
print(d.clipboard)
```
Allumer/éteindre l'écran
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Obtenir l'état actuel de l'écran
d . info . get ( 'screenOn' ) # require Android >= 4.4
Appuyez sur la touche matérielle/écran
d . press ( "home" ) # press the home key, with key name
d . press ( "back" ) # press the back key, with key name
d . press ( 0x07 , 0x02 ) # press keycode 0x07('0') with META ALT(0x02)
Ces noms de clés sont actuellement pris en charge :
Vous pouvez trouver toutes les définitions de codes clés sur Android KeyEvnet
Déverrouiller l'écran
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Cliquez sur l'écran
d . click ( x , y )
Double-cliquez
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Clic long sur l'écran
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Glisser
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Fonction d'extension SwipeExt
d . swipe_ext ( "right" ) # 手指右滑,4选1 "left", "right", "up", "down"
d . swipe_ext ( "right" , scale = 0.9 ) # 默认0.9, 滑动距离为屏幕宽度的90%
d . swipe_ext ( "right" , box = ( 0 , 0 , 100 , 100 )) # 在 (0,0) -> (100, 100) 这个区域做滑动
# 实践发现上滑或下滑的时候,从中点开始滑动成功率会高一些
d . swipe_ext ( "up" , scale = 0.8 ) # 代码会vkk
# 还可以使用Direction作为参数
from uiautomator2 import Direction
d . swipe_ext ( Direction . FORWARD ) # 页面下翻, 等价于 d.swipe_ext("up"), 只是更好理解
d . swipe_ext ( Direction . BACKWARD ) # 页面上翻
d . swipe_ext ( Direction . HORIZ_FORWARD ) # 页面水平右翻
d . swipe_ext ( Direction . HORIZ_BACKWARD ) # 页面水平左翻
Traîner
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Points de balayage
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)
# time will speed 0.2s bwtween two points
d . swipe_points ([( x0 , y0 ), ( x1 , y1 ), ( x2 , y2 )], 0.2 ))
Il est principalement utilisé pour déverrouiller le modèle Jiugong. Les coordonnées relatives de chaque point peuvent être obtenues à l'avance (le pourcentage est pris en charge ici). Pour une utilisation plus détaillée, veuillez vous référer à cet article. Utilisez u2 pour déverrouiller le modèle Jiugong.
Toucher et draper (bêta)
Cette interface est une interface primitive de niveau relativement bas. Elle ne semble pas parfaite, mais elle est utilisable. Remarque : Cet endroit ne prend pas en charge les pourcentages
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
Remarque : les opérations de clic, de glissement et de glissement prennent en charge les valeurs de position en pourcentage. Exemple :
d.long_click(0.5, 0.5)
signifie un clic long au centre de l'écran
Récupérer/Définir l'orientation de l'appareil
Les orientations possibles :
natural
ou n
left
ou l
right
ou r
upsidedown
ou u
(ne peut pas être réglé) # retrieve orientation. the output could be "natural" or "left" or "right" or "upsidedown"
orientation = d . orientation
# WARNING: not pass testing in my TT-M1
# set orientation and freeze rotation.
# notes: setting "upsidedown" requires Android>=4.3.
d . set_orientation ( 'l' ) # or "left"
d . set_orientation ( "l" ) # or "left"
d . set_orientation ( "r" ) # or "right"
d . set_orientation ( "n" ) # or "natural"
Rotation geler/dégeler
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Prendre une capture d'écran
# take screenshot and save to a file on the computer, require Android>=4.2.
d . screenshot ( "home.jpg" )
# get PIL.Image formatted images. Naturally, you need pillow installed first
image = d . screenshot () # default format="pillow"
image . save ( "home.jpg" ) # or home.png. Currently, only png and jpg are supported
# get opencv formatted images. Naturally, you need numpy and cv2 installed first
import cv2
image = d . screenshot ( format = 'opencv' )
cv2 . imwrite ( 'home.jpg' , image )
# get raw jpeg data
imagebin = d . screenshot ( format = 'raw' )
open ( "some.jpg" , "wb" ). write ( imagebin )
Vider la hiérarchie de l'interface utilisateur
# get the UI hierarchy dump content
xml = d . dump_hierarchy ()
# compressed=True: include not import nodes
# pretty: format xml
# max_depth: limit xml depth, default 50
xml = d . dump_hierarchy ( compressed = False , pretty = False , max_depth = 50 )
Ouvrir la notification ou les paramètres rapides
d . open_notification ()
d . open_quick_settings ()
Le sélecteur est un mécanisme pratique pour identifier un objet d'interface utilisateur spécifique dans la fenêtre actuelle.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
Le sélecteur prend en charge les paramètres ci-dessous. Reportez-vous à la documentation Java UiSelector pour des informations détaillées.
text
, textContains
, textMatches
, textStartsWith
className
, classNameMatches
description
, descriptionContains
, descriptionMatches
, descriptionStartsWith
checkable
, checked
, clickable
, longClickable
scrollable
, enabled
, focusable
, focused
, selected
packageName
, packageNameMatches
resourceId
, resourceIdMatches
index
, instance
enfants
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
frères et sœurs
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
enfants par texte, description ou instance
# get the child matching the condition className="android.widget.LinearLayout"
# and also its children or grandchildren with text "Bluetooth"
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Bluetooth" , className = "android.widget.LinearLayout" )
# get children by allowing scroll search
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text (
"Bluetooth" ,
allow_scroll_search = True ,
className = "android.widget.LinearLayout"
)
child_by_description
consiste à rechercher les enfants dont les petits-enfants ont la description spécifiée, les autres paramètres étant similaires à child_by_text
.
child_by_instance
consiste à rechercher des enfants avec un élément d'interface utilisateur enfant n'importe où dans sa sous-hiérarchie qui se trouve à l'instance spécifiée. Cela est effectué sur des vues visibles sans défilement .
Voir les liens ci-dessous pour des informations détaillées :
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
Les méthodes ci-dessus prennent en charge les appels chaînés, par exemple pour la hiérarchie inférieure
< node index = " 0 " text = " " resource-id = " android:id/list " class = " android.widget.ListView " ...>
< node index = " 0 " text = " WIRELESS & NETWORKS " resource-id = " " class = " android.widget.TextView " .../>
< node index = " 1 " text = " " resource-id = " " class = " android.widget.LinearLayout " ...>
< node index = " 1 " text = " " resource-id = " " class = " android.widget.RelativeLayout " ...>
< node index = " 0 " text = " Wi‑Fi " resource-id = " android:id/title " class = " android.widget.TextView " .../>
node >
< node index = " 2 " text = " ON " resource-id = " com.android.settings:id/switchWidget " class = " android.widget.Switch " .../>
node >
...
node >
Pour cliquer sur le widget de commutation directement sur TextView 'Wi-Fi', nous devons d'abord sélectionner les widgets de commutation. Cependant, selon la hiérarchie de l'interface utilisateur, plusieurs widgets de commutation existent et ont presque les mêmes propriétés. La sélection par nom de classe ne le sera pas. Alternativement, la stratégie de sélection ci-dessous aiderait :
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
positionnement relatif
Nous pouvons également utiliser les méthodes de positionnement relatif pour obtenir la vue : left
, right
, top
, bottom
.
d(A).left(B)
, sélectionne B sur le côté gauche de A.d(A).right(B)
, sélectionne B sur le côté droit de A.d(A).up(B)
, sélectionne B au-dessus de A.d(A).down(B)
, sélectionne B sous A.Donc pour les cas ci-dessus, on peut alternativement le sélectionner avec :
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
Plusieurs instances
Parfois, l'écran peut contenir plusieurs vues avec les mêmes propriétés, par exemple du texte, vous devrez alors utiliser la propriété "instance" dans le sélecteur pour choisir l'une des instances éligibles, comme ci-dessous :
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
De plus, uiautomator2 fournit une API de type liste (similaire à jQuery) :
# get the count of views with text "Add new" on current screen
d ( text = "Add new" ). count
# same as count property
len ( d ( text = "Add new" ))
# get the instance via index
d ( text = "Add new" )[ 0 ]
d ( text = "Add new" )[ 1 ]
...
# iterator
for view in d ( text = "Add new" ):
view . info # ...
Notes : lorsque vous utilisez des sélecteurs dans un bloc de code qui parcourent la liste des résultats, vous devez vous assurer que les éléments de l'interface utilisateur à l'écran restent inchangés, sinon une erreur d'élément non trouvé pourrait se produire lors de l'itération dans la liste.
Vérifiez si l'objet d'interface utilisateur spécifique existe
d ( text = "Settings" ). exists # True if exists, else False
d . exists ( text = "Settings" ) # alias of above property.
# advanced usage
d ( text = "Settings" ). exists ( timeout = 3 ) # wait Settings appear in 3s, same as .wait(3)
Récupérer les informations de l'objet d'interface utilisateur spécifique
d ( text = "Settings" ). info
Voici une sortie possible :
{ u'contentDescription': u'',
u'checked': False,
u'scrollable': False,
u'text': u'Settings',
u'packageName': u'com.android.launcher',
u'selected': False,
u'enabled': True,
u'bounds': {u'top': 385,
u'right': 360,
u'bottom': 585,
u'left': 200},
u'className': u'android.widget.TextView',
u'focused': False,
u'focusable': True,
u'clickable': True,
u'chileCount': 0,
u'longClickable': True,
u'visibleBounds': {u'top': 385,
u'right': 360,
u'bottom': 585,
u'left': 200},
u'checkable': False
}
Obtenir/Définir/Effacer le texte d'un champ modifiable (par exemple, widgets EditText)
d ( text = "Settings" ). get_text () # get widget text
d ( text = "Settings" ). set_text ( "My text..." ) # set the text
d ( text = "Settings" ). clear_text () # clear the text
Obtenir le point central du widget
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
Prendre une capture d'écran du widget
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
Effectuer un clic sur l'objet spécifique
# click on the center of the specific ui object
d ( text = "Settings" ). click ()
# wait element to appear for at most 10 seconds and then click
d ( text = "Settings" ). click ( timeout = 10 )
# click with offset(x_offset, y_offset)
# click_x = x_offset * width + x_left_top
# click_y = y_offset * height + y_left_top
d ( text = "Settings" ). click ( offset = ( 0.5 , 0.5 )) # Default center
d ( text = "Settings" ). click ( offset = ( 0 , 0 )) # click left-top
d ( text = "Settings" ). click ( offset = ( 1 , 1 )) # click right-bottom
# click when exists in 10s, default timeout 0s
clicked = d ( text = 'Skip' ). click_exists ( timeout = 10.0 )
# click until element gone, return bool
is_gone = d ( text = "Skip" ). click_gone ( maxretry = 10 , interval = 1.0 ) # maxretry default 10, interval default 1.0
Effectuez un clic long sur l'objet spécifique de l'interface utilisateur
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
Faites glisser l'objet UI vers un autre point ou un autre objet UI
# notes : drag can not be used for Android<4.3.
# drag the UI object to a screen point (x, y), in 0.5 second
d ( text = "Settings" ). drag_to ( x , y , duration = 0.5 )
# drag the UI object to (the center position of) another UI object, in 0.25 second
d ( text = "Settings" ). drag_to ( text = "Clock" , duration = 0.25 )
Faites glisser votre doigt du centre de l'objet d'interface utilisateur vers son bord
Swipe prend en charge 4 directions :
d ( text = "Settings" ). swipe ( "right" )
d ( text = "Settings" ). swipe ( "left" , steps = 10 )
d ( text = "Settings" ). swipe ( "up" , steps = 20 ) # 1 steps is about 5ms, so 20 steps is about 0.1s
d ( text = "Settings" ). swipe ( "down" , steps = 20 )
Geste en deux points d'un point à un autre
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
Geste en deux points sur l'objet d'interface utilisateur spécifique
Prend en charge deux gestes :
In
, du bord au centreOut
, du centre vers le bord # notes : pinch can not be set until Android 4.3.
# from edge to center. here is "In" not "in"
d ( text = "Settings" ). pinch_in ( percent = 100 , steps = 10 )
# from center to edge
d ( text = "Settings" ). pinch_out ()
Attendez que l'interface utilisateur spécifique apparaisse ou disparaisse
# wait until the ui object appears
d ( text = "Settings" ). wait ( timeout = 3.0 ) # return bool
# wait until the ui object gone
d ( text = "Settings" ). wait_gone ( timeout = 1.0 )
Le délai d'expiration par défaut est de 20 s, voir les paramètres globaux pour plus de détails.
Effectuer un lancer sur l'objet ui spécifique (défilement)
Propriétés possibles :
horiz
ou vert
forward
ou backward
ou toBeginning
ou toEnd
# fling forward(default) vertically(default)
d ( scrollable = True ). fling ()
# fling forward horizontally
d ( scrollable = True ). fling . horiz . forward ()
# fling backward vertically
d ( scrollable = True ). fling . vert . backward ()
# fling to beginning horizontally
d ( scrollable = True ). fling . horiz . toBeginning ( max_swipes = 1000 )
# fling to end vertically
d ( scrollable = True ). fling . toEnd ()
Effectuer un défilement sur l'objet ui spécifique (défilement)
Propriétés possibles :
horiz
ou vert
forward
ou backward
ou toBeginning
ou toEnd
, ou to
# scroll forward(default) vertically(default)
d ( scrollable = True ). scroll ( steps = 10 )
# scroll forward horizontally
d ( scrollable = True ). scroll . horiz . forward ( steps = 100 )
# scroll backward vertically
d ( scrollable = True ). scroll . vert . backward ()
# scroll to beginning horizontally
d ( scrollable = True ). scroll . horiz . toBeginning ( steps = 100 , max_swipes = 1000 )
# scroll to end vertically
d ( scrollable = True ). scroll . toEnd ()
# scroll forward vertically until specific ui object appears
d ( scrollable = True ). scroll . to ( text = "Security" )
Le watch_context actuel est démarré à l'aide du threading et est vérifié toutes les 2 secondes. Actuellement, il n'y a qu'une seule opération de déclenchement : cliquer.
with d . watch_context () as ctx :
# 当同时出现 (立即下载 或 立即更新)和 取消 按钮的时候,点击取消
ctx . when ( "^立即(下载|更新)" ). when ( "取消" ). click ()
ctx . when ( "同意" ). click ()
ctx . when ( "确定" ). click ()
# 上面三行代码是立即执行完的,不会有什么等待
ctx . wait_stable () # 开启弹窗监控,并等待界面稳定(两个弹窗检查周期内没有弹窗代表稳定)
# 使用call函数来触发函数回调
# call 支持两个参数,d和el,不区分参数位置,可以不传参,如果传参变量名不能写错
# eg: 当有元素匹配仲夏之夜,点击返回按钮
ctx . when ( "仲夏之夜" ). call ( lambda d : d . press ( "back" ))
ctx . when ( "确定" ). call ( lambda el : el . click ())
# 其他操作
# 为了方便也可以使用代码中默认的弹窗监控逻辑
# 下面是目前内置的默认逻辑,可以加群at群主,增加新的逻辑,或者直接提pr
# when("继续使用").click()
# when("移入管控").when("取消").click()
# when("^立即(下载|更新)").when("取消").click()
# when("同意").click()
# when("^(好的|确定)").click()
with d . watch_context ( builtin = True ) as ctx :
# 在已有的基础上增加
ctx . when ( "@tb:id/jview_view" ). when ( '//*[@content-desc="图片"]' ). click ()
# 其他脚本逻辑
Une autre façon d'écrire
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
Il est plus recommandé d'utiliser WatchContext pour l'écrire de manière plus concise.
Vous pouvez enregistrer des observateurs pour effectuer certaines actions lorsqu'un sélecteur ne trouve pas de correspondance.
Avant la version 2.0.0, la méthode [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) fournie dans la bibliothèque uiautomator-jar était utilisée, mais en pratique, il a été constaté qu'une fois la connexion uiautomator a échoué Après le redémarrage, toutes les configurations du watcher sont perdues, ce qui est définitivement inacceptable.
Par conséquent, la méthode actuelle consiste à exécuter un thread en arrière-plan (en s'appuyant sur la bibliothèque de threads), puis à vider la hiérarchie de temps en temps et à effectuer les opérations correspondantes après avoir fait correspondre les éléments.
Exemples d'utilisation
Suivi des inscriptions
# 常用写法,注册匿名监控
d . watcher . when ( "安装" ). click ()
# 注册名为ANR的监控,当出现ANR和Force Close时,点击Force Close
d . watcher ( "ANR" ). when ( xpath = "ANR" ). when ( "Force Close" ). click ()
# 其他回调例子
d . watcher . when ( "抢红包" ). press ( "back" )
d . watcher . when ( "//*[@text = 'Out of memory']" ). call ( lambda d : d . shell ( 'am force-stop com.im.qq' ))
# 回调说明
def click_callback ( d : u2 . Device ):
d . xpath ( "确定" ). click () # 在回调中调用不会再次触发watcher
d . xpath ( "继续" ). click () # 使用d.xpath检查元素的时候,会触发watcher(目前最多触发5次)
# 开始后台监控
d . watcher . start ()
Surveiller les opérations
# 移除ANR的监控
d . watcher . remove ( "ANR" )
# 移除所有的监控
d . watcher . remove ()
# 开始后台监控
d . watcher . start ()
d . watcher . start ( 2.0 ) # 默认监控间隔2.0s
# 强制运行所有监控
d . watcher . run ()
# 停止监控
d . watcher . stop ()
# 停止并移除所有的监控,常用于初始化
d . watcher . reset ()
De plus, il existe encore de nombreux documents qui n'ont pas été écrits. Il est recommandé d'accéder directement au code source watcher.py.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
La plupart des autres configurations sont actuellement concentrées dans d.settings
, et la configuration peut être augmentée ou diminuée en fonction des besoins ultérieurs.
print ( d . settings )
{ 'operation_delay' : ( 0 , 0 ),
'operation_delay_methods' : [ 'click' , 'swipe' ],
'wait_timeout' : 20.0 }
# 配置点击前延时0.5s,点击后延时1s
d . settings [ 'operation_delay' ] = ( .5 , 1 )
# 修改延迟生效的方法
# 其中 double_click, long_click 都对应click
d . settings [ 'operation_delay_methods' ] = [ 'click' , 'swipe' , 'drag' , 'press' ]
d . settings [ 'wait_timeout' ] = 20.0 # 默认控件等待时间(原生操作,xpath插件的等待时间)
d . settings [ 'max_depth' ] = 50 # 默认50,限制dump_hierarchy返回的元素层级
Pour les mises à niveau de version, lors de la définition de configurations expirées, le message Obsolète sera demandé, mais aucune exception ne sera levée.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
paramètres du mode de récupération d'uiautomator
Si vous faites attention, vous constaterez peut-être qu’il y a en réalité deux APK installés sur votre téléphone, dont l’un est visible au premier plan (la petite voiture jaune). Un package nommé com.github.uiautomator.test
n'est pas visible en arrière-plan. Ces deux apks sont signés avec le même certificat. L'application invisible est en fait un package de test qui contient tout le code de test, et le service de test principal est également démarré via celui-ci. Mais lorsqu'il fonctionne, le système a besoin que la petite voiture jaune tourne tout le temps (elle peut également fonctionner en arrière-plan). Une fois la petite application de voiture jaune supprimée, le service de test exécuté en arrière-plan sera également rapidement supprimé. Même si vous ne faites rien, l'application sera rapidement recyclée par le système si elle est en arrière-plan. (J’espère que les experts pourront me donner ici quelques conseils sur la façon d’éviter de recourir à de fausses applications. Je pense que c’est théoriquement possible, mais je ne sais pas encore comment le faire).
Il existe deux façons de laisser la petite voiture jaune fonctionner en arrière-plan. La première consiste à démarrer l'application et à la mettre en arrière-plan (par défaut). De plus, vous pouvez également démarrer un service en arrière-plan via am startservice
.
Ce comportement peut être ajusté via d.settings["uiautomator_runtest_app_background"] = True
. True signifie démarrer l'application, False signifie démarrer le service.
Paramètres de délai d'attente dans UiAutomator (méthode cachée)
>> d . jsonrpc . getConfigurator ()
{ 'actionAcknowledgmentTimeout' : 500 ,
'keyInjectionDelay' : 0 ,
'scrollAcknowledgmentTimeout' : 200 ,
'waitForIdleTimeout' : 0 ,
'waitForSelectorTimeout' : 0 }
>> d . jsonrpc . setConfigurator ({ "waitForIdleTimeout" : 100 })
{ 'actionAcknowledgmentTimeout' : 500 ,
'keyInjectionDelay' : 0 ,
'scrollAcknowledgmentTimeout' : 200 ,
'waitForIdleTimeout' : 100 ,
'waitForSelectorTimeout' : 0 }
Afin d'empêcher le programme client de répondre à un timeout, waitForIdleTimeout
et waitForSelectorTimeout
ont maintenant été modifiés à 0
Réfs : Configurateur Google uiautomator
Cette méthode est généralement utilisée pour les entrées dont le contrôle est inconnu.
# 目前采用从剪贴板粘贴的方式输入
d . send_keys ( "你好123abcEFG" )
d . send_keys ( "你好123abcEFG" , clear = True )
d . clear_text () # 清除输入框所有内容
d . send_action () # 根据输入框的需求,自动执行回车、搜索等指令, Added in version 3.1
# 也可以指定发送的输入法action, eg: d.send_action("search") 支持 go, search, send, next, done, previous
print ( d . current_ime ()) # 获取当前输入法ID
Plus de référence : IME_ACTION_CODE
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
Corrigé dans la version 3.2.0
Java uiautoamtor ne prend pas en charge XPath par défaut, il s'agit donc d'une fonction étendue. La vitesse n'est pas si rapide.
Par exemple : le contenu d'un des nœuds
">< android .widget.TextView index = " 2 " text = " 05:19 " resource-id = " com.netease.cloudmusic:id/qf " package = " com.netease.cloudmusic " content-desc = " " checkable = " false " checked = " false " clickable = " false " enabled = " true " focusable = " false " focused = " false " scrollable = " false " long-clickable = " false " password = " false " selected = " false " visible-to-user = " true " bounds = " [957,1602][1020,1636] " />
positionnement et utilisation de XPath
Les noms de certains attributs ont été modifiés et doivent être notés.
description -> content-desc
resourceId -> resource-id
Utilisation courante
# wait exists 10s
d . xpath ( "//android.widget.TextView" ). wait ( 10.0 )
# find and click
d . xpath ( "//*[@content-desc='分享']" ). click ()
# check exists
if d . xpath ( "//android.widget.TextView[contains(@text, 'Se')]" ). exists :
print ( "exists" )
# get all text-view text, attrib and center point
for elem in d . xpath ( "//android.widget.TextView" ). all ():
print ( "Text:" , elem . text )
# Dictionary eg:
# {'index': '1', 'text': '999+', 'resource-id': 'com.netease.cloudmusic:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false', 'enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}
print ( "Attrib:" , elem . attrib )
# Coordinate eg: (100, 200)
print ( "Position:" , elem . center ())
Cliquez pour voir d'autres utilisations courantes de XPath
Enregistrement vidéo (obsolète), utilisez plutôt scrcpy
La commande screenrecord fournie avec le téléphone mobile n'est pas utilisée ici. Il s'agit d'une méthode de synthèse vidéo en obtenant des images du téléphone mobile, vous devez donc installer d'autres dépendances, telles que imageio, imageio-ffmpeg, numpy, etc. les dépendances sont relativement importantes, il est recommandé d'utiliser une installation miroir. Exécutez simplement la commande suivante.
pip3 install -U " uiautomator2[image] " -i https://pypi.doubanio.com/simple
Comment utiliser
d.screenrecord('output.mp4')
time.sleep(10)
# or do something else
d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开
Vous pouvez également spécifier des images par seconde (actuellement 20) lors de l'enregistrement. Cette valeur est inférieure à la vitesse des images de sortie minicap. Je ne vous recommande pas de la modifier.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
Ou
logger = logging.getLogger("uiautomator2")
# setup logger
Lorsque le programme Python se termine, UiAutomation se ferme. Cependant, vous pouvez également arrêter le service via la méthode d'interface.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
Autres contributeurs
Le classement est en ordre, bienvenue à ajouter
MIT