UiAutomator2+Pytest+Allure+PO Model for Android Automated Testing

Introduction to Uiautomator2

uiautomator2 Is a library for UI automation of Android devices using Python. The underlying layer is based on Google uiautomator, which provides a library of uiautomators that can take and manipulate any control property of any APP on the screen

Environment Setup

  1. Install JDK, see This article

  2. Install Android SDK, build tool version needs to be greater than 24, download and install toolkit note version, SDK configuration refer to This article

  3. Install uiautomator2

    pip install uiautomator2	# Install uiautomator2
    uiautomator2 version		# View Version
    uiautomator2 --help			# view help
    
  4. Install a viewer for element positioning assistance

    pip install weditor	# Install weditor
    weditor -v			# View Version
    weditor --help		# view help
    

    Start the viewer, type weditor at the command line, or create weditor desktop shortcut weditor --shortcut to run the program from the desktop icon

Connect device

The first time you connect to the device, [ATX] and [com.github.uiautomator.test] are installed.

  1. Mobile phone on [developer mode], switch on [USB debugging mode] data cable to connect to mobile phone, select [Transfer files]

  2. cmd to the command line page, adb devices to see if the device is connected successfully, adb-related commands to see This article

  3. When the device is connected successfully, open the viewer, click on Connect on the viewer page, a green leaf appears to indicate the connection is successful, and a mobile phone projection screen appears on the left (mobile phone is bright)

  4. Connect your phone through a Python script:

    import uiautomator2 as u2	# Import the uiautomator2 library and rename it u2
    driver = u2.connect()		# Connect mobile phone, if only one mobile phone is connected to the computer, no device information is required
    print(driver.info)			# Print Device Information
    

    Enter the following system information to indicate that the connection to your mobile phone is successful and you can start using uiautomator2 Library

    {'currentPackageName': 'com.oppo.launcher', 'displayHeight': 2297, 'displayRotation': 0, 'displaySizeDpX': 360, 'displaySizeDpY': 800, 'displayWidth': 1080, 'productName': 'OnePlus9R_CH', 'screenOn': True, 'sdkInt': 30, 'naturalOrientation': True}
    
    driver = u2.connect("48fd6742")	# If your computer is connected to more than one mobile phone, you need to add a device serial number
    

Common operations

driver = u2.connect("48fd6742")	# Connect device
driver.screen_on()  # Light up the screen
driver.screen_off() # Screen off
driver.unlock() 	# Unlock, machine test found that password entry page could not be located, and some APP s could not synchronize login page to cause the situation can not be located
print(driver.app_info("com.sankuai.meituan"))   # Get information about the specified APP
print(driver.app_list_running())# List all running APP s
print(driver.app_current()) 	# Get the currently open APP information
print(driver.window_size()) 	# Get screen size
print(driver.device_info)   	# Get detailed device information
print(driver.serial)	    	# Get device serial number
print(driver.wlan_ip)   		# Get device IP address
driver.press("back")			# Click on the return key
driver.open_notification()  	# Open notification bar message page, close notification bar with slideshow
driver.open_quick_settings()    # Open Quick Settings in Notification Bar
driver.info.get("screen_on")	# Gets if the current screen is on
driver.swipe(552,2066,552,700)  # Slide the screen in absolute coordinates for a default sliding time of 0.5s
driver.swipe_ext("up",scale=0.5)# Slide the screen in direction, setting the sliding distance to 50% of the screen width, defaulting to 90%
driver.open_url("https://www.baidu.com ") #Call the default browser directly and visit the specified website
driver(description="information").click()  		# Click SMS APP
driver.double_click(0.375, 0.496)		 # Double-click the specified relative coordinates
driver(description="information").long_click(1) 	# Long press SMS APP, default long press 0.5s
driver(description="Short message").send_keys("12")# Locate elements and enter text
driver(description="Short message").set_text("A12")# Also locate elements and enter text
driver(description="Short message").clear_text()	# Empty the entered information
driver(resourceId="com.sankuai.meituan:id/passport_mobile_phone").get_text()	# Get Text Content
driver(text="Meituan").drag_to(0.375, 0.375,duration=1) # Drag Meituan APP to specified location for 1s, default 0.5s
driver.screenshot(r"D:\Download\test.png")			# Save screenshots to specified location
driver.push(r"C:\Download\test.png","/sdcard/")		# Push screenshots to your mobile phone
driver.pull("/sdcard/rider.txt","rider.txt")		# Transfer files from your mobile phone to your computer
driver.app_icon("com.sankuai.meituan").save(r"D:\Download\icon.png") # Get the APP icon and save it to the specified location
driver.app_start("com.sankuai.meituan",stop=True)	# Specify the package name to start the APP and end the application run state before starting
driver.implicitly_wait(10)  # Native operation, implicit wait, globally valid
driver(description="Tien Tie Red Bag").exists()		   # Determines whether an element appears, returns True, or returns False
driver(description="Film/show").wait(timeout=5)		# Wait for element to appear, timeout is 5s
driver(description="Running Legs").wait_gone(timeout=5)	 # Wait for element to disappear, timeout time is 5s
driver.app_wait("com.sankuai.meituan",timeout=30,front=True)		# Wait for program to start foreground running, default timeout 20s
driver.wait_activity("com.meituan.mmp.lib.mp.MPActivity0",timeout=5) # Wait for active page loading to complete, default timeout of 10s
driver.app_install(r"D:\Download\meituan.apk")	# Install using a local installation package
driver.app_install("http://www.meituan.com/mobile/download/meituan/android/meituan?from=new ") #Download and install online
driver.app_uninstall("com.sankuai.meituan")		# Unload APP
driver.app_stop("com.sankuai.meituan")			# Close APP

