Автоматизация тестирования с ADB

Рейтинг: 49% · 10 голосов
Полный курс по Android Debug Bridge: установка, подключение, shell, логи, dumpsys, автоматизация, root, беспроводная отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
android_roman
Сообщения: 45
Зарегистрирован: 11 май 2026, 05:31

Автоматизация тестирования с ADB

Сообщение android_roman »

АкадемияADB: Android Debug BridgeГлава 24 из 29
Оглавление курса (29)
  1. Введение в Android Debug Bridge
  2. Установка и настройка рабочей среды
  3. Подключение устройства (проводное и беспроводное)
  4. Базовые команды ADB и управление сервером
  5. Команды состояния и перезагрузки
  6. Навигация по файловой системе
  7. Управление пакетами приложений
  8. Логирование с помощью logcat
  9. Системные дампы и диагностика (dumpsys)
  10. Анализ производительности в реальном времени
  11. Эмуляция ввода (input)
  12. Управление Activity и Intent (am)
  13. Работа с оконным менеджером (wm)
  14. Захват экрана и запись видео
  15. Root-доступ и его применение
  16. Модификация системных настроек через settings
  17. Команды для поставщиков контента (content)
  18. Резервное копирование и восстановление (backup)
  19. Проброс портов и туннелирование
  20. Беспроводная отладка (Wi-Fi)
  21. Взаимодействие с эмуляторами
  22. Написание скриптов на Bash/CMD/PowerShell
  23. ADB в языках программирования
  24. Автоматизация тестирования с ADB (вы здесь)
  25. Безопасность и лучшие практики
  26. ADB на Android TV, Wear OS и IoT
  27. Восстановление и низкоуровневые операции
  28. Расширенные возможности оболочки и инструменты
  29. Отладка самого ADB и устранение неполадок
К этой главе у вас на руках весь нужный инструментарий: эмуляция ввода (глава 11), запуск Activity (глава 12), захват экрана (глава 14), скрипты (главы 22-23). Пора собрать из этого автоматизацию тестирования: от голого uiautomator dump до ночных прогонов на стойке реальных телефонов в CI.

UIAutomator: дамп иерархии и разбор XML:

uiautomator - это одновременно фреймворк для тестов на Kotlin/Java и консольная утилита на самом устройстве. Нас интересует вторая: она выгружает дерево UI текущего экрана в XML.

Код: Выделить всё

$ adb shell uiautomator dump
UI hierchary dumped to: /sdcard/window_dump.xml
Опечатка hierchary не моя, она живет в исходниках AOSP много лет. Путь можно задать самому, файл забираем через pull:

Код: Выделить всё

$ adb shell uiautomator dump /sdcard/ui.xml
$ adb pull /sdcard/ui.xml .
/sdcard/ui.xml: 1 file pulled. 2.1 MB/s (48211 bytes in 0.022s)
Трюк без промежуточного файла, XML летит сразу в стандартный вывод:

Код: Выделить всё

$ adb exec-out uiautomator dump /dev/tty
На части прошивок /dev/tty недоступен, тогда возвращайтесь к схеме dump + pull. Каждый элемент в дампе выглядит так:

Код: Выделить всё

<node index="0" text="Войти" resource-id="ru.example.app:id/btn_login"
  class="android.widget.Button" package="ru.example.app"
  content-desc="" checkable="false" checked="false" clickable="true"
  enabled="true" focusable="true" scrollable="false"
  bounds="[84,1648][996,1816]"/>
Рабочие атрибуты: resource-id, text, content-desc, clickable и bounds. bounds - прямоугольник элемента, по нему считается точка тапа. Вытащить его удобно через xmllint:

Код: Выделить всё

$ xmllint --xpath 'string(//node[@resource-id="ru.example.app:id/btn_login"]/@bounds)' ui.xml
[84,1648][996,1816]
Связка с input tap из главы 11 дает готовый кликер без всяких фреймворков:

Код: Выделить всё

tap_by_id() {
  adb shell uiautomator dump /sdcard/d.xml >/dev/null
  adb pull /sdcard/d.xml /tmp/d.xml >/dev/null
  b=$(xmllint --xpath "string(//node[@resource-id='$1']/@bounds)" /tmp/d.xml)
  read x1 y1 x2 y2 <<< "$(echo "$b" | tr -d '[]' | tr ',' ' ')"
  adb shell input tap $(( (x1+x2)/2 )) $(( (y1+y2)/2 ))
}
tap_by_id "ru.example.app:id/btn_login"
Частые ошибки. Первая:

