框架采用python3 + selenium3 + PO + yaml + ddt + unittest等技术编写成基础测试框架,并在uiintest基础上增加了session级用例前置,用例失败重跑,用例失败自动截图,美化了测试报告。能适应日常测试工作需要。
下图为项目整体结构
基础方法封装
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : base.py # project : Caps_UI_Test # time : 2020/11/27 18:39 # Describe : 基础方法 # --------------------------------------- import os,sys import psycopg2 import logging.config import time sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import NoSuchFrameException,NoSuchWindowException,NoAlertPresentException,NoSuchElementException CON_LOG = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+'/config/log.conf' logging.config.fileConfig(CON_LOG) logging = logging.getLogger() class BaseView(object): def __init__(self, driver): self.driver = driver self.base_url = 'http://mcenter.test.mall' self.timeout = 11 def _open(self, url): """ 打开浏览器并访问URL地址 :param url: url地址 :return: """ url_ = self.base_url + url logging.info("this page is %s" % url_) self.driver.maximize_window() self.driver.get(url_) assert self.driver.current_url == url_, 'Did ont land on %s' % url_ def open(self): """ 调用私有函数 :return: """ self._open(self.url) def __locate_Element_selector(self, selector): """ 八种定位方式选择 :param selector: 传入的格式必须为:定位方式,定位元素值,顺序不可改变 :return: 返回定位方式 """ selector_by = selector.split(',')[0].strip() selector_value = selector.split(',')[1].strip() if selector_by in ('i', 'id'): locator = (By.ID, selector_value) elif selector_by in ('n', 'name'): locator = (By.NAME, selector_value) elif selector_by in ('c', 'class'): locator = (By.CLASS_NAME, selector_value) elif selector_by in ('x', 'xpath'): locator = (By.XPATH, selector_value) elif selector_by in ('s', 'css'): locator = (By.CSS_SELECTOR, selector_value) elif selector_by in ('t', 'tag_name'): locator = (By.TAG_NAME, selector_value) elif selector_by in ('l', 'link_text'): locator = (By.LINK_TEXT, selector_value) elif selector_by in ('ll', 'partial_link_text'): locator = (By.PARTIAL_LINK_TEXT, selector_value) else: raise Exception('selector Error') return locator def find_element(self, selector): """ 单个元素定位 :param loc:定位方式和元素属性 :return: """ time.sleep(0.5) try: loc = self.__locate_Element_selector(selector) WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(loc)) return self.driver.find_element(*loc) except: logging.error('-------------定位异常{0}--------------'.format(selector)) def find_elements(self, selector): """ 多个元素定位 :param loc:定位方式和元素属性 :return: """ time.sleep(0.5) try: loc = self.__locate_Element_selector(selector) WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(loc)) return self.driver.find_elements(*loc) except: logging.error('-------------定位异常{0}--------------'.format(selector)) def select_text(self, selector, text): """ 点击指定文本 :param selector:定位到的一组元素 :param text: 想要点击的元素文本 :return: """ listtext = self.find_elements(selector) for i in listtext: if i.text == text: i.click() break else: logging.error("没有找到想要的元素%s" % text) def switch_frame(self, loc): """ 多表单嵌套切换 :param loc: 传元素的属性值 :return: 定位到的元素 """ try: return self.driver.switch_to_frame(loc) except NoSuchFrameException as msg: logging.error("查找iframe异常-> {0}".format(msg)) def switch_windows(self, loc): """ 多窗口切换 :param loc: :return: """ try: return self.driver.switch_to_window(loc) except NoSuchWindowException as msg: logging.error("查找窗口句柄handle异常-> {0}".format(msg)) def switch_alert(self): """ 警告框处理 :return: """ try: return self.driver.switch_to_alert() except NoAlertPresentException as msg: logging.error("查找alert弹出框异常-> {0}".format(msg)) def get_element_attribute(self, selector: str, value='value') -> str: """获取元素属性""" ele = self.find_element(selector) return ele.get_attribute(value) def execute_script(self, js) -> None: """执行js脚本""" self.driver.execute_script(js) def exhibition_element(self, selector: str) -> None: """将元素显示到可见窗口中 """ ele = self.find_element(selector) js = "arguments[0].scrollIntoView();" self.driver.execute_script(js, ele) def get_conceal_text(self, selector): """获取一组元素文本,包含隐藏元素""" ele = self.find_elements(selector) textlist = [] js = "return arguments[0].textContent" for i in ele: # text = i.get_attribute('textContent') 效果相同 text = self.driver.execute_script(js, i) textlist.append(text) return textlist def addAttribute(self, selector, attributeName, value): ''' 封装向页面标签添加新属性的方法 调用JS给页面标签添加新属性,arguments[0]~arguments[2]分别 会用后面的element,attributeName和value参数进行替换 添加新属性的JS代码语法为:element.attributeName=value 比如input.name='test' ''' ele = self.find_element(selector) self.driver.execute_script("arguments[0].%s=arguments[1]" % attributeName, ele, value) def setAttribute(self, selector, attributeName, value): ''' 封装设置页面对象的属性值的方法 调用JS代码修改页面元素的属性值,arguments[0]~arguments[1]分别 会用后面的element,attributeName和value参数进行替换 ''' ele = self.find_element(selector) self.driver.execute_script("arguments[0].setAttribute(arguments[1],arguments[2])", ele, attributeName, value) def get_pgsql_database(self, sql, database='test库名'): """连接数据库,返回查询数据""" conn = psycopg2.connect(database=database, user="postgres", password=None, host="192.168.0.202", port="5432") cur = conn.cursor() cur.execute(sql) data = cur.fetchall() cur.close() logging.info(data) return data # 重写定义send_keys方法 def send_key(self, selector, value, clear_first=True, click_first=True): loc = self.__locate_Element_selector(selector) try: #loc = getattr(self, "_%s" % loc) # getattr相当于实现self.loc if click_first: self.driver.find_element(*loc).click() if clear_first: self.driver.find_element(*loc).clear() self.driver.find_element(*loc).send_keys(value) except AttributeError: logging.error("%s 页面中未能找到 %s 元素" % (self, loc))
公共方法介绍
这里放几个常用的方法示例
实例化driver
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : driver.py # project : Test_Ui_Yt # time : 2020/11/27 18:52 # Describe : # --------------------------------------- from selenium import webdriver def browser(web=None): if web == 'Ie': driver = webdriver.Ie() elif web == 'Firefox': driver = webdriver.Firefox() elif web == 'ChromeOptions': option = webdriver.ChromeOptions() option.add_argument('--no-sandbox') #以无头模式运行 option.add_argument('--headless') option.add_argument('--window-size=1920,1080') option.add_argument('lang = zh_CN.UTF - 8') driver = webdriver.Chrome(chrome_options=option) else: driver = webdriver.Chrome() driver.maximize_window() driver.implicitly_wait(5) return driver
读取yaml文件
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : readyaml.py # project : Test_Ui_Yt # time : 2020/11/27 19:02 # Describe : # --------------------------------------- import yaml import os class ReadYaml(object): def __init__(self, yaml_path): self.yaml_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + yaml_path def read_yaml(self): """读取yaml文件""" with open(self.yaml_path, 'r', encoding='UTF-8')as fp: yaml_data = yaml.safe_load(fp) return yaml_data if __name__ == '__main__': data = ReadYaml('configcompany_deposit.yaml').read_yaml() print(data['playername'])
用例前后置
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : myunit.py # project : Test_Ui_Yt # time : 2020/11/27 18:52 # Describe : # --------------------------------------- import time import unittest import logging from common.driver import browser from businessview.login_business import LoginBusin from common.readini import ReadIni from common.screenshot import insert_img datapath = '/data/useremail.ini' data = ReadIni(datapath) class StartEnd(unittest.TestCase): """用例执行前后置,供TestCase继承""" #放这里实现session级前置 driver = browser(data.get_value('driver')) # 使用cookie登录 # cls.driver.get('http://mcenter.uat.mall/mcenter/main/#/spaLogin') # cls.driver.add_cookie({'name': 'SID', 'value': data.get_value('cookie','ds')}) # 输入账号密码登录 lg = LoginBusin(driver) lg.lgbusiness(data.get_value('user'), data.get_value('password'), 1) def setUp(self) -> None: logging.info('----------开始执行用例-----------') def tearDown(self) -> None: #用例出现异常就截图 for method_name, error in self._outcome.errors: if error: insert_img(self.driver) self.driver.refresh() time.sleep(2)
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','utf-8') [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
yaml文件编写示例
handle操作层示例
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : transferRecordPage.py # project : Test_Ui_Yt # time : 2020/12/17 16:19 # Describe : # --------------------------------------- import time import datetime from baseview.base import BaseView from common.readyaml import ReadYaml data = ReadYaml('/config/transferRecord').read_yaml() class TransferRecordPage(BaseView): """转账记录模块""" url = data['url'] def send_username(self, value): """输入账号""" return self.find_element(data['username']).send_keys(value) def click_select(self): """点击查询""" return self.find_element(data['select']).click() def click_user_type(self): """点击用户类型""" return self.find_element(data['user_type']).click() def click_top_agent(self): """点击总代""" return self.find_element(data['top_agent']).click() def click_agent(self): """点击代理""" return self.find_element(data['agent']).click() def send_order_number(self, value): """输入订单号""" return self.find_element(data['order_number']).send_keys(value) def send_create_time(self, value): """输入创建时间""" self.addAttribute(data['create_time'], 'id', 456789) js = "document.getElementById('456789').removeAttribute('readonly')" self.execute_script(js) # today = datetime.datetime.strptime(time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()), '%Y/%m/%d %H:%M:%S') # last_month = today - datetime.timedelta(days=30) # today = str(today).replace('-', '/') # last_month = str(last_month).replace('-', '/') # value = (last_month + ' - ' + today) # print(value) return self.find_element(data['create_time']).send_keys(value) def send_max_money(self, value): """输入最大金额""" return self.find_element(data['max_money']).send_keys(value) def click_game_type(self): """点击游戏类型""" return self.find_element(data['game_type']).click() def click_game_name(self, value): """点击游戏名""" return self.select_text(data['game_name'], value) def click_high_class(self): """点击高级查询""" return self.find_element(data['high_class']).click() def get_name_text(self): """获取账号文本""" return self.get_conceal_text(data['name_text']) def get_game_text(self): """获取游戏文本""" return self.get_conceal_text(data['game_text']) def get_order_text(self): """获取订单号文本""" return self.find_elements(data['order_text']) def get_money_text(self): """获取金额文本""" return self.get_conceal_text(data['money_text']) def get_size_number(self): """获取结果条数""" text = self.find_element(data['size_number']).text number = text.split(' ')[1] return number def click_next_page(self): """点击下一页""" return self.find_element(data['next_page']).click() def click_size(self): """点击每页条数""" return self.find_element(data['size']).click() def click_size200(self): """点击每页200""" return self.find_element(data['size200']).click() def click_order_type(self): """点击订单状态""" return self.find_element(data['order_type']).click() def click_being_processed(self): """点击处理中""" return self.find_element(data['being_processed']).click() def click_success(self): """点击成功""" return self.find_element(data['success']).click() def click_failed(self): """点击失败""" return self.find_element(data['failed']).click() def get_status_text(self): """获取状态文本""" return self.get_conceal_text(data['status_text']) def get_time_text(self): """获取时间文本""" return self.get_conceal_text(data['time_text'])
case示例
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : agentTransfer_test.py # project : Test_Ui_Yt # time : 2020/12/2 14:53 # Describe : # --------------------------------------- import unittest import ddt from businessview.agent_Transfer_business import AgentTransferBusin from common.runfailed import Retry from common.readexcel import ReadExcel from common.myunit import StartEnd data = ReadExcel('/data/selectagentTransfer.xls').get_data() @ddt.ddt() @Retry(max_n=2) class AgentTransfer(StartEnd): @ddt.data(*data) def test_agentTransfer(self, data): yw = AgentTransferBusin(self.driver) types, value = data result = yw.call_select(types, value) self.assertTrue(result,'测试失败') if __name__ == '__main__': unittest.main()
业务层示例
# -*- coding = UTF-8 -*- # Autohr : 叶松桥 # File : agent_Transfer_business.py # project : Test_Ui_Yt # time : 2020/12/2 14:08 # Describe : # --------------------------------------- from handle.agentTransferPage import AgentTransferPage from baseview.base import logging import time import datetime class AgentTransferBusin(AgentTransferPage): def select_name(self, value): """账号查询""" self.send_username(value) self.click_select() name_text = self.get_name_text() for i in name_text: if value not in i: logging.error('-----------账号查询异常,异常账号 % s ' % i) return False logging.info('------------账号查询测试通过-----------------') return True def select_order(self, value): """订单号查询""" self.send_ordernum(value) self.click_select() order = self.get_order_text() logging.info(order) if value == order: logging.info('------------订单号查询测试通过-----------------') return True else: logging.error('-----------账号查询异常,异常订单号 % s ' % order) return False def select_operator(self, value): """操作人查询""" self.send_operator(value) self.click_select() operator_text = self.get_operator_text() for i in operator_text: if value not in i: logging.error('-----------操作人查询异常,异常操作人 % s ' % i) return False logging.info('------------操作人查询测试通过-----------------') return True def select_time(self): """时间查询""" self.click_time() self.click_days30() self.click_confirm() timevalue = self.get_time_value() self.click_select() logging.info(timevalue) str_start_time = timevalue.split('-')[0].strip() str_end_time = timevalue.split('-')[1].strip() logging.info(str_start_time) logging.info(str_end_time) start_time = datetime.datetime.strptime(str_start_time, '%Y/%m/%d %H:%M:%S') end_time = datetime.datetime.strptime(str_end_time, '%Y/%m/%d %H:%M:%S') time_list = self.get_time_text() for i in time_list: i = datetime.datetime.strptime(i, '%Y/%m/%d %H:%M:%S') if i<start_time or i>end_time: logging.error('-----------时间查询异常,异常时间 % s ' % i) return False logging.info('------------时间查询测试通过-----------------') return True def select_type(self): """类型查询""" self.click_types() self.click_huabo() type_text1 = self.get_type_text() for i in type_text1: if i!= '劃撥': logging.error('-----------类型查询异常,异常类型 % s ' % i) time.sleep(2) self.click_types() self.click_huishou() type_text2 = self.get_type_text() for i in type_text2: if i!= '回收': logging.error('-----------类型查询异常,异常类型 % s ' % i) logging.info('------------类型查询测试通过-----------------') return True def call_select(self, types, value): """调用所有查询""" try: self.open() self.click_size() self.click_size200() if types == '账号': return self.select_name(value) elif types == '订单号': return self.select_order(value) elif types == '操作人': return self.select_operator(value) elif types == '时间': return self.select_time() elif types == '类型': return self.select_type() except BaseException as er: logging.error('--------出现异常,异常信息 % s ' % er) return False
testrun模块,执行所有用例
# _*_ coding:utf-8 _*_ __author__ = '叶松桥' import configparser import os import sys import requests import unittest import time sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from config.BeautifulReport import BeautifulReport #from common.newreport import new_report #from common.sendemail import send_email def add_case(): """加载路径下以test.py结尾的测试用例""" discover = unittest.defaultTestLoader.discover(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+'/testcase/', pattern='*test.py') return discover def run_case(all_case): """执行所有的测试用例""" #获取时间 now = time.strftime("%Y-%m-%d %H_%M_%S") #测试报告名字 report_path =os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+'/report/' #+ now + 'result.html' filename = now + 'result.html' result = BeautifulReport(all_case) result.report(filename=filename, description='积分商城UI测试报告', log_path=report_path) # 调用模块生成最新的报告 #report = new_report(report_path) #send_mail(report) #调用发送邮件模块 def ClearTestResult(path): """每次运行前清空测试结果文件""" for i in os.listdir(path): path_file = os.path.join(path, i) if os.path.isfile(path_file): os.remove(path_file) else: ClearTestResult(path_file) def write_cookie(): url = 'http://mcenter.uat.mall/api-mcenter/passport/login.html' data = {'username': 'caps1', 'password': 'yt123.', 'authentication': 1} r = requests.post(url, data) value = r.headers['Set-Cookie'].split(';')[0].split('=', 1)[1] r.close() cfg = configparser.ConfigParser() path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+'/data/useremail.ini' cfg.add_section('ds') cfg.set('ds', "cookie", value) with open(path, mode="r+", encoding="utf-8") as f: cfg.write(f) if __name__ == '__main__': # path = r'E:Test_Ui_Ytlogs' # ClearTestResult(path) #write_cookie() cases = add_case() run_case(cases)
测试报告一览
这里不便放所有代码,完整项目代码打赏后可以私信获取