Implementation of deep UI traversal tool code implementation based on Appium

Series of articles:

Implementing a deep UI traversal tool based on Appium

Implementing a deep UI traversal tool based on Appium (2)

Implementing a deep UI traversal tool based on Appium (3)

I finally came to the place where the code was written. In advance, all the code will be put on github as my open source project and will be maintained in the future. I will post the open source address at the end of the article.

Let's create the project appium_uicrawler, and then create the directory mentioned in the previous article,

In the configuration file, we configure some commonly used data. is used as the main entry of the project. The apk to be tested is placed in installapk, and the installation and package name are all obtained through this.

The above is the approximate directory structure. Next, it is to write some commonly used configuration files as some configurations for running traversal tests, which are written in yaml files to facilitate maintenance. This configuration file is different from config. config is used for some common data in the code, and the yaml file is the traversal strategy. Later, we initialize the UI traversal configuration execution according to the yaml file configuration.

The configured yaml is named config.yaml, placed in the file path, and executed to obtain the configuration file. A flexible path can be used in the later stage. A bottom-up strategy is configured here. If the latest one cannot be loaded, we will execute it according to this bottom-line strategy. Can be executed in the default configuration, the initialization strategy is as follows

  #Turn on automatic login
  AUTOLOGIN: false

  #Android login related elements and operations
        XPATH: '//*[@resource-id="login"]'
        ACTION: input
        VALUE: '13691034101'
        XPATH: '//*[@resource-id="password"]'
        ACTION: input
        VALUE: '123456'
        XPATH: '//*[@resource-id="test_login_button"]'
        ACTION: click
        VALUE: '1'
#Global configuration

  #hit count
  #time to find an element
  #Find the interval between elements
  #Whether to ignore Crash, when set to true,
  #Running time limit (minutes)
  #Number of swipes Whether the page is swiped without change,
  SCPRE_AUTO: true
  #Number of swipes Number of interactions without page change
  #Controls whether to generate screenshots. When false, no screenshots will be generated, but it can improve the running speed.
  #Control whether to generate video
  VIDEO: true
  #The screenshot shows the number of steps when crashing
  #Traversal depth
  MAX_DEPTH: 800
  ANDROID_BOTTOM_TAB_BAR_ID: '@resource-id="com.qihoo.browser:id/title"'
  #Type of input text
    - android.widget.EditText

  #The text to be entered is in a 1:1 ratio
    - 'leizi'
    - '123'

  #When the following text appears in the UI element, trigger the back key (iOS triggers the return operation by swiping from left to right)
    - return
    - Privacy Agreement

  #When pp jumps to the following app, trigger the back key

  #Trigger the back key when the following Activity is currently encountered

  #Controls that contain the following text are not clicked
    - customer service
    - quit
    - Telephone
    - Refuse
    - Photograph
    - prohibit
    - call
    - low power mode

  #In addition to the package name of the APP itself, it is judged whether to jump out of the APP according to the following package names. When the app jumps to the following app, it is considered legal, and the traversal operation will continue to be implemented.

  #Whitelist, when encountering a control containing the following text, it will be clicked multiple times (by default, all controls are only clicked once), which is used in UI traversal
    - Sure
    - allow
    - Cancel
    - submit
    - Cancel

  #Do not click the following types of controls and their child elements
    - XCUIElementTypeKeyboard

  #Do not click on elements of the following types
    - XCUIElementTypeApplication

  #Do not click on elements of the following types
    - android.widget.ImageButton

After initializing the Ui traversal execution time, whether to log in automatically, etc., we will write a file to read the configuration.

import yaml

class Parse(object):
    def __init__(self, filepath):
        self.filepath = filepath
        self.reslut = {}
        self.reslut = self.__init()

    def __init(self):
        file = open(self.filepath, 'r', encoding="utf-8")
        file_data =
        self.reslut = yaml.load(file_data)
        return self.reslut

    def getlogin(self):
        return self.reslut['LOGINELEMENT']

    def getgeneral(self):
        return self.reslut['GENERAL']

    def getlist(self):
        return self.reslut['LIST']

    def get_tar(self):
        return self.getlist()['ANDROID_BOTTOM_TAB_BAR_ID']

    def get_text_input(self):
        return self.getlist()['INPUT_CLASS_LIST']

    def get_sendText(self):
        return self.getlist()['INPUT_TEXT_LIST']

    def get_back(self):
        return self.getlist()['PRESS_BACK_TEXT_LIST']
    def get_back_packname(self):
        return self.getlist()['PRESS_BACK_PACKAGE_LIST']

    def get_activity(self):
        return self.getlist()['PRESS_BACK_ACTIVITY_LIST']

    def get_block(self):
        return self.getlist()['ITEM_BLACKLIST']

    def get_white(self):
        return self.getlist()['ITEM_WHITE_LIST']

    def get_not_click(self):
        return self.getlist()['ANDROID_EXCLUDE_TYPE']

    def get_ios_bar(self):
        return self.getlist()['IOS_EXCLUDE_BAR']

    def get_run_time(self):
        return self.getgeneral()['RUNNING_TIME']

    def get_ignor_crash(self):
        return self.getgeneral()['IGNORE_CRASH']

    def get_find_element_wait(self):
        return self.getgeneral()['INTERVAL_SEC']

    def get_find_element_timeout(self):
        return self.getgeneral()['DEFAULT_WAIT']

    def get_click_ccont(self):
        return self.getgeneral()['MAX_CLICK_COUNT']

    def get_max_deep(self):
        return self.getgeneral()['MAX_DEPTH']

    def crashPic(self):
        return self.getgeneral()['CRASH_PIC_COUNT']

    def screen(self):
        return self.getgeneral()['SCREENSHOT_COUNT']

    def vido(self):
        return self.getgeneral()['VIDEO']

    def issceern(self):
        return self.getgeneral()['SCREEN_SHOT']
    def opearlogin(self):
        for item in reslut:
            for key,value in item.items():
        return all_login

    def getmonkeyConfig(self):
        return self.reslut['MONKEYCONFIG']

    def auto_loggin(self):
        return  self.getlogin()['AUTOLOGIN']

    def  scoreauto(self):
        return  self.getgeneral()['SCPRE_AUTO']

    def autoscorecount(self):
        return  self.getgeneral()['SCPRE_NUM']

After the writing is completed, we will encapsulate a preliminary script to read the configuration. The above is the reading of the script. There is no logic. It is to read the file normally, and then read the configuration of yaml. yaml file reading is also very simple, and after reading, it is a dict, just parse the corresponding dict.

In the process of running, it is also necessary to obtain the test log of the execution process. We encapsulate a method for obtaining the log during the execution process. Create

import os
def run_adb_log(dev, path):
    implement adb Obtain log
    cmd = 'adb -s {} shell logcat -c'.format(dev)
    filepath = os.path.join(path, dev)
    cmdlog = 'adb -s {} logcat -v threadtime >{}.log'.format(dev, filepath)

Here, just use python to execute the adb command. When starting, if you want to get the log, you can use a separate thread/process to handle this.

For the log of the running process, that is, to record the log of our code, create

import os
import logbook
from logbook.more import ColorizedStderrHandler
from functools import wraps
from common.Makecasenum import call_num
check_path = '.'
LOG_DIR = os.path.join(check_path, 'testlog')
if not os.path.exists(LOG_DIR):
def get_logger(name='UI_bianli', level=''):
    """ get logger Factory function """
    ColorizedStderrHandler(bubble=False, level=level).push_thread()
    filename = name + call_num
        os.path.join(LOG_DIR, '%s.log' % filename), bubble=True, encoding='utf-8').push_thread()
    return logbook.Logger(name)
LOG = get_logger(level='INFO')

Makecasenum used here

import datetime
import hashlib

def make_md5(make_user):
    md5make ='md5', make_user.encode('utf-8')).hexdigest()
    return md5make
call_num = make_md5('%Y-%m-%d-%H:%M'))

An md5 formed according to the timestamp is used as an identifier.

This time, I shared the configuration of the general strategy, the module to read, the module to run the log, and the module to record the mobile phone log. The next article will share the code of webdriver encapsulation and so on.

In addition, the appium environment construction can refer to

This article takes you through the pits encountered in building the appium test environment on mac

All code addresses:

Find problems and solve them. If you encounter problems, solve them slowly.

Posted by TobesC on Sat, 21 May 2022 07:12:43 +0300