Всем привет! Решил написать гайд, как можно автоматизировать ваши Android устройства или эмуляторы. Тема достаточно объёмная и здесь будет лишь база, чтобы начать изучать этот вопрос. Желательно минимальное знание синтаксика Python и чуть-чуть ООП. uiautomator2 - это Python обертка для сервиса UIAutomator2, который позволяет управлять действиями на устройствах Android через интерфейс HTTP. Для чего это нужно? Для любой автоматизации ваших действий на Android устройствах. Например, я делал ферму для регистрации аккаунтов и залива видео в TikTok, когда арбитражил на дейтинг. Тогда я использовал Appium, но суть та же. Можно реализовать всё что, угодно (например, любые автореггеры). Разработчикам приложений это пригодится для тестирования. Подготовка Прежде всего, установите Python 3.8+. Также потребуется установить драйвер ADB под свою операционную систему. Пример установки на Arch Linux: sudo pacman -S android-tools Пользователи Windows могут воспользоваться инструкцией из другой моей темы. После этого подключите устройство к компьютеру и включите отладку по USB. Подробнее в той же теме, пункт "Настройка Android". Установка Подготовка директории Создаём директорию, где будет лежать наш проект и переходим в неё. Я использую Linux, поэтому приведу команды для него, но на Windows последовательность будет такая же. Поочерёдно вводим команды: mkdir project cd project bash mkdir project cd project Создание виртуального окружения Создаём виртуальное окружение В разных дистрибутивах Linux (и в Windows, если вы меняли псевдонимы приложений) команда запуска Python может отличаться (например, python или python3): python -m venv venv Второй аргумент venv - название папки виртуального окружения. Активируем виртуальное окружение Linux source venv/bin/activate Windows PowerShell: venv/Scripts/Activate.ps1 Командная строка: venv\Scripts\activate Установка uiautomator2 и uiauto uiauto — это проект, независимый от uiautomator2, используемый для просмотра структуры приложений. Устанавливаем обе библиотеки: pip install uiautomator2 uiautodev На этом установка окончена и мы переходим к коду. Использование Подключение устройства Мы можем подключить устройство 2 способами: Через WiFi По USB В данной теме я не буду затрагивать подключение по WiFi, поэтому вы можете самостоятельно с ним ознакомиться. Импортируем библиотеку и подключаем устройство: import uiautomator2 as u2 device = u2.connect() python import uiautomator2 as u2 device = u2.connect() В данном примере uiautomator2 автоматически выберет наше единственное подключённое устройство. Если устройств будет несколько, мы можем указать серийный номер устройства из вывода adb devices : import uiautomator2 as u2 device = u2.connect('сериный номер из вывода adb devices') python import uiautomator2 as u2 device = u2.connect('сериный номер из вывода adb devices') Базовые настройки Время ожидания новой команды По-умолчанию uiautomator2 будет ожидать новых команд от клиента в течение 3-х минут и после этого завершит свою работу. Мы можем изменить это значение: device.set_new_command_timeout(300) # Время в секундах (5 минут) python device.set_new_command_timeout(300) # Время в секундах (5 минут) Неявное ожидание элемента Если нам в дальнейшем потребуется произвести какие-то действия с элементом, но его не будет на экране, то мы свалимся с ошибкой, что элемент не существует. Чтобы это предотвратить, мы можем установить неявное ожидание, в течение которого клиент будет искать элемент: device.implicitly_wait(10.0) # Время в секундах. Если в течение 10 секунд элемент не появится на экране, то будет поднято исключение UiObjectNotFoundError python device.implicitly_wait(10.0) # Время в секундах. Если в течение 10 секунд элемент не появится на экране, то будет поднято исключение UiObjectNotFoundError Если хотим обратно вернуть значение по-умолчанию, то вызываем без аргументов: device.implicitly_wait() python device.implicitly_wait() Взаимодействие с приложениями Запуск Запуск приложения происходит не по его имени, а по названию его пакета: device.app_start( 'com.android.settings', use_monkey=True ) python device.app_start( 'com.android.settings', use_monkey=True ) В разных оболочках название пакета можно узнать по-разному. Самый простой - зайти в информацию об приложении, название будет снизу: Скрин Другой способ - скачать это приложение и ввести название приложения нужного вам. Остановка Остановка производится аналогично по названию пакета: device.app_stop('com.android.settings') python device.app_stop('com.android.settings') Очистка данных приложения device.app_clear('com.android.settings') python device.app_clear('com.android.settings') Взаимодействие с экраном Клик по координатам device.click(x, y) python device.click(x, y) Свайп по координатам device.swipe(start_x, start_y, end_x, end_y) # Начальные и конечные координаты свайпа device.swipe(start_x, start_y, end_x, end_y, 0.5) # Также можно указать скорость свайпа в секундах python device.swipe(start_x, start_y, end_x, end_y) # Начальные и конечные координаты свайпа device.swipe(start_x, start_y, end_x, end_y, 0.5) # Также можно указать скорость свайпа в секундах Расширенный свайп Можно указать направление свайпа: # right # left # up # down device.swipe_ext('up') python # right # left # up # down device.swipe_ext('up') Можно импортировать направления свайпа из библиотеки: from uiautomator2 import Direction device.swipe_ext(Direction.FORWARD) # вверх device.swipe_ext(Direction.BACKWARD) # вниз device.swipe_ext(Direction.HORIZ_FORWARD) # вправо device.swipe_ext(Direction.HORIZ_BACKWARD) # влево python from uiautomator2 import Direction device.swipe_ext(Direction.FORWARD) # вверх device.swipe_ext(Direction.BACKWARD) # вниз device.swipe_ext(Direction.HORIZ_FORWARD) # вправо device.swipe_ext(Direction.HORIZ_BACKWARD) # влево Все методы для взаимодействия тут. Взаимодействие с конкретным элементом на экране Селекторы Многим из вас знакомы селекторы (например, из Selenium). Селектор - это механизм для определения конкретного объекта пользовательского интерфейса. В Android мы также можем искать элементы с помощью селекторов. Как найти нужный селектор? + краткий обзор uiauto В этом нам поможет библиотека uiauto. Запускаем её, желательно в отдельном окне терминала: uiauto.dev Команда даст следующий вывод, в котором нам нужен IP адрес (в моём случае - 127.0.0.1:20242): INFO: Started server process [57249] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:20242 (Press CTRL+C to quit) Переходим по нему и попадаем на выбор устройства: Скрин Выбираем наше единственное устройство и попадаем на главную страницу, где мы и будем производить все манипуляции: Скрин Здесь мы видим экран нашего устройства (думаю, не стоит говорить, что экран должен быть разблокирован) и пару вкладок: Hierarchy Иерархия элементов того, что сейчас происходит на экране. Справа видим элементы, при нажатии на которые отобразятся параметры, которые мы сможем использовать в дальнейшем. Самое главное, что мы отсюда возьмём - это XPath. Это самый беспроигрышный вариант поиска элементов, но об этом позже. Также элемент выделится на экране: Скрин Нажав на любой элемент на экране (они обводятся пунктиром разных цветов), мы провалимся в параметры конкретно данного элемента: Скрин Property Тут отображается краткая сводка о выбранном элементе Скрин Color Можем узнать цвет каждого пикселя Скрин Поиск элемента по XPath Поиск по XPath в контексте мобильной автоматизации может быть менее эффективным из-за особенностей мобильных приложений и их элементов. Вместо XPath часто рекомендуется использовать другие методы поиска, такие как поиск по id, тексту, классу и т.д. Но XPath на текущий момент является самым стабильным методом поиска, ведь он продолжит работать, даже если приложение обновится и его интерфейс изменится. С классами и т.д. такое не прокатит. Единственный случай, когда я не использую XPath - когда у элемента есть параметр resource-id , потому что он уникален и другого элемента с таким id нет. Но в некоторых приложениях, например в том же TikTok, эти id генерируются динамически. Чтобы найти элемент по XPath, мы можем либо вручную написать путь, либо скопировать его из uiauto Например, я автоматизирую запуск приложения настроек и клик на строку поиска. Для начала запустите приложение вручную, вернитесь на главную страницу uiauto и нажмите зелёную кнопку обновления сверху экрана (или просто нажмите клавишу R ). Это обновит экран устройства в браузере. Далее кликаем на нужный элемент, в моём случае это строка ввода: Скрин В большинстве случаев, у любых текстовых элементов класс будет android.widget.TextView . Сверху рядом с надписью XPath by выбираем text , чтобы искать элемент по тексту. У этого элемента текст Search settings : Скрин Копируем XPath, в моём случае это //*[@text="Search settings")] . Обратите внимание на знак * в пути. Он означает, что мы пройдём по всем элементам и выберем тот, у которого текст равен Search settings. text - это один из параметров, которые мы видим на вкладке Hierarchy . Пишем код: import uiautomator2 as u2 def main(): device = u2.connect() # Подключаем устройство device.implicitly_wait(10.0) # Выставляем неявное ожидание элемента в 10 секунд device.app_start('com.android.settings', use_monkey=True) # Запускаем приложение настроек по имени его пакета device.xpath('//*[@text="Search settings")]').click() # Ищем элемент по XPath и кликаем на него if __name__ == '__main__': main() python import uiautomator2 as u2 def main(): device = u2.connect() # Подключаем устройство device.implicitly_wait(10.0) # Выставляем неявное ожидание элемента в 10 секунд device.app_start('com.android.settings', use_monkey=True) # Запускаем приложение настроек по имени его пакета device.xpath('//*[@text="Search settings")]').click() # Ищем элемент по XPath и кликаем на него if __name__ == '__main__': main() Обратите внимание, что XPath заключён в одинарные кавычки, соответственно для текста мы используем уже двойные. Запускаем код и бум, мы сделали свою первую автоматизацию! Демонстрация Давайте поищем что-то в этой строке. Для этого нам нужно получить селектор для поля ввода текста. Повторяем шаги, идём на страницу uiauto, обновляем экран и выбираем поле ввода: Скрин Здесь мы можем воспользоваться поиском по resource-id, копируем его и вносим изменения в код: import uiautomator2 as u2 def main(): device = u2.connect() device.implicitly_wait(10.0) device.app_start('com.android.settings', use_monkey=True) device.xpath('//*[@text="Search settings"]').click() search_input = device(resourceId='android:id/search_src_text') # Сначала ищем элемент и присваиваем его к переменной search_input.clear_text() # На всякий случай очищаем поле ввода (это необязательно, но пригодится если поле не пустое) search_input.set_text('Battery') # Вводим нужный текст if __name__ == '__main__': main() python import uiautomator2 as u2 def main(): device = u2.connect() device.implicitly_wait(10.0) device.app_start('com.android.settings', use_monkey=True) device.xpath('//*[@text="Search settings"]').click() search_input = device(resourceId='android:id/search_src_text') # Сначала ищем элемент и присваиваем его к переменной search_input.clear_text() # На всякий случай очищаем поле ввода (это необязательно, но пригодится если поле не пустое) search_input.set_text('Battery') # Вводим нужный текст if __name__ == '__main__': main() Запускаем код: Демонстрация И текст введён. Это лишь базовый пример того, что можно сделать. А более подробное описание всех методов можно прочитать в репозитории библиотеки. Бонус Установка приложений У библиотеки есть возможность устанавливать приложения, но только по ссылке. Для установки приложений (и не только) мы будем использовать другую библиотеку - pure-python-adb . Установим: pip install pure-python-adb Для начала создадим экземпляр класса Client, получим список всех устройств и выберем наше единственное: from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] if __name__ == '__main__': main() python from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] if __name__ == '__main__': main() В эту же папку я положил APK файл. Установим его: from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] adb_device.install('Open***.apk') if __name__ == '__main__': main() python from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] adb_device.install('Open***.apk') if __name__ == '__main__': main() Запустим скрипт и на устройство начнёт устанавливаться приложение. Если мы хотим проверить, что приложение ещё не установлено, то нам нужно узнать название его пакета (об этом я писал выше): from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] if not adb_device.is_installed('net.open***.open***'): adb_device.install('Open***.apk') if __name__ == '__main__': main() python from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] if not adb_device.is_installed('net.open***.open***'): adb_device.install('Open***.apk') if __name__ == '__main__': main() Метод is_installed с аргументом названия пакета проверяет, установлено ли приложение. Мы делаем проверку и если нет, то устанавливаем его. Таким образом, мы можем закинуть APK файлы в отдельную папку и пройтись по ним циклом. Изменение системных параметров Метод shell открывает нам доступ к терминалу устройства. С помощью него мы, например, можем отключить таймаут экрана, подключиться к WiFi, переключить Bluetooth, GPS и многое другое: from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] adb_device.shell('settings put system screen_off_timeout 1800000') # Отключить таймаут экрана adb_device.shell('svc bluetooth disable') # Отключить Bluetooth. enable чтобы включить adb_device.shell('settings put secure location_mode 0') # 1 чтобы включить # Отключить GPS adb_device.shell('svc wifi enable') # Включить модуль WiFi adb_device.shell('cmd wifi connect-network WIFI_SSID wpa2 WIFI_PASSWORD') # Подключиться к сети if __name__ == '__main__': main() python from ppadb.client import Client as ADBClient def main(): adb_client = ADBClient(host='127.0.0.1', port=5037) adb_device = adb_client.devices()[0] adb_device.shell('settings put system screen_off_timeout 1800000') # Отключить таймаут экрана adb_device.shell('svc bluetooth disable') # Отключить Bluetooth. enable чтобы включить adb_device.shell('settings put secure location_mode 0') # 1 чтобы включить # Отключить GPS adb_device.shell('svc wifi enable') # Включить модуль WiFi adb_device.shell('cmd wifi connect-network WIFI_SSID wpa2 WIFI_PASSWORD') # Подключиться к сети if __name__ == '__main__': main() Это может сработать не на всех устройствах, поэтому тестируйте. Я использую устройство на Android 12, где это работает. Заключение Таким образом вы можете автоматизировать как реальные устройства, так и эмуляторы. Единственное ограничение - это ваша фантазия. Библиотека uiautomator2, в отличие от Appium, не требует дополнительных манипуляций с системой, но синтаксис Appium мне кажется более лаконичным и понятным. Благодарю тех, кто дочитал до конца! ;)