Selenoid без симуляции: настройка, отладка и автоматизация на физическом Android-устройстве

от автора

Постановка задач

В предыдущей статье наш коллега писал про Selenoid с Android-эмуляторами. Однако это решение было пробой пера и проверкой работоспособности Selenoid. Применение данного решения выявило несколько проблем:

  • Эмулятор — это не реальное устройство, возможен пропуск различных дефектов в нашем приложении. Есть много Android-устройств с различными версиями ОС, экранами, процессорами и другими характеристиками. Тестирование на реальных устройствах помогает убедиться, что приложение работает корректно на максимально возможном количестве конфигураций.

  • Загруженность процессора хоста значительно выше из-за необходимости эмуляции аппаратных и программных компонентов устройства. Это может привести к снижению общей производительности системы, особенно при запуске большого количества эмуляторов.

  • Не учитываются все особенности работы устройства, включая производительность, энергопотребление и особенности сетевого соединения.

Если кратко, лучше реальность, чем симуляция.

Однако не всё так гладко. У развёртывания Selenoid на реальном железе, есть некоторые проблемы:

  1. Сейчас нет универсального и готового к использованию решения для развёртывания сервера, которое бы автоматизировано обновляло конфигурацию Selenoid хаба в зависимости от подключения/отключения устройств Android в USB-порт. В большинстве случаев инженеры по тестированию вынуждены вручную настраивать сервер и конфигурации для каждого нового устройства, что требует значительных временных и трудовых затрат.

  2. После подключения/отключения новых устройств конфигурационные файлы необходимо обновлять вручную, что увеличивает риск ошибок и требует дополнительных ресурсов. Для автоматизации процесса подключения и конфигурирования новых устройств требуются кастомные скрипты, которые необходимо разрабатывать и поддерживать. Внедрение таких решений может значительно улучшить качество тестирования и сократить затраты на его проведение.

Решение трудностей — развернуть Selenoid без Докера на сервере для работы с реальными устройствами, а также обеспечить полную автоматизацию при создании конфигураций для подключаемых Android девайсов через USB-порт.

Об этой и других задачах развёртывания инфраструктуры для запуска тестов на реальных Android-устройствах на Linux и Mac расскажу в этой статье. А также продемонстрирую реализованные нами кастомные скрипты для генерации конфигурационных файлов для подключенных устройств.

Начнём с виртуализации.

Шаг 1. Виртуализация на сервере

В первую очередь необходимо включить виртуализацию на нашем Linux-сервере.

№1. Перезагружаем компьютер. При включении постоянно нажимаем кнопку подсказки, чтобы попасть в BIOS. Если подсказки нет, жмём «F9» или «F10».

№2. Выбираем «Security» — «System Security».

№3. Находим «Intel Virtual Technology». Выбираем «Enabled» с помощью клавиш «→», а затем жмём «F10».

№4. Вернёмся в меню «File» и выберем «Save Changes and Exit».

Шаг 2. Настройка окружения сервера

На следующем этапе перейдем к установке требуемого ПО для настройки сервера перед запуском автотестов.

№1. Создаём папку на сервере, где будут находится Selenoid и Selenoid UI, а также скрипты для автоматизированного создания конфигурационных файлов для Selenoid и Appium.

Пример:

mkdir selenoid

№2. Переходим в директорию, которая была создана на предыдущем шаге.

cd selenoid

№3. Переходим в репозиторий Selenoid. Копируем ссылку на бинарник и скачиваем бинарник на сервер (может отличаться от того, что указано в примере).

wget -o selenoid бинарник

№4. Переходим по ссылке в репозиторий Selenoid UI, копируем ссылку на бинарник. Также скачиваем бинарник на сервер.

wget -o selenoid-ui бинарник

№5. Устанавливаем nvm.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR="NVM_DIR/nvm.sh" ] && . "NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"  # This loadsnvm bash_completion

№6. Устанавливаем Node.js.

sudo apt-get install -y nodejs

№7. Устанавливаем npm.

sudo apt install -y npm

№8. Устанавливаем Appium.

npm install -g appium

№9. Устанавливаем драйвер uiautomator.

appium driver install uiautomator2

№10. Устанавливаем необходимую версию Java Azul. Подробные инструкции об установке.

№11. Установим Android SDK.

wget https://dl.google.com/android/repository/tools_r25.2.3-linux.zip unzip tools_r25.2.3-linux.zip -d sdk cd /sdk/tools ./android update sdk --no-ui

№12. Создаём файл с переменными средами в домашней директории пользователя.

touch $HOME/.bash_profile

№13. В текстовом редакторе nano прописываем следующие переменные:

№14.  Экспортируем настройки переменных энвайронмента из файла.

