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:

  1. 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.

  2. 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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *