小门板儿

Menu

pytest+appium+allure移动端自动化测试框架

一、环境安装配置

1.1、python安装及配置略过

1.2、java安装及配置略过

1.3、android sdk安装

https://jiner.wang/monkey%e5%ae%89%e8%a3%85%e4%b8%8e%e9%85%8d%e7%bd%ae/

1.4、appium安装

https://jiner.wang/appium%e7%8e%af%e5%a2%83%e6%90%ad%e5%bb%ba%e5%8f%8a%e4%bd%bf%e7%94%a8/

1.5、allure安装

https://jiner.wang/pytestallure%e7%94%9f%e6%88%90%e6%b5%8b%e8%af%95%e6%8a%a5%e5%91%8a/

二、项目实例

项目框架如下:

base目录:基础操作方法,如app_action.py,包含判断元素是否存在,左滑,右滑等方法,get_config_data.py,包含获取配置文件数据方法;email_action.py,包含发送测试结果邮件方法

common目录:执行测试过程中需要的公共方法,如登录操作、页面弹窗提示操作

config目录:设备、数据库、邮箱等配置信息

log目录:测试中产生的日志文件

page_locators目录:测试APP各个页面的元素定位

report目录:allure测试报告文件

test_case目录:测试用例文件

conftest.py文件:定义共享的fixture

pytest.ini文件:pytest的主配置文件

2.1、封装公共方法

2.1.1、base目录下新建app_action.py,用来存放一些公用的方法,比如查找元素、查找一组元素、输入、左右上下滑动、截图等

app_action.py内容如下:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
​
class Action(object):
    '''
    封装元素基本方法
    '''
    #初始化driver
    def __init__(self,driver):
        self.driver=driver
​
​
    def is_element_exist(self,identify_by,what,timeout):
        '''
        如果元素存在,即返回该元素
        '''
        element_exist=None
        self.driver.implicitly_wait(timeout)
        try:
            if identify_by in [By.ID, By.XPATH, By.CLASS_NAME, By.LINK_TEXT, By.PARTIAL_LINK_TEXT, By.NAME, By.TAG_NAME,
                               By.CSS_SELECTOR]:
                element_exist = self.driver.find_element(identify_by, what)
​
        except:
            element_exist = False
​
        finally:
​
            return element_exist
​
​
    def element_visable(self,loc,timeout=20,poll_frequency = 0.5):
        # 显式等待
        WebDriverWait(self.driver, timeout, poll_frequency).until(EC.visibility_of_element_located(loc))
​
​
    def  swipe_up_relative(self,loc, ratio=0.3, t=500, n=1):
        """
        以相对坐标,向上滑动
        :param loc:
        :param ratio:
        :param t:
        :param n:
        """
        # 相对坐标,防止溢出
        el_tmp = self.driver.find_element(*loc).location
        pass
​
    def swipe_up_org2des(self, origin_el, ratio, destination_el):
        """
        以相对坐标向上滑动,直到找到目标元素
        :param origin_el:
        :param ratio:
        :param destination_el:
        """
        # 向上滑动,找到某元素
        # loc为元组格式
        pass
​
    def swip_left(self, t=500, n=1):
        """
        向左滑动,固定0.7个屏幕
        :param t:
        :param n:
        """
        # 减少通讯
        size_tmp = self.driver.get_window_size()
        to_y = 0.8 * size_tmp['height']
        to_x = 0.1 * size_tmp['width']
        cur_y = 0.8 * size_tmp['height']
        cur_x = 0.8 * size_tmp['width']
        for i in range(n):
            self.driver.swipe(cur_x, cur_y, to_x, to_y, t)
2.1.2、base目录下新建get_config_data.py,用来读取config.xml文件中email、设备数据,并且返回初始化driver以及email信息

get_config_data.py内容

import os
import xml.etree.cElementTree as ET
​
from appium import webdriver
​
​
class InitAction(object):
    __slot__=["root",'mobile_config','platformVersion','remote_server_host','app_config','implicitly_wait',
                 'checkin', 'func', 'database', 'email', 'email_format', 'email_monkey', 'email_data', 'monkey_data',
                 'jenkins', "platformName", "deviceName", "appPackage", "appActivity"]