source .bash_profile

№15. Устанавливаем adb.

sudo apt-get install adb

№16. Инициализируем локальный репозиторий и связываем его с центральным.

git init git remote add origin <Ссылка на ваш репозиторий>  git pull origin master

Шаг 3. Листинг bash скриптов

После реализации и отладки скриптов в нашем репозитории получилась следующая структура:

healthCheck.sh

Этот скрипт предназначен для создания новой сессии Appium с использованием команды curl. Служит для проверки развернутого Selenoid.

  #!/bin/bash        HOST=$1    PORT=$2    PLATFORM_NAME=$3    DEVICE_NAME=$4        if [ "$PLATFORM_NAME" = "android" ]; then      APP_PATH=""    else      APP_PATH=""    fi        REQUEST_BODY=$(echo '{    "capabilities": {    "alwaysMatch": {    "browserVersion": "deviceNameToReplace",    "selenoid:options": {    "name": "Session started using curl command...",    "sessionTimeout": "1m"    },    "appium:deviceName": "platformNameToReplace",    "appium:app": "appPathToReplace"    }    }    }')        REQUEST_BODY=${REQUEST_BODY/deviceNameToReplace/$DEVICE_NAME}    REQUEST_BODY=${REQUEST_BODY/appPathToReplace/$APP_PATH}    REQUEST_BODY=${REQUEST_BODY/platformNameToReplace/$PLATFORM_NAME}        curl -H'Content-Type: application/json' http://$HOST:$PORT/wd/hub/session -d"$REQUEST_BODY"

Этот скрипт выполняет следующие действия:

  • Определяет уникальные идентификаторы для поиска скриптов.

  • Экспортирует переменные окружения, необходимые для работы Selenoid и Appium.

  • Ищет и определяет абсолютные пути к скриптам и конфигурациям.

  • Экспортирует дополнительные переменные окружения.

  • Выводит значения переменных в режиме отладки, если скрипт запускается с аргументом --debug .

Таким образом, скрипт настраивает окружение для работы с Selenoid и Appium, обеспечивая возможность поиска и использования необходимых скриптов и конфигураций.

Листинг:

   #!/bin/bash        get_realpath_from_egrep() {    search_pattern=$1    search_dir=$2    DIRS=$(egrep -r --include=*.sh --exclude-dir=$HOME/Library --exclude-dir=$HOME/.Trash "$search_pattern" "$search_dir")    echo $DIRS | sed "s/:/\\n/" | head -n1 | xargs realpath    }        get_last_segment_from_pattern() {    search_pattern=$1    echo "$search_pattern" | sed "s/\//\\n/g" | tail -n1    }        FILE_NAME=".zshrc"        if [ -f "$HOME/$FILE_NAME" ]; then    #for mac os    source $HOME/$FILE_NAME    else        FILE_NAME=".bash_profile"    #for linux    if [ -f "$HOME/$FILE_NAME" ]; then    source $HOME/$FILE_NAME    fi    fi        APPIUM_SCRIPT_FIND_BY="5cc2bd0d-96c5-4567-b82a-a896695af033"    DEVICES_SCRIPT_FIND_BY="94843c3a-f128-4bb6-8819-4644156699d9"    SELENOID_SCRIPT_FIND_BY="1b6f0b38-04e2-421c-b122-a54ab8a68bbd"    SELENOID_CONFIG_SCRIPT_FIND_BY="660368f2-f0cc-49d8-bf2e-4a7d0f2c84d8"        export SELENOID_PORT=4444    export CRON_SETTINGS="*/15 * * * *"    export SELENOID_UI_PORT=8080    export SELENOID_CONFIG_NAME=devices.json        SELENOID_LIMITS="-disable-docker -limit 20 -retry-count 1000"    SELENOID_TIMEOUTS="-max-timeout 20m -session-attempt-timeout 15m -timeout 10m -service-startup-timeout 10m"        SELENOID_PATH=$(get_realpath_from_egrep "$SELENOID_SCRIPT_FIND_BY" "$HOME")    SELENOID_HOME=$(echo $SELENOID_PATH | xargs dirname)    APPIUM_CONFIG_PATH=$(get_realpath_from_egrep "$APPIUM_SCRIPT_FIND_BY" "$SELENOID_HOME")    SELENOID_CONFIG_PATH=$(get_realpath_from_egrep "$SELENOID_CONFIG_SCRIPT_FIND_BY" "$SELENOID_HOME")        export SELENOID_HOME=$SELENOID_HOME    export SELENOID_LOGS_DIR=$SELENOID_HOME/logs    export COMMON_CONFIG_DIR=$(echo $SELENOID_CONFIG_PATH | xargs dirname)    export APPIUM_CONFIG_DIR=$(echo $APPIUM_CONFIG_PATH | xargs dirname)    export DEVICES_WATCHER_PATH=$(get_realpath_from_egrep "$DEVICES_SCRIPT_FIND_BY" "$SELENOID_HOME")    export SELENOID_SCRIPT_NAME=$(get_last_segment_from_pattern "$SELENOID_PATH")    export APPIUM_CONFIG_CREATER=$(get_last_segment_from_pattern "$APPIUM_CONFIG_PATH")    export SELENOID_CONFIG_CREATER=$(get_last_segment_from_pattern "$SELENOID_CONFIG_PATH")        CONFIG=$COMMON_CONFIG_DIR/$SELENOID_CONFIG_NAME        export SELENOID_ARGS="$SELENOID_LIMITS -listen :$SELENOID_PORT -conf $CONFIG $SELENOID_TIMEOUTS -log-output-dir $SELENOID_LOGS_DIR"    export SELENOID_UI_ARGS="-listen :$SELENOID_UI_PORT --selenoid-uri=http://localhost:$SELENOID_PORT"        if [ "$1" = "--debug" ]; then            echo $SELENOID_LOGS_DIR        echo $COMMON_CONFIG_DIR        echo $APPIUM_CONFIG_DIR        echo $DEVICES_WATCHER_PATH        echo $SELENOID_CONFIG_NAME        echo $SELENOID_SCRIPT_NAME        echo $APPIUM_CONFIG_CREATER        echo $SELENOID_CONFIG_CREATER        fi

