appium---PO model design

We should all have heard of the PO model when we do automation, so what is the PO model? What is the role of PO model in automation?

PO model

PO is actually: Page Object Model, also known as POM Model. PO is actually a design pattern, which has become popular in automated testing to enhance test maintenance and reduce code duplication. Page object is an object-oriented class, which is used as the interface of the page and the device under test. Then, as long as the tests need to interact with the UI of the page, these tests will use the method of the page object class. The advantage is that if the UI of the page changes, there is no need to change the test itself, just change the code in it. The page object needs to be changed. Subsequently, all changes that support the new UI are in one place. In fact, speaking of low is a sentence: treat each page as a class, separate the element information on the page from the code operation, and then we manage the code and element content later

PO stratification

PO layering means layering our automation code, which can be divided into the following basic levels:

1. Base layer: encapsulate some positioning methods, click, input, sliding and other operations

2. Common layer: get element method, operate element method, get CMD information and other methods

3. Business layer: page element information.

4. Logic layer: some functions, such as login and registration.

5. Data layer: test information storage place

emmm, here is our own understanding. Of course, everyone may have a different understanding of PO layering, and the bosses may share more details than I do here. (share and learn together)

It's quiet and simple here. Take the project to actually introduce the content of PO

First, let's look at how to write test cases before

# coding:utf-8
from appium import webdriver
import time
import unittest
class login(unittest.TestCase):
    def setUp(self):
        desired_caps = {
                        'platformName': 'Android',  # Test version
                        'deviceName': 'emulator-5554',   # Device name
                        'platformVersion': '5.1.1', # System version
                        'appPackage': 'com.taobao.taobao', #Package name of apk
                       'appActivity': 'com.ali.user.mobile.login.ui.UserLoginActivity', # launcherActivity of apk
                        'noReset':True , # Clear cache
                        }
        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    def tearDown(self):
        self.driver.quit()
    def test01(self):
        '''
        Account password error
        '''
        self.driver.implicitly_wait(40)
        self.driver.find_element_by_id("com.eboss2.sbs:id/tv_username").send_keys("22222")
        time.sleep(2)
        self.driver.find_element_by_id("com.eboss2.sbs:id/tv_password").send_keys("33333333")
        time.sleep(2)
        self.driver.find_element_by_id("com.eboss2.sbs:id/btn_login").click()
        time.sleep(5)
        x = self.driver.find_element_by_id("com.eboss2.sbs:id/shopName_TextView").text
        print(x)
        self.assertEqual(x,'Please enter the correct mobile phone number')
if __name__ == '__main__':
    unittest.main()

I believe that most students will write test cases like this when they write code for the first time

PO model design framework

Someone must ask? What is an automation framework? What are the benefits of an automation framework? Let's not say the answer in this place. Let's write it later

First of all, the framework designed here is listed as a whole and analyzed one by one

appium_python    # Target Engineering
    
    - case        # Case storage
        test_login.py    # Write use cases

    - common    # Common method
        appium_start.py    # Start appium
        Base.py    # Encapsulate basic content
        dos_cmd.py    # cmd execution
        HTmlTestRunner.py    # Report document
        logger.py    # journal
        read_yaml.py    # Read yaml file
    
    - config    # Page element storage
        appium.py    # login page storage
        
    - function     # Function point
        login.py    # Login logic
        
    - logs    # Log storage content
        
    - pages    # Get page element information
        login_page.py    # Get login element information
        
    -report    # Report storage place
        
    runTest.py    # Main function

We need so much content here to complete the above simple operation.

config directory
Here we mainly store some page element information. We also wrote two methods to encapsulate page elements

# appium.yaml
LoginPage:
  dec: Sign in
  locators:
    -
      name: user name
      type: id
      value: com.taobao.taobao:id/aliuser_login_mobile_et
    -
      name: password
      type: android
      value: resourceId("com.taobao.taobao:id/aliuser_register_sms_code_et")
    -
      name: Login button
      type: className
      value: android.widget.Button

Imagine if we need to read the element information when the element information is available. When reading element information, do you need to list the elements of each page through the method of PO model

common directory
The common directory contains some common parts, such as reading yaml methods, executing cmd contents, and common methods in appium

