Grupo de comunicación QQ: 815453846 Discord: https://discord.gg/PbJhnZJKDd
No he mantenido este proyecto por un tiempo (tal vez dos años), pero recientemente necesito volver a investigar la automatización nativa de Android. Por supuesto, también investigué Appium y descubrí que la velocidad de ejecución del proyecto uiautomator2. es realmente bueno. Es rápido, desde detectar elementos hasta hacer clic, todo es en milisegundos y el código es más fácil de entender. Realmente no esperaba haber escrito un proyecto tan mágico antes. ¿Cómo es posible que un proyecto tan bueno acumule polvo? Es necesario reorganizarlo y limpiar parte del código basura. Entonces la versión del proyecto se actualizó de 2.xx a 3.xx.
Los usuarios que todavía usan la versión 2.xx pueden echar un vistazo a 2to3 antes de decidir si actualizar a 3.xx (personalmente recomiendo encarecidamente actualizar)
Después de todo, las versiones 2 a 3 son una actualización importante y se han eliminado muchas funciones. Lo primero que debe eliminar es atx-agent y, en segundo lugar, hay un montón de funciones relacionadas con atx-agent. Funciones obsoletas como init.
Números de versión de varias bibliotecas dependientes.
UiAutomator es una biblioteca Java proporcionada por Google para pruebas de automatización de Android, basada en el servicio de Accesibilidad. Es muy potente y puede probar aplicaciones de terceros, obtener cualquier atributo de control de cualquier APP en la pantalla y realizar cualquier operación sobre ella, pero tiene dos desventajas: 1. El script de prueba solo puede usar el lenguaje Java 2. La prueba script Debe empaquetarse en un paquete jar o apk y cargarse en el dispositivo para ejecutarse.
Esperamos que la lógica de prueba se pueda escribir en Python y pueda controlar el teléfono móvil mientras se ejecuta en la computadora. Me gustaría agradecer mucho a Xiaocong He (@xiaocong) por implementar esta idea (ver xiaocong/uiautomator). El principio es ejecutar un servicio http rpc en el teléfono móvil, abrir las funciones en uiautomator y luego usar estos http. La interfaz está encapsulada en una biblioteca de Python. Porque la biblioteca xiaocong/uiautomator
no se ha actualizado durante mucho tiempo. Así que bifurcamos directamente una versión para que sea más fácil de distinguir, agregamos 2 openatx/uiautomator2 al final. También bifurqué una copia del código fuente del paquete de Android correspondiente, openatx/android-uiautomator-server.
Además de corregir errores en la biblioteca original, también se han agregado muchas características nuevas. Incluye principalmente las siguientes partes:
Permítanme explicarlo aquí primero, porque muchas personas a menudo preguntan que openatx/uiautomator2 no admite pruebas de iOS. Si necesita pruebas automatizadas de iOS, puede ir a esta biblioteca openatx/facebook-wda.
PD: esta biblioteca
https://github.com/NeteaseGame/ATXYa no está en mantenimiento, reemplácelo lo antes posible.
Aquí hay una GUÍA DE REFERENCIA RÁPIDA adecuada para aquellos que ya han comenzado. Los comentarios son bienvenidos.
Primero prepare un teléfono Android (no dos) con开发者选项
activadas, conéctelo a la computadora y asegúrese de poder ver el dispositivo conectado ejecutando adb devices
.
Ejecute pip3 install -U uiautomator2
para instalar uiautomator2
Ejecute python
desde la línea de comando para abrir la ventana interactiva de Python. Luego ingrese el siguiente comando en la ventana.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
Cuando vea un resultado similar al siguiente, podrá comenzar a usar oficialmente nuestra biblioteca. Debido a que esta biblioteca tiene demasiadas funciones y hay mucho contenido detrás, debes leerla lentamente....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
Además, para mantener la estabilidad, debes habilitar el permiso de ventana flotante小黄车
. Artículo de referencia py-uiautomator2 hace que los servicios estén disponibles durante mucho tiempo a través de ventanas flotantes
Normalmente lo consigue, pero puede haber sorpresas. Puede unirse al grupo QQ para informar problemas (el número del grupo está en la parte superior). Hay muchos tipos importantes en el grupo que pueden ayudarlo a resolver problemas.
¡Gracias a todos nuestros patrocinadores!
Vacío
Excelentes recomendaciones de artículos (bienvenidos al grupo QQ para recibir comentarios)
成都-测试只会一点点
Instalación
Conectarse a un dispositivo
línea de comando
Configuraciones globales
Gestión de aplicaciones
Automatización de la interfaz de usuario
Colaboradores
LICENCIA
Instalar uiautomator2
pip install -U uiautomator2
Pruebe si la instalación se realizó correctamente uiautomator2 --help
inspector de interfaz de usuario
pip install uiautodev
# 启动
uiauto.dev
Abra https://uiauto.dev en el navegador para ver la estructura de la interfaz del dispositivo actual.
uiauto.dev
uiauto.dev es un proyecto independiente de uiautomator2, que se utiliza para ver la estructura de capas. Es una versión reconstruida del antiguo editor de proyectos y se puede cobrar más adelante (el precio definitivamente vale la pena) para respaldar el mantenimiento continuo del proyecto actual. Si estás interesado puedes unirte al grupo de discusión (incluyendo hacer solicitudes) grupo QQ 536481989
use serialno para conectar el dispositivo, por ejemplo, 123456f
(visto desde adb devices
)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
El serial se puede pasar a través de env-var ANDROID_SERIAL
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
representa la dirección IP del dispositivo
Si necesita especificar el dispositivo, debe pasar --serial
como python3 -m uiautomator2 --serial bff1234
, SubCommand es un subcomando (captura de pantalla, actual, etc.)
1.0.3 Agregado:
python3 -m uiautomator2
es igual auiautomator2
captura de pantalla: captura de pantalla
$ uiautomator2 screenshot screenshot.jpg
actual: obtiene el nombre y la actividad del paquete actual
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
desinstalar: desinstalar la aplicación
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
detener: detener la aplicación
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
doctor:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Cuando Python se cierra, el servicio UiAutomation también se cierra.
Imprima la información de la solicitud HTTP detrás del código
> >> 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
Establecer el tiempo de espera de búsqueda de elementos (predeterminado 20 segundos)
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
Esta función tendrá influencia en click
, long_click
, drag_to
, get_text
, set_text
, clear_text
, etc.
Esta parte muestra cómo realizar la gestión de aplicaciones.
Solo admitimos la instalación de un APK desde una 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(默认)
Agregado en la versión 1.2.0
enviar un archivo al dispositivo
# 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 )
extraer un archivo del dispositivo
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"
Esta parte muestra cómo realizar operaciones comunes de dispositivos:
Ejecute un comando de shell de corta duración con protección de tiempo de espera (tiempo de espera predeterminado de 60 segundos).
Nota: el soporte de tiempo de espera requiere atx-agent >=0.3.3
La función adb_shell
está en desuso. Utilice shell
en su lugar.
Uso sencillo
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
El primer argumento puede ser una lista, por ejemplo.
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
Esto devuelve una cadena para stdout fusionada con stderr. Si el comando es un comando de bloqueo, shell
también se bloqueará hasta que se complete el comando o se active el tiempo de espera. No se recibirá ninguna salida parcial durante la ejecución del comando. adecuado para comandos de ejecución prolongada. El comando de shell proporcionado se ejecuta en un entorno similar a adb shell
, que tiene un nivel de permiso de Linux de adb
o shell
(superior al permiso de una aplicación).
Ejecutar un comando de shell de larga duración (eliminado)
La sesión representa el ciclo de vida de una aplicación. Se puede utilizar para iniciar la aplicación y detectar fallas.
Iniciar y cerrar la aplicación
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Utilice Python with
iniciar y cerrar la aplicación.
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
Adjuntar a la aplicación en ejecución
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
Detectar fallas en la aplicación
# 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
Obtener información básica
d . info
A continuación se muestra un posible resultado:
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Obtener tamaño de ventana
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Obtenga información actual de la aplicación Para algunos dispositivos Android, la salida podría estar vacía (consulte el ejemplo de salida 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}
Esperar actividad
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Obtener el número de serie del dispositivo
print ( d . serial )
# output example: 74aAEDR428Z9
Obtener IP WLAN
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
Obtenga información detallada del dispositivo d.device_info
información_dispositivo
print ( d . device_info )
A continuación se muestra un posible resultado:
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Obtener el contenido del portapapeles establecido
Establecer contenido del portapapeles u obtener contenido
portapapeles/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
Obtener contenido del portapapeles
obtener el portapapeles requiere que IME (com.github.uiautomator/.AdbKeyboard) llame
d.set_input_ime()
antes de usarlo.
```python
# get clipboard content
print(d.clipboard)
```
Activar/desactivar pantalla
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Obtener el estado actual de la pantalla
d . info . get ( 'screenOn' ) # require Android >= 4.4
Presione la tecla dura/soft
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)
Estos nombres clave son actualmente compatibles:
Puede encontrar todas las definiciones de códigos clave en Android KeyEvnet
Desbloquear pantalla
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Haga clic en la pantalla
d . click ( x , y )
Doble clic
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Haga clic largo en la pantalla
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Golpe fuerte
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Función de extensión 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 ) # 页面水平左翻
Arrastrar
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Puntos de deslizamiento
# 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 ))
Se usa principalmente para desbloquear el patrón Jiugong. Las coordenadas relativas de cada punto se pueden obtener con anticipación (aquí se admite el porcentaje para un uso más detallado, consulte esta publicación. Use u2 para desbloquear el patrón Jiugong).
Tocar y arrastrar (Beta)
Esta interfaz es una interfaz primitiva de nivel relativamente bajo. No parece perfecta, pero es utilizable. Nota: Este lugar no admite porcentajes.
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
Nota: las operaciones de hacer clic, deslizar y arrastrar admiten valores de posición porcentuales.
d.long_click(0.5, 0.5)
significa clic largo en el centro de la pantalla
Recuperar/Establecer la orientación del dispositivo
Las posibles orientaciones:
natural
o n
left
o l
right
o r
upsidedown
o u
(no se puede configurar) # 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"
Congelar/descongelar rotación
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Tomar captura de pantalla
# 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 )
Volcar jerarquía de UI
# 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 )
Abrir notificación o configuración rápida
d . open_notification ()
d . open_quick_settings ()
El selector es un mecanismo útil para identificar un objeto de interfaz de usuario específico en la ventana actual.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
El selector admite los siguientes parámetros. Consulte el documento Java de UiSelector para obtener información detallada.
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
niños
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
hermanos
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
niños por texto o descripción o instancia
# 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
es encontrar hijos cuyos nietos tengan la descripción especificada, siendo otros parámetros similares a child_by_text
.
child_by_instance
es buscar elementos secundarios que tengan un elemento de interfaz de usuario secundario en cualquier lugar dentro de su subjerarquía que esté en la instancia especificada. Se realiza en vistas visibles sin desplazamiento .
Consulte los enlaces a continuación para obtener información detallada:
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
Los métodos anteriores admiten la invocación encadenada, por ejemplo, para la jerarquía inferior
< 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 >
Para hacer clic en el widget de cambio directamente a TextView 'Wi-Fi', primero debemos seleccionar los widgets de cambio. Sin embargo, según la jerarquía de la interfaz de usuario, existen más de un widget de cambio y tienen casi las mismas propiedades. Alternativamente, la siguiente estrategia de selección ayudaría:
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
posicionamiento relativo
También podemos usar los métodos de posicionamiento relativo para obtener la vista: left
, right
, top
, bottom
.
d(A).left(B)
, selecciona B en el lado izquierdo de A.d(A).right(B)
, selecciona B en el lado derecho de A.d(A).up(B)
, selecciona B encima de A.d(A).down(B)
, selecciona B debajo de A.Entonces, para los casos anteriores, podemos seleccionarlo alternativamente con:
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
Varias instancias
A veces, la pantalla puede contener varias vistas con las mismas propiedades, por ejemplo, texto, entonces tendrá que usar la propiedad "instancia" en el selector para elegir una de las instancias calificadas, como se muestra a continuación:
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
Además, uiautomator2 proporciona una API similar a una lista (similar a 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 # ...
Notas : cuando utilice selectores en un bloque de código que recorra la lista de resultados, debe asegurarse de que los elementos de la interfaz de usuario en la pantalla se mantengan sin cambios. De lo contrario, podría ocurrir un error de Elemento no encontrado al recorrer la lista.
Compruebe si el objeto de interfaz de usuario específico 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)
Recuperar la información del objeto de UI específico
d ( text = "Settings" ). info
A continuación se muestra un posible resultado:
{ 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
}
Obtener/Establecer/Borrar texto de un campo editable (por ejemplo, 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
Obtener el punto central del widget
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
Tomar captura de pantalla del widget
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
Realizar clic en el objeto específico.
# 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
Realice un clic prolongado en el objeto de la interfaz de usuario específico
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
Arrastra el objeto UI hacia otro punto u otro objeto 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 )
Desliza el dedo desde el centro del objeto de la interfaz de usuario hasta su borde
Deslizar admite 4 direcciones:
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 )
Gesto de dos puntos de un punto a otro.
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
Gesto de dos puntos en el objeto de la interfaz de usuario específico
Admite dos gestos:
In
, desde el borde al centroOut
, del centro al borde # 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 ()
Espere hasta que la interfaz de usuario específica aparezca o desaparezca
# 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 )
El tiempo de espera predeterminado es 20 segundos. Consulte la configuración global para obtener más detalles.
Realizar lanzamiento en el objeto de interfaz de usuario específico (desplazable)
Posibles propiedades:
horiz
o vert
forward
o backward
o toBeginning
o 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 ()
Realizar desplazamiento en el objeto de interfaz de usuario específico (desplazable)
Posibles propiedades:
horiz
o vert
forward
o backward
o toBeginning
o toEnd
, o 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" )
El watch_context actual se inicia mediante subprocesos y se verifica cada 2 segundos. Actualmente, solo hay una operación de activación: hacer clic.
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 ()
# 其他脚本逻辑
Otra forma de escribir
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
Se recomienda más utilizar WatchContext para escribirlo de forma más concisa.
Puede registrar observadores para realizar algunas acciones cuando un selector no encuentra una coincidencia.
Antes de 2.0.0, se usaba el método [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) proporcionado en la biblioteca uiautomator-jar, pero en la práctica se descubrió que una vez la conexión de uiautomator falló. Después de reiniciar, todas las configuraciones del observador se pierden, lo cual es definitivamente inaceptable.
Por lo tanto, el método actual es ejecutar un subproceso en segundo plano (confiando en la biblioteca de subprocesos) y luego volcar la jerarquía de vez en cuando y realizar las operaciones correspondientes después de hacer coincidir los elementos.
Ejemplos de uso
Seguimiento de registro
# 常用写法,注册匿名监控
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 ()
Monitorear operaciones
# 移除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 ()
Además, todavía hay muchos documentos que no se han escrito. Se recomienda ir directamente al código fuente watcher.py.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
La mayoría de las otras configuraciones se concentran actualmente en d.settings
y la configuración se puede aumentar o disminuir según las necesidades posteriores.
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返回的元素层级
Para las actualizaciones de versión, al establecer configuraciones caducadas, se le solicitará Obsoleto, pero no se generará ninguna excepción.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
configuración del modo de recuperación de uiautomator
Si tiene cuidado, puede encontrar que en realidad hay dos APK instalados en su teléfono, uno de los cuales está visible en primer plano (el pequeño auto amarillo). Un paquete llamado com.github.uiautomator.test
no es visible en segundo plano. Estas dos aplicaciones están firmadas con el mismo certificado. La aplicación invisible es en realidad un paquete de prueba que contiene todo el código de prueba y a través de ella también se inicia el servicio de prueba principal. Pero cuando está en marcha, el sistema necesita que el pequeño coche amarillo esté funcionando todo el tiempo (también se puede ejecutar en segundo plano). Una vez que se cancela la aplicación del pequeño auto amarillo, el servicio de prueba que se ejecuta en segundo plano también se eliminará rápidamente. Incluso si no hace nada, el sistema reciclará rápidamente la aplicación si está en segundo plano. (Espero que los expertos puedan darme alguna orientación sobre cómo evitar depender de aplicaciones falsas. Creo que es teóricamente posible, pero no sé cómo hacerlo todavía).
Hay dos formas de dejar que el pequeño carro amarillo se ejecute en segundo plano. Una es iniciar la aplicación y ponerla en segundo plano (predeterminado). Además, también puede iniciar un servicio en segundo plano a través de am startservice
.
Este comportamiento se puede ajustar mediante d.settings["uiautomator_runtest_app_background"] = True
. Verdadero significa iniciar la aplicación, Falso significa iniciar el servicio.
Configuración de tiempo de espera en UiAutomator (método oculto)
>> 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 }
Para evitar que el programa cliente responda a un tiempo de espera, waitForIdleTimeout
y waitForSelectorTimeout
ahora se han cambiado a 0
Refs: Configurador de Google uiautomator
Este método se utiliza normalmente para entradas en las que se desconoce el control.
# 目前采用从剪贴板粘贴的方式输入
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
Más referencia: IME_ACTION_CODE
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
Corregido en la versión 3.2.0
Java uiautoamtor no admite xpath de forma predeterminada, por lo que se trata de una función extendida. La velocidad no es tan rápida.
Por ejemplo: el contenido de uno de los nodos.
">< 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] " />
posicionamiento y uso de xpath
Los nombres de algunos atributos se han modificado y es necesario anotarlos.
description -> content-desc
resourceId -> resource-id
Uso común
# 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 ())
Haga clic para ver otros usos comunes de XPath
Grabación de video (obsoleta), use scrcpy en su lugar
El comando screenrecord que viene con el teléfono móvil no se utiliza aquí. Es un método para sintetizar videos obteniendo imágenes del teléfono móvil, por lo que es necesario instalar algunas otras dependencias, como imageio, imageio-ffmpeg, numpy, etc. Las dependencias son relativamente grandes, se recomienda utilizar la instalación espejo. Simplemente ejecute el siguiente comando.
pip3 install -U " uiautomator2[image] " -i https://pypi.doubanio.com/simple
como usar
d.screenrecord('output.mp4')
time.sleep(10)
# or do something else
d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开
También puede especificar fps (actualmente 20) al grabar. Este valor es menor que la velocidad de las imágenes de salida minicap. No le recomiendo que lo modifique.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
O
logger = logging.getLogger("uiautomator2")
# setup logger
Cuando se cierra el programa Python, se cierra UiAutomation. Sin embargo, también puede detener el servicio mediante el método de interfaz.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
Otros contribuyentes
La clasificación está en orden, bienvenido a agregar
MIT