Этот скрипт автоматизирует процесс мониторинга подключенных Android-устройств и перенастраивает Selenoid в случае изменений в списке устройств. Он загружает переменные окружения, проверяет текущий список устройств, сравнивает его с предыдущим состоянием и, при необходимости, перезапускает Selenoid, обновляя конфигурацию.

#!/bin/bash #94843c3a-f128-4bb6-8819-4644156699d9 - don't delete        DIR_TO_SCRIPT=$(realpath "$0" | xargs dirname)        if [ "$#" != "1" ]; then      echo "Необходимо передать путь до .env"      exit 120    fi        if [ -f $1 ]; then      cd $(dirname $1)      source $1    else      exit 120    fi        DEVICES_FILE="devices"    DEVICES_REGISTRY_PREV=""        cd $DIR_TO_SCRIPT        if [ -f $DEVICES_FILE ]; then      DEVICES_REGISTRY_PREV=$(cat $DEVICES_FILE)    else      touch $DEVICES_FILE    fi        DEVICES=$(adb devices -l| grep -Eo "[a-zA-Z0-9-]{4,}\s{2,}" | xargs -n1 echo)    DEVICES_COUNT=$(echo $DEVICES | wc -w)        for ((DEVICE_INDEX=1; DEVICE_INDEX <= $((DEVICES_COUNT)); DEVICE_INDEX++))    do      DEVICE=$(echo $DEVICES | cut -d' ' -f$DEVICE_INDEX)      DEVICES_TO_LINE=$DEVICE,$DEVICES_TO_LINE    done        DEVICES_TO_LINE=${DEVICES_TO_LINE:0:$((${#DEVICES_TO_LINE}-1))}    IS_RECONFIGURE_SELENOID="false"        if [ -z "$DEVICES_REGISTRY_PREV" ]; then          IS_RECONFIGURE_SELENOID="true"        else          IFS=',' read -r -a DEVICE_UDIDS <<< "$DEVICES_REGISTRY_PREV"          #previously registry device was disconnected      for UDID in "${DEVICE_UDIDS[@]}"      do        DEVICE_LINE=$(echo $DEVICES | grep $UDID)            if [ -z "$DEVICE_LINE" ]; then           IS_RECONFIGURE_SELENOID="true"        fi      done          #registry new device was connected      for ((DEVICE_INDEX=1; DEVICE_INDEX <= $((DEVICES_COUNT)); DEVICE_INDEX++))      do         DEVICE=$(echo $DEVICES | cut -d' ' -f$DEVICE_INDEX)         DEVICE_LINE=$(echo $DEVICES_REGISTRY_PREV | grep $DEVICE)             if [ -z "$DEVICE_LINE" ]; then            IS_RECONFIGURE_SELENOID="true"         fi      done    fi        if [ "$IS_RECONFIGURE_SELENOID" = "true" ]; then          echo "Reconfigure selenoid"      $SELENOID_HOME/"$SELENOID_SCRIPT_NAME" reconfigure &          DEVICES_TO_LINE=""          for ((DEVICE_INDEX=1; DEVICE_INDEX <= $((DEVICES_COUNT)); DEVICE_INDEX++))      do         DEVICE=$(echo $DEVICES | cut -d' ' -f$DEVICE_INDEX)         APPIUM_CONFIG=$(cat $APPIUM_CONFIG_DIR/$DEVICE.json 2> /dev/null | grep $DEVICE)         SELENOID_CONFIG=$(cat $COMMON_CONFIG_DIR/$SELENOID_CONFIG_NAME 2> /dev/null | grep $DEVICE)             if [ -z "$APPIUM_CONFIG" ]; then            continue         fi             if [ -z "$SELENOID_CONFIG" ]; then            continue         fi             DEVICES_TO_LINE=$DEVICE,$DEVICES_TO_LINE      done          DEVICES_TO_LINE=${DEVICES_TO_LINE:0:$((${#DEVICES_TO_LINE}-1))}      echo -n "$DEVICES_TO_LINE" > $DEVICES_FILE    fi        exit 0

Отслеживание новой версии Selenoid и Selenoid UI. Загрузка и обновление бинарников в случае появления новой версии.

#!/bin/bash        function getLatestVersion() {            echo $(curl -s $1 | grep "/aerokube/$2/releases/tag" | grep -Eo "[0-9]{1,}[.][0-9]{1,}[.][0-9]{1,}" | head -n1)    }        function getCurrentVersion() {            echo $($SELENOID_HOME/$1 --version 2> /dev/null | grep -Eo "[0-9]{1,}[.][0-9]{1,}[.][0-9]{1,}" || echo $BAD_CODE)    }        function downloadBinary() {            echo "Download $3"            DOWNLOAD_URL=$1        DOWNLOAD_URL=${DOWNLOAD_URL/"{os}"/$OS}        DOWNLOAD_URL=${DOWNLOAD_URL/"{osPlatform}"/$OS_PLATFORM}        DOWNLOAD_URL=${DOWNLOAD_URL/"{latest_version}"/$2}            cd "$SELENOID_HOME" || exit $BAD_CODE        rm -f $3        curl -s -L -o $SELENOID_HOME/$3 $DOWNLOAD_URL || exit $BAD_CODE        chmod 766 $3        cd "$CURRENT_DIR_PATH" || exit $BAD_CODE    }        if [ "$#" != "1" ]; then      echo "Необходимо передать путь до .env"      exit 120    fi        CURRENT_DIR_PATH=$(realpath "$0" | xargs dirname)    PATH_TO_ENV=$(realpath "$1")        if [ -f $PATH_TO_ENV ]; then      DIR=$(dirname PATH_TO_ENV)      cd $DIR || exit $BAD_CODE      source $PATH_TO_ENV    else      echo "Неверно передан путь до .env"      exit 120    fi        OS=$(uname | tr A-Z a-z)    OS_PLATFORM=$(uname -m)    BAD_CODE="126"    SELENOID_URL="https://github.com/aerokube/selenoid/releases"    SELENOID_UI_URL="https://github.com/aerokube/selenoid-ui/releases"        SELENOID_DOWNLOAD_URL="https://github.com/aerokube/selenoid/releases/download/{latest_version}/selenoid_{os}_{osPlatform}"    SELENOID_UI_DOWNLOAD_URL="https://github.com/aerokube/selenoid-ui/releases/download/{latest_version}/selenoid-ui_{os}_{osPlatform}"        SELENOID_LATEST_VERSION=$(getLatestVersion $SELENOID_URL "selenoid")    SELENOID_UI_LATEST_VERSION=$(getLatestVersion $SELENOID_UI_URL "selenoid-ui")    CURRENT_SELENOID_VERSION=$(getCurrentVersion "selenoid")    CURRENT_SELENOID_UI_VERSION=$(getCurrentVersion "selenoid-ui")        if [ "$CURRENT_SELENOID_VERSION" = "$BAD_CODE" ]; then        CURRENT_SELENOID_VERSION="0.0.0"    fi        if [ "$CURRENT_SELENOID_UI_VERSION" = "$BAD_CODE" ]; then        CURRENT_SELENOID_UI_VERSION="0.0.0"    fi        OS_PLATFORM=${OS_PLATFORM/"x86_64"/"amd64"}    IS_START_SELENOID="false"        if [[ "$SELENOID_LATEST_VERSION" != *"$CURRENT_SELENOID_VERSION"* ]]; then      downloadBinary $SELENOID_DOWNLOAD_URL $SELENOID_LATEST_VERSION "selenoid"      IS_START_SELENOID="true"    else      echo "Selenoid version latest: $SELENOID_LATEST_VERSION"    fi        if [[ "$SELENOID_UI_LATEST_VERSION" != *"$CURRENT_SELENOID_UI_VERSION"* ]]; then      downloadBinary $SELENOID_UI_DOWNLOAD_URL $SELENOID_UI_LATEST_VERSION "selenoid-ui"      IS_START_SELENOID="true"    else      echo "Selenoid UI version latest: $SELENOID_UI_LATEST_VERSION"    fi        if [ "$IS_START_SELENOID" = "true" ]; then      echo "Start selenoid and selenoid ui"      eval "$SELENOID_HOME/$SELENOID_SCRIPT_NAME restart"    fi        exit 0