Element Positioning

The attributes of the selected elements can be used to locate. The purpose of these attributes is to get a unique locating element. Attributes can be combined to locate. Common locations are as follows

driver(text="Sports Health")			# Locate by text
driver(description="My")	 # Locate by description
driver.xpath('//*[@text="Today's special"]') 				 #  Locate by Xpath
driver(className="android.widget.ImageView")		# Locate by className
driver(resourceId="com.sankuai.meituan:id/button")	# Locate by ID
driver(resourceId="com.android.systemui:id/tile_label", text="Power saving mode")	# Locate by combination

You can also locate nodes in the hierarchy as follows

# In android. Widget. Find className=android in the sibling node of GridLayout. View. Elements of View
driver(className="android.widget.GridLayout").sibling(className="android.view.View")
# In android. Widget. Find the fourth className=android in a child node of GridLayout. View. Elements of View
driver(className="android.widget.GridLayout").child_by_instance(3,className="android.view.View")
# In android. Widget. Find className=android in the child node of LinearLayout. Widget. TextView element with text "cycling", allow_scroll_search=True means allow sliding screen to find
driver(className="android.widget.LinearLayout")\
    .child_by_text("Cycling",allow_scroll_search=True,className="android.widget.TextView")
# In android. Widget. Find className=android in the child node of FrameLayout. View. Element of ViewGroup described as Comment Illustration
driver(className="android.widget.FrameLayout")\
    .child_by_description("Comment illustration",allow_scroll_search=True,className="android.view.ViewGroup")
# Locate elements by up/down/left/right, as follows: Locate and click APP to the right of NetEase Cloud Music APP
driver(text="NetEase cloud music").right(className="android.widget.TextView").click()

Assertion

Use assert to determine whether the actual results are consistent with the expected results

# Get hints about login failure
text = driver(resourceId="com.sankuai.meituan:id/passport_account_tips").get_text()
# Determine if the prompt is correct
assert text == "Account or password error, please re-enter"
# Since the growth value does not appear until the login is successful, check to see if the growth value element exists to determine whether the login is successful
assert driver(resourceId="com.sankuai.meituan:id/grouth_tv").exists

Use toast for assertions with no focus, a time-limited prompt box

tips = driver.toast.get_message()	 # Get error message
assert "ERROR Incorrect username or password" in tips		# Determine if there is a "user name or password error" in the information you get

Case Demonstration

Example 1: Setting the graphics authentication code and unlocking, the real machine could not be implemented, and could not enter the unlocking interface, which may be a permission problem, and was executed successfully with the simulator

import time
import uiautomator2 as u2

driver = u2.connect()  # Connect device
driver(text="Set up").click()  # Click Set APP
# _Locate elements using node mode
driver(className="android.widget.LinearLayout").child_by_text("security", allow_scroll_search=True,className="android.widget.TextView").click()
driver(resourceId="android:id/title", text="Screen lock").click()  # Locate elements using ID
# _Locate elements using xpath
driver.xpath('//*[@resource-id="com.android.settings:id/list"]/android.widget.LinearLayout[3]/android.widget.RelativeLayout[1]').click()
driver.swipe_points([(0.223, 0.658), (0.5, 0.832), (0.5, 0.652), (0.78, 0.487)], 0.2)  # Multipoint Slide, Set Graphic Password
driver(resourceId="com.android.settings:id/footerRightButton").click()  # Click Continue
driver.swipe_points([(0.223, 0.658), (0.5, 0.832), (0.5, 0.652), (0.78, 0.487)], 0.2)
driver(resourceId="com.android.settings:id/footerRightButton").click()  # Click OK
driver(resourceId="com.android.settings:id/redaction_done_button").click()  # Click Finish
driver.press("power")  # Click on the power key
time.sleep(3)  # Too fast will only turn off and light up the screen, so wait 3 seconds to lock the device
driver.unlock()  # Unlock operation
driver.swipe_points([(0.273, 0.728), (0.5, 0.867), (0.5, 0.728), (0.719, 0.591)], 0.2)  # Draw graphic password
assert driver(text="security").exists # Determine whether to return to a secure page

Example 2: Take Meituan APP as an example, to test login, view orders, search for commodities related operations, because there are a large number of positioning element operations, it is not easy to maintain without using the framework, so use Pytest+Allure and PO model to achieve this operation, the structure is as follows:

In the case of login, create the base class BasePage first. Py file, encapsulates some common methods, such as: locate, click, enter, clear, get text information, assert, etc.

import re

class BasePage():  # Constructor
    def __init__(self, driver):
        self.driver = driver

    def click(self, element):  # click
        if str(element).startswith("com"):  # Use ID location if starting with com
            self.driver(resourceId=element).click()  # Click to locate elements
        elif re.findall("//", str (element): #If // begins with regular expression matching and then positioned with xpath"
            self.driver.xpath(element).click()  # Click to locate elements
        else:  # If neither of the above is true, use description to locate
            self.driver(description=element).click()  # Click to locate elements

    def click_text(self, element):  # Click to locate according to text
        self.driver(text=element).click()  # Click to locate elements

    def clear(self, element):	# Empty the contents of the input box
        if str(element).startswith("com"):  # Use ID location if starting with com
            self.driver(resourceId=element).clear_text()  # Clear Text
        elif re.findall("//", str (element): #If // begins with regular expression matching and then positioned with xpath"
            self.driver.xpath(element).clear_text()  # Clear Text
        else:  # If neither of the above is true, use description to locate
            self.driver(description=element).clear_text()  # Clear Text

    def find_elements(self, element, timeout=5):  # Find Elements
        is_exited = False
        try:
            while timeout > 0:
                xml = self.driver.dump_hierarchy()  # Get Page Hierarchy
                if re.findall(element, xml):
                    is_exited = True
                    break
                else:
                    timeout -= 1
        except:
            print("Element not found!")
        finally:
            return is_exited

    def assert_exited(self, element):  # Assert whether an element exists
        assert self.find_elements(element) == True, "The assertion failed,{}Element does not exist!".format(element)

Then create a way to encapsulate the appropriate page actions, such as a login operation, and create a login_page.py file, this layer is mainly the encapsulation operation flow

from base.basepage import BasePage	# Importing methods encapsulated in base classes
import allure	# Import allure

class LoginPage(BasePage):
    # Element Location, Element Location Information
    agreement = "com.sankuai.meituan:id/permission_agree_btn"
    get_loc_info = "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
    get_pho_perm = "com.android.permissioncontroller:id/permission_deny_button"
    notice = "Not yet"
    login_in_now = "com.sankuai.meituan:id/button"
    pwd_login = "com.sankuai.meituan:id/user_password_login"
    username = "com.sankuai.meituan:id/passport_mobile_phone"
    password = "com.sankuai.meituan:id/edit_password"
    tick = "com.sankuai.meituan:id/passport_account_checkbox"
    click_login = "com.sankuai.meituan:id/login_button"
    mine = "My"
    growth = "com.sankuai.meituan:id/grouth_tv"

    # Behavior, Page Action
    @allure.story('Test Meituan APP')
    @allure.title("APP Sign in")
    def login(self,user,pwd):			# Login process, for example, running APP for the first time
        self.click(self.agreement)		# Agree to use the APP protocol
        self.click(self.get_loc_info)	# Get the location information and select [Allow when using]
        self.click(self.get_pho_perm)	# Get phone permissions, select Reject
        self.click_text(self.notice)	# Whether to turn on message notifications, select "Not yet"
        self.click(self.login_in_now)	# Click on [Sign in now] on the first page
        self.click(self.pwd_login)		# Login mode selection [password login]
        self.input(self.username,user)	# enter one user name
        self.input(self.password,pwd)	# Input password
        self.click(self.tick)			# Check to agree to user agreement
        self.click(self.click_login)	# Click Login
        self.click(self.mine)			# Click [My]
        self.assert_exited(self.growth) # Asserts that the growth value is only displayed because the login was successful, so whether or not the login was successful is determined by the presence of this element
        self.click(self.homepage)		# Cut to Home Page

Finally, pass in the parameter and perform the use-case operation, because the Pytest framework is used, so this file takes care of the format, and the file name needs to start with test to create test_login.py file

import uiautomator2 as u2
import pytest
from pageobject.login_page import LoginPage	# Import the classes from the previous action flow

# Connect your phone and start APP for login
class TestLogin:	# Class begins with Test
    def test_login(self):   # Method to test_ Or _ Beginning of test
        driver = u2.connect("48fd6742")
        driver.app_start("com.sankuai.meituan", stop=True)
        login_page = LoginPage(driver=driver)
        login_page.login("16666666666","123456abc")

Once the login operation is complete, you can execute the test.

The same is true for search operations, which write common methods directly to the base class file and create search_separately by the process Page. Py file

from base.basepage import BasePage	# Import base class and call public methods directly
import allure

class SearchPage(BasePage):
    # Element Positioning Information
    inputfield = "com.sankuai.meituan:id/search_edit_flipper_container"
    clicksearch = "com.sankuai.meituan:id/search"
    inputvalue = "com.sankuai.meituan:id/search_edit"
    back = "com.sankuai.meituan:id/back"

    allure.title("search")
    def search(self,value):					# Search Operational Process
        self.click(self.inputfield)			# Click on the search box
        self.clear(self.inputvalue)			# Empty the search box on each search front
        self.input(self.inputvalue,value)	# Locate the input box and enter keywords
        self.click(self.clicksearch)		# Click the Search button
        self.click(self.back)				# Return to previous level
        self.click(self.back)				# Return to Home Page

Create a file test_to pass parameters and perform search operations Search. PY

import uiautomator2 as u2
import pytest
from pageobject.search_page import SearchPage	# Import the classes from the previous action flow

class TestSearch:
    keyword = ["Tea with milk","Table tennis","Scenic spot"]
    @pytest.mark.parametrize("value",keyword)	# Using pytest parameterization, search for three keywords
    def test_search(self,value):	
        driver = u2.connect("48fd6742")
        driver.app_start("com.sankuai.meituan")
        search_page = SearchPage(driver=driver)
        search_page.search(value)

Use allure to execute tests and generate reports

pytest --alluredir=./result testcases # Execute all test cases under the testcases file and generate intermediate results into the result directory
allure serve ./result	# Generate final report

Check out the usage of Pytest, Allure This article

See about PO models This article

Keywords: Python Android Android Studio

Added by zoki on Sun, 02 Jan 2022 18:24:42 +0200