Группа связи QQ: 815453846 Дискорд: https://discord.gg/PbJhnZJKDd
Я не поддерживал этот проект какое-то время (возможно, два года), но недавно мне нужно было заново изучить нативную автоматизацию Android. Конечно, я также исследовал Appium. После сравнения я обнаружил, что скорость работы проекта uiautomator2. действительно хорош. Он быстрый, от обнаружения элементов до нажатия, все занимает миллисекунды, и код легче понять. Я действительно не ожидал, что раньше написал такой волшебный проект. Как мог такой хороший проект позволить ему пылиться? Его нужно реорганизовать и почистить от ненужного кода. Итак, версия проекта была обновлена с 2.xx до 3.xx.
Пользователи, которые все еще используют версию 2.xx, могут взглянуть на 2to3, прежде чем решить, стоит ли обновляться до 3.xx (лично я настоятельно рекомендую обновиться)
В конце концов, версия со 2 по 3 является основным обновлением версии, и многие функции были удалены. Первое, что нужно удалить, это atx-agent, а во-вторых, есть куча функций, связанных с atx-агентом. Устаревшие функции, такие как init.
Номера версий различных зависимых библиотек
UiAutomator — это библиотека Java, предоставляемая Google для тестирования автоматизации Android на основе службы специальных возможностей. Он очень мощный и может тестировать сторонние приложения, получать любые атрибуты управления любого приложения на экране и выполнять с ним любые операции, но у него есть два недостатка: 1. Тестовый сценарий может использовать только язык Java. 2. Тест. скрипт. Его необходимо упаковать в пакет jar или apk и загрузить на устройство для запуска.
Мы надеемся, что логика теста может быть написана на Python и сможет управлять мобильным телефоном во время работы на компьютере. Я хотел бы поблагодарить Xiaocong He (@xiaocong) за реализацию этой идеи (см. xiaocong/uiautomator). Принцип заключается в том, чтобы запустить службу http rpc на мобильном телефоне, открыть функции в uiautomator и затем использовать эти http. Интерфейс инкапсулирован в библиотеку Python. Потому что библиотека xiaocong/uiautomator
давно не обновлялась. Поэтому мы напрямую создали версию. Чтобы было легче отличить, мы добавили 2 openatx/uiautomator2 в конце. Я также создал копию соответствующего исходного кода пакета Android, openatx/android-uiautomator-server.
Помимо исправления ошибок в исходной библиотеке, также было добавлено множество новых функций. В основном он включает в себя следующие части:
Позвольте мне сначала объяснить, потому что многие люди часто спрашивают, что openatx/uiautomator2 не поддерживает тестирование iOS. Если вам нужно автоматическое тестирование iOS, вы можете перейти к этой библиотеке openatx/facebook-wda.
PS: Эта библиотека
https://github.com/NeteaseGame/ATXОн больше не находится на обслуживании, пожалуйста, замените его как можно скорее.
Вот краткое справочное руководство, подходящее для тех, кто уже начал. Комментарии приветствуются.
Сначала подготовьте один (а не два) телефон Android с включенными开发者选项
, подключите его к компьютеру и убедитесь, что вы видите подключенное устройство, запустив adb devices
.
Запустите pip3 install -U uiautomator2
чтобы установить uiautomator2.
Запустите python
из командной строки, чтобы открыть интерактивное окно Python. Затем введите следующую команду в окно.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
Когда вы увидите результат, аналогичный приведенному ниже, вы можете официально начать использовать нашу библиотеку. Поскольку в этой библиотеке слишком много функций и за ней стоит много контента, читать ее нужно медленно....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
Кроме того, чтобы сохранить стабильность, вам необходимо включить разрешение плавающего окна小黄车
. Справочная статья py-uiautomator2 делает сервисы доступными в течение длительного времени через плавающие окна.
Обычно это удается, но могут быть и сюрпризы. Вы можете присоединиться к группе QQ, чтобы сообщать о проблемах (номер группы вверху). В группе много крутых ребят, которые могут помочь вам решить проблемы.
Спасибо всем нашим спонсорам!
Пустой
Отличные рекомендации по статьям (добро пожаловать ко мне в группу QQ для обратной связи)
成都-测试只会一点点
Установка
Подключиться к устройству
Командная строка
Глобальные настройки
Управление приложениями
автоматизация пользовательского интерфейса
Авторы
ЛИЦЕНЗИЯ
Установите uiautomator2
pip install -U uiautomator2
Проверьте, прошла ли установка успешно uiautomator2 --help
Инспектор пользовательского интерфейса
pip install uiautodev
# 启动
uiauto.dev
Откройте https://uiauto.dev в браузере, чтобы просмотреть структуру интерфейса текущего устройства.
uiauto.dev
uiauto.dev — это проект, независимый от uiautomator2, используемый для просмотра структуры слоев. Это реконструированная версия старого редактора проекта, за которую позже может взиматься плата (цена определенно стоит своих денег) для поддержки дальнейшего обслуживания текущего проекта. Если вам интересно, вы можете присоединиться к группе для обсуждения (включая запросы) Группа QQ 536481989
используйте серийный номер для подключения устройства, например 123456f
(видно на adb devices
)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
Серийный номер можно передать через env-var ANDROID_SERIAL
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
представляет IP-адрес устройства.
Если вам нужно указать устройство, вам нужно передать --serial
, например python3 -m uiautomator2 --serial bff1234
, SubCommand — это подкоманда (скриншот, текущий и т. д.)
1.0.3 Добавлено:
python3 -m uiautomator2
равноuiautomator2
скриншот: скриншот
$ uiautomator2 screenshot screenshot.jpg
current: получить текущее имя и активность пакета.
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
удалить: удалить приложение
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
стоп: остановить приложение
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
доктор:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Когда Python завершает работу, служба UiAutomation также завершает работу.
Распечатайте информацию HTTP-запроса, стоящую за кодом.
> >> 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
Установить время ожидания поиска элемента (по умолчанию 20 с)
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
Эта функция будет влиять на click
, long_click
, drag_to
, get_text
, set_text
, clear_text
и т. д.
В этой части показано, как управлять приложениями.
Мы поддерживаем установку APK только по 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(默认)
Добавлено в версии 1.2.0
отправить файл на устройство
# 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 )
вытащить файл с устройства
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"
В этой части показано, как выполнять распространенные операции с устройством:
Запустите кратковременную команду оболочки с защитой по тайм-ауту (тайм-аут по умолчанию 60 с).
Примечание. Для поддержки тайм-аута требуется atx-agent >=0.3.3
Функция adb_shell
устарела. Вместо этого используйте shell
.
Простое использование
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
Первым аргументом может быть, например, список.
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
Это возвращает строку для стандартного вывода, объединенную со стандартным выводом stderr. Если команда является блокирующей командой, shell
также будет блокироваться до тех пор, пока команда не будет завершена или не истечет время ожидания. Во время выполнения команды не будет получен частичный вывод. подходит для длительных команд. Данная команда оболочки выполняется в среде, аналогичной adb shell
, которая имеет уровень разрешений Linux adb
или shell
(выше, чем разрешение приложения).
Запуск длительной команды оболочки (удалено)
Сеанс представляет жизненный цикл приложения. Может использоваться для запуска приложения и обнаружения сбоя приложения.
Запустить и закрыть приложение
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Используйте Python with
запуска и закрытия приложения
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
Прикрепите к работающему приложению
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
Обнаружение сбоя приложения
# 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
Получите основную информацию
d . info
Ниже приведен возможный результат:
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Получить размер окна
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Получить текущую информацию о приложении. Для некоторых устройств Android выходные данные могут быть пустыми (см. Пример вывода 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}
Ожидание активности
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Получить серийный номер устройства
print ( d . serial )
# output example: 74aAEDR428Z9
Получить IP-адрес WLAN
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
Получить подробную информацию об устройстве d.device_info
информация_устройства
print ( d . device_info )
Ниже приведен возможный результат:
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Получение заданного содержимого буфера обмена
Установите содержимое монтажного стола или получите содержимое
буфер обмена/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
Получить содержимое буфера обмена
Для получения буфера обмена требуется вызов IME(com.github.uiautomator/.AdbKeyboard)
d.set_input_ime()
перед его использованием.
```python
# get clipboard content
print(d.clipboard)
```
Включить/выключить экран
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Получить текущий статус экрана
d . info . get ( 'screenOn' ) # require Android >= 4.4
Нажмите аппаратную/мягкую клавишу
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)
В настоящее время поддерживаются следующие имена ключей:
Вы можете найти все определения кодов клавиш на Android KeyEvnet.
Разблокировать экран
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Нажмите на экран
d . click ( x , y )
Двойной щелчок
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Долгий клик по экрану
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Проведите пальцем по экрану
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Функция расширения 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 ) # 页面水平左翻
Тащить
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Проведите по точкам
# 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 ))
В основном он используется для разблокировки шаблона Цзюгун. Относительные координаты каждой точки можно получить заранее (здесь поддерживается процентное соотношение).
Коснитесь и перетащите (бета)
Этот интерфейс представляет собой относительно низкоуровневый примитивный интерфейс. Он не кажется идеальным, но его можно использовать. Примечание. Это место не поддерживает проценты.
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
Примечание. Операции щелчка, пролистывания и перетаскивания поддерживают процентные значения позиции. Пример:
d.long_click(0.5, 0.5)
означает долгое нажатие в центре экрана.
Получить/установить ориентацию устройства
Возможные направления:
natural
или n
left
или l
right
или r
upsidedown
или u
(невозможно установить) # 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"
Вращение «заморозить/разморозить»
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Сделать скриншот
# 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 )
Дамп иерархии пользовательского интерфейса
# 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 )
Открыть уведомление или быстрые настройки
d . open_notification ()
d . open_quick_settings ()
Селектор — удобный механизм для идентификации конкретного объекта пользовательского интерфейса в текущем окне.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
Selector поддерживает следующие параметры. Подробную информацию см. в документе UiSelector Java.
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
дети
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
братья и сестры
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
дети по тексту, описанию или экземпляру
# 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
предназначен для поиска детей, чьи внуки имеют указанное описание, остальные параметры аналогичны child_by_text
.
child_by_instance
предназначен для поиска дочерних элементов, у которых есть дочерний элемент пользовательского интерфейса в любом месте его подиерархии, которая находится в указанном экземпляре. Это выполняется для видимых представлений без прокрутки .
Подробную информацию смотрите по ссылкам ниже:
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
Вышеуказанные методы поддерживают цепный вызов, например, для нижней иерархии.
< 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 >
Чтобы щелкнуть виджет переключателя прямо в TextView «Wi-Fi», нам нужно сначала выбрать виджеты переключателя. Однако в соответствии с иерархией пользовательского интерфейса существует более одного виджета переключателя, которые имеют почти одинаковые свойства. Выбор по имени класса невозможен. Альтернативно, поможет следующая стратегия выбора:
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
относительное позиционирование
Также мы можем использовать методы относительного позиционирования для получения представления: left
, right
, top
, bottom
.
d(A).left(B)
, выбирает B слева от A.d(A).right(B)
, выбирает B справа от A.d(A).up(B)
, выбирает B над A.d(A).down(B)
, выбирает B под A.Итак, для вышеуказанных случаев мы можем альтернативно выбрать его с помощью:
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
Несколько экземпляров
Иногда экран может содержать несколько представлений с одинаковыми свойствами, например текстом, тогда вам придется использовать свойство «экземпляр» в селекторе, чтобы выбрать один из подходящих экземпляров, как показано ниже:
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
Кроме того, uiautomator2 предоставляет API в виде списка (похожий на 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 # ...
Примечания : при использовании селекторов в блоке кода, которые просматривают список результатов, вы должны убедиться, что элементы пользовательского интерфейса на экране остаются неизменными. В противном случае при итерации по списку может возникнуть ошибка «Элемент не найден».
Проверьте, существует ли конкретный объект пользовательского интерфейса
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)
Получить информацию о конкретном объекте пользовательского интерфейса
d ( text = "Settings" ). info
Ниже приведен возможный результат:
{ 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
}
Получить/установить/очистить текст редактируемого поля (например, виджеты 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
Получить центральную точку виджета
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
Сделать скриншот виджета
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
Выполните щелчок по конкретному объекту
# 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
Выполните длинный щелчок по конкретному объекту пользовательского интерфейса.
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
Перетащите объект пользовательского интерфейса в другую точку или другой объект пользовательского интерфейса.
# 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 )
Проведите пальцем от центра объекта пользовательского интерфейса к его краю.
Свайп поддерживает 4 направления:
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 )
Двухточечный жест из одной точки в другую
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
Двухточечный жест на конкретном объекте пользовательского интерфейса
Поддерживает два жеста:
In
, от края к центруOut
, от центра к краю # 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 ()
Подождите, пока конкретный интерфейс не появится или не исчезнет.
# 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 )
Тайм-аут по умолчанию составляет 20 секунд. Дополнительные сведения см. в глобальных настройках.
Выполните переброску конкретного объекта пользовательского интерфейса (с возможностью прокрутки)
Возможные свойства:
horiz
или vert
forward
или backward
, toBeginning
или 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 ()
Выполнить прокрутку конкретного объекта пользовательского интерфейса (прокручиваемый)
Возможные свойства:
horiz
или vert
forward
или backward
, или toBeginning
, или toEnd
, или 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" )
Текущий контекст наблюдения запускается с использованием потоковой обработки и проверяется каждые 2 секунды. В настоящее время существует только одна триггерная операция: щелчок.
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 ()
# 其他脚本逻辑
Другой способ написать
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
Более рекомендуется использовать WatchContext , чтобы написать его более кратко.
Вы можете зарегистрировать наблюдателей для выполнения некоторых действий, когда селектор не находит совпадения.
До версии 2.0.0 использовался метод [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html), предоставленный в библиотеке uiautomator-jar, но на практике выяснилось, что однажды Не удалось подключиться к uiautomator. После перезапуска все конфигурации наблюдателя теряются, что определенно недопустимо.
Таким образом, текущий метод заключается в запуске потока в фоновом режиме (полагаясь на библиотеку потоков), а затем время от времени дамп иерархии и выполнение соответствующих операций после сопоставления элементов.
Примеры использования
Мониторинг регистрации
# 常用写法,注册匿名监控
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 ()
Мониторинг операций
# 移除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 ()
Кроме того, еще много не написанных документов. Рекомендуется сразу перейти к исходному коду watcher.py.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
Большинство других конфигураций в настоящее время сосредоточены в d.settings
, и конфигурация может быть увеличена или уменьшена в соответствии с дальнейшими потребностями.
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返回的元素层级
При обновлении версии при установке конфигураций с истекшим сроком действия будет предложено «Устарело», но исключение не будет создано.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
настройки режима восстановления uiautomator
Если вы будете осторожны, вы можете обнаружить, что на вашем телефоне установлено два APK-файла, один из которых виден на переднем плане (маленький желтый автомобиль). Пакет с именем com.github.uiautomator.test
не виден в фоновом режиме. Эти два приложения подписаны одним и тем же сертификатом. Невидимое приложение на самом деле представляет собой тестовый пакет, содержащий весь тестовый код, и через него также запускается основная служба тестирования. Но во время работы системе необходимо, чтобы маленькая желтая машинка работала постоянно (ее также можно запускать в фоновом режиме). Как только маленькое желтое автомобильное приложение будет закрыто, тестовая служба, работающая в фоновом режиме, также будет быстро закрыта. Даже если вы ничего не предпримете, приложение будет быстро переработано системой, если оно находится в фоновом режиме. (Я надеюсь, что эксперты дадут мне несколько советов о том, как не полагаться на поддельные приложения. Я думаю, что это теоретически возможно, но пока не знаю, как это сделать).
Есть два способа запустить маленькую желтую машинку в фоновом режиме. Один из них — запустить приложение и перевести его в фоновый режим (по умолчанию). Кроме того, вы также можете запустить фоновую службу через am startservice
.
Это поведение можно настроить с помощью d.settings["uiautomator_runtest_app_background"] = True
. True означает запуск приложения, False означает запуск службы.
Настройки таймаута в UiAutomator (скрытый метод)
>> 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 }
Чтобы клиентская программа не реагировала на таймаут, waitForIdleTimeout
и waitForSelectorTimeout
теперь изменены на 0
Ссылки: Конфигуратор Google uiautomator.
Этот метод обычно используется для ввода, где элемент управления неизвестен.
# 目前采用从剪贴板粘贴的方式输入
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
Дополнительная ссылка: IME_ACTION_CODE.
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
Исправлено в версии 3.2.0
Java uiautoamtor по умолчанию не поддерживает xpath, поэтому это расширенная функция. Скорость не такая уж высокая.
Например: содержимое одного из узлов
">< android .widget.TextView index = " 2 " text = " 05:19 " resource-id = " com.netease.cloudmusic:id/qf " package = " com.netease.cloudmusic " content-desc = " " checkable = " false " checked = " false " clickable = " false " enabled = " true " focusable = " false " focused = " false " scrollable = " false " long-clickable = " false " password = " false " selected = " false " visible-to-user = " true " bounds = " [957,1602][1020,1636] " />
Расположение и использование XPath
Имена некоторых атрибутов были изменены, и их необходимо принять к сведению.
description -> content-desc
resourceId -> resource-id
Общее использование
# 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 ())
Нажмите, чтобы увидеть другие распространенные варианты использования XPath
Запись видео (устарело), вместо этого используйте scrcpy
Команда screenrecord, идущая в комплекте с мобильным телефоном, здесь не используется. Это метод синтеза видео путем получения изображений с мобильного телефона, поэтому вам необходимо установить некоторые другие зависимости, такие как imageio, imageio-ffmpeg, numpy и т. д. Потому что некоторые. зависимости относительно велики, рекомендуется использовать зеркальную установку. Просто выполните следующую команду.
pip3 install -U " uiautomator2[image] " -i https://pypi.doubanio.com/simple
Как использовать
d.screenrecord('output.mp4')
time.sleep(10)
# or do something else
d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开
Вы также можете указать частоту кадров (в настоящее время 20) при записи. Это значение ниже, чем скорость вывода изображений minicap. Я не рекомендую вам его изменять.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
Или
logger = logging.getLogger("uiautomator2")
# setup logger
Когда программа Python завершает работу, UiAutomation завершает работу. Однако вы также можете остановить службу с помощью метода интерфейса.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
Другие участники
Рейтинг в порядке, добро пожаловать на добавление
Массачусетский технологический институт