Этот скрипт автоматически генерирует конфигурационные файлы для Appium на основе подключённых Android-устройств. Он извлекает список UDID устройств с помощью adb, создаёт для каждого устройства конфигурационный файл с соответствующими параметрами и сохраняет его в формате JSON.

#!/bin/bash #5cc2bd0d-96c5-4567-b82a-a896695af033 - don't delete        IFS=" "    IDS=$(adb devices -l | grep -v 'List of devices attached' | grep -Eo '[0-9a-zA-Z-]{8,}\s')    IDS=$(echo "${IDS}" | tr -d "\r\n")        echo "Удаляем конфигурационные файлы:"    ls *.json    rm -f *.json        read -ra UDIDS <<< "$IDS"    for UDID in "${UDIDS[@]}"    do        FILE_NAME=$UDID".json"        FILE_CONTENT=$(echo '{        "server": {        "address": "127.0.0.1",        "allow-cors": true,        "allow-insecure": [            "get_server_logs",            "adb_shell"        ],        "base-path": "/wd/hub",        "debug-log-spacing": true,        "default-capabilities": {            "platformName": "android",            "appium:androidNaturalOrientation": true,            "appium:deviceName": "android",            "appium:udid": "udidToReplace",            "appium:automationName": "UiAutomator2",            "appium:enforceAppInstall": true,            "appium:newCommandTimeout": 90,            "appium:autoGrantPermissions": false,            "appium:noReset": noResetToReplace,            "appium:ignoreHiddenApiPolicyError": true,            "appium:appActivity": "ru.alfabank.mobile.android.splash.presentation.activity.SplashActivity",            "appium:appPackage": "ru.alfabank.mobile.android.feature"        },        "log-level": "debug",        "log-no-colors": true        }      }')          XIAOMI_LINE=$(adb -s $UDID shell getprop ro.vendor.build.fingerprint | sed 's/\//\n/g' | head -n1 | grep "Xiaomi")            if [ -z "$XIAOMI_LINE" ]; then          NO_RESET_ENABLE="false";        else          NO_RESET_ENABLE="true";        fi            FILE_CONTENT=${FILE_CONTENT/udidToReplace/$UDID}        FILE_CONTENT=${FILE_CONTENT/noResetToReplace/$NO_RESET_ENABLE}            echo "создаем конфигурационный файл "${FILE_NAME}        touch $FILE_NAME        echo "настройки конфигурационного файла:"        FILE_CONTENT_PRETTY=$(echo $FILE_CONTENT | json_reformat 2> /dev/null)            if [ "$?" = 0 ]; then          echo $FILE_CONTENT_PRETTY > $FILE_NAME        else          echo $FILE_CONTENT > $FILE_NAME        fi            cat $FILE_NAME    done        exit 0 createSelenoidConfig.sh 

Этот скрипт на языке Bash предназначен для создания конфигурационного файла Selenoid на основе подключенных Android-устройств.

Листинг:

#!/bin/bash #660368f2-f0cc-49d8-bf2e-4a7d0f2c84d8 - don't delete        if [ "$#" != "1" ]; then      echo "Необходимо передать путь до .env"      exit 120    fi        if [ -f $1 ]; then      cd $(dirname $1)      source $1      cd $COMMON_CONFIG_DIR    else      exit 120    fi        FILE_CONTENT=$(echo '{      "android": {        "default": "defaultToReplace",        "versions": {          versionsToReplace        }      }    }')    VERSION=$(echo '    "deviceNameToReplace": {      "image": ["pathToAppium", "--config", "configNameToReplace"]    }')    VERSIONS=""    DEVICES_COUNT=$(adb devices -l | wc -l)    DEVICES_DEFAULT=""    APPIUM_PATH=$(echo $APPIUM_HOME)appium        for ((DEVICE_INDEX=2; DEVICE_INDEX <= $((DEVICES_COUNT-1)); DEVICE_INDEX++))    do          ADB_LINE=$(adb devices -l | sed $DEVICE_INDEX'!D')      USB=$(echo $ADB_LINE | grep -Eo "usb:[0-9-]{1,}" | grep -Eo "[0-9-]{1,}" )      UDID=$(echo $ADB_LINE | grep -Eo "[0-9A-Za-z-]+\s" | head -n1 | xargs -n1 echo | head -n1)          if [ -z "$USB" ]; then          #for emulators          MODEL=$(adb -s $UDID shell getprop ro.boot.qemu.avd_name | sed 's/.*/&/')          DEVICE=$(adb -s $UDID shell getprop ro.product.vendor.manufacturer | sed 's/.*/&/')      else          #for real devices          MODEL=$(adb -s $UDID shell getprop ro.product.model | sed 's/.*/\u&/')          DEVICE=$(adb -s $UDID shell getprop ro.vendor.build.fingerprint | sed 's/\//\n/g' | head -n1 | sed 's/.*/\u&/')      fi          DEVICES_DEFAULT="${DEVICE//_/ } ${MODEL//_/ }"      PATH_TO_APPIUM_CONFIG_FILE="$APPIUM_CONFIG_DIR/$UDID.json"      VER=$(echo $VERSION)      VER=${VER/pathToAppium/$APPIUM_PATH}      VER=${VER/deviceNameToReplace/$DEVICES_DEFAULT}      VER=${VER/configNameToReplace/$PATH_TO_APPIUM_CONFIG_FILE}      VERSIONS=$(echo -e "$VER,$VERSIONS")    done        LENGTH=$(echo ${#VERSIONS})    LENGTH=$((LENGTH-1))        if [ "$LENGTH" != "-1" ]; then          echo "Удаляем файл конфигурации: "$SELENOID_CONFIG_NAME      rm -f $SELENOID_CONFIG_NAME          VERSIONS=${VERSIONS:0:$LENGTH}      FILE_CONTENT=${FILE_CONTENT/versionsToReplace/$VERSIONS}      FILE_CONTENT=${FILE_CONTENT/defaultToReplace/$DEVICES_DEFAULT}      FILE_CONTENT_PRETTY=$(echo $FILE_CONTENT | json_reformat 2> /dev/null)          EXIT_CODE=$?      echo "Создаем файл конфигурации: "$SELENOID_CONFIG_NAME          if [ "$EXIT_CODE" = 0 ]; then        echo $FILE_CONTENT_PRETTY > $SELENOID_CONFIG_NAME      else        echo $FILE_CONTENT > $SELENOID_CONFIG_NAME      fi          echo "Настройки конфигурационного файла:"      cat $SELENOID_CONFIG_NAME    fi    exit 0 selenoid.sh 

Скрипт верхней оркестрации, управляющий скриптами, описанными выше. Позволяет запуска, останавливать, перенастраивать Selenoid с генерацией необходимых конфигурационных файлов.