Код: Выделить всё

ERROR: could not get idle state.
Экран постоянно анимируется (баннер, спиннер), dump ждет покоя и не дожидается. Отключите анимации через settings (глава 16) или ловите статичный экран. Вторая:

Код: Выделить всё

java.lang.IllegalStateException: UiAutomationService ... already registered!
На устройстве уже висит другая UiAutomation-сессия, обычно Appium или запущенный instrumentation-тест. Соединение одно на устройство, сначала завершите ту сессию. Третья беда: для Flutter, Unity и тяжелых WebView дамп почти пустой, там нет нативных View, берите Appium с контекстами или Maestro. Флаг --compressed выкидывает узлы-обертки и сильно уменьшает файл, но иногда вместе с нужным элементом, проверяйте.

Monkey: стресс-тест:

Monkey - генератор псевдослучайных событий, живет прямо на устройстве (com.android.commands.monkey). Минимальный запуск:

Код: Выделить всё

$ adb shell monkey -p ru.example.app -v 500
:Monkey: seed=1718012345 count=500
:AllowPackage: ru.example.app
:Sending Touch (ACTION_DOWN): 0:(517.0,1228.0)
...
Events injected: 500
// Monkey finished
500 случайных тапов, свайпов и клавиш строго внутри пакета. Боевой вариант с воспроизводимостью:

Код: Выделить всё

$ adb shell monkey -p ru.example.app -s 42 --throttle 300 \
    --pct-touch 60 --pct-motion 25 --pct-appswitch 5 --pct-syskeys 0 \
    --ignore-timeouts --monitor-native-crashes -v -v 5000
-s 42 фиксирует seed: та же последовательность событий, краш воспроизводится. --throttle 300 ставит паузу 300 мс, ближе к живому пальцу. --pct-syskeys 0 запрещает системные клавиши (Power, Home, громкость), на реальном устройстве это обязательно. При падении monkey печатает:

Код: Выделить всё

// CRASH: ru.example.app (pid 8412)
// Short Msg: java.lang.NullPointerException
// Long Msg: java.lang.NullPointerException: Attempt to invoke virtual method ...
и останавливается, если не задан --ignore-crashes. Полный стек ищите в logcat (глава 8) по тегу AndroidRuntime. Разогнавшегося monkey убивают на устройстве:

Код: Выделить всё

$ adb shell 'kill $(pidof com.android.commands.monkey)'
Без -p запускать на личном телефоне не надо: monkey вылезет в системный UI и накликает там что угодно, вплоть до смены настроек.

MonkeyRunner, не путать с monkey. Это хостовый инструмент из старого пакета tools Android SDK: скрипты на Jython 2.5 (синтаксис Python 2), управление через ADB. Google его давно не развивает, пакет tools убран из SDK Manager, для нового кода берите uiautomator2, Appium или Maestro. Но в легаси встречается:

Код: Выделить всё

# legacy_smoke.py, синтаксис Python 2
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

device = MonkeyRunner.waitForConnection(15)
device.installPackage('build/app-debug.apk')
device.startActivity(component='ru.example.app/.MainActivity')
MonkeyRunner.sleep(3)
device.press('KEYCODE_BACK', MonkeyDevice.DOWN_AND_UP)
device.takeSnapshot().writeToFile('smoke.png', 'png')

Код: Выделить всё

$ monkeyrunner legacy_smoke.py
Appium, Espresso, Maestro: где тут ADB:

Все три стоят на плечах ADB, просто прячут его. Appium с драйвером uiautomator2 при старте сессии делает то, что вы умеете из глав 7 и 19: ставит на устройство служебные APK и пробрасывает порт.

Код: Выделить всё

$ adb shell pm list packages | grep appium
package:io.appium.settings
package:io.appium.uiautomator2.server
package:io.appium.uiautomator2.server.test
$ adb forward --list
RF8N31XXXXX tcp:8200 tcp:6790
Сервер на устройстве он поднимает через instrumentation:

Код: Выделить всё

