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)
生成测试报告:
发送测试邮件
小星星