def click_element(self, loc):
‘’’
封装点击操作函数
‘’’
self.find_element(loc).click()
def input_text(self, loc, text):
‘’’
封装输入操作函数
‘’’
self.fm = self.find_element(loc)
self.fm.clear() # 需要先清空输入框,防止有默认内容 self.fm.send_keys(text)
自定义了一个可以自动帮我们拼接 xpath 路径的工具函数def make_xpath(self, feature):
start_path = “//*[”
end_path = “]”
res_path = ""if isinstance(feature, str):
如果是字符串 我们不能直接上来就拆我们可以判断一下它是否是默认正确的 xpath 写法if feature.startswith(“//*[”):
return feature
如果用户输入的是字符串,那么我们就拆成列表再次进行判断
split_list = feature.split(“,”)
if len(split_list) == 2:
//*[contains(@text,‘设’)]
res_path = “%scontains(@%s,‘%s’)%s” % (start_path, split_list[0], split_list[1], end_path)
elif len(split_list) == 3:
//[@text=‘设置’]
res_path = “%s@%s=‘%s’%s” % (start_path, split_list[0], split_list[1], end_path)
else:
print(“请按规则使用”)
elif isinstance(feature, tuple):
for item in feature:
默认用户在元组当中定义的数据都是字符串
split_list2 = item.split(‘,’)
if len(split_list2) == 2:
res_path += "contains(@%s,‘%s’) and " % (split_list2[0], split_list2[1])
elif len(split_list2) == 3:
res_path += "@%s=‘%s’ and " % (split_list2[0], split_list2[1])
else:
print(“请按规则使用”)
andIndex = res_path.rfind(" and")
res_path = res_path[0:andIndex]
res_path = start_path + res_path + end_path
else:
print(“请按规则使用”)
return res_path
def assert_ele_in(self, text, element):
‘’’
封装断言操作函数
‘’'try:
assert text in self.find_element(element).text
assert 0
except Exception:
assert 1
def get_assert_text(self, element):
ele = self.find_element(element, timeout=5, poll=0.1)
return ele.text
自定义一个获取 toast内容的方法def get_toast_content(self, message):
tmp_feature = By.XPATH, “//*[contains(@text,‘%s’)]” % message
ele = self.find_element(tmp_feature)
return ele.text
自定义一个工具函数,可以接收用户传递的部分 toast 信息,然后返回一个布尔值,来告诉# 用户,目标 toast 到底是否存在def is_toast_exist(self, mes):
拿着用户传过来的 message 去判断一下包含该内容的 toast 到底是否存在。try:
self.get_toast_content(mes)
return True
except Exception:
如果目标 toast 不存在那么就说明我们的实际结果和预期结果不一样# 因此我们想要的是断言失败return False
def get_mysql(self, table, value):
‘’‘连接数据库’‘’# 打开数据库连接
db = pymysql.connect(host=‘’, port=, db=, user=‘’, passwd=‘’, charset=‘utf8’)
使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
try:
使用 execute() 方法执行 SQL 查询 cursor.execute(value)
db.commit()
except Exception as e:
print(e)
db.rollback()
使用 fetchone() 方法获取单条数据.
data = cursor.fetchone()
关闭数据库连接 db.close()
return data
def get_xpath(self, value):
‘’‘封装获取xpath方法’‘’
text = By.XPATH, ‘//*[@text=“%s”]’ % value
return text
自定义一个获取当前设备尺寸的功能def get_device_size(self):
x = self.driver.get_window_size()[“width”]
y = self.driver.get_window_size()[“height”]
return x, y
自定义一个功能,可以实现向左滑屏操作。def swipe_left(self):
start_x = self.get_device_size()[0] * 0.9
start_y = self.get_device_size()[1] * 0.5
end_x = self.get_device_size()[0] * 0.4
end_y = self.get_device_size()[1] * 0.5
self.driver.swipe(start_x, start_y, end_x, end_y)
自定义一个功能,可以实现向上滑屏操作。def swipe_up(self):
start_x = self.get_device_size()[0] * 1/2
start_y = self.get_device_size()[1] * 1/2
end_x = self.get_device_size()[0] * 1/2
end_y = self.get_device_size()[1] * 1/7
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
切换到微信def switch_weixxin(self):
self.driver.start_activity(“com.tencent.mm”, “.ui.LauncherUI”)
切换到医生端def switch_doctor(self):
self.driver.start_activity(“com.rjjk_doctor”, “.MainActivity”)
切换到销售端def switch_sale(self):
self.driver.start_activity(“com.rjjk_sales”, “.MainActivity”)
def switch_webview(self):
切换到webviewprint(self.driver.contexts)
time.sleep(5)
self.driver.switch_to.context(“WEBVIEW_com.tencent.mm:tools”)
print(“切换成功”)
time.sleep(3)
自定义根据坐标定位def taptest(self, a, b):
设定系数,控件在当前手机的坐标位置除以当前手机的最大坐标就是相对的系数了# 获取当前手机屏幕大小X,Y
X = self.driver.get_window_size()[‘width’]
Y = self.driver.get_window_size()[‘height’]
屏幕坐标乘以系数即为用户要点击位置的具体坐标
self.driver.tap([(a * X, b * Y)])
自定义截图函数def take_screenShot(self):
‘’’
测试失败截图,并把截图展示到allure报告中
‘’’
tm = time.strftime(“%Y-%m-%d-%H-%M-%S”, time.localtime(time.time()))
self.driver.get_screenshot_as_file(
os.getcwd() + os.sep + “image/%s.png” % tm)
allure.attach.file(os.getcwd() + os.sep + “image/%s.png” %
tm, attachment_type=allure.attachment_type.PNG)
自定义随机生成11位手机号def create_phone(self):
第二位数字
second = [3, 4, 5, 7, 8][random.randint(0, 4)]
第三位数字
third = {
3: random.randint(0, 9),
4: [5, 7, 9][random.randint(0, 2)],
5: [i for i in range(10) if i != 4][random.randint(0, 8)],
7: [i for i in range(10) if i notin [4, 9]][random.randint(0, 7)],
8: random.randint(0, 9),
}[second]
最后八位数字
suffix = random.randint(9999999, 100000000)
拼接手机号return"1{}{}{}".format(second, third, suffix)
Basic/deiver.py
APP启动的前置条件,一个是普通的app,一个是微信公众号,配置微信公众号自动化测试和一般的APP是有点区别的,微信需要切换webview才能定位到公众号
from appium import webdriver
def init_driver():
desired_caps = {}
手机 系统信息
desired_caps[‘platformName’] = ‘Android’
desired_caps[‘platformVersion’] = ‘9’# 设备号
desired_caps[‘deviceName’] = ‘emulator-5554’# 包名
desired_caps[‘appPackage’] = ‘’# 启动名
desired_caps[‘appActivity’] = ‘’
desired_caps[‘automationName’] = ‘Uiautomator2’# 允许输入中文
desired_caps[‘unicodeKeyboard’] = True
desired_caps[‘resetKeyboard’] = True
desired_caps[‘autoGrantPermissions’] = True
desired_caps[‘noReset’] = False
手机驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver
def driver_weixin():
desired_caps = {}
手机 系统信息
desired_caps[‘platformName’] = ‘Android’
desired_caps[‘platformVersion’] = ‘9’# 设备号
desired_caps[‘deviceName’] = ‘’# 包名
desired_caps[‘appPackage’] = ‘com.tencent.mm’# 启动名
desired_caps[‘appActivity’] = ‘.ui.LauncherUI’# desired_caps[‘automationName’] = ‘Uiautomator2’# 允许输入中文
desired_caps[‘unicodeKeyboard’] = True
desired_caps[‘resetKeyboard’] = True
desired_caps[‘noReset’] = True
desired_caps[“newCommandTimeout”] = 30# desired_caps[‘fullReset’] = ‘false’# desired_caps[‘newCommandTimeout’] = 10# desired_caps[‘recreateChromeDriverSessions’] = True
desired_caps[‘chromeOptions’] = {‘androidProcess’: ‘com.tencent.mm:tools’}
手机驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver
Basic/get_data.py
这是获取测试数据的方法
import os
import yaml
def getData(funcname, file):
PATH = os.getcwd() + os.sep
with open(PATH + ‘Data/’ + file + ‘.yaml’, ‘r’, encoding=“utf8”) as f:
data = yaml.load(f, Loader=yaml.FullLoader)
1 先将我们获取到的所有数据都存放在一个变量当中
tmpdata = data[funcname]
2 所以此时我们需要使用循环走进它的内心。
res_arr = list()
for value in tmpdata.values():
tmp_arr = list()
for j in value.values():
tmp_arr.append(j)
res_arr.append(tmp_arr)
return res_arr
Basic/Log.py
日志文件,不多介绍
-*- coding: utf-8 -*-“”"
封装log方法
“”"import logging
import os
import time
LEVELS = {
‘debug’: logging.DEBUG,
‘info’: logging.INFO,
‘warning’: logging.WARNING,
‘error’: logging.ERROR,
‘critical’: logging.CRITICAL
}
logger = logging.getLogger()
level = 'default’def create_file(filename):
path = filename[0:filename.rfind(‘/’)]
ifnot os.path.isdir(path):
os.makedirs(path)
ifnot os.path.isfile(filename):
fd = open(filename, mode=‘w’, encoding=‘utf-8’)
fd.close()
else:
passdef set_handler(levels):
if levels == ‘error’:
logger.addHandler(MyLog.err_handler)
logger.addHandler(MyLog.handler)
def remove_handler(levels):
if levels == ‘error’:
logger.removeHandler(MyLog.err_handler)
logger.removeHandler(MyLog.handler)
def get_current_time():
return time.strftime(MyLog.date, time.localtime(time.time()))
class MyLog:
path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
log_file = path+‘/Log/log.log’
err_file = path+‘/Log/err.log’
logger.setLevel(LEVELS.get(level, logging.NOTSET))
create_file(log_file)
create_file(err_file)
date = ‘%Y-%m-%d %H:%M:%S’
handler = logging.FileHandler(log_file, encoding=‘utf-8’)
err_handler = logging.FileHandler(err_file, encoding=‘utf-8’)
@staticmethod
def debug(log_meg):
set_handler(‘debug’)
logger.debug("[DEBUG " + get_current_time() + “]” + log_meg)
remove_handler(‘debug’)
@staticmethod
def info(log_meg):
set_handler(‘info’)
logger.info("[INFO " + get_current_time() + “]” + log_meg)
remove_handler(‘info’)
@staticmethod
def warning(log_meg):
set_handler(‘warning’)
logger.warning("[WARNING " + get_current_time() + “]” + log_meg)
remove_handler(‘warning’)
@staticmethod
def error(log_meg):
set_handler(‘error’)
logger.error("[ERROR " + get_current_time() + “]” + log_meg)
remove_handler(‘error’)
@staticmethod
def critical(log_meg):
set_handler(‘critical’)
logger.error("[CRITICAL " + get_current_time() + “]” + log_meg)
remove_handler(‘critical’)
if__name__ == “__main__”:
MyLog.debug(“This is debug message”)
MyLog.info(“This is info message”)
MyLog.warning(“This is warning message”)
MyLog.error(“This is error”)
MyLog.critical(“This is critical message”)
Basic/Shell.py
执行shell语句方法
-*- coding: utf-8 -*-
@Time : 2018/8/1 下午2:54
@Author : WangJuan
@File : Shell.py"“”
封装执行shell语句方法
“”"import subprocess
class Shell:
@staticmethod
def invoke(cmd):
output, errors = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
o = output.decode(“utf-8”)
return o
Page/page.py
class Page:
def__init__(self, driver):
self.driver = driver
@property
def initloginpage(self):
return Login_Page(self.driver)
Test/test_login.py
登陆的测试用,我贴一条使用数据文件的用例
class Test_login:
@pytest.mark.parametrize(“args”, getData(“test_login_error”, ‘data_error_login’))
def test_error_login(self, args):
“”“错误登陆”“”
self.page.initloginpage.input_user(args[0])
self.page.initloginpage.input_pwd(args[1])
self.page.initloginpage.click_login()
toast_status = self.page.initloginpage.is_toast_exist(args[2])
if toast_status == False:
self.page.initpatientpage.take_screenShot()
assert False
pytest.ini
pytest配置文件,注释的是启动失败重试3次,因为appium会因为一些不可控的原因失败,所有正式运行脚本的时候需要加上这个
[pytest]
;addopts = -s --html=report/report.html --reruns 3
addopts = -s --html=report/report.html
testpaths = ./Test
python_files = test_*.py
python_classes = Test*
python_functions = test_add_prescription_list
requirements.txt
框架中需要的患教,直接pip install -r requirements.txt 安装就可以了,可能会失败,多试几次