0.前言
此框架为真实项目实战,所以有些数据不便展示,只展示架构和思想
工具:python+selenium+ddt+unittest
1.架构说明
2.代码封装
Commom层
base_page.py
#__author__=19044168
#date=2021/8/26
import logging
import datetime
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
import time
from Common import dir_config
from Common.logger import GetLogger
logger = GetLogger.get_logger()
#1.封装基本函数,执行日志,异常处理,失败截图
#2.所有的页面公共行为,非业务层面的行为
class BasePage:
def __init__(self,driver):
self.driver = driver
#等待元素可见
def wait_eleVisible(self,locator,wait_times=30,poll_frequency=0.5,doc=""):
"""
:param locator: 元素定位元组形式(定位类型,定位方式)
:param times:
:param poll_frequency:
:param doc: 模块名_页面名_操作名
:return: None
"""
logger.info("等待元素{0}可见".format(locator))
try:
#开始等待时间
start = datetime.datetime.now().timestamp()#转化为s
WebDriverWait(self.driver,wait_times,poll_frequency).until(EC.visibility_of_element_located(locator))
# 结束等待时间
end = datetime.datetime.now().timestamp()
#求一个时间差值写到日志中
sub_time = end - start
logger.info("等待时长为:{}".format(sub_time))
# self.save_screenshot(doc)
except:
logger.exception('等待元素可见失败!!!')
#截图操作
self.save_screenshot(doc)
raise
#待元素不可见
def wait_eleNotVisible(self,locator,wait_times=30,poll_frequency=0.5,doc=""):
"""
:param locator: 元素定位元组形式(定位类型,定位方式)
:param times:
:param poll_frequency:
:param doc: 模块名_页面名_操作名
:return: None
"""
logger.info("{0}等待元素{1}不可见".format(doc,locator))
try:
# 开始等待时间
start = datetime.datetime.now() # 转化为s
WebDriverWait(self.driver,wait_times, poll_frequency).until(EC.invisibility_of_element_located(locator))
# 结束等待时间
end = datetime.datetime.now()
# 求一个时间差值,写到日志中
wait_time = (end - start).seconds
logger.info("{0}:元素{1}已可见,等待起始时间:{2},等待结束时间:{3},等待时长为:{4}".format(doc,locator,start,end,wait_time))
# self.save_screenshot(doc)
except:
logger.exception('等待元素不可见失败!!!')
# 截图操作
self.save_screenshot(doc)
raise
#等待元素存在
def wait_elePresence(self,locator,wait_times=30,poll_frequency=0.5,doc=""):
logger.info("等待元素{0}存在".format(locator))
try:
# 开始等待时间
start = datetime.datetime.now().timestamp() # 转化为s
WebDriverWait(self.driver, wait_times, poll_frequency).until(EC.presence_of_element_located(locator))
# 结束等待时间
end = datetime.datetime.now().timestamp()
# 求一个时间差值写到日志中
sub_time = end - start
logger.info("等待时长为:{}".format(sub_time))
# self.save_screenshot(doc)
except:
logger.exception('等待元素存在失败!!!')
# 截图操作
self.save_screenshot(doc)
raise
#查找元素
def get_element(self,locator,doc=""):
logger.info('{0}查找元素:{1}'.format(doc,locator))
try:
return self.driver.find_element(*locator)
except:
logger.exception("查找元素失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
#点击操作
def click_elemnet(self,locator,doc=""):
#找元素
ele = self.get_element(locator,doc)
logger.info("{0}点击元素:{1}".format(doc,locator))
#元素操作
try:
ele.click()
except:
logger.exception("点击元素失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
#输入操作
def input_text(self,locator,text,doc=""):
# 找元素
ele = self.get_element(locator,doc)
logger.info("{0}输入元素{1}".format(doc,locator))
try:
ele.send_keys(text)
except:
logger.exception("输入元素失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
#获取元素文本内容
def get_text(self,locator,doc=""):
ele = self.get_element(locator,doc)
logger.info("{0}获取文本内容{1}".format(doc,locator))
try:
return ele.text
except:
logger.exception("获取文本内容失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
#获取元素属性
def get_element_attribute(self,locator,attr,doc=""):
ele = self.get_element(locator, doc)
logger.info("{0}获取元素{1}的{2}属性的值".format(doc,locator,attr))
try:
return ele.get_attribute(attr)
except:
logger.exception("获取元素属性失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
# 获取所有句柄
def get_handles(self,doc):
logger.info("获取所有页面句柄")
try:
return self.driver.window_handles
except:
logger.exception("获取所有页面句柄失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
# 获取当前句柄
def get_current_handle(self, doc):
logger.info("获取当前{}页面句柄".format(doc))
try:
return self.driver.current_window_handle
except:
logger.exception("获取获取当前页面句柄失败!!!")
# 截图操作
self.save_screenshot(doc)
raise
# 窗口切换
def switch_window(self,handle,doc):
logger.info("从当前窗口{}切换到指定窗口".format(doc))
try:
self.driver.switch_to.window(handle)
except:
logger.exception("切换窗口失败")
self.save_screenshot(doc)
raise
#获取当前url
def get_current_url(self,doc):
logger.info("获取当前页面{}url".format(doc))
try:
return self.driver.current_url
except:
logger.exception("获取当前页面url失败")
self.save_screenshot(doc)
raise
#移动到元素
def move_to_element(self,target,doc):
logger.info("{0},移动元素到{1}".format(doc,target))
try:
ActionChains(self.driver).move_to_element(target).perform()
except:
logger.exception("移动元素失败")
self.save_screenshot(doc)
raise
#alert处理
def alert_action(self,action='accept'):
pass
#iframe切换
def switch_iframe(self,iframe_reference):
pass
#上传操作
def upload_file(self):
pass
#截图操作
def save_screenshot(self,doc):
#图片名称:模块名_页面名_操作名_年-月-日-时-分-秒.png
# now = datetime.datetime.now()
# format_time = now.strftime("%Y-%m-%d-%H-%M-%S")
# filename = 'OutPuts/screenshots/' + "{0}_{1}.png".format(name,format_time)
# self.driver.save_screenshot(filename)
# logging.info('截取网页成功,文件路径为{}'.format(filename))
filePath = dir_config.screenshot_dir + '/'+ '{0}_{1}.png'.format(doc,time.strftime("%Y-%m-%d-%H-%M-%S",time.localtime()))
try:
self.driver.save_screenshot(filePath)
logger.info("截屏成功,截屏路径为{0}".format(filePath))
except:
logger.exception('截屏失败!!')
raise
#滚动条操作...
if __name__ == '__main__':
pass
dir_config.py
email_fundation.py
#__author__=19044168
#date=2021/8/13
import smtplib
from email.mime.text import MIMEText#这是发正文
from email.mime.multipart import MIMEMultipart#这是发附件
import os
class Email:
def __init__(self):
self.send_user = 'xx@xxing.com'
self.receive_user = 'yy@yying.com'
# global user_list
self.user_list = ['xx@xxing.com','ss4@ssing.com']
self.server_host = 'smtp.suning.com'
self.username = 'dfdds@ddning.com'
self.password = 'dfsdfsdfsd'
def sendEmail(self,new_file):
f = open(new_file,'rb')
mail_body = f.read()
f.close()
msg = MIMEMultipart('mixed')
html_file = MIMEText(mail_body,'html','utf-8')
# msg = MIMEMultipart(mail_body,'html','utf-8')
msg['Subject'] = "Hbase WebAutoTest Report"
context = MIMEText('<html><h2>您好,这是Hbase服务化测试报告</h2></html>', 'html', 'utf-8')
msg.attach(context)
msg.attach(html_file)
# 发动邮件
server = smtplib.SMTP()
server.connect(self.server_host)
server.login(self.username, self.password)
# server.sendmail(self.send_user, self.receive_user, msg.as_string())
server.sendmail(self.send_user, self.user_list, msg.as_string())
server.close()
def new_report(self,reportpath):
lists = os.listdir(reportpath)
lists.sort(key=lambda fn:os.path.getmtime((reportpath + '/' + fn)))
print(lists)
new_file = os.path.join(reportpath,lists[-1])
print(new_file)
return new_file
e = Email()
if __name__ == '__main__':
print(os.getcwd())
e = Email()
test_report = '../OutPuts/reports'
new_report = e.new_report(test_report)
e.sendEmail(new_report)
logger.py
#__author__=19044168
#date=2021/8/27
import logging.handlers
import time
from Common.dir_config import logs_dir
class GetLogger():
logger = None
@classmethod
def get_logger(cls):
if cls.logger == None:
# 日志器实例
cls.logger = logging.getLogger()
# 设置日志级别
cls.logger .setLevel(level=logging.INFO)
# 控制台处理器实例
ch = logging.StreamHandler()
# 以时间切分日志文件处理器
filename = logs_dir + '/' + time.strftime('%Y-%m-%d-%H-%M-%S') + '_WebUiTestLog.log'
th = logging.handlers.TimedRotatingFileHandler(filename=filename,encoding='utf-8')
# 设置日志格式
fmt = "%(asctime)s %(levelname)s [%(name)s] [filename: %(filename)s - module: %(module)s - func: %(funcName)s %(lineno)d line] - %(message)s"
fm = logging.Formatter(fmt)
# 将日志格式添加到处理器
ch.setFormatter(fm)
th.setFormatter(fm)
# 将处理器添加到日志器
cls.logger .addHandler(ch)
cls.logger .addHandler(th)
return cls.logger
if __name__ == '__main__':
print(time.strftime())
OutPuts层,不需要封装,代码定义好目录即可
PageLocators层,根据页面层次进行组织
loginpage_locators.py,登录页面
其他页面自行封装
PageObjects层,根据页面层次进行组织
login_page.py,登录页面
#__author__=19044168
#date=2021/8/19
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PagesObjects.index_page import IndexPage
from PageLocators.loginpage_locators import LoginPageLocator as loc #直接用,不要继承,不然对象可选方法变量太多
from time import sleep
from Common.logger import GetLogger
logger = GetLogger.get_logger()
from Common.base_page import BasePage
class LoginPage(BasePage):
# #用户名输入框
# username_loc = (By.XPATH,'//input[@id="userId"]')
# #密码输入框
# password_loc = (By.XPATH,'//input[@id="password"]')
# #登录按钮
# login_loc = (By.XPATH,'//a[@id="submit_btn"]')
# #是否记住密码勾选框
# remember_loc = (By.XPATH,'//input[@id="remember"]')
# #登录入口链接
# enter_loc = (By.XPATH,'//a[@class="btn-login"]')
# #用户名格式错误验证文本
# errorMsg_from_wrongUser = (By.XPATH,'//div[@class="layui-layer-content"]')
# #账号密码错误提示文本
# msg_loc = (By.XPATH,'//div[@id ="error"]')
# def __init__(self,driver):
# self.driver = driver
#登录入口
def login_enter(self):
doc = '登录地址入口页面_登录跳转功能'
self.wait_eleVisible(loc.enter_loc,doc=doc)
self.click_elemnet(loc.enter_loc,doc=doc)
# enter_loc = '//a[@class="btn-login"]'
# WebDriverWait(self.driver,10).until(EC.visibility_of_element_located(loc.enter_loc))
# self.driver.find_element_by_xpath(enter_loc).click()
# self.driver.find_element(*self.enter_loc).click()#解包元组
# self.driver.find_element(*loc.enter_loc).click()#解包元组
#登录页面
def login(self,username,password,is_remember=False):
# 这些元素属性可以定义成类的属性,最好注释说明。但是有弊端,因为定位方式不止一种,修改定位就要修改元素操作方式
# 不方便优化
# username_loc = '//input[@id="userId"]'
# password_loc = '//input[@id="password"]'
# login_loc = '//a[@id="submit_btn"]'
# remember_loc = '//input[@id="remember"]'
# WebDriverWait(self.driver,10).until(EC.visibility_of_element_located(loc.username_loc))
doc = "登录页面_登录功能"
self.wait_eleVisible(loc.username_loc,doc=doc)
self.input_text(loc.username_loc,username,doc=doc)
self.input_text(loc.password_loc,password,doc=doc)
# self.driver.find_element(*loc.username_loc).send_keys(username)
# self.driver.find_element(*loc.password_loc).send_keys(password)
#is_remember来判断是否需要记住账号密码
if is_remember:
# self.driver.find_element(*loc.remember_loc).click()
self.click_elemnet(loc.remember_loc,doc=doc)
else:
pass
# self.driver.find_element(*loc.login_loc).click()
self.click_elemnet(loc.login_loc,doc=doc)
#获取账号格式错误信息
def get_errorMsg_from_wrongUser(self):
# ret = self.driver.find_element(*loc.errorMsg_from_wrongUser)
doc = "登录页面_账号格式错误信息"
ret = self.get_text(loc.errorMsg_from_wrongUser,doc=doc)
return ret
#获取账号或者密码错误
def get_errorMsg_from_pageCenter(self):
doc = "登录页面_账号格式错误信息"
self.wait_eleVisible(loc.msg_loc,doc=doc)
# WebDriverWait(self.driver,10).until(EC.visibility_of_element_located(loc.msg_loc))
ret = self.get_text(loc.msg_loc,doc=doc)
return ret
#帮助链接页面
def help_link(self):
current_handle = self.get_current_handle(doc="登录页面句柄")#其实也可以在base_page封装
doc = "登录页面_帮助链接功能"
self.wait_eleVisible(loc.help_link_loc,doc=doc)
self.click_elemnet(loc.help_link_loc,doc=doc)
handle = self.get_handles(doc="获取所有页面句柄")[-1]
self.switch_window(handle,doc="帮助链接页面")#其实也可以在base_page封装
help_url = self.get_current_url(doc='帮助链接页面')
self.switch_window(current_handle,doc='登录页面')#其实也可以在base_page封装
return help_url
# 忘记密码链接页面
def forget_pwd_link(self):
current_handle = self.get_current_handle(doc="登录页面的句柄")#其实也可以在base_page封装
doc = "登录页面_忘记密码链接功能"
self.wait_eleVisible(loc.forget_pwd_link_loc,doc=doc)
self.click_elemnet(loc.forget_pwd_link_loc,doc=doc)
self.switch_window(self.get_handles(doc='获取所有页面句柄')[-1],doc='忘记密码链接页面')#其实也可以在base_page封装
forget_pwd_url = self.get_current_url(doc='忘记密码链接url')#其实也可以在base_page封装
self.switch_window(current_handle,doc='登录页面')#其实也可以在base_page封装
return forget_pwd_url
if __name__ == '__main__':
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()
driver.maximize_window()
driver.get('http://datakingdom.cnsuning.com/')
lg = LoginPage(driver)
ip = IndexPage(driver)
lg.login_enter()
# print(lg.help_link())
# print(lg.forget_pwd_link())
# lg.login_redirect()
lg.login('19044168','19044168')
sleep(1)
driver.find_element_by_link_text('控制台').click()
sleep(1)
driver.find_element_by_xpath('//div[contains(text(),"平台基础服务")]').click()
driver.find_element_by_css_selector('ul[role="menubar"] > li:nth-child(4) > ul > li:nth-child(1)').click()
sleep(1)
driver.find_element_by_xpath('//tr[2]//a').click()
driver.find_element_by_xpath('//ul[@x-placement="bottom-end"]/li[contains(text(),"进入雨花prd环境")]').click()
sleep(3)
# print(lg.get_errorMsg_from_pageCenter())
# target = driver.find_element_by_xpath('//div[@class="user-info"]')
# ActionChains(driver).move_to_element(target).perform()
# a = driver.find_element_by_link_text('退出')
# print(a.text)
# ip.isExist_logout_ele()
driver.quit()
TestCases层
#__author__=19044168
#date=2021/8/20
from selenium import webdriver
import time
from PagesObjects.login_page import LoginPage
from PagesObjects.index_page import IndexPage
import unittest
from TestDatas import Common_Datas as CD
from TestDatas import Login_Datas as LD
import ddt
from Common.logger import GetLogger
logger = GetLogger.get_logger()
@ddt.ddt
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
logger.info("======整个用例前置:打开浏览器,只执行一次=======")
#通过excel获取本功能当中所需要的测试数据,但是比较麻烦。建议使用python文件保存测试数据
cls.driver = webdriver.Chrome()
cls.driver.maximize_window()
cls.driver.get(CD.web_login_url)
cls.lg = LoginPage(cls.driver)
cls.lg.login_enter()#将该步骤提前到初始化步骤中,节省时间
# cls.driver.save_screenshot()
@classmethod
def tearDownClass(cls):
logger.info("=======整个用例后置:关闭浏览器,只执行一次=======")
cls.driver.quit()
#每个案例执行后进行页面刷新
def tearDown(self):
logger.info("=======每个用例后置:刷新一次页面======")
self.driver.refresh()
time.sleep(0.5)
def test05_login_success(self):
# self.lg.login_enter()#将该步骤提前到初始化步骤中,节省时间
logger.info("=======登录用例:正常场景->登陆成功======")
self.lg.login(LD.success_data['user'],LD.success_data['passwd'])
self.assertTrue(IndexPage(self.driver).isExist_logout_ele(),'退出元素未出现')
#异常用例--账号格式不正确(大于8位,小于8位,等于8位且包含非数字)
#ddt,步骤断言都一致,只有文本内容不一致
@ddt.data(*LD.user_data)
def test01_login_wrongUserFormat(self,data):
# self.lg.login_enter()#将该步骤提前到初始化步骤中,节省时间
logger.info("=======登录用例:异常场景->账号格式错误,登陆失败======")
self.lg.login(data['user'], data['passwd'])
self.assertEqual(self.lg.get_errorMsg_from_wrongUser(),data['check'], '账号格式错误提示信息不对')
# 异常用例--用户名或者密码错误
@ddt.data(*LD.user_pwd_data)
def test02_login_wrongUserOrPwd(self,data):
# self.lg.login_enter()#将该步骤提前到初始化步骤中,节省时间
logger.info("=======登录用例:异常场景->用户名或者密码错误,登陆失败======")
self.lg.login(data['user'],data['passwd'])
self.assertEqual(self.lg.get_errorMsg_from_pageCenter(),data['check'],'非用户名或密码错误')
#帮助链接验证用例
def test03_help_link(self):
logger.info("=======登录帮链接用例:正常场景->帮助链接有效======")
self.assertIn(LD.help_url,self.lg.help_link(),'帮助链接地址不包含在内')
# 忘记密码链接验证用例
def test04_forget_pwd_link(self):
logger.info("=======登录忘记密码帮链接用例:正常场景->忘记密码链接有效======")
self.assertIn(LD.forget_pwd_url,self.lg.forget_pwd_link(),'忘记密码链接地址不包含在内')
if __name__ == '__main__':
unittest.main()
TestDatas层
run.py
#__author__=19044168
#date=2021/8/27
import unittest
from HTMLTestRunner import HTMLTestRunner
from Common.dir_config import htmlreport_dir,testcases_dir
import time
from Common.send_mail_file import *
#实例化套件对象
suite= unittest.TestSuite()
#TestLoader的用法
#1.实例化Testloader对象
#2.使用discover去找到一个目录下的所有用例
#3.使用suite
loader = unittest.TestLoader()
# suite.addTests(loader.discover(testcases_dir,pattern='log*'))
suite.addTests(loader.discover(testcases_dir,pattern='*test.py'))
#运行
# runner = unittest.TextTestRunner()
# runner.run(suite)
cur_time = time.strftime("%Y-%m-%d-%H-%M-%S",time.localtime())
fp = open(htmlreport_dir + '/' + cur_time + '-AutoTest_report.html','wb')
runner = HTMLTestRunner(stream=fp,title='web测试报告',description="这是hbase的web测试报告",tester='小猪')
runner.run(suite)
e.sendEmail(e.new_report(htmlreport_dir))
运行run.py文件即可
收工!!!