adb shell am instrument -w io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner
Два следствия. Если Appium не видит устройство, сначала adb devices и переменная ANDROID_HOME, ошибка "Could not find 'adb'" в девяти случаях из десяти про окружение. И пока сессия жива, uiautomator dump и свои instrumentation-тесты не запустятся, та самая ошибка already registered.

Espresso исполняется внутри процесса приложения, но запускается все равно через ADB:

Код: Выделить всё

$ adb shell am instrument -w -r \
    -e class ru.example.app.LoginTest \
    ru.example.app.test/androidx.test.runner.AndroidJUnitRunner
INSTRUMENTATION_STATUS: class=ru.example.app.LoginTest
INSTRUMENTATION_STATUS: numtests=3
...
OK (3 tests)
Команда ./gradlew connectedDebugAndroidTest делает ровно это плюс adb install обоих APK. Список раннеров на устройстве:

Код: Выделить всё

adb shell pm list instrumentation
. Espresso Device API (артефакт androidx.test.espresso:espresso-device) добавляет в тесты повороты экрана и режимы складных устройств; команды до устройства доводит тестовая платформа Gradle по тому же ADB-каналу, поэтому из голого am instrument эти тесты не работают.

Maestro описывает сценарии в YAML и сам ходит к устройству через adb: при первом запуске ставит свой драйвер (instrumentation на базе UiAutomator) и пробрасывает gRPC-порт через adb forward.

Код: Выделить всё

# login_flow.yaml
appId: ru.example.app
---
- launchApp
- tapOn: "Войти"
- inputText: "qa@example.ru"
- tapOn: "Продолжить"
- assertVisible: "Профиль"

Код: Выделить всё

$ maestro test login_flow.yaml
Running on RF8N31XXXXX
 > Flow: login_flow
   [Passed] Launch app "ru.example.app"
   [Passed] Tap on "Войти"
   [Passed] Assert visible "Профиль"
Если Maestro не видит телефон, диагноз тот же: adb devices, он использует общий adb-сервер.

CI/CD с реальными устройствами:

Эмуляторы в облаке хороши (глава 21), но камеру, NFC, push на китайских прошивках и реальный тепловой тротлинг они не покрывают. Реальные устройства вешают на self-hosted агент. Подготовка устройства, один раз:

Код: Выделить всё

$ adb shell settings put global window_animation_scale 0
$ adb shell settings put global transition_animation_scale 0
$ adb shell settings put global animator_duration_scale 0
$ adb shell svc power stayon usb
Отключенные анимации убирают флаки и требуются Espresso, stayon не дает экрану гаснуть под USB. Ключ агента (~/.android/adbkey) должен быть принят устройством заранее, иначе ночной прогон упрется в unauthorized; на ферме ключи складывают в каталог и указывают через ADB_VENDOR_KEYS.

GitHub Actions, self-hosted runner:

Код: Выделить всё

name: e2e-real-device
on: [pull_request]

jobs:
  e2e:
    runs-on: [self-hosted, android-lab]
    env:
      ANDROID_SERIAL: RF8N31XXXXX
    steps:
      - uses: actions/checkout@v4
      - name: Device health check
        run: |
          adb start-server
          adb devices -l
          adb wait-for-device
          [ "$(adb shell getprop sys.boot_completed | tr -d '\r')" = "1" ]
      - name: Clean state
        run: |
          adb logcat -c
          adb uninstall ru.example.app || true
      - name: Run instrumented tests
        run: ./gradlew connectedDebugAndroidTest
      - name: Collect logs on failure
        if: failure()
        run: adb logcat -d > logcat.txt
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: device-logs
          path: logcat.txt
ANDROID_SERIAL читают и adb, и Android Gradle Plugin, поэтому прогон идет строго на одном телефоне, даже если к агенту подключен десяток. Для эмуляторов на облачных раннерах есть reactivecircus/android-emulator-runner, на ubuntu-раннерах перед ним включают KVM (подробности в главе 21).

Jenkins, декларативный pipeline:

Код: Выделить всё

