1、日志模块
# 导入日志模块
import logging
# 日志文件输出
from logging.handlers import RotatingFileHandler
import time
# 日志存在目录
from Common import dir_config
# 设置日志格式
"""
asctime:字符串形式的当前时间
levelname:文本形式的日志级别
filename:调用函数所在模块的文件名称
funcName:日志输出的函数名
lineno:函数语句执行所在代码行
message:用户输出信息
"""
log_fmt = " %(asctime)s %(levelname)s %(filename)s %(funcName)s [line:%(lineno)d] %(message)s "
"""
%a:本地星期的简称
%d:一个月中的第几天
%b:本地月份的简称
%Y:年份的完整表达形式
%H:24小时制的小时
%M:分钟
%S:秒数
"""
date_fmt = " %a, %d %b %Y %H:%M:%S "
# 获取当前时间,且将其格式转换成字符串(strftime将时间格式转换成字符串显示;strptime将字符串转换成时间格式显示)
cur_time = time.strftime("%Y-%m-%d %H%M", time.localtime())
# 将日志信息输出到Stream
handler_1 = logging.StreamHandler()
# 将日志信息输出到磁盘文件,且设置其文件名称
handler_2 = RotatingFileHandler(
dir_config.logs_path + "/Web_Autotest_{0}.log".format(cur_time),
backupCount=20,
encoding='utf-8'
)
# 设置日志输出格式
logging.basicConfig(
format=log_fmt,
datefmt=date_fmt,
level=logging.INFO,
handlers=[handler_1, handler_2]
)
2、文件存储
# 导入os模块
import os
# 根目录
base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
logs_path = os.path.join(base_path, "Outputslogs")
reports_path = os.path.join(base_path, "Outputs\reports")
screenshots_path = os.path.join(base_path, "Outputsscreenshots")
Testcases_path = os.path.join(base_path, "Testcases")
Testdatas_path = os.path.join(base_path, "Testdatas")
# os.path.realpath(__file__) 当前文件的绝对路径
# os.path.dirname() 当前文件所属的文件夹,可嵌套使用
# os.path.join(base_path, "Testcases") 添加文件
3、Common封装
from selenium.webdriver.remote.webdriver import WebDriver
# 导入时间等待
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
# expected_conditions模块包含一系列可用于判断的条件
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
# 导入日历
import calendar as cal
# 导入时间
import time
# 导入日期
import datetime
# 模拟键盘操作(Python自带的插件)
import win32api
# 控制键盘(Python自带的插件)
import win32con
# 脚本字段的复制、粘贴
import pyperclip
# 导入日志模块
from Common import logger_fun
# 导入问价目录
from Common.dir_config import screenshots_path
class basePage:
"""
包含了PageObjects当中,用到所有的selenium底层方法
还可以包含通用的一些元素操作,如alert,iframe,windows...
实现日志记录、实现失败截图
"""
# 初始化驱动
def __init__(self, driver: WebDriver):
self.driver = driver
# 等待元素可见(该方法不单独使用,由其他方法调用)
def wait_elevisible(self, loc, timeout=120, frequency=0.5, doc="" ):
"""
:param loc:要等待的元素定位(必传)
:param timeout:超时时间
:param frequency:轮询周期
:param doc:备注
:return:
"""
# 计算程序执行的时间(系统时间的时间戳)
start_time = time.time()
try:
WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))
except:
# 模块打印异常
logger_fun.logging.exception("等待{}元素可见超时".format(loc))
# 失败截图(调用自定义函数:见下面)
self.do_save_screenshot(doc)
raise
else:
end_time = time.time()
# 两次调用的时间差即为系统经过的总时间
duration = end_time - start_time
# 日志输出
logger_fun.logging.info("等待{}元素可见,耗时{}".format(loc, duration))
# 查找元素:查找某个元素(该方法不单独使用,由其他方法调用)
def get_element(self, loc, doc=""):
"""
:param loc:要查找元素的元素定位(必传)
:param doc:备注
:return:
"""
try:
# find_element系统自带的查找元素,*loc不定长参数
ele = self.driver.find_element(*loc)
except:
logger_fun.logging.exception("查找{}元素存在,失败!".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("查找{}的元素{}成功。".format(doc, loc))
return ele
# 页面截图:(该方法不单独使用,由其他方法调用)
def do_save_screenshot(self, doc=""):
"""
:param doc:备注
:return:
"""
# 获取本地当前时间,且将其时间格式转换为字符串格式
cur_time = datetime.datetime.strftime(datetime.datetime.now(), '%Y%m%d%H%M%S')
# 设置文件的存储路径及文件名称
file = screenshots_path+"/{}_{}.png".format(doc, cur_time)
try:
# 调用系统自带的截图函数
self.driver.save_screenshot(file)
except:
logger_fun.logging.exception("网页截图操作失败")
else:
logger_fun.logging.info("截图成功,存储路径为:{0}".format(file))
# 查找元素是否显示:返回布尔值(存在返回True 不存在返回False)
def is_element_present(self, loc, timeout=10, frequency=0.5, doc=""):
"""
:param loc:要查找元素的元素定位(必传)
:param timeout:超时时间
:param frequency:轮询周期(多久查看一下元素是否在页面中出现)
:param doc:备注
:return:
"""
time.sleep(0.5)
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# is_displayed:自带的函数判断元素是否对用户可见
ele.is_displayed()
except:
logger_fun.logging.exception("{}元素不可见".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("{}元素可见".format(loc))
# 输入框输入文本:
def input_text(self, loc, value, timeout=60, frequency=0.5, doc=""):
"""
:param loc:文本输入框的输入框元素定位(必传)
:param value:输入的值(必传)
:param timeout:超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 模拟输入元素send_keys
ele.send_keys(value)
except:
logger_fun.logging.exception("向{}元素输入{}失败".format(loc, value))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("向{}元素输入{}成功".format(loc, value))
# 清空文本输入框内容:
def clear_text(self, loc, timeout=60, frequency=0.5, doc=""):
"""
:param loc:要清空文本输入框的输入框元素定位(必传)
:param timeout:超时时间
:param frequency:轮询周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 如果文本是文本输入文本,则清除文本信息
ele.clear()
except:
logger_fun.logging.exception("清除{}内容失败".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("清除{}内容成功".format(loc))
# 点击:场景需要点击某个元素时使用的
# “->”符号表示返回值注解符号,通过允许将元数据附加到描述其参数和返回值的函数来扩展其功能
def click(self, loc: object, timeout: object = 60, frequency: object = 0.5, doc: object = "") -> object:
"""
:param loc:要点击元素的元素定位(必传)
:param timeout:超时时间
:param frequency:轮询周期
:param doc:备注
:return:
"""
time.sleep(0.5)
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 系统自带点击函数
ele.click()
except:
logger_fun.logging.exception("向{}元素点击失败".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("向{}元素点击成功".format(loc))
# 获取元素文本值:获取页面中单个元素的文本值
def get_element_text(self, loc, timeout=60, frequency=0.5, doc=""):
"""
:param loc:要获取元素文本值的元素定位(必传)
:param timeout:超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 获取元素文本值
text = ele.text
except:
logger_fun.logging.exception("获取{}元素文本值失败".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("获取{}元素文本值成功".format(loc))
return text
# 获取元素属性值:
def get_element_attribute(self, loc, attr, timeout=60, frequency=0.5, doc=""):
"""
:param loc:要获取元素属性值的元素定位(必传)
:param attr:要获取元素属性值的元素属性名(必传)
:param timeout:超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 获取元素属性
value = ele.get_attribute(attr)
except:
logger_fun.logging.exception("获取{}元素属性值失败".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("获取{}元素属性值成功".format(loc))
return value
# 获取列表数据长度:列表中没有“共XX条”汇总数据;需要从表格中获取一共有多少条数据;返回整数类型
def get_list_length(self, loc, timeout=60, frequency=0.5, doc=""):
"""
:param loc:要获取元素数量的元素定位
:param timeout:超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
#查找元素
ele = self.get_elements(loc, doc)
try:
# 获取元素列表数据长度
value = len(ele)
except:
logger_fun.logging.exception("获取{}元素属性值失败".format(loc))
self.do_save_screenshot(doc)
return 0
else:
logger_fun.logging.info("获取{}元素属性值成功".format(loc))
return value
# 获取列表数据单列内容
def get_list_single_column(self, table_tbody, j, doc=''):
"""
:param table_tbody: 表格定位路径
:param j:获取第几列元素
:param doc:备注信息
:return:将列内容返回至列表中
"""
# 定位表格路径
element = self.driver.find_element_by_xpath(table_tbody)
# 获取每一行的数据tr
table_tr_list = element.find_elements_by_tag_name("tr")
time.sleep(1)
table_list = []
# 获取列表数据
total_numbers = len(table_tr_list)
if total_numbers >= 1:
for i in range(1, total_numbers + 1):
table_td_list = self.driver.find_elements_by_xpath(table_tbody + '//tr[' + str(i) + ']//td[' + str(j) + ']')[0]
# 获取其文本信息
table_td_list1 = table_td_list.get_attribute('textContent')
table_list.append(table_td_list1)
# 去除空白字符
new_table_list = [x.strip() for x in table_list if x.strip() != '']
return new_table_list
else:
ret_info = "列表数据为空"
return ret_info
# 获取列表页面全部数据
def get_list_all_datas(self, table_tbody):
time.sleep(1)
list_1 = []
# 定位表格路径
element = self.driver.find_element_by_xpath(table_tbody)
# 获取每一行数据tr
table_tr_list = element.find_elements_by_tag_name("tr")
# 按行查询表格的数据,取出的数据是一整行
for tr in table_tr_list:
# tr.text获取表格每行的文本内容、切割字符串
list_2 = (tr.text).split()
list_1.append(list_2)
if list_1 != []:
return list_1
else:
ret_info = "列表数据为空"
return ret_info
# 切换窗口/页面:应用场景:点击按钮等操作出现一个新的页面,需要操作新页面中的元素时要先切换到新页面中。最新出现的窗口下标为-1
def switch_window(self, doc=""):
"""
:param doc: 备注
:return:
"""
try:
# 获取所有的window列表
windows = self.driver.window_handles
#切换到最新窗口
self.driver.switch_to.window(windows[-1])
except:
logger_fun.logging.exception("切换窗口失败")
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("切换窗口成功")
# 获取当前日期:返回当前日期,格式为年月日
def get_date(self):
return time.strftime("%Y-%m-%d", time.localtime(time.time()))
# 获取元素默认值:
def get_default_value(self, loc, timeout=60, frequency=0.5, doc=""):
"""
:param loc:获取元素的元素定位(必传)
:param timeout: 超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 获取属性默认值
default_value = ele.get_attribute('value')
except:
logger_fun.logging.exception("获取{}元素文本值失败".format(loc))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("获取{}元素文本值成功".format(loc))
return default_value
# 通过id切换iframe:
def switch_iframe_by_id(self, id, doc=""):
self.driver.switch_to.frame(self.driver.find_element_by_id(id))
# 通过xpath切换iframe:
def switch_iframe_by_xpath(self, xpath, doc=""):
self.driver.switch_to.frame(self.driver.find_element_by_xpath(xpath))
# 通过element切换iframe:
def switch_iframe_by_element(self, element, doc=""):
self.driver.switch_to.frame(self.driver.find_element(*element))
# 输入框输入文本形式上传文件(前提:标签名为input且type="file")
def input_text_uploadfile(self, loc, value, timeout=60, frequency=0.5, doc=""):
"""
:param loc:要上传文件的上传按钮元素定位(必传)
:param value:要上传的文件名(必传:绝对路径)
:param timeout:超时时间
:param frequency:轮循周期
:param doc:备注
:return:
"""
# 元素可见
self.wait_elevisible(loc, timeout, frequency, doc)
# 查找元素
ele = self.get_element(loc, doc)
try:
# 直接上传文件的绝对路径(基本上同文本输入)
ele.send_keys(value)
except:
logger_fun.logging.exception("向{}元素输入{}失败".format(loc, value))
self.do_save_screenshot(doc)
raise
else:
logger_fun.logging.info("向{}元素输入{}成功".format(loc, value))
# 非input标签上传文件
def isnot_input_uploadfile(self, eleLoc, filePath, doc=''):
"""
使用python的win32api,win32con模拟按键输入,实现文件上传操作
:param eleLoc: 页面中的上传文件按钮
:param filePath: 要上传的文件地址(绝对路径)
:param doc: 备注信息
:return:
"""
try:
# 复制文件路径到剪切板
pyperclip.copy(filePath)
self.click(eleLoc)
time.sleep(3)
# 发送 ctrl(17) + V(86)按钮(将剪切的内容粘贴)
win32api.keybd_event(17, 0, 0, 0)
win32api.keybd_event(86, 0, 0, 0)
# 松开按键
win32api.keybd_event(86, 0, win32con.KEYEVENTF_KEYUP, 0)
win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0)
time.sleep(1)
# 点击回车键
win32api.keybd_event(13, 0, 0, 0)
win32api.keybd_event(13, 0, win32con.KEYEVENTF_KEYUP, 0)
except:
time.sleep(1)
logger_fun.logging.info("向{}元素上传文件失败".format(eleLoc))
self.do_save_screenshot(eleLoc)
raise
else:
time.sleep(1)
logger_fun.logging.info("向{}元素上传文件成功".format(eleLoc))
# 移动到某元素上:当元素定位无法定位到唯一值时,需要先移动到相近位置的某个元素上,才能对指定元素进行操作
def move_to_element(self, loc, Index=1, timeout=120, frequency=0.5, doc=""):
"""
:param loc: 要移动到某个元素的元素定位(必传)
:param Index: 元素下标
:param timeout: 超时时间
:param frequency: 轮循周期
:param doc: 备注
:return:
"""
ac = ActionChains(self.driver)
# 找到要操作的元素
ele = self.get_elements(loc, doc)[Index]
# 鼠标悬浮
ac.move_to_element(ele)
ac.perform()
4、main函数
from Common.dir_config import logs_path, screenshots_path, Testcases_path, reports_path
# 导入测试报告BeautifulReport 模块
from BeautifulReport import BeautifulReport
# 导入unittest模块
import unittest
# 导入删除日志、测试报告自定义模块
from Common.delete_log import DeleteLogFile as DF
"""Windows系统"""
# 删除目录下的日志文件、测试报告文件
Del_log = DF(r"{}".format(logs_path), r"{}".format(screenshots_path))
Del_log.delete_file()
if __name__ == '__main__':
# 批量调用测试用例
suite_tests = unittest.defaultTestLoader.discover(r"{}".format(Testcases_path))
BeautifulReport(suite_tests).report(
filename='Auto_Testing_01自动化测试报告',
description='Auto_Testing_01自动化测试报告',
report_dir=r'{}'.format(reports_path)
)
5、删除日志、测试报告
import os
class DeleteLogFile:
# 构造初始化函数
def __init__(self, path_1, path_2):
self.path_1 = path_1
self.path_2 = path_2
def delete_file(self):
for path in [self.path_1, self.path_2]:
# 获取path路径下的全部文件
all_log_files = os.listdir(path)
# 对列表文件进行排序
all_log_files.sort()
for num in range(len(all_log_files)):
# 批量删除文件目录
os.remove(os.path.join(path,all_log_files[num]))
6、测试用例
import unittest
from selenium import webdriver
import time
class TestLogin(unittest.TestCase):
# setUp():每个测试方法运行前进行(测试前初始化工作,一条用例执行一次,若N次用例就需要执行N次)
# setUpclass():所有的测试方法运行前运行,为单元测试做前期准备,但必用@classmethod装饰器修饰,整个测试过程中只执行一次
# tearDown():每个测试方法运行结束后运行,测试后的清理工作。一条用例执行一次,若N次用例就执行N次。
# tearDownClass():所有的测试方法运行结束后运行,为单元测试做后期清理工作,但必须使用@classmethod装饰器进行修饰,整个测试过程中只执行一次
# 测试开始前创建浏览器
# @classmethod
def setUp(self):
executable_path = r"G:BaiduNetdiskDownloadchromedriver.exe"
self.driver = webdriver.Chrome(executable_path)
self.driver.get(GD.login_url_mainframe)
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()