框架功能
业务功能的封装
测试用例封装
测试包管理
截图处理
断言处理
日志获取
测试报告生成
数据驱动
数据配置
测试案例
测试环境
Mac
Appium v1.18.3
app软件
华为mate30
覆盖用例
1.登录场景
2.下单场景
框架设计图
代码实现
driver配置封装
kyb_caps.yaml 配置表
# 测试平台 iOS和Android
platformName: Android
# 连接模拟器
# 平台版本
#platformVersion: 6.0.1
# 设备名字
deviceName: HUAIWEI Mate 30 Pro 5G
# 连接真机
platformVersion: '10.0'
uuid: *************3142
#便于后期维护,可以只修改appname
appname: ********debug.apk(包名)
# 支持中文输入
unicodeKeyboard: True
# 重置输入法到原有状态
resetKeyboard: True
# 控制是否清除session信息
noReset: False
# app的包
appPackage: com.**********
# app的 activity
appActivity: com.**************.******Activity
# appium server的IP和port
ip: 127.0.0.1
port: 4723
desired_caps.py文件
from appium import webdriver
import yaml
import logging
import logging.config
import os
CON_LOG='../config/log.conf'
logging.config.fileConfig(CON_LOG)
logging=logging.getLogger()
def appium_desired():
with open('../config/********caps.yaml', 'r', encoding='utf-8') as file:
data = yaml.load(file,Loader = yaml.FullLoader)
desired_caps={}
desired_caps['platformName'] = data['platformName']
desired_caps['platformVersion'] = data['platformVersion']
desired_caps['deviceName'] = data['deviceName']
desired_caps['uuid'] = data['uuid']
base_dir = os.path.dirname(os.path.dirname(__file__))
app_path = os.path.join(base_dir, 'appname', data['appname'])
desired_caps['appname'] = app_path
desired_caps['appPackage'] = data['appPackage']
desired_caps['appActivity'] = data['appActivity']
desired_caps['noReset'] = data['noReset']
desired_caps['unicodeKeyboard'] = data['unicodeKeyboard']
desired_caps['resetKeyboard'] = data['resetKeyboard']
logging.info('start app...')
# driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver = webdriver.Remote('http://' + str(data['ip']) + ':' + str(data['port']) + '/wd/hub',desired_caps)
return driver
if __name__ == '__main__':
appium_desired()
相对路径符号含义
“.”表示当前目录
“..” 表示当前目录的上一级目录。
“./”表示当前目录下的某个文件或文件夹,视后面跟着的名字而定
“../”表示当前目录上一级目录的文件或文件夹,视后面跟着的名字而定。
基类封装
baseView.py文件
class BaseView(object):
def __init__(self,driver):
self.driver = driver
def find_element(self,*loc):
return self.driver.find_element(*loc)
def find_elements(self,*loc):
return self.driver.find_elements(*loc)
def get_window_size(self):
return self.driver.get_window_size()
def swipe(self,start_x,start_y,end_x,end_y,duration):
return self.driver.swipe(start_x,start_y,end_x,end_y,duration)
common公共模块封装
公共方法封装 : common_fun.py
from baseView.baseViews import BaseView
from common.desired_caps import appium_desired
from selenium.common.exceptions import NoSuchElementException
import logging
from selenium.webdriver.common.by import By
import time,os
import csv
class Common(BaseView):
#当前时间
def gettime(self):
self.now = time.strftime("%Y-%m-%d %H_%M_%S")
return self.now
#截图
def getScreenShot(self,module):
time =self.gettime()
image_file = os.path.dirname(os.path.dirname(__file__))+'/screenshots/%s_%s.png' %(module,time)
print(image_file)
logging.info('get %s screenshot' %module)
self.driver.get_screenshot_as_file(image_file)
#读取csv文件
def get_csv_data(self,csv_file,line):
logging.info('========get_csv_data=========')
with open(csv_file,'r',encoding='utf-8-sig') as file:
reader = csv.reader(file)
for index,row in enumerate(reader,1):
if index == line:
return row
#获取屏幕尺寸
def get_size(self):
x =self.driver.get_window_size()['width']
y =self.driver.get_window_size()['height']
return x, y
if __name__ == '__main__':
driver = appium_desired()
com = Common(driver)
com.swipeLeft()
com.getScreenShot(str('startapp'))
业务模块封装
1.登录页面业务逻辑模块
import logging
from common.commin_fun import Common,NoSuchElementException
from common.desired_caps import appium_desired
from selenium.webdriver.common.by import By
from page.loginpage import LoginPage
import time
#登录
class LoginView(Common):
# 跳转登录
logBtn = (By.ID, '*********:id/t*******e')
# 用户名
username_type = (By.XPATH,'/**************************ditText')
# 密码
password_type = (By.XPATH,'/*****************itText')
# 登录按钮
loginBtn = (By.ID, 'com.**********:id/bt****gin')
# homepage = (By.ID,'com.***********:id/iv**nt')
tip_commit = (By.ID, 'com.***********:id/tv_****dits')
def login_action(self,username,password):
logging.info('click logBtn')
self.driver.find_element(*self.logBtn).click()
logging.info('click logBtn finished')
time.sleep(3)
logging.info('=========login_action=======')
logging.info('username is:%s' %username)
self.driver.find_element(*self.username_type).send_keys(username)
logging.info('usernameinput finished')
time.sleep(3)
logging.info('=========login_action=======')
logging.info('password is:%s' %password)
self.driver.find_element(*self.password_type).send_keys(password)
logging.info('passwordinput finished')
time.sleep(3)
logging.info('click loginBtn')
self.driver.find_element(*self.loginBtn).click()
logging.info('login loginBtn finished')
time.sleep(3)
def check_account_alert(self):
logging.info('============check_account_alert===========')
try:
element = self.driver.find_element(self.tip_commit)
except NoSuchElementException:
pass
else:
logging.info('Logged in')
def check_login_staus(self):
logging.info('=======check_login_staus========')
# self.check_market_ad()
self.check_account_alert()
try:
self.driver.find_element(*self.tip_commit)
except NoSuchElementException:
logging.error('login Fail')
self.getScreenShot('login Fail')
return False
else:
logging.info('===========login success==============')
return True
if __name__ == '__main__':
driver = appium_desired()
l = LoginView(driver)
l.login_action('1584********692','q******************/an******View')
#跳过动画
skip_animation = (By.ID,'com.*********:id/skip')
#跳过弹窗
skip_popup = (By.ID,'com.**********:id/tv_ok')
#首页
homepage = (By.ID, 'com.*******:id/iv*****ent')
bj_cbd = (By.ID,'com.*************:id/ite****mg')
reserve_room = (By.XPATH,'/***********************out')
#同意
agree = (By.ID,'com.**********:id/c*********s')
#提交订单
submit = (By.ID,'com.***************:id/b************it')
#立即支付
immediate_payment = (By.ID,'com.**************id/b*******y')
#账户余额
account_balance = (By.ID,'com.***************id/c***********nt')
#确认支付
confirm_payment = (By.ID,'com.*********************id/b*******y')
def Yizhan_xiadan(self):
logging.info('click homepage')
self.driver.find_element(*self.homepage).click()
logging.info('login homepage finished')
time.sleep(3)
logging.info('=========click yizhan_homepage=========')
self.driver.find_element(*self.yizhan_homepage).click()
logging.info('==============click yizhan_homepage finished===============')
time.sleep(3)
logging.info('=========click skip_animation=========')
self.driver.find_element(*self.skip_animation).click()
logging.info('==============click skip_animation finished===============')
time.sleep(3)
logging.info('=========click skip_popup=========')
self.driver.find_element(*self.skip_popup).click()
logging.info('==============click skip_popup finished===============')
time.sleep(3)
logging.info('============swipe up=============')
self.swipe(868,1735,868,757,500)
logging.info('============swipe up finished=============')
logging.info('==============cilck bj_cbd=================')
self.driver.find_element(*self.bj_cbd).click()
logging.info('==============bj_cbd finished===============')
logging.info('============swipe up two=============')
self.swipe(835, 1857, 835, 776, 696)
logging.info('============swipe up two finished=============')
logging.info('==================click reserve_room==============')
self.driver.find_element(*self.reserve_room).click()
logging.info('============reserve_room finished=============')
logging.info('==================click agree==============')
self.driver.find_element(*self.agree).click()
logging.info('============agree finished=============')
logging.info('==================click submit==============')
self.driver.find_element(*self.submit).click()
logging.info('============submit finished=============')
logging.info('==================click immediate_payment==============')
self.driver.find_element(*self.immediate_payment).click()
logging.info('============immediate_payment finished=============')
logging.info('==================click account_balance==============')
self.driver.find_element(*self.account_balance).click()
logging.info('============account_balance finished=============')
logging.info('==================click confirm_payment==============')
self.driver.find_element(*self.confirm_payment).click()
logging.info('============confirm_payment finished=============')
data数据封装
使用背景
在实际项目过程中,我们的数据可能是存储在一个数据文件中,如txt,excel、csv文件类型。我们可以封装一些方法来读取文件中的数据来实现数据驱动。
案例
将测试账号存储在account.csv文件,内容如下:
130*******745q******
enumerate()简介
enumerate()是python的内置函数
enumerate在字典上是枚举、列举的意思
对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值
enumerate多用于在for循环中得到计数。
enumerate()使用
如果对一个列表,既要遍历索引又要遍历元素时,首先可以这样写:
list= ["这", "是", "一个", "测试","数据"]
foriinrange(len(list)):
print(i,list[i])
>>>
0 这
1 是
2 一个
3 测试
4 数据
上述方法有些累赘,利用enumerate()会更加直接和优美:
list1 = ["这", "是", "一个", "测试","数据"]
forindex,iteminenumerate(list1):
print(index,item)
>>>
0 这
1 是
2 一个
3 测试
4 数据
数据读取方法封装
import csv
def get_csv_data(self,csv_file,line):
logging.info('========get_csv_data=========')
with open(csv_file,'r',encoding='utf-8-sig') as file:
reader = csv.reader(file)
for index,row in enumerate(reader,1):
if index == line:
return row
csv_file='../data/account.csv'
data=get_csv_data(csv_file,3)
print(data)
utf-8与utf-8-sig两种编码格式的区别
UTF-8以字节为编码单元,它的字节顺序在所有系统中都是一样的,没有字节序的问题,也因此它实际上并不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM。
config文件配置
日志文件配置 log.config
[loggers]
keys=root,infoLogger
[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler
[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0
[handlers]
keys=consoleHandler,fileHandler
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stderr,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('../logs/runlog.log', 'a')
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
测试用例封装
1.测试用例执行开始结束操作封装myunit.py
import unittest
from common.desired_caps import appium_desired
import logging
from time import sleep
class StartEnd(unittest.TestCase):
def setUp(self):
logging.info('=========start============')
self.driver = appium_desired()
def tearDown(self):
logging.info('===========end==========')
sleep(5)
self.driver.close_app()
2.登录和下单用例:test_login.py
from common.myunit import StartEnd
from businessView.loginVIew import LoginView
from businessView.homePage import HomePage
from businessView.misu_xiadan import YizhanXiadan
import logging,unittest
from BSTestRunner import BSTestRunner
import time
#登录
class LoginYest(StartEnd):
csv_file ='../data/account.csv'
def test_login_cg(self):
logging.info('===============test_page===============')
h = HomePage(self.driver)
l = LoginView(self.driver)
y = YizhanXiadan(self.driver)
data = l.get_csv_data(self.csv_file, 1)
h.get_into()
l.login_action(data[0], data[1])
self.assertTrue(l.check_login_staus(), msg=('login fail!!!'))
time.sleep(3)
y.Yizhan_xiadan()
if __name__ == '__main__':
unittest.main()
执行测试用例&报告生成
run.py文件
from BSTestRunner import BSTestRunner
import unittest
import time,logging
test_dir = '../test_case'
report_dir = '../reports'
discover = unittest.defaultTestLoader .discover(test_dir,pattern='test_login.py')
now = time.strftime('%Y-%m-%d %H_%M_%S')
report_name = report_dir + '/' + now + 'test_report.html'
with open(report_name,'wb') as f:
runner = BSTestRunner(stream=f,title='***测试报告',description='*******登录下单流程')
logging.info('start run rest case')
runner.run(discover)
注意:
pattern参数可以控制运行不同模块的用例,如下所示表示运行指定路径以test开头的模块
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')