pipeline {
    agent { label 'android-lab' }
    environment { ANDROID_SERIAL = 'RF8N31XXXXX' }
    options { lock(resource: 'pixel-8-rack-2') }
    stages {
        stage('Prepare') {
            steps { sh 'adb logcat -c && adb shell pm clear ru.example.app || true' }
        }
        stage('E2E') {
            steps { sh './gradlew connectedDebugAndroidTest' }
        }
    }
    post {
        failure {
            sh 'adb logcat -d > logcat.txt || true'
            sh 'adb bugreport bugreport.zip || true'
            archiveArtifacts artifacts: 'logcat.txt, bugreport.zip', allowEmptyArchive: true
        }
    }
}
lock из плагина Lockable Resources держит телефон за одной джобой: два параллельных прогона на одном устройстве дают кашу из instrumentation-сессий.

Грабли CI, проверено на себе. Никогда не пишите adb kill-server в шагах джобы: adb-сервер на агенте один на всех, и вы убьете чужой прогон. USB-хабы без внешнего питания теряют устройства под нагрузкой, берите активные. Телефоны, годами висящие на 100% заряда, вздуваются; нужны хабы с управлением питанием или ограничение заряда в прошивке. На Xiaomi/HyperOS отладка по USB иногда отключается после OTA, поэтому health check в начале джобы обязателен.

Если своя стойка не нужна, есть облачные фермы: Firebase Test Lab (gcloud firebase test android run), BrowserStack, AWS Device Farm. Для локальной фермы из собственных телефонов смотрите DeviceFarmer (бывший OpenSTF), это веб-доступ к устройствам поверх все того же ADB. С учетом проблем с оплатой зарубежных сервисов из СНГ стойка из десятка бэушных Xiaomi плюс DeviceFarmer закрывает большинство задач.

В главе 25 поговорим про обратную сторону: что бывает, когда CI-агент с авторизованными ADB-ключами достается не тем людям, и как этого не допустить.
👍1 ❤️ 🔥1 😄 🤔2
✔ Лучший ответ сформирован автоматически — vaultuser
android_roman писал(а):пока сессия жива, uiautomator dump и ваши собственные instrumentation-тесты не запустятся, та самая ошибка already registered а как тогда отлаживать локаторы? у меня appium-сессия висит часами, каждый раз ее убивать ради dump - тоска. или внутри сессии есть свой способ достать иерархию?
Перейти к ответу →
Аватара пользователя
vaultuser
Сообщения: 1
Зарегистрирован: 13 май 2026, 17:57

Re: Автоматизация тестирования с ADB

Сообщение vaultuser »

✔ Лучший ответ — сформирован автоматически
android_roman писал(а):пока сессия жива, uiautomator dump и ваши собственные instrumentation-тесты не запустятся, та самая ошибка already registered
а как тогда отлаживать локаторы? у меня appium-сессия висит часами, каждый раз ее убивать ради dump - тоска. или внутри сессии есть свой способ достать иерархию?
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
exwife
Сообщения: 2
Зарегистрирован: 14 май 2026, 14:24

Re: Автоматизация тестирования с ADB

Сообщение exwife »

по поводу seed у monkey: гонял с -s 42 на двух разных телефонах и удивился, что падает в разных местах. потом дошло - последовательность событий та же, но координаты завязаны на разрешение и разметку экрана, плюс тайминги другие. так что воспроизводимость работает только на том же устройстве и той же сборке приложения, имейте в виду
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
ologin
Сообщения: 2
Зарегистрирован: 13 май 2026, 14:37

Re: Автоматизация тестирования с ADB

Сообщение ologin »

а flutter-приложения maestro реально видит? dump у меня на флаттере отдает один node на весь экран, уже думал придется координатами тыкать как в 11 главе
👍2 ❤️ 🔥1 😄 🤔
Аватара пользователя
gundi
Сообщения: 1
Зарегистрирован: 01 июн 2026, 14:08

Re: Автоматизация тестирования с ADB

Сообщение gundi »

добавлю про ферму: svc power stayon usb это хорошо, но за полгода у нас на паре амолед-xiaomi выгорел статусбар от вечно включенного экрана. лечится просто - после подготовки скручивайте яркость: adb shell settings put system screen_brightness 0, тестам все равно, а панелям сильно легче
👍 ❤️4 🔥 😄 🤔
Ответить
← Предыдущая глава
ADB в языках программирования
Следующая глава →
Безопасность и лучшие практики

Все главы курса «ADB: Android Debug Bridge»

Поделиться темой: ✈ Telegram VK

Вернуться в «ADB: Android Debug Bridge»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость