Selenoid без симуляции: настройка, отладка и автоматизация на физическом Android-устройстве
Постановка задач
В предыдущей статье наш коллега писал про Selenoid с Android-эмуляторами. Однако это решение было пробой пера и проверкой работоспособности Selenoid. Применение данного решения выявило несколько проблем:
Эмулятор — это не реальное устройство, возможен пропуск различных дефектов в нашем приложении. Есть много Android-устройств с различными версиями ОС, экранами, процессорами и другими характеристиками. Тестирование на реальных устройствах помогает убедиться, что приложение работает корректно на максимально возможном количестве конфигураций.
Загруженность процессора хоста значительно выше из-за необходимости эмуляции аппаратных и программных компонентов устройства. Это может привести к снижению общей производительности системы, особенно при запуске большого количества эмуляторов.
Не учитываются все особенности работы устройства, включая производительность, энергопотребление и особенности сетевого соединения.
Если кратко, лучше реальность, чем симуляция.
Однако не всё так гладко. У развёртывания Selenoid на реальном железе, есть некоторые проблемы:
Сейчас нет универсального и готового к использованию решения для развёртывания сервера, которое бы автоматизировано обновляло конфигурацию Selenoid хаба в зависимости от подключения/отключения устройств Android в USB-порт. В большинстве случаев инженеры по тестированию вынуждены вручную настраивать сервер и конфигурации для каждого нового устройства, что требует значительных временных и трудовых затрат.
После подключения/отключения новых устройств конфигурационные файлы необходимо обновлять вручную, что увеличивает риск ошибок и требует дополнительных ресурсов. Для автоматизации процесса подключения и конфигурирования новых устройств требуются кастомные скрипты, которые необходимо разрабатывать и поддерживать. Внедрение таких решений может значительно улучшить качество тестирования и сократить затраты на его проведение.
Решение трудностей — развернуть 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— установка и настройка операционной системы.