# read_yaml.py

import yaml
import os
class   GetYaml():
    def __init__(self,file_path):
        # Determine whether the file exists
        if os.path.exists(file_path):
            self.file_path = file_path
        else:
            print('Can't find%s File path'%file_path)
        self.data = self.read_yaml()
    def read_yaml(self):
         with open(self.file_path,'r',encoding='utf-8')as f:
            p = f.read()
            return p
    def get_data(self,key=None):
        result = yaml.load(self.data,Loader=yaml.FullLoader)
        if key == None:
            return result
        else:
            return result.get(key)

if __name__ == '__main__':
    read_yaml = GetYaml('E:/appium_python/config/appium.yaml')
    xx = read_yaml.get_data('LoginPage')
    print(xx['locators'])

pages directory
Here, each class represents a page to get all the information on the page

# coding:utf-8
from common.Base import BaseApp
import os
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.loger import Logger
path = os.path.dirname(os.path.realpath(__file__))
yaml_path = os.path.join(os.path.join(os.path.dirname(path),'config'),'appium.yaml')
class Login_element:
    def __init__(self,driver):
        self.log = Logger('element.py')
        self.driver = driver
        self.get_element = BaseApp(self.driver)
    def user_element(self):
        ''' Get user name element'''
        self.log.info('Getting user name element information---------------------------------------')
        element = self.get_element.get_element(yaml_path,'LoginPage')['locators'][0]
        self.log.info('The user name element information is:%s'%element)
        return element

    def password_element(self):
        ''' Get password element'''
        self.log.info('Getting user name element information-------------------------------------')
        element = self.get_element.get_element(yaml_path,'LoginPage')['locators'][1]
        self.log.info('The password element information is:%s'%element)
        return element

    def login_boot(self):
        ''' Get login button element'''
        self.log.info('Getting user name element information-------------------------------------')
        element = self.get_element.get_element(yaml_path,'LoginPage')['locators'][2]
        self.log.info('The login button element information is:%s'%element)
        return element

    def toast(self,message):
        '''obtain toast information'''
        toast_loc = ("xpath", ".//*[contains(@text,'%s')]"%message)
        element = WebDriverWait(self.driver, 30, 0.1).until(EC.presence_of_element_located(toast_loc)).text
        return element

All the use case elements of the page have been obtained, so we can encapsulate some operation contents, such as login and registration, and then store our data directly

function directory
The function directory represents each test point, for example; Login and registration are all encapsulated separately. When used, they can be called directly

# login.py

# coding:utf-8
from pages.login_page2 import Login_element
class LoginTest:
    def __init__(self,driver):
        self.element = Login_element(driver)
        self.app = self.element.get_element

    def login(self,username,password):
        self.app.send_text(self.element.user_element(),username)
        self.app.send_text(self.element.password_element(),password)
        self.app.click(self.element.login_boot())

case directory
case indicates the directory where the test cases are stored

# test_login.py

from function.login import LoginTest
from common.appium_start import start
import unittest
import threading
import time
from common.loger import Logger
import warnings
warnings.simplefilter("ignore", ResourceWarning)
class BaseDriver(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        '''start-up apk'''
        cls.log = Logger('anjing')
        cls.log.info('app Starting')
        cls.driver = start()
        cls.log.info('app Start complete')
        cls.login = LoginTest(cls.driver)
    def test01(self):
        '''Account password error'''
        self.log.info('Case name: wrong account and password, test data: Account Name: 11111, password: 22222,')
        self.login.login('11111','22222')
        element= self.login.element.toast('cell-phone number')
        self.log.info('test01 obtain toast The information is:%s'%element)
        self.assertEqual(element,'Please enter the correct mobile phone number')

    def test02(self):
        '''Account password error 1'''
        self.log.info('Case name: account password error 1, test data: Account Name: 222, password: 33333,')
        self.login.login('2222','33333')
        element= self.login.element.toast('cell-phone number')
        self.log.info('test02 obtain toast The information is:%s' %element)
        self.assertEqual(element,'Please enter the correct mobile phone number')

    @classmethod
    def tearDownClass(cls):
        '''sign out APK'''
        cls.driver.quit()

if __name__ == '__main__':
    t1 = threading.Thread(target=start)
    t1.start()
    time.sleep(20)
    t2 = threading.Thread(target=unittest.main())
    t2.start()   

logs directory
Logs refers to the place where the printed logs and appium logs are stored during the execution of the use case

report directory
Report indicates the location where the test report is stored

runTest.py file
This main execution file is used to execute all use cases, generate test reports and send emails.

# coding:utf-8
import unittest
from common import HTMLTestRunner_cn
import time
import os
import smtplib
import threading
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from common.appium_start import start
import warnings
warnings.simplefilter("ignore", ResourceWarning)
# Script path
path = os.path.dirname(os.path.realpath(__file__))
# Case use case
case_path = os.path.join(path,'case')
# Execute the use case and return discover
def add_case(rule="test*.py"):
    '''Load all test cases'''
    # If the case folder does not exist, one is automatically created
    if not os.path.exists(case_path):os.mkdir(case_path)
    # Define the parameters of the discover method
    discover = unittest.defaultTestLoader.discover(case_path,
                                                  pattern=rule,
                                                  top_level_dir=None)
    return discover

# Implementation report
def run_case(discover):
    report_path =  os.path.join(path,'report')
    now = time.strftime("%Y-%m-%d-%H-%M-%S")  # Latest report
    report_abspath = os.path.join(report_path, now+"result.html")  # Location of the report
    rp=open(report_abspath,"wb")
    runner=HTMLTestRunner_cn.HTMLTestRunner(rp,
                                        title=u"Test report",
                                        description=u"Implementation of use cases")
    runner.run(discover)
    return report_abspath


def sen_mail(file_path):
    smtpserver = 'smtp.163.com'
    # Send email user name and password
    user = 'xxxxxx@163.com'
    password = 'xxxxxx'
    # Send email
    sender = 'xxxxxx@163.com'
    # Receiving mailbox
    receiver ='821006052@qq.com'
    #Import Report
    with open(file_path, "rb") as fp:
        mail_body = fp.read()
    msg=MIMEMultipart()
    body=MIMEText(mail_body,_subtype="html",_charset="utf-8")
    msg['Subject']=u'Automated test report'
    msg['from']=sender # Send mail
    msg['to']=receiver # Take over mail
    msg.attach(body)
    att = MIMEText(mail_body, "base64", "utf-8")   # Generate attachments
    att["Content-Type"] = "application/octet-stream"
    att["Content-Disposition"] = 'attachment; filename="report.html"' # Generate attachment name
    msg.attach(att)
    smtp = smtplib.SMTP()
    smtp.connect(smtpserver)   # Connect server
    smtp.login(user,password)  # logon server
    # Send mail split (',') separator
    smtp.sendmail(sender, receiver.split(','), msg.as_string())     # close
    print ("Mail sending")

def main():
     discover = add_case() # Call execution use case
     file_path = run_case(discover) # Use case generation Report
     # sen_mail(file_path) # Send Report

if __name__=="__main__":
    # add_case()
    t1 = threading.Thread(target=start)
    t1.start()
    time.sleep(20)
    t2 = threading.Thread(target=main)
    t2.start()

The overall PO model design has been completed. I believe everyone will have an experience. How do you feel that the original method is relatively simple, with less code and simple, but what if there are more test cases? So do you think this method is very simple and has been read.

Here we reply to the previous question? What is the use of an automation framework?

If the automation framework is established, some students with weak code foundation in the group can write test cases according to a template. If the page element or UI changes, we only need to find the corresponding page and element information to modify, so that we can continue to maintain the previous use cases.

So the PO model also accounts for a lot of use, which clearly makes our code more concise and has been read. It is also convenient for maintenance.

Through the above content, I believe you have a simple understanding of the PO model and different layers. You can leave a message here to discuss together and learn more convenient and simple methods

Like and follow ~ ~ keep sharing, join us and learn more. 642830685, get the latest software testing factory interview materials and Python automation, interface and framework building learning materials for free! Technical cattle solve doubts and answer questions, and communicate with peers.

Tags: Python Android software testing Testing Appium

Posted by Gazan on Fri, 20 May 2022 09:27:22 +0300