QQ-Kommunikationsgruppe: 815453846 Discord: https://discord.gg/PbJhnZJKDd
Ich habe dieses Projekt eine Weile nicht gepflegt (vielleicht zwei Jahre), aber vor kurzem musste ich die native Automatisierung von Android erneut untersuchen. Nach dem Vergleich habe ich festgestellt, dass die Ausführungsgeschwindigkeit des uiautomator2-Projekts höher ist ist wirklich gut, vom Erkennen von Elementen bis zum Klicken, alles in Millisekunden und der Code ist einfacher zu verstehen. Ich hätte wirklich nicht erwartet, dass ich schon einmal so ein magisches Projekt geschrieben habe. Wie kann es passieren, dass es verstaubt? Es muss neu organisiert werden und etwas Junk-Code muss bereinigt werden. Daher wurde die Projektversion von 2.xx auf 3.xx aktualisiert
Nutzer, die noch Version 2.xx nutzen, können sich zunächst 2to3 ansehen, bevor sie sich für ein Upgrade auf 3.xx entscheiden (ich persönlich empfehle ein Upgrade dringend)
Schließlich handelt es sich bei Version 2 auf 3 um ein großes Versionsupgrade, bei dem viele Funktionen gestrichen wurden. Das erste, was gelöscht werden muss, ist atx-agent, und zweitens gibt es eine Reihe von atx-agent-bezogenen Funktionen. Veraltete Funktionen wie init.
Versionsnummern verschiedener abhängiger Bibliotheken
UiAutomator ist eine von Google bereitgestellte Java-Bibliothek für Android-Automatisierungstests, die auf dem Accessibility-Dienst basiert. Es ist sehr leistungsstark und kann Apps von Drittanbietern testen, alle Steuerattribute jeder APP auf dem Bildschirm abrufen und beliebige Vorgänge darauf ausführen. Es hat jedoch zwei Nachteile: 1. Das Testskript kann nur die Java-Sprache verwenden. 2. Der Test Skript Es muss in ein JAR- oder APK-Paket gepackt und zur Ausführung auf das Gerät hochgeladen werden.
Wir hoffen, dass die Testlogik in Python geschrieben werden kann und das Mobiltelefon steuern kann, während es auf dem Computer läuft. Ich möchte Xiaocong He (@xiaocong) für die Umsetzung dieser Idee danken (siehe xiaocong/uiautomator). Das Prinzip besteht darin, einen http-rpc-Dienst auf dem Mobiltelefon auszuführen, die Funktionen in uiautomator zu öffnen und diese dann zu verwenden Die Schnittstelle ist in einer Python-Bibliothek gekapselt. Denn die xiaocong/uiautomator
-Bibliothek wurde schon lange nicht mehr aktualisiert. Deshalb haben wir direkt eine Version geforkt, um die Unterscheidung zu erleichtern. Am Ende habe ich auch eine Kopie des entsprechenden Android-Paketquellcodes hinzugefügt, openatx/android-uiautomator-server.
Neben der Behebung von Fehlern in der Originalbibliothek wurden auch viele neue Funktionen hinzugefügt. Es umfasst hauptsächlich die folgenden Teile:
Lassen Sie mich das zunächst erklären, da viele Leute oft fragen, dass openatx/uiautomator2 keine iOS-Tests unterstützt. Wenn Sie automatisierte iOS-Tests benötigen, können Sie zu dieser Bibliothek openatx/facebook-wda gehen.
PS: Diese Bibliothek
https://github.com/NeteaseGame/ATXEs wird nicht mehr gewartet. Bitte ersetzen Sie es so schnell wie möglich.
Hier ist eine Kurzanleitung für diejenigen, die bereits begonnen haben. Kommentare sind willkommen.
Bereiten Sie zunächst ein (nicht zwei) Android-Telefon mit aktivierten开发者选项
vor, schließen Sie es an den Computer an und stellen Sie sicher, dass Sie das angeschlossene Gerät sehen können, indem Sie adb devices
ausführen.
Führen Sie pip3 install -U uiautomator2
aus, um uiautomator2 zu installieren
Führen Sie python
über die Befehlszeile aus, um das interaktive Python-Fenster zu öffnen. Geben Sie dann den folgenden Befehl in das Fenster ein.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
Wenn Sie eine Ausgabe ähnlich der folgenden sehen, können Sie offiziell mit der Nutzung unserer Bibliothek beginnen. Da diese Bibliothek zu viele Funktionen hat und viel Inhalt dahinter steckt, müssen Sie sie langsam lesen....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
Um die Stabilität aufrechtzuerhalten, müssen Sie außerdem die Erlaubnis zum schwebenden Fenster小黄车
aktivieren. Referenzartikel py-uiautomator2 macht Dienste über schwebende Fenster für lange Zeit verfügbar
Normalerweise gelingt es, aber es kann Überraschungen geben. Sie können der QQ-Gruppe beitreten, um Probleme zu melden (die Gruppennummer steht oben). In der Gruppe gibt es viele große Leute, die Ihnen bei der Lösung von Problemen helfen können.
Vielen Dank an alle unsere Sponsoren!
Leer
Empfehlung ausgezeichneter Artikel (willkommen bei mir in der QQ-Gruppe für Feedback)
成都-测试只会一点点
Installation
Mit einem Gerät verbinden
Befehlszeile
Globale Einstellungen
App-Management
UI-Automatisierung
Mitwirkende
LIZENZ
Installieren Sie uiautomator2
pip install -U uiautomator2
Testen Sie, ob die Installation erfolgreich ist uiautomator2 --help
UI-Inspektor
pip install uiautodev
# 启动
uiauto.dev
Öffnen Sie https://uiauto.dev im Browser, um die Schnittstellenstruktur des aktuellen Geräts anzuzeigen.
uiauto.dev
uiauto.dev ist ein von uiautomator2 unabhängiges Projekt, das zum Anzeigen der Ebenenstruktur verwendet wird. Es handelt sich um eine rekonstruierte Version des alten Projekteditors und kann später in Rechnung gestellt werden (der Preis ist definitiv sein Geld wert), um die weitere Wartung des aktuellen Projekts zu unterstützen. Wenn Sie interessiert sind, können Sie der Gruppe zur Diskussion (einschließlich der Einreichung von Anfragen) beitreten. QQ-Gruppe 536481989
Verwenden Sie die Seriennummer, um das Gerät anzuschließen, z. B. 123456f
(von adb devices
aus gesehen).
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
Serial kann über env-var ANDROID_SERIAL
übergeben werden
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
stellt die IP-Adresse des Geräts dar
Wenn Sie das Gerät angeben müssen, müssen Sie --serial
wie python3 -m uiautomator2 --serial bff1234 <SubCommand>
übergeben. SubCommand ist ein Unterbefehl (Screenshot, aktuell usw.).
1.0.3 Hinzugefügt:
python3 -m uiautomator2
entsprichtuiautomator2
Screenshot: Screenshot
$ uiautomator2 screenshot screenshot.jpg
current: Rufen Sie den aktuellen Paketnamen und die aktuelle Aktivität ab
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
deinstallieren: App deinstallieren
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
stop: App stoppen
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
Arzt:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Wenn Python beendet wird, wird auch der UiAutomation-Dienst beendet.
Drucken Sie die HTTP-Anfrageinformationen hinter dem Code aus
> >> 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
Wartezeit für die Elementsuche festlegen (Standard: 20 Sekunden)
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
Diese Funktion hat Einfluss auf click
, long_click
, drag_to
, get_text
, set_text
, clear_text
usw.
In diesem Teil wird gezeigt, wie Sie die App-Verwaltung durchführen
Wir unterstützen nur die Installation einer APK von einer 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(默认)
In Version 1.2.0 hinzugefügt
Pushen Sie eine Datei auf das Gerät
# 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 )
Ziehen Sie eine Datei vom Gerät
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"
In diesem Teil wird gezeigt, wie gängige Gerätevorgänge ausgeführt werden:
Führen Sie einen kurzlebigen Shell-Befehl mit einem Timeout-Schutz aus (Standard-Timeout 60 Sekunden).
Hinweis: Timeout-Unterstützung erfordert atx-agent >=0.3.3
adb_shell
-Funktion ist veraltet. Verwenden Sie stattdessen shell
.
Einfache Nutzung
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
Das erste Argument kann beispielsweise list sein
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
Dies gibt eine mit stderr zusammengeführte Zeichenfolge für stdout zurück. Wenn der Befehl ein blockierender Befehl ist, blockiert shell
auch, bis der Befehl abgeschlossen ist oder die Zeitüberschreitung eintritt. Während der Ausführung des Befehls wird keine Teilausgabe empfangen Geeignet für Befehle mit langer Laufzeit. Der angegebene Shell-Befehl wird in einer ähnlichen Umgebung wie adb shell
ausgeführt, die über die Linux-Berechtigungsstufe adb
oder shell
verfügt (höher als eine App-Berechtigung).
Führen Sie einen Shell-Befehl mit langer Laufzeit aus (entfernt)
Die Sitzung stellt einen App-Lebenszyklus dar. Kann zum Starten einer App und zum Erkennen eines App-Absturzes verwendet werden.
App starten und schließen
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Verwenden Sie Python with
die App zu starten und zu schließen
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
An die laufende App anhängen
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
App-Absturz erkennen
# 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
Erhalten Sie grundlegende Informationen
d . info
Nachfolgend finden Sie eine mögliche Ausgabe:
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Fenstergröße ermitteln
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Aktuelle App-Informationen abrufen Bei einigen Android-Geräten kann die Ausgabe leer sein (siehe Ausgabebeispiel 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}
Warteaktivität
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Holen Sie sich die Seriennummer des Geräts
print ( d . serial )
# output example: 74aAEDR428Z9
Holen Sie sich WLAN-IP
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
Erhalten Sie detaillierte Geräteinformationen d.device_info
Geräteinfo
print ( d . device_info )
Nachfolgend finden Sie eine mögliche Ausgabe:
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Holen Sie sich den Inhalt der festgelegten Zwischenablage
Legen Sie den Inhalt des Pasteboards fest oder rufen Sie Inhalte ab
Zwischenablage/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
Holen Sie sich den Inhalt der Zwischenablage
Für das Abrufen der Zwischenablage ist ein IME(com.github.uiautomator/.AdbKeyboard)-Aufruf
d.set_input_ime()
erforderlich, bevor es verwendet werden kann.
```python
# get clipboard content
print(d.clipboard)
```
Bildschirm ein-/ausschalten
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Aktuellen Bildschirmstatus abrufen
d . info . get ( 'screenOn' ) # require Android >= 4.4
Drücken Sie die Hard-/Softtaste
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)
Diese Schlüsselnamen werden derzeit unterstützt:
Alle Schlüsselcodedefinitionen finden Sie bei Android KeyEvnet
Bildschirm entsperren
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Klicken Sie auf den Bildschirm
d . click ( x , y )
Doppelklick
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Klicken Sie lange auf den Bildschirm
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Wischen
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
SwipeExt-Erweiterungsfunktion
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 ) # 页面水平左翻
Ziehen
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Wischpunkte
# 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 ))
Es wird hauptsächlich zum Entsperren des Jiugong-Musters verwendet. Die relativen Koordinaten jedes Punkts können im Voraus ermittelt werden (Prozentsatz wird hier unterstützt). Weitere Informationen zur Verwendung finden Sie in diesem Beitrag.
Berühren und drapieren (Beta)
Diese Schnittstelle ist eine relativ einfache, primitive Schnittstelle. Sie fühlt sich nicht perfekt an, ist aber verwendbar. Hinweis: Dieser Ort unterstützt keine Prozentsätze
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
Hinweis: Klick-, Wisch- und Ziehvorgänge unterstützen prozentuale Positionswerte. Beispiel:
d.long_click(0.5, 0.5)
bedeutet langes Klicken in der Mitte des Bildschirms
Geräteausrichtung abrufen/einstellen
Die möglichen Ausrichtungen:
natural
oder n
left
oder l
right
oder r
upsidedown
oder u
(kann nicht eingestellt werden) # 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 einfrieren/freigeben
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Machen Sie einen Screenshot
# 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 )
Dump-UI-Hierarchie
# 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 )
Benachrichtigung oder Schnelleinstellungen öffnen
d . open_notification ()
d . open_quick_settings ()
Selector ist ein praktischer Mechanismus zum Identifizieren eines bestimmten UI-Objekts im aktuellen Fenster.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
Ausführliche Informationen finden Sie im UiSelector-Java-Dokument.
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
Kinder
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
geschwister