​
    def __init__(self):
        file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)) + '/config/config.xml')
        tree = ET.parse(file_path)
        self.root = tree.getroot()
        self.mobile_config = self.root.findall("mobile_config")
        self.platformName=self.mobile_config[0].find('platformName').text
        self.platformVersion = self.mobile_config[0].find('platformVersion').text
        self.deviceName = self.mobile_config[0].find('deviceName').text
        self.versionName = self.mobile_config[0].find('versionName').text
        self.app_config = self.root.findall("app_config")
        self.appPackage = self.app_config[0].find('appPackage').text
        self.appActivity = self.app_config[0].find('appActivity').text
        self.email = self.root.findall("Email")
        self.email_format = self.root.findall("Email_format")
​
    def device_info(self):
        platformName =self.platformName
        platformVersion =self.platformVersion
        deviceName=self.deviceName
        versionName=self.versionName
        return platformName, platformVersion, deviceName,versionName
​
    def init_driver(self):
        desired_caps = {}
        desired_caps['platformName'] =  self.platformName  # 被测手机是安卓
        desired_caps['platformVersion'] =  self.platformVersion  # 手机安卓版本
        desired_caps['deviceName'] =  self.deviceName  # 设备名,安卓手机可以随意填写
        desired_caps['appPackage'] =  self.appPackage  # 启动APP Package名称
        desired_caps['appActivity'] =  self.appActivity  # 启动Activity名称
        return webdriver.Remote(self.mobile_config[0].find('remote_server_host').text, desired_caps)
​
    def get_email_config(self):
        email_data_dict={}
        email_data_dict['send_server']=self.email[0].find('send_server').text
        email_data_dict['send_addr'] = self.email[0].find('send_addr').text
        email_data_dict['send_password'] = self.email[0].find('send_password').text
        receiver_list=[]
        for receive_addr in self.email[0].findall("receive_addr"):
            receiver_list.append(receive_addr.text)
            email_data_dict['receive_addr'] = receiver_list
        return email_data_dict
​
    def get_email_format(self):
        email_formate_list = []
        email_formate_list.append(self.email_format[0].find('title').text)
        email_formate_list.append(self.email_format[0].find('table_name').text)
        email_formate_list.append(self.email_format[0].find('title_fail').text)
        return email_formate_list
2.1.3、common目录下新建login.py,用于登录
import time
​
from base.app_action import Action
from common.pop_up import PopUp
​
​
class Login(object):
    def __init__(self,drver):
        self.driver=drver
​
    def pre_login(self):
        el1 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/btn_ok_pr")
        el1.click()
        #滑动引导,进入APP
        end_time = time.time() + 100
        while True:
            Action(self.driver).swip_left()
            time.sleep(0.5)
            if Action(self.driver).is_element_exist("id", "XXX.XXXXX.XXXXXX:id/start_new_world", timeout=3):
                break
            if time.time() > end_time:
                break
        el2 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/start_new_world")
        el2.click()
​
    def mobile_login(self):
        self.pre_login()
        el8 = self.driver.find_element_by_xpath("//*[@content-desc=\"我的\"]")
        el8.click()
​
        el9 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/name_tv")
        el9.click()
​
        el10 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX/password_login")
        el10.click()
​
        el11 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/phone_et")
        el11.send_keys('18627811314')
​
        el12 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/vcode_et")
        el12.send_keys('123456')
​
        # 防止网络卡顿
        self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/login_tv").click()
​
        el14 = self.driver.find_element_by_id("XXX.XXXXX.XXXXXX:id/btn_ok")
        el14.click()
2.1.4、page_locators目录下按照各页面分别创建文件,添加各页面元素定位

我的页面元素定位

