Grupo de comunicação QQ: 815453846 Discord: https://discord.gg/PbJhnZJKDd
Faz um tempo que não mantenho esse projeto (talvez dois anos), mas recentemente precisei reestudar 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 grande atualização 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
Recomendação de artigos excelentes (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. Pode haver uma taxa no futuro (o preço definitivamente vale o dinheiro) 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>
, 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