#!/bin/bash #1b6f0b38-04e2-421c-b122-a54ab8a68bbd        function get_pids(){          PIDS=$(ps -ax | grep "$1" | grep -v grep | grep -v ggr | grep -Eo "^\s{0,6}[0-9]+\s" | grep -Eo "[0-9]+" | grep -v "$$")    }        function kill_procs(){          if [ -n "$1" ]; then         echo "$1" | xargs kill $2 > /dev/null 2> /dev/null      fi    }        function kill_appium_procs(){           get_pids "appium"       kill_procs "$PIDS" -9    }        function kill_selenoid_procs(){           get_pids "./selenoid $SELENOID_ARGS"       kill_procs "$PIDS" -9    }        function kill_selenoid_ui_procs(){           get_pids "./selenoid-ui $SELENOID_UI_ARGS"       kill_procs "$PIDS" -9    }        function terminate() {          kill_appium_procs      kill_procs "$SELENOID_PID" -TERM      kill_procs "$SELENOID_UI_PID" -TERM    }        function start_selenoid(){          cd $SELENOID_HOME      chmod 766 selenoid          if [ "$VERBOSE" = "true" ]; then        clear_crontab        echo "Запускаем selenoid:"        trap terminate SIGINT SIGTERM        ./selenoid $SELENOID_ARGS        SELENOID_PID=$!        wait      else        SELENOID_LOG="$SELENOID_LOGS_DIR/selenoid-output-$DATE_TIME.log"        touch $SELENOID_LOG        ./selenoid $SELENOID_ARGS > $SELENOID_LOG 2> $SELENOID_LOG &      fi    }        function start_selenoid_ui(){          cd $SELENOID_HOME      SELENOID_UI_LOG="$SELENOID_LOGS_DIR/selenoid-ui-output-$DATE_TIME.log"      touch $SELENOID_UI_LOG      chmod 766 selenoid-ui      $(sleep 5; ./selenoid-ui $SELENOID_UI_ARGS > $SELENOID_UI_LOG 2> $SELENOID_UI_LOG)&    }        function createConfigs() {            cd $APPIUM_CONFIG_DIR        chmod 766 $APPIUM_CONFIG_CREATER        ./$APPIUM_CONFIG_CREATER        cd $COMMON_CONFIG_DIR        chmod 766 $SELENOID_CONFIG_CREATER        ./$SELENOID_CONFIG_CREATER $SELENOID_HOME/.env    }        function start() {          createConfigs      start_selenoid_ui      start_selenoid    }        function full_kill_procs(){      kill_appium_procs      kill_selenoid_ui_procs      kill_selenoid_procs    }        function help() {      echo "Ожидается два аргумента:"      echo "1) Обязательный команды: help|stop|start|restart"      echo "2) Необязательный флаг: --debug"    }        function clear_crontab(){      echo "Удаляем запись из crontab:"      crontab -l      crontab -r 2> /dev/null    }        function add_to_crontab(){          CRONTAB_CMD="$CRON_SETTINGS $DEVICES_WATCHER_PATH"      CRONTAB_LINE=$(crontab -l 2> /dev/null)          if [[ "$CRONTAB_LINE" == *"$CRONTAB_CMD"* ]]; then        echo "Уже был настроен crontab:"        crontab -l        exit 0      fi          if [[ "$CRONTAB_LINE" == *"$DEVICES_WATCHER_PATH"* ]]; then        clear_crontab      fi          chmod 766 $DEVICES_WATCHER_PATH      echo "Добавляем запись в crontab:"      TEMP_FILE="usbd"      echo "$CRON_SETTINGS $DEVICES_WATCHER_PATH $SELENOID_HOME/.env > $SELENOID_LOGS_DIR/cron.log" | tee $TEMP_FILE      crontab $TEMP_FILE      rm -f $TEMP_FILE    }        cd $(realpath "$0" | xargs dirname)    source .env        DATE_TIME=$(date +%Y_%m_%d_%H_%M_%S)    HELP_COMMAND=$(echo $@ | grep help)        if [ -z "$HELP_COMMAND" ]; then          IS_ARG_USE=false      DEBUG_MODE=$(echo $@ | grep debug)          if [ -z "$DEBUG_MODE" ]; then        export VERBOSE=false      else        export VERBOSE=true      fi          mkdir -p $SELENOID_LOGS_DIR          for flag in "$@"      do        case "${flag}" in              stop)                full_kill_procs            rm -f $APPIUM_CONFIG_DIR/*.json            rm -f $COMMON_CONFIG_DIR/$SELENOID_CONFIG_NAME            clear_crontab                exit 0          ;;              start)                start            IS_ARG_USE=true          ;;              restart)                full_kill_procs            start            IS_ARG_USE=true          ;;              reconfigure)                createConfigs            get_pids "./selenoid $SELENOID_ARGS"            kill -HUP $PIDS                exit 0          ;;        esac      done          if [ "$VERBOSE" = "false" ]; then            if [ "$IS_ARG_USE" = "false" ]; then          help          exit 120;        fi            sleep 6            IS_SELENOID_STARTED=$(ps -x | grep " :$SELENOID_PORT")            if [ -z "$IS_SELENOID_STARTED" ]; then          echo "Selenoid не был запущен:"          cat $SELENOID_LOG          exit 120        fi            IS_SELENOID_UI_STARTED=$(ps -x | grep " :$SELENOID_UI_PORT")            if [ -z "$IS_SELENOID_UI_STARTED" ]; then          echo "Selenoid UI не был запущен:"          cat $SELENOID_UI_LOG          exit 120        fi            add_to_crontab      fi    else      help    fi        exit 0  Шаг 4. Запуск 

Наконец, мы приблизились непосредственно к запуску Selenoid и тестированию жизнеспособности нашего окружения.

№1. Запускаем скрипт выполняющий загрузку и обновление бинарников в случае появления новой версии ./updateSelenoidVersion.sh.

./selenoid.sh start --debug

№2. В другом окне терминала запускаем наш health_check  ./check/health_check.sh 127.0.0.1 4444 <имя устройства из команды adb devices>.

Наблюдаем следующие логи:

2024/06/24 22:21:34 [-] [INIT] [Loading configuration files...] 2024/06/24 22:21:34 [-] [INIT] [Loaded configuration from config/browsers.json]  2024/06/24 22:21:34 [-] [INIT] [Logs Dir: /home/am_user2/selenoid/logs]  2024/06/24 22:21:34 [-] [INIT] [Timezone: Local]  2024/06/24 22:21:34 [-] [INIT] [Listening on :4444]  2024/06/24 22:22:18 [-] [NEW_REQUEST] [unknown] [127.0.0.1]  2024/06/24 22:22:18 [-] [NEW_REQUEST_ACCEPTED] [unknown] [127.0.0.1]  2024/06/24 22:22:18 [7] [LOCATING_SERVICE] [android] [Google Pixel 5]  2024/06/24 22:22:18 [7] [USING_DRIVER] [android] [Google Pixel 5]  2024/06/24 22:22:18 [7] [ALLOCATING_PORT]  2024/06/24 22:22:18 [7] [ALLOCATED_PORT] [35659]  2024/06/24 22:22:18 [7] [STARTING_PROCESS] [[appium --config /home/am_user2/selenoid/config/appium/08221FDD4006R1.json --port=35659]]  2024/06/24 22:22:20 [7] [PROCESS_STARTED] [258659] [1.69s]  2024/06/24 22:22:20 [7] [PROXY_TO] [http://127.0.0.1:35659]  2024/06/24 22:22:20 [7] [SESSION_ATTEMPTED] [http://127.0.0.1:35659] [1]  2024/06/24 22:22:20 [7] [SESSION_ATTEMPTED] [http://127.0.0.1:35659/wd/hub] [2]  2024/06/24 22:22:31 [7] [SESSION_CREATED] [8bf1b30e-1b37-423b-9271-5423c652ba18] [2] [12.41s]

Строка говорит о том, что сессия создана успешно:

2024/06/24 22:22:31 [7] [SESSION_CREATED] [8bf1b30e-1b37-423b-9271-5423c652ba18] [2] [12.41s]

Нюансы

Во время подготовки окружения столкнулись со следующими особенностями — с ошибками при попытке запуске на Xiaomi Mi Mix. Для китайских девайсов могут встречаться проблемы с доступом к управлению приложениями через Appium.

04-19 21:09:59.581   927   927 E libc    : Access denied finding property "ro.hardware.fp.fod" 04-19 21:09:59.581   927   927 E libc    : Access denied finding property "ro.hardware.fp.sideCap" 04-19 21:09:59.573   927   927 W surfaceflinger: type=1400 audit(0.0:3966956): avc: denied { read } for name="u:object_r:vendor_fp_prop:s0" dev="tmpfs" ino=22608 scontext=u:r:surfaceflinger:s0 tcontext=u:object_r:vendor_fp_prop:s0 tclass=file permissive=0 04-19 21:09:59.588  1716 11879 I Timeline: Timeline: App_transition_ready time:704191047  04-19 21:09:59.588  1716 11879 I Timeline: Timeline: App_transition_stopped time:704191048  04-19 21:09:59.588  5313  5313 D EventBus: [5313, u0] send(AppTransitionFinishedEvent)  04-19 21:09:59.588  5313  5313 D EventBus: [5313, u0]  -> ForcedResizableInfoActivityController [0x17a1178, P1] onBusEvent(AppTransitionFinishedEvent) 04-19 21:09:59.588  5313  5313 D EventBus: [5313, u0] onBusEvent(AppTransitionFinishedEvent) duration: 8 microseconds, avg: 20  04-19 21:09:59.591 24287 24287 D Launcher.Lifecycle: onResume:UserHandle{0},67eca74,true  04-19 21:09:59.591 24287 24287 D ScreenElementRoot: resume  04-19 21:09:59.591  1716  2230 E Pm      : install msg : Failure [INSTALL_CANCELED_BY_USER

Для решения проблемы в настройках Xiaomi необходимо включить режим разработчика и разрешить отладку по USB. Однако даже после включения этих настроек, устройства могут периодически отключать отладку или требовать повторного подтверждения.

Оптимизация USB: MIUI может включать функции оптимизации USB, которые могут прерывать соединение ADB или ограничивать доступ к устройству.

Также необходимо в конфигах Appium прописать следующие capabilities:

autoGrantPermission=false, noReset=true

Выводы

Эмуляторы подходят для масштабируемого тестирования, когда требуется запускать большое количество тестов параллельно, но при этом увеличивается нагрузка на процессор хоста и может снижаться точность тестов.

А реальные устройства подходят для тестирования с минимальной нагрузкой на процессор хоста и для получения более точных результатов, соответствующих реальным условиям использования.

Выбор между реальными устройствами и эмуляторами зависит от конкретных целей тестирования, доступных ресурсов и необходимой точности результатов. В идеале, лучше использовать комбинированный подход, чтобы воспользоваться преимуществами обоих методов.


Статья написана:
ДАРРП, клиентский путь «Платежи и переводы»:
@wanro,@ILeonteva — подготовка и отладка окружения для тестирования на Android устройствах, написание скриптов и отладка Selenoid.
ДАРРП, клиентский путь «Самозанятые:
@pbezpal— установка и настройка операционной системы.


ссылка на оригинал статьи https://habr.com/ru/articles/833136/