Setting up, debugging and automation on a physical Android device
Setting goals
In the previous article, our colleague wrote about Selenoid with Android emulators. However, this solution was a trial run and a test of Selenoid's functionality. The use of this solution revealed several problems:
An emulator is not a real device, and it is possible that various defects in our application will be missed. There are many Android devices with different OS versions, screens, processors, and other characteristics. Testing on real devices helps to ensure that the application works correctly on as many configurations as possible.
The host CPU load is significantly higher due to the need to emulate the hardware and software components of the device. This can lead to a decrease in overall system performance, especially when running a large number of emulators.
Not all device performance features are taken into account, including performance, power consumption, and network connection features.
In short, reality is better than simulation.
However, not everything is so smooth. Deploying Selenoid on real hardware has some problems:
Currently, there is no universal and ready-to-use solution for server deployment that would automatically update the Selenoid hub configuration depending on the connection/disconnection of Android devices to the USB port. In most cases, test engineers are forced to manually configure the server and configurations for each new device, which requires significant time and labor costs.
After connecting/disconnecting new devices, configuration files must be updated manually, which increases the risk of errors and requires additional resources. Automating the process of connecting and configuring new devices requires custom scripts that must be developed and maintained. Implementing such solutions can significantly improve the quality of testing and reduce the costs of conducting it.
The solution to the difficulties is to deploy Selenoid without Docker on the server to work with real devices, and also to provide full automation when creating configurations for Android devices connected via a USB port.
I will tell you about this and other tasks of deploying infrastructure for running tests on real Android devices on Linux and Mac in this article. I will also demonstrate the custom scripts we implemented for generating configuration files for connected devices.
Let's start with virtualization.
Step 1. Virtualization on the server
First of all, we need to enable virtualization on our Linux server.
№1. Reboot the computer. When turning on, constantly press the hint button to get into the BIOS. If there is no hint, press “F9” or “F10”.
№2. Select “Security” — “System Security”.
№3. Find “Intel Virtual Technology”. Select “Enabled” using the “→” keys, and then press “F10”.
#4. Let's go back to the “File” menu and select “Save Changes and Exit”.
Step 2: Setting up the server environment
In the next step, we will move on to installing the required software to configure the server before running automated tests.
№1. Create a folder on the server where Selenoid and Selenoid UI will be located, as well as scripts for automated creation of configuration files for Selenoid and Appium.
Example:
mkdir selenoid
№2. Go to the directory that was created in the previous step.
cd selenoid
#3. Let's move on. to the Selenoid repository. Copy the link to the binary and download the binary to the server (may differ from what is indicated in the example).
wget -o selenoid бинарник
№4. Follow the link to the Selenoid UI repositorycopy the link to the binary. Also download the binary to the server.
wget -o selenoid-ui бинарник
№5. Install 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. Install Node.js.
sudo apt-get install -y nodejs
№7. Install npm.
sudo apt install -y npm
№8. Install Appium.
npm install -g appium
№9. Install the uiautomator driver.
appium driver install uiautomator2
№10. Install the required version of Java Azul. Detailed installation instructions.
No. 11. Let's install the 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. Create a file with environment variables in the user's home directory.
touch $HOME/.bash_profile
№13. In the nano text editor, we write the following variables:
№14. Exporting environment variable settings from a file.
source .bash_profile
№15. Install adb.
sudo apt-get install adb
#16. Initialize a local repository and link it to the central one.
git init
git remote add origin <Ссылка на ваш репозиторий>
git pull origin master
Step 3. Listing bash scripts
After implementing and debugging the scripts, our repository had the following structure:
healthCheck.sh
This script is designed to create a new Appium session using the command curl
. Serves to check the deployed 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"
This script does the following:
Defines unique identifiers for searching scripts.
Exports environment variables required for Selenoid and Appium to work.
Searches and determines absolute paths to scripts and configurations.
Exports additional environment variables.
Prints variable values in debug mode if the script is run with an argument
--debug
.
Thus, the script configures the environment for working with Selenoid and Appium, providing the ability to search and use the necessary scripts and configurations.
Listing:
#!/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
This script automates the process of monitoring connected Android devices and reconfigures Selenoid in case of changes in the list of devices. It loads environment variables, checks the current list of devices, compares it with the previous state and, if necessary, restarts Selenoid, updating the configuration.
#!/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
Tracking new version of Selenoid and Selenoid UI. Downloading and updating binaries when a new version appears.
#!/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
This script automatically generates configuration files for Appium based on connected Android devices. It retrieves a list of device UDIDs using adb, creates a configuration file for each device with the appropriate parameters, and saves it in JSON format.
#!/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
This Bash script is designed to create a Selenoid configuration file based on connected Android devices.
Listing:
#!/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
Upper orchestration script that controls the scripts described above. Allows you to start, stop, reconfigure Selenoid with generation of the necessary configuration files.
#!/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. Запуск
Finally, we are getting closer to actually running Selenoid and testing the viability of our environment.
№1. Run a script that downloads and updates binaries when a new version appears ./updateSelenoidVersion.sh
.
./selenoid.sh start --debug
№2. In another terminal window, we launch our health_check ./check/health_check.sh 127.0.0.1 4444 <имя устройства из команды adb devices>
.
We observe the following logs:
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]
The line indicates that the session was created successfully:
2024/06/24 22:22:31 [7] [SESSION_CREATED] [8bf1b30e-1b37-423b-9271-5423c652ba18] [2] [12.41s]
Nuances
During the preparation of the environment, we encountered the following features – errors when trying to launch on Xiaomi Mi Mix. For Chinese devices, there may be problems with access to application management via 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
To solve the problem, you need to enable developer mode and allow USB debugging in Xiaomi settings. However, even after enabling these settings, devices may periodically disable debugging or require re-confirmation.
USB Optimization: MIUI may include USB optimization features that may interrupt ADB connection or restrict access to the device.
It is also necessary to register the following capabilities in the Appium configs:
autoGrantPermission=false, noReset=true
conclusions
Emulators are suitable for scalable testing where you need to run a large number of tests in parallel, but this increases the load on the host processor and may reduce the accuracy of the tests.
And real devices are suitable for testing with minimal load on the host processor and for obtaining more accurate results that correspond to real-world usage conditions.
The choice between real devices and emulators depends on the specific testing goals, available resources, and the required accuracy of the results. Ideally, it is better to use a combined approach to take advantage of the advantages of both methods.
The article was written by:
DARRP, client path “Payments and transfers”:
@wanro,@ILeonteva — preparing and debugging the environment for testing on Android devices, writing scripts and debugging Selenoid.
DARRP, client path “Self-employed:
@pbezpal— installation and configuration of the operating system.