from selenium.webdriver.common.by import By
​
​
class MinePageLoc(object):
    def __init__(self):
        #底部导航栏
        self.mine_button=(By.XPATH,"//android.widget.FrameLayout[@content-desc=\"我的\"]/android.widget.ImageView")
        # 【我的】
        # 登录入口
        self.un_login_button=(By.ID,"XXX.XXXXX.XXXXXX:id/name_tv")
        # 密码登录按钮
        self.password_login_button=(By.ID,"XXX.XXXXX.XXXXXX.iyoucloud:id/password_login")
        #手机号输入框
        self.phone_input = (By.ID, "XXX.XXXXX.XXXXXX:id/phone_et")
        # 密码输入框
        self.vcode_input = (By.ID, "XXX.XXXXX.XXXXXX:id/vcode_et")
        # 登录按钮
        self.login_button = (By.ID, "XXX.XXXXX.XXXXXX:id/login_tv")

2.2、conftest.py

在根目录下创建conftest.py ,使全局共用这个文件,故可以这个文件中添加login方法,每个用例都可以调用,配合yield使用,fixture相当于set up (前置步骤),yield就类似tear down(后置步骤) yield之前为前置,yield之后为后置

@pytest.fixture(autouse=True)
def login(request):
    logging.info(u'------------------------------%s 测试用例:开始----------------------------------'%request.module)
    obj=InitAction()
    driver=obj.init_driver()
    driver.implicitly_wait(10)
    Login(driver).mobile_login()
    yield driver
    logging.info(u'------------------------------%s 测试用例:结束----------------------------------'%request.module)
    driver.quit()
    logging.info(u'------------------------------driver退出----------------------------------')

2.3、测试用例

测试文件名以test标识开头,方法名以test开头,方法名前使用@allure.feature(),添加测试功能点描述,将在allure报告中展示

@pytest.mark.checkin,为测试用例添加标记,为测试用例分类

@allure.feature('测试首页广告banner位及跳转')
@pytest.mark.checkin
def testA(login):
    driver=login
    driver.implicitly_wait(5)
    #回到首页
​
    example_action = Action(driver)
    example_home_page = HomePageLocators()
    e1 = example_action.is_element_exist(*example_home_page.iv_home_button, timeout=20)
    if e1:
        e1.click()
    else:
        raise Exception("首页不见了")
    e2 = example_action.is_element_exist(*example_home_page.iv_home_banner, timeout=20)
    if e2:
        e2.click()
    else:
        raise Exception("没找到banner--黑色情人节")
​
    #断言
    assert driver.find_element(*example_home_page.tv_title)

2.4、执行用例并生成报告

run.py内容

执行pytest单元测试,生成 Allure 报告需要的数据存在 /report/data 目录:pytest.main(['-s','-q','-m','checkin','./test_case','--alluredir', './report/data'])

执行命令 allure generate ./temp -o ./report --clean ,生成测试报告

# -*- encoding: utf-8 -*-
import logging
import os
​
import pytest
# 打印日志
import time
​
from base import email_action
from base import get_config_data
​
LOGGING_FILE = time.strftime('%Y-%m-%d-%H-%M-%S')
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(filename)s[line:%(lineno)d]-%(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='./log/' + LOGGING_FILE + '.log',
                    filemode='a')
​
if __name__ =="__main__":
    caseTime=time.strftime("%Y-%m-%d:%H-%M-%S", time.localtime(time.time()))
    platformName, platformVersion, deviceName, versionName=get_config_data.InitAction().device_info()
    # 执行pytest单元测试,生成 Allure 报告需要的数据存在 /report/data 目录
    pytest.main(['-s','-q','-m','checkin','./test_case','--alluredir', './report/data'])
    # 执行命令 allure generate ./temp -o ./report --clean ,生成测试报告
    os.system('allure generate ./report/data -o ./report/html --clean')
    report_url='http://localhost:63342/suileyoo/report/html/index.html?_ijt=8nht5t0i3nbcc1gdr552jmc4gu'
    email_action.EmailAction().send_email(caseTime,platformName, platformVersion, deviceName, versionName,report_url)

生成测试报告:

发送测试邮件

— 于 共写了9520个字
— 标签:

908条回应:“pytest+appium+allure移动端自动化测试框架”

  1. jiner说道:

    小星星