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 d'abord 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
Recommandation d'excellents 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>
, 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 défini du presse-papiers
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