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
Benutzer, die noch Version 2.xx verwenden, können einen Blick auf 2to3 werfen, bevor sie sich für ein Upgrade auf 3.xx entscheiden (ich persönlich empfehle dringend ein Upgrade).
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
Hervorragende Artikelempfehlungen (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
ü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
Starten und Schließen der App
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
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
Kinder nach Text, Beschreibung oder Instanz
# 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
dient dazu, Kinder zu finden, deren Enkelkinder die angegebene Beschreibung haben, wobei andere Parameter denen von child_by_text
ähneln.
child_by_instance
dient dazu, Kinder zu finden, die irgendwo in ihrer Unterhierarchie ein untergeordnetes UI-Element haben, das sich an der angegebenen Instanz befindet. Dies wird in sichtbaren Ansichten ohne Scrollen durchgeführt.
Detaillierte Informationen finden Sie unter den folgenden Links:
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
Die oben genannten Methoden unterstützen verkettete Aufrufe, z. B. für die folgende Hierarchie
< 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 >
Um auf das Schalter-Widget direkt neben der Textansicht „Wi-Fi“ zu klicken, müssen wir zunächst die Schalter-Widgets auswählen. Gemäß der UI-Hierarchie sind jedoch mehrere Schalter-Widgets vorhanden, die nahezu dieselben Eigenschaften haben Alternativ würde die folgende Auswahlstrategie helfen:
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
relative Positionierung
Wir können auch die relativen Positionierungsmethoden verwenden, um die Ansicht zu erhalten: left
, right
, top
, bottom
.
d(A).left(B)
, wählt B auf der linken Seite von A aus.d(A).right(B)
, wählt B auf der rechten Seite von A aus.d(A).up(B)
, wählt B über A aus.d(A).down(B)
, wählt B unter A aus.Für die oben genannten Fälle können wir es also alternativ auswählen mit:
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
Mehrere Instanzen
Manchmal enthält der Bildschirm mehrere Ansichten mit denselben Eigenschaften, z. B. Text. Dann müssen Sie die Eigenschaft „Instanz“ im Selektor verwenden, um eine der qualifizierenden Instanzen auszuwählen, wie unten:
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
Darüber hinaus stellt uiautomator2 eine listenartige API bereit (ähnlich wie 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 # ...
Hinweise : Wenn Sie Selektoren in einem Codeblock verwenden, der die Ergebnisliste durchläuft, müssen Sie sicherstellen, dass die UI-Elemente auf dem Bildschirm unverändert bleiben. Andernfalls könnte beim Durchlaufen der Liste ein Fehler „Element nicht gefunden“ auftreten.
Überprüfen Sie, ob das spezifische UI-Objekt vorhanden ist
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)
Rufen Sie die Informationen des spezifischen UI-Objekts ab
d ( text = "Settings" ). info
Nachfolgend finden Sie eine mögliche Ausgabe:
{ 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
}
Text eines bearbeitbaren Felds abrufen/festlegen/löschen (z. B. EditText-Widgets)
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
Widget-Mittelpunkt abrufen
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
Machen Sie einen Screenshot des Widgets
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
Führen Sie einen Klick auf das jeweilige Objekt aus
# 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
Führen Sie einen langen Klick auf das spezifische UI-Objekt aus
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
Ziehen Sie das UI-Objekt zu einem anderen Punkt oder einem anderen UI-Objekt
# 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 )
Wischen Sie von der Mitte des UI-Objekts zum Rand
Swipe unterstützt 4 Richtungen:
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 )
Zwei-Punkt-Geste von einem Punkt zum anderen
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
Zweipunktgeste auf dem spezifischen UI-Objekt
Unterstützt zwei Gesten:
In
, vom Rand zur MitteOut
, von der Mitte zum Rand # 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 ()
Warten Sie, bis die spezifische Benutzeroberfläche angezeigt oder ausgeblendet wird
# 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 )
Das Standard-Timeout beträgt 20 Sekunden. Weitere Informationen finden Sie in den globalen Einstellungen
Führen Sie einen Fling-Vorgang für das spezifische UI-Objekt durch (scrollbar).
Mögliche Eigenschaften:
horiz
oder vert
forward
oder backward
oder toBeginning
oder 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 ()
Führen Sie einen Bildlauf für das spezifische UI-Objekt durch (scrollbar).
Mögliche Eigenschaften:
horiz
oder vert
forward
oder backward
oder toBeginning
oder toEnd
oder 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" )
Der aktuelle watch_context wird per Threading gestartet und alle 2 Sekunden überprüft. Derzeit gibt es nur eine Triggeroperation: Klicken.
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 ()
# 其他脚本逻辑
Eine andere Art zu schreiben
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
Es wird eher empfohlen, WatchContext zu verwenden, um es prägnanter zu schreiben.
Sie können Beobachter registrieren, um einige Aktionen auszuführen, wenn ein Selektor keine Übereinstimmung findet.
Vor 2.0.0 wurde die in der uiautomator-jar-Bibliothek bereitgestellte Methode [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) verwendet, in der Praxis wurde dies jedoch einmal festgestellt Die uiautomator-Verbindung ist fehlgeschlagen. Nach dem Neustart gehen alle Watcher-Konfigurationen verloren, was definitiv inakzeptabel ist.
Daher besteht die aktuelle Methode darin, einen Thread im Hintergrund auszuführen (unter Berufung auf die Threading-Bibliothek), dann von Zeit zu Zeit die Hierarchie zu sichern und nach dem Abgleichen der Elemente die entsprechenden Vorgänge auszuführen.
Anwendungsbeispiele
Registrierungsüberwachung
# 常用写法,注册匿名监控
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 ()
Überwachen Sie den Betrieb
# 移除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 ()
Darüber hinaus gibt es noch viele Dokumente, die nicht geschrieben wurden. Es wird empfohlen, direkt zum Quellcode watcher.py zu gehen.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
Die meisten anderen Konfigurationen sind derzeit in d.settings
konzentriert, und die Konfiguration kann je nach späterem Bedarf erhöht oder verringert werden.
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返回的元素层级
Bei Versionsaktualisierungen wird beim Festlegen abgelaufener Konfigurationen die Meldung „Veraltet“ angezeigt, es wird jedoch keine Ausnahme ausgelöst.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
Einstellungen für den uiautomator-Wiederherstellungsmodus
Wenn Sie vorsichtig sind, stellen Sie möglicherweise fest, dass tatsächlich zwei APKs auf Ihrem Telefon installiert sind, von denen eines im Vordergrund sichtbar ist (das kleine gelbe Auto). Ein Paket namens com.github.uiautomator.test
ist im Hintergrund nicht sichtbar. Diese beiden APKs sind mit demselben Zertifikat signiert. Die unsichtbare Anwendung ist eigentlich ein Testpaket, das den gesamten Testcode enthält und über den auch der Kerntestdienst gestartet wird. Beim Laufen benötigt das System jedoch, dass das kleine gelbe Auto die ganze Zeit läuft (es kann auch im Hintergrund laufen). Sobald die kleine gelbe Autoanwendung beendet wird, wird auch der im Hintergrund laufende Testdienst schnell beendet. Auch wenn Sie nichts unternehmen, wird die Anwendung schnell vom System wiederverwendet, wenn sie im Hintergrund ausgeführt wird. (Ich hoffe, Experten können mir hier eine Anleitung geben, wie ich vermeiden kann, auf Fake-Apps zu setzen. Ich halte es theoretisch für möglich, weiß aber noch nicht, wie ich das machen soll.)
Es gibt zwei Möglichkeiten, das kleine gelbe Auto im Hintergrund laufen zu lassen. Eine besteht darin, die Anwendung zu starten und in den Hintergrund zu stellen (Standard). Darüber hinaus können Sie über am startservice
auch einen Hintergrunddienst starten.
Dieses Verhalten kann über d.settings["uiautomator_runtest_app_background"] = True
angepasst werden. True bedeutet, dass die Anwendung gestartet wird, False bedeutet, dass der Dienst gestartet wird.
Timeout-Einstellungen in UiAutomator (versteckte Methode)
>> 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 }
Um zu verhindern, dass das Client-Programm auf einen Timeout reagiert, wurden waitForIdleTimeout
und waitForSelectorTimeout
nun auf 0
geändert
Referenzen: Google uiautomator Configurator
Diese Methode wird normalerweise für Eingaben verwendet, bei denen die Steuerung unbekannt ist.
# 目前采用从剪贴板粘贴的方式输入
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
Weitere Referenz: IME_ACTION_CODE
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
In Version 3.2.0 behoben
Java uiautoamtor unterstützt XPath standardmäßig nicht, daher handelt es sich hierbei um eine erweiterte Funktion. Die Geschwindigkeit ist nicht so hoch.
Zum Beispiel: der Inhalt eines der Knoten
">< 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] " />
XPath-Positionierung und -Nutzung
Die Namen einiger Attribute wurden geändert und müssen beachtet werden.
description -> content-desc
resourceId -> resource-id
Allgemeiner Gebrauch
# 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 ())
Klicken Sie hier, um weitere häufige Einsatzmöglichkeiten von XPath anzuzeigen
Videoaufzeichnung (veraltet), stattdessen scrcpy verwenden
Der mit dem Mobiltelefon gelieferte Befehl „Screenrecord“ wird hier nicht verwendet. Es handelt sich um eine Methode zum Synthetisieren von Videos durch Abrufen von Mobiltelefonbildern. Daher müssen einige andere Abhängigkeiten installiert werden, z. B. imageio, imageio-ffmpeg, numpy usw. Weil einige Die Abhängigkeiten sind relativ groß. Es wird empfohlen, eine Spiegelinstallation zu verwenden. Führen Sie einfach den folgenden Befehl aus.
pip3 install -U " uiautomator2[image] " -i https://pypi.doubanio.com/simple
Wie zu verwenden
d.screenrecord('output.mp4')
time.sleep(10)
# or do something else
d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开
Sie können bei der Aufnahme auch fps (derzeit 20) angeben. Dieser Wert ist niedriger als die Geschwindigkeit von Minicap-Ausgabebildern. Ich empfehle Ihnen, ihn nicht zu ändern.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
Oder
logger = logging.getLogger("uiautomator2")
# setup logger
Wenn das Python-Programm beendet wird, wird UiAutomation beendet. Sie können den Dienst jedoch auch über die Schnittstellenmethode stoppen.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
Andere Mitwirkende
Das Ranking ist in Ordnung, gerne Ergänzung
MIT