本人小白一枚,想着把學習時的東西以博客的方式記錄下來,文章中有不正確的地方請大佬多多指點!!共同學習
前期准備
安裝python3、selenium、下載對應版本的webdriver;安裝所需的第三方庫,不多贅述,最基礎的東西,不會的自行跳轉^0^
項目介紹
功能簡述
- 對webdriver常用方法進行二次封裝,使用起來更方便,同時會有log記錄
- log日志會同時打印在控制台和寫入log文件中
- 測試完成后,會自動發送郵件,郵件信息conf.ini可配置
- 采用PO模式編寫,元素信息維護在對應頁面中
- 支持chrome,Firefox,IE瀏覽器
- 支持web和wap模式切換,和-headless模式運行
項目結構
目錄結構如圖所示

該框架采用PO模式編寫,PO提供了一種業務流程與頁面元素操作分離的模式,這使得測試代碼變得更加清晰。頁面對象與用例分離,使得我們更好的復用對象,可復用的頁面方法代碼會變得更加優化,更加有效的命名方式使得我們更加清晰的知道方法所操作的UI元素。當某些頁面的元素發生改變時,只需要對應的頁面類即可,后面會有詳細介紹^
1 log輸出模塊
使用python的logging模塊分別輸出日志到文件和控制台,其實現方法供參考
def __printconsole(self, level, message):
# 創建一個logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 創建一個handler,用於寫入日志文件
fh = logging.FileHandler(self.logname, 'a', encoding='utf-8')
fh.setLevel(logging.DEBUG)
# 再創建一個handler,用於輸出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 定義handler的輸出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 給logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)
# 記錄一條日志
if level == 'info':
logger.info(message)
elif level == 'debug':
logger.debug(message)
elif level == 'warning':
logger.warning(message)
elif level == 'error':
logger.error(message)
logger.removeHandler(ch)
logger.removeHandler(fh)
# 關閉打開的文件
fh.close()
def debug(self, message):
self.__printconsole('debug', message)
def info(self, message):
self.__printconsole('info', message)
def warning(self, message):
self.__printconsole('warning', message)
def error(self, message):
self.__printconsole('error', message)
2 讀取配置文件
配置文件我選擇的時.ini格式的,其實可以寫成一個python文件或者txt文件,對應的讀取方法都大同小異。
我的conf.ini文件如下
[browserType] ;browserName = Firefox browserName = Chrome #browserName = IE [testServer] URL = https://www.baidu.com [pattern] options = Web is_visible = T [Mail] mail_subject = 郵件主題 mail_server = smtp.xxxx.com mail_from = xxxx@xxx.com mail_to = xxxxx@xx.com,xxxxx@xxx.com mail_from_pwd = xxxxxx
讀取的方法也很簡單,網上也有很多教程,貼出來僅供參考
class ReadConfig:
"""
專門讀取配置文件的,.ini文件格式
"""
def __init__(self, filename=conf_path):
with open(filename, 'r', encoding='UTF-8') as f:
data = f.read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
files = codecs.open(filename, "w")
files.write(data)
files.close()
self.cf = configparser.ConfigParser()
self.cf.read(filename, encoding='UTF-8')
def getValue(self, env, name):
"""讀取配置文件中的值"""
return self.cf.get(env, name)
3 選擇瀏覽器
這部分要做到的是根據配置文件選擇要使用的瀏覽器,以及相應的運行模式,先上代碼*
def open_browser(self, driver):
read = ReadConfig()
browser = read.getValue("browserType", "browserName")
options = read.getValue("pattern", "options")
url = read.getValue("testServer", "URL")
is_visible = read.getValue("pattern", "is_visible")
if options.lower() == 'web':
if browser == "Firefox":
if is_visible == 'F':
options = webdriver.FirefoxOptions()
options.add_argument('-headless')
# options.add_argument('--disable-gpu')
driver = webdriver.Firefox(executable_path=firefox_driver, options=options)
else:
driver = webdriver.Firefox(executable_path=firefox_driver)
log.info("啟動{}瀏覽器".format(browser))
elif browser == "Chrome":
if is_visible == 'F':
option = webdriver.ChromeOptions()
option.add_argument('headless')
driver = webdriver.Chrome(chrome_driver, chrome_options=option)
else:
driver = webdriver.Chrome(chrome_driver)
log.info("啟動{}瀏覽器".format(browser))
elif options.lower() == 'wap':
mobileEmulation = {"deviceName": "iPhone 6"}
options = webdriver.ChromeOptions()
options.add_experimental_option('mobileEmulation', mobileEmulation)
options.add_argument('--disable-search-geolocation-disclosure')
driver = webdriver.Chrome(chrome_options=options)
通過讀取配置文件獲取相應的信息,選擇相應的瀏覽器,支持web和wap兩種模式,如果你想運行時,隱藏瀏覽器窗口可以使用瀏覽器的headless模式,代碼中 if is_visible == 'F' 時,執行的就行瀏覽器的headless模式,瀏覽器在后台運行,不影響測試效果。
4 二次封裝selenium方法
二次封裝selenium中的常用方法可以讓我們更方便的使用,也可以定制化我們的需求,比如加上日志輸出,執行耗時等等
class BasePage:
"""測試基類"""
def __init__(self, driver):
self.driver = driver
def get_img(self, rq=time.strftime('%Y%m%d%H%M', time.localtime(time.time()))):
"""截圖"""
path = os.path.join(os.path.abspath('..'), 'report', 'img')
# path = os.path.join(getcwd.get_cwd(), 'screenshots/') # 拼接截圖保存路徑
# rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time())) # 按格式獲取當前時間
screen_name = path + rq + '.png' # 拼接截圖文件名
# noinspection PyBroadException
try:
self.driver.get_screenshot_as_file(screen_name)
log.info("截圖保存成功{}".format(screen_name))
except BaseException as e:
log.error("截圖失敗{}".format(e))
def find_element(self, selector):
"""定位元素"""
by = selector[0]
value = selector[1]
element = None
if by in ['id', 'name', 'class', 'tag', 'link', 'plink', 'css', 'xpath']:
# noinspection PyBroadException
try:
if by == 'id':
element = self.driver.find_element_by_id(value)
elif by == 'name':
element = self.driver.find_element_by_name(value)
elif by == 'class':
element = self.driver.find_element_by_class_name(value)
elif by == 'tag':
element = self.driver.find_element_by_tag_name(value)
elif by == 'link':
element = self.driver.find_element_by_link_text(value)
elif by == 'plink':
element = self.driver.find_element_by_partial_link_text(value)
elif by == 'css':
element = self.driver.find_element_by_css_selector(value)
elif by == 'xpath':
element = self.driver.find_element_by_xpath(value)
else:
log.error('沒有找到元素')
log.info('元素定位成功。定位方式:%s,使用的值%s:' % (by, value))
return element
except NoSuchElementException as e:
log.error("報錯信息:{}".format(e))
self.get_img() # 調用截圖
else:
log.error('輸入的元素定位方式錯誤,參考[id, name, class, tag, link, plink, css,xpath]')
def type(self, selector, value):
"""輸入內容"""
element = self.find_element(selector)
element.clear()
log.info('清空輸入內容')
# noinspection PyBroadException
try:
element.send_keys(value)
log.info('輸入的內容:%s' % value)
except BaseException as e:
log.error('內容輸入報錯{}'.format(e))
self.get_img()
def click(self, selector):
"""點擊元素"""
element = self.find_element(selector)
# noinspection PyBroadException
try:
element.click()
log.info('點擊元素成功')
except BaseException as e:
display = self.isdisplayed(element)
if display is True:
self.my_sleep(3)
element.click()
log.info('點擊元素成功')
else:
self.get_img()
log.error('點擊元素報錯{}'.format(e))
這里只貼出一部分的常用的方法的封裝示例,可以根據自己的需求DIY
5 用例篩選
當你的用例寫到一定量時,如果你驗證一部分用例,每次全部執行一遍太耗時了,所以加個用例篩選功能,自己選擇要執行的用例文件,不想執行的直接注釋掉
user/test01case #user/test02case #user/test03case #user/test04case #user/test05case #shop/test_shop_list #shop/test_my_shop #shop/test_new_shop
然后再寫一個方法去逐行讀取這個文件,如果行首沒有#號,就把這個用例添加到suite中執行即可,具體實現:
class AllTest: # 定義一個類AllTest
def __init__(self): # 初始化一些參數和數據
global report_name
report_path = os.path.join(root_path, 'report', 'testreport')
now = time.strftime('%Y-%m-%d_%H_%M_%S')
report_name = os.path.join(report_path, 'TestResult{}.html'.format(now))
self.caseListFile = os.path.join('.\\', "caselist.txt") # 配置執行哪些測試文件的配置文件路徑
self.caseFile = os.path.join('.\\', "testcase") # 真正的測試斷言文件路徑
self.caseList = []
log.info('report_name' + report_name) # 將resultPath的值輸入到日志,方便定位查看問題
log.info('caseListFile' + self.caseListFile) # 同理
log.info('caseList' + str(self.caseList)) # 同理
def set_case_list(self):
"""
讀取caselist.txt文件中的用例名稱,並添加到caselist元素組
:return:
"""
fb = open(self.caseListFile)
for value in fb.readlines():
data = str(value)
if data != '' and not data.startswith("#"): # 如果data非空且不以#開頭
self.caseList.append(data.replace("\n", "")) # 讀取每行數據會將換行轉換為\n,去掉每行數據中的\n
fb.close()
def set_case_suite(self):
"""
:return:
"""
self.set_case_list() # 通過set_case_list()拿到caselist元素組
test_suite = unittest.TestSuite()
suite_module = []
for case in self.caseList: # 從caselist元素組中循環取出case
case_name = case.split("/")[-1] # 通過split函數來將aaa/bbb分割字符串,-1取后面,0取前面
# 批量加載用例,第一個參數為用例存放路徑,第一個參數為路徑文件名
discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None)
suite_module.append(discover) # 將discover存入suite_module元素組
if len(suite_module) > 0: # 判斷suite_module元素組是否存在元素
for suite in suite_module: # 如果存在,循環取出元素組內容,命名為suite
for test_name in suite: # 從discover中取出test_name,使用addTest添加到測試集
test_suite.addTest(test_name)
else:
log.error('suite_module中沒有測試集')
return None
return test_suite # 返回測試集
def run(self):
"""
run test
:return:
"""
try:
suit = self.set_case_suite() # 調用set_case_suite獲取test_suite
if suit is not None: # 判斷test_suite是否為空
# bf(suite).report(description='用例名稱xx', filename=now, log_path=report_path)
with open(report_name, 'wb') as f: # encoding='UTF-8'
# 調用HTMLTestRunner
runner = HTMLTestRunner.HTMLTestRunner(stream=f, title='Test Report',
description='Test Description')
runner.run(suit)
else:
log.error('沒有case')
except Exception as e:
log.error('{}'.format(e))
當然,如果你不想使用這中方式也可以全部執行……
源碼地址:https://gitee.com/fh1105/selenium_PO 需要的自行clone
第一次寫博客記錄學習,不好的地方請指出來,謝謝!
