Grupo de comunicação QQ: 815453846 Discord: https://discord.gg/PbJhnZJKDd
Não mantenho esse projeto há algum tempo (talvez dois anos), mas recentemente precisei pesquisar novamente a automação nativa do Android. Claro, também investiguei o Appium e descobri que a velocidade de execução do projeto uiautomator2. é muito bom. É rápido, desde a detecção dos elementos até o clique, tudo em milissegundos e o código é mais fácil de entender. Eu realmente não esperava ter escrito um projeto tão mágico antes. Como um projeto tão bom poderia deixá-lo acumular poeira? Ele precisa ser reorganizado e alguns códigos inúteis deveriam ser limpos. Portanto, a versão do projeto foi atualizada de 2.xx para 3.xx
Os usuários que ainda usam a versão 2.xx podem dar uma olhada no 2to3 antes de decidir se desejam atualizar para a 3.xx (eu pessoalmente recomendo a atualização)
Afinal, as versões 2 a 3 são uma atualização importante de versão e muitas funções foram excluídas. A primeira coisa a excluir é o atx-agent e, em segundo lugar, há várias funções relacionadas ao atx-agent. Funções obsoletas, como init.
Números de versão de várias bibliotecas dependentes
UiAutomator é uma biblioteca Java fornecida pelo Google para testes de automação Android, baseada no serviço Acessibilidade. É muito poderoso e pode testar aplicativos de terceiros, obter qualquer atributo de controle de qualquer APP na tela e realizar qualquer operação nele, mas tem duas desvantagens: 1. O script de teste só pode usar a linguagem Java 2. O teste script Ele deve ser empacotado em um pacote jar ou apk e carregado no dispositivo para ser executado.
Esperamos que a lógica de teste possa ser escrita em Python e possa controlar o telefone celular enquanto é executado no computador. Gostaria de agradecer muito a Xiaocong He (@xiaocong) por concretizar essa ideia (veja xiaocong/uiautomator). O princípio é executar um serviço http rpc no celular, abrir as funções no uiautomator e depois usar essas http. A interface é encapsulada em uma biblioteca Python. Porque a biblioteca xiaocong/uiautomator
não é atualizada há muito tempo. Portanto, bifurcamos diretamente uma versão para facilitar a distinção, adicionamos 2 openatx/uiautomator2 no final. Também bifurcamos uma cópia do código-fonte do pacote Android correspondente, openatx/android-uiautomator-server.
Além de corrigir bugs na biblioteca original, muitos novos recursos também foram adicionados. Inclui principalmente as seguintes partes:
Deixe-me explicar aqui primeiro, porque muitas pessoas costumam perguntar que openatx/uiautomator2 não suporta testes iOS. Se você precisar de testes automatizados iOS, você pode acessar esta biblioteca openatx/facebook-wda.
PS: Esta biblioteca
https://github.com/NeteaseGame/ATXNão está mais em manutenção, substitua-o o mais rápido possível.
Aqui está um GUIA DE REFERÊNCIA RÁPIDA adequado para quem já começou. Comentários são bem-vindos.
Primeiro, prepare um (não dois) telefone Android com开发者选项
ativadas, conecte-o ao computador e certifique-se de ver o dispositivo conectado executando adb devices
.
Execute pip3 install -U uiautomator2
para instalar o uiautomator2
Execute python
na linha de comando para abrir a janela interativa do python. Em seguida, digite o seguinte comando na janela.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
Ao ver um resultado semelhante ao abaixo, você pode começar oficialmente a usar nossa biblioteca. Como esta biblioteca tem muitas funções e muito conteúdo por trás dela, você precisa lê-la devagar....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
Além disso, para manter a estabilidade, é necessário habilitar a permissão da janela flutuante小黄车
. Artigo de referência py-uiautomator2 disponibiliza serviços por muito tempo por meio de janelas flutuantes
Geralmente dá certo, mas pode haver surpresas. Você pode ingressar no grupo QQ para relatar problemas (o número do grupo está no topo).
Obrigado a todos os nossos patrocinadores!
Vazio
Excelentes recomendações de artigos (bem-vindo ao grupo QQ para feedback)
成都-测试只会一点点
Instalação
Conecte-se a um dispositivo
Linha de comando
Configurações globais
Gerenciamento de aplicativos
Automação da IU
Colaboradores
LICENÇA
Instale o uiautomator2
pip install -U uiautomator2
Teste se a instalação foi bem-sucedida uiautomator2 --help
Inspetor de IU
pip install uiautodev
# 启动
uiauto.dev
Abra https://uiauto.dev no navegador para visualizar a estrutura da interface do dispositivo atual.
uiauto.dev
uiauto.dev é um projeto independente do uiautomator2, usado para visualizar a estrutura das camadas. É uma versão reconstruída do antigo editor de projetos e pode ser cobrada posteriormente (o preço definitivamente vale o dinheiro gasto) para apoiar a manutenção contínua do projeto atual. Se você estiver interessado, pode entrar no grupo para discussão (inclusive fazer solicitações) Grupo QQ 536481989
use serialno para conectar o dispositivo, por exemplo, 123456f
(visto em adb devices
)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
Serial pode ser passado através de env-var ANDROID_SERIAL
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
representa o endereço IP do dispositivo
Se você precisar especificar o dispositivo, precisará passar --serial
como python3 -m uiautomator2 --serial bff1234
, SubCommand é um subcomando (captura de tela, atual, etc.)
1.0.3 Adicionado:
python3 -m uiautomator2
é igual auiautomator2
captura de tela: captura de tela
$ uiautomator2 screenshot screenshot.jpg
atual: obtém o nome e a atividade do pacote atual
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
desinstalar: desinstalar aplicativo
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
parar: parar o aplicativo
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
doutor:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Quando o python é encerrado, o serviço UiAutomation também é encerrado.
Imprima as informações da solicitação HTTP por trás do 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
Definir o tempo de espera da pesquisa do elemento (padrão 20s)
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 função terá influência em click
, long_click
, drag_to
, get_text
, set_text
, clear_text
, etc.
Esta parte mostra como realizar o gerenciamento de aplicativos
Oferecemos suporte apenas à instalação de um APK a partir de um 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(默认)
Adicionado na versão 1.2.0
enviar um arquivo para o 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 )
extrair um arquivo do 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 mostra como realizar operações comuns do dispositivo:
Execute um comando shell de curta duração com proteção de tempo limite (tempo limite padrão 60s).
Nota: o suporte de tempo limite requer atx-agent >=0.3.3
A função adb_shell
shell
obsoleta.
Uso simples
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
O primeiro argumento pode ser uma lista, por exemplo.
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
Isso retorna uma string para stdout mesclada com stderr. Se o comando for um comando de bloqueio, shell
também bloqueará até que o comando seja concluído ou o tempo limite seja atingido. Nenhuma saída parcial será recebida durante a execução do comando. adequado para comandos de longa execução O comando shell fornecido é executado em um ambiente semelhante ao adb shell
, que possui um nível de permissão do Linux adb
ou shell
(maior que uma permissão de aplicativo).
Execute um comando shell de longa duração (removido)
A sessão representa o ciclo de vida do aplicativo. Pode ser usada para iniciar o aplicativo e detectar falhas no aplicativo.
Inicie e feche o aplicativo
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Use python with
iniciar e fechar o aplicativo
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
Anexe ao aplicativo em execução
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
Detectar falha no aplicativo
# 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
Obtenha informações básicas
d . info
Abaixo está uma saída possível:
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Obtenha o tamanho da janela
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Obtenha informações atuais do aplicativo Para alguns dispositivos Android, a saída pode estar vazia (consulte Exemplo de saída 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}
Atividade de espera
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Obtenha o número de série do dispositivo
print ( d . serial )
# output example: 74aAEDR428Z9
Obtenha o IP da WLAN
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
Obtenha informações detalhadas do dispositivo d.device_info
informações_do_dispositivo
print ( d . device_info )
Abaixo está uma saída possível:
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Obter o conteúdo definido da área de transferência
Defina o conteúdo do Pasteboard ou obtenha conteúdo
área de transferência/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
Obtenha o conteúdo da área de transferência
obter área de transferência requer chamada IME (com.github.uiautomator/.AdbKeyboard)
d.set_input_ime()
antes de usá-lo.
```python
# get clipboard content
print(d.clipboard)
```
Ligar/desligar tela
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Obtenha o status atual da tela
d . info . get ( 'screenOn' ) # require Android >= 4.4
Pressione a tecla física/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)
Atualmente, estes nomes de chave são suportados:
Você pode encontrar todas as definições de código-chave em Android KeyEvnet
Tela de desbloqueio
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Clique na tela
d . click ( x , y )
Clique duas vezes
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Clique longo na tela
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Deslizar
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Função de extensão 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 ) # 页面水平左翻
Arrastar
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Deslizar pontos
# 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 ))
É usado principalmente para desbloquear o padrão Jiugong. As coordenadas relativas de cada ponto podem ser obtidas antecipadamente (a porcentagem é suportada aqui).
Tocar e arrastar (Beta)
Esta interface é uma interface primitiva de nível relativamente baixo. Não parece perfeita, mas é utilizável. Nota: Este lugar não suporta porcentagens
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
Nota: as operações de clicar, deslizar e arrastar suportam valores de posição percentual.
d.long_click(0.5, 0.5)
significa clique longo no centro da tela
Recuperar/definir orientação do dispositivo
As orientações possíveis:
natural
ou n
left
ou l
right
ou r
upsidedown
ou u
(não pode ser definido) # 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"
Rotação congelar/descongelar
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Faça uma captura de tela
# 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 )
Hierarquia da IU de despejo
# 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 )
Abra notificação ou configurações rápidas
d . open_notification ()
d . open_quick_settings ()
O seletor é um mecanismo útil para identificar um objeto de UI específico na janela atual.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
O seletor suporta os parâmetros abaixo. Consulte o documento UiSelector Java para obter informações detalhadas.
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
crianças
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
irmãos
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
crianças por texto ou descrição ou instância
# 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
serve para encontrar filhos cujos netos tenham a descrição especificada, sendo outros parâmetros semelhantes a child_by_text
.
child_by_instance
é encontrar filhos com um elemento de UI filho em qualquer lugar dentro de sua sub-hierarquia que esteja na instância especificada. Ele é executado em visualizações visíveis sem rolagem .
Veja os links abaixo para obter informações detalhadas:
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
Os métodos acima suportam invocação encadeada, por exemplo, para hierarquia abaixo
< 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 clicar no widget de troca diretamente no TextView 'Wi-Fi', precisamos primeiro selecionar os widgets de troca. No entanto, de acordo com a hierarquia da UI, existem mais de um widget de troca e terão quase as mesmas propriedades. A seleção por className não terá. Alternativamente, a estratégia de seleção abaixo ajudaria:
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
posicionamento relativo
Também podemos usar os métodos de posicionamento relativo para obter a visualização: left
, right
, top
, bottom
.
d(A).left(B)
, seleciona B no lado esquerdo de A.d(A).right(B)
, seleciona B no lado direito de A.d(A).up(B)
, seleciona B acima de A.d(A).down(B)
, seleciona B em A.Portanto, para os casos acima, podemos alternativamente selecioná-lo com:
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
Várias instâncias
Às vezes a tela pode conter múltiplas visualizações com as mesmas propriedades, por exemplo, texto, então você terá que usar a propriedade "instance" no seletor para escolher uma das instâncias qualificadas, como abaixo:
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
Além disso, uiautomator2 fornece uma API semelhante a uma lista (semelhante ao 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 : ao usar seletores em um bloco de código que percorre a lista de resultados, você deve garantir que os elementos da UI na tela permaneçam inalterados, caso contrário, poderá ocorrer um erro de elemento não encontrado ao iterar pela lista.
Verifique se o objeto de UI 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 as informações do objeto de UI específico
d ( text = "Settings" ). info
Abaixo está uma saída possível:
{ 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
}
Obter/Definir/Limpar texto de um campo editável (por exemplo, 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
Obtenha o ponto central do widget
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
Faça uma captura de tela do widget
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
Clique no 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
Execute um clique longo no objeto de UI específico
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
Arraste o objeto de UI para outro ponto ou outro objeto de 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 )
Deslize do centro do objeto da UI até a borda
Deslizar suporta 4 direções:
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 dois pontos de um ponto a outro
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
Gesto de dois pontos no objeto de IU específico
Suporta dois gestos:
In
, de ponta a pontaOut
, do centro para a borda # 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 até que a IU específica apareça ou desapareça
# 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 )
O tempo limite padrão é 20s, consulte as configurações globais para obter mais detalhes.
Execute o arremesso no objeto de interface do usuário específico (rolável)
Propriedades possíveis:
horiz
ou vert
forward
ou backward
ou toBeginning
ou toEnd
# fling forward(default) vertically(default)
d ( scrollable = True ). fling ()
# fling forward horizontally
d ( scrollable = True ). fling . horiz . forward ()
# fling backward vertically
d ( scrollable = True ). fling . vert . backward ()
# fling to beginning horizontally
d ( scrollable = True ). fling . horiz . toBeginning ( max_swipes = 1000 )
# fling to end vertically
d ( scrollable = True ). fling . toEnd ()
Execute a rolagem no objeto de interface do usuário específico (rolável)
Propriedades possíveis:
horiz
ou vert
forward
ou backward
ou toBeginning
ou toEnd
, ou to
# scroll forward(default) vertically(default)
d ( scrollable = True ). scroll ( steps = 10 )
# scroll forward horizontally
d ( scrollable = True ). scroll . horiz . forward ( steps = 100 )
# scroll backward vertically
d ( scrollable = True ). scroll . vert . backward ()
# scroll to beginning horizontally
d ( scrollable = True ). scroll . horiz . toBeginning ( steps = 100 , max_swipes = 1000 )
# scroll to end vertically
d ( scrollable = True ). scroll . toEnd ()
# scroll forward vertically until specific ui object appears
d ( scrollable = True ). scroll . to ( text = "Security" )
O watch_context atual é iniciado usando threading e verificado a cada 2 segundos. Atualmente, há apenas uma operação de gatilho: clique.
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 ()
# 其他脚本逻辑
Outra maneira de escrever
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
É mais recomendado usar WatchContext para escrevê-lo de forma mais concisa.
Você pode registrar observadores para realizar algumas ações quando um seletor não encontrar uma correspondência.
Antes da versão 2.0.0, o método [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) fornecido na biblioteca uiautomator-jar era usado, mas na prática descobriu-se que uma vez a conexão do uiautomator falhou. Após a reinicialização, todas as configurações do inspetor são perdidas, o que é definitivamente inaceitável.
Portanto, o método atual é executar um thread em segundo plano (dependendo da biblioteca de threading) e, em seguida, despejar a hierarquia de vez em quando e executar as operações correspondentes após combinar os elementos.
Exemplos de uso
Monitoramento 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 ()
Monitorar operações
# 移除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 ()
Além disso, ainda existem muitos documentos que não foram escritos. Recomenda-se ir diretamente ao código-fonte watcher.py.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
A maioria das outras configurações estão atualmente concentradas em d.settings
, e a configuração pode ser aumentada ou diminuída de acordo com necessidades 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 atualizações de versão, ao definir configurações expiradas, será solicitado Deprecated, mas nenhuma exceção será lançada.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
configurações do modo de recuperação do uiautomator
Se você tomar cuidado, poderá descobrir que existem dois APKs instalados no seu telefone, um dos quais está visível em primeiro plano (o pequeno carro amarelo). Um pacote chamado com.github.uiautomator.test
não está visível em segundo plano. Esses dois apks são assinados com o mesmo certificado. O aplicativo invisível é na verdade um pacote de teste que contém todo o código de teste, e o serviço de teste principal também é iniciado por meio dele. Mas durante a execução, o sistema precisa que o carrinho amarelo esteja funcionando o tempo todo (também pode funcionar em segundo plano). Depois que o pequeno aplicativo de carro amarelo for encerrado, o serviço de teste em execução em segundo plano também será encerrado rapidamente. Mesmo que você não faça nada, o aplicativo será rapidamente reciclado pelo sistema se estiver em segundo plano. (Espero que os especialistas possam me dar alguma orientação aqui sobre como evitar depender de aplicativos falsos. Acho que é teoricamente possível, mas ainda não sei como fazer).
Existem duas maneiras de deixar o carrinho amarelo rodar em segundo plano. Uma é iniciar o aplicativo e colocá-lo em segundo plano (padrão). Além disso, você também pode iniciar um serviço em segundo plano por meio de am startservice
.
Este comportamento pode ser ajustado via d.settings["uiautomator_runtest_app_background"] = True
. True significa iniciar o aplicativo, False significa iniciar o serviço.
Configurações de tempo limite no 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 o programa cliente responda a um tempo limite, waitForIdleTimeout
e waitForSelectorTimeout
foram alterados para 0
Refs: Configurador Google uiautomator
Este método é normalmente usado para entradas onde o controle é desconhecido.
# 目前采用从剪贴板粘贴的方式输入
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
Mais referência: IME_ACTION_CODE
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
Corrigido na versão 3.2.0
Java uiautoamtor não suporta xpath por padrão, portanto esta é uma função estendida. A velocidade não é tão rápida.
Por exemplo: o conteúdo de um dos nós
">< 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] " />
posicionamento e uso do XPath
Os nomes de alguns atributos foram modificados e precisam ser anotados.
description -> content-desc
resourceId -> resource-id
Uso comum
# 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 ())
Clique para ver outros usos comuns do XPath
Gravação de vídeo (obsoleto), use scrcpy
O comando screenrecord que vem com o celular não é usado aqui. É um método de sintetizar vídeo obtendo imagens do celular, então é necessário instalar algumas outras dependências, como imageio, imageio-ffmpeg, numpy, etc. as dependências são relativamente grandes, é recomendado usar a instalação espelhada. Basta executar o seguinte 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文件才能打开
Você também pode especificar fps (atualmente 20) ao gravar. Este valor é inferior à velocidade das imagens de saída do minicap.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
Ou
logger = logging.getLogger("uiautomator2")
# setup logger
Quando o programa Python é encerrado, o UiAutomation é encerrado. No entanto, você também pode interromper o serviço por meio do método de interface.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
Outros contribuidores
A classificação está em ordem, bem-vindo para adicionar
MIT