環境:Python2.7.10, selenium3.141.0, pytest4.6.6, pytest-html1.22.0, Windows-7-6.1.7601-SP1css
特色:
- 二次封裝了selenium,編寫Case更加方便。
- 採用PO設計思想,一個頁面一個Page.py,並在其中定義元素和操做方法;在TestCase中直接調用頁面中封裝好的操做方法操做頁面。
- 一次測試只啓動一次瀏覽器,節約時間提升效率(適合公司業務的纔是最好的)。
- 加強pytest-html報告內容,加入失敗截圖、用例描述列、運行日誌。
- 支持命令行參數。
- 支持郵件發送報告。html
目錄結構:
- config
- config.py:存放全局變量,各類配置、driver等
- drive:各瀏覽器驅動文件,如chromedriver.exe
- file
- download:下載文件夾
- screenshot:截圖文件夾
- upload:上傳文件夾
- page_object:一個頁面一個.py,存放頁面對象、操做方法
- base_page.py:基礎頁面,封裝了selenium的各類操做
- hao123_page.py:hao123頁面
- home_page.py:百度首頁
- news_page.py:新聞首頁
- search_page.py:搜索結果頁
- report:
- report.html:pytest-html生成的報告
- test_case
- conftest.py:pytest特有文件,在裏面增長了報告失敗截圖、用例描述列
- test_home.py:百度首頁測試用例
- test_news.py:新聞首頁測試用例
- test_search.py:搜索結果頁測試用例
- util:工具包
- log.py:封裝了日誌模塊
- mail.py:封裝了郵件模塊,使用發送報告郵件功能須要先設置好相關配置,如用戶名密碼
- run.py:作爲運行入口,封裝了pytest運行命令;實現全部測試用例共用一個driver;實現了運行參數化(結合Jenkins使用);log配置初始化;可配置發送報告郵件。 node
代碼實現:python
1 # coding=utf-8 2 3 import os 4 5 6 def init(): 7 global _global_dict 8 _global_dict = {} 9 10 # 代碼根目錄 11 root_dir = os.getcwd() 12 13 # 存放程序所在目錄 14 _global_dict['root_path'] = root_dir 15 # 存放正常截圖文件夾 16 _global_dict['screenshot_path'] = "{}\\file\\screenshot\\".format(root_dir) 17 # 下載文件夾 18 _global_dict['download_path'] = "{}\\file\\download\\".format(root_dir) 19 # 上傳文件夾 20 _global_dict['upload_path'] = "{}\\file\\upload\\".format(root_dir) 21 # 存放報告路徑 22 _global_dict['report_path'] = "{}\\report\\".format(root_dir) 23 24 # 保存driver 25 _global_dict['driver'] = None 26 27 # 設置運行環境網址主頁 28 _global_dict['site'] = 'https://www.baidu.com/' 29 # 運行環境,默認preview,可設爲product 30 _global_dict['environment'] = 'preview' 31 32 33 def set_value(name, value): 34 """ 35 修改全局變量的值 36 :param name: 變量名 37 :param value: 變量值 38 """ 39 _global_dict[name] = value 40 41 42 def get_value(name, def_val='no_value'): 43 """ 44 獲取全局變量的值 45 :param name: 變量名 46 :param def_val: 默認變量值 47 :return: 變量存在時返回其值,不然返回'no_value' 48 """ 49 try: 50 return _global_dict[name] 51 except KeyError: 52 return def_val
定義了全局的字典,用來存放全局變量,其key爲變量名,value爲變量值,可跨文件、跨用例傳遞參數。git
其中set_value、get_value分別用來存、取全局變量。github
1 # coding=utf-8 2 3 import logging 4 import time 5 import config.config as cf 6 7 8 class Logger(object): 9 """封裝的日誌模塊""" 10 11 def __init__(self, logger, cmd_level=logging.DEBUG, file_level=logging.DEBUG): 12 try: 13 self.logger = logging.getLogger(logger) 14 self.logger.setLevel(logging.DEBUG) # 設置日誌輸出的默認級別 15 '''pytest報告能夠自動將log整合進報告,不用再本身單獨設置保存 16 # 日誌輸出格式 17 fmt = logging.Formatter( 18 '%(asctime)s[%(levelname)s]\t%(message)s') 19 # 日誌文件名稱 20 curr_time = time.strftime("%Y-%m-%d %H.%M.%S") 21 log_path = cf.get_value('log_path') 22 self.log_file = '{}log{}.txt'.format(log_path, curr_time) 23 # 設置控制檯輸出 24 sh = logging.StreamHandler() 25 sh.setFormatter(fmt) 26 sh.setLevel(cmd_level) 27 # 設置文件輸出 28 fh = logging.FileHandler(self.log_file) 29 fh.setFormatter(fmt) 30 fh.setLevel(file_level) 31 # 添加日誌輸出方式 32 self.logger.addHandler(sh) 33 self.logger.addHandler(fh) 34 ''' 35 except Exception as e: 36 raise e 37 38 def debug(self, msg): 39 self.logger.debug(msg) 40 41 def info(self, msg): 42 self.logger.info(msg) 43 44 def error(self, msg): 45 self.logger.error(msg) 46 47 def warning(self, msg): 48 self.logger.warning(msg)
封裝的log模塊web
1 # coding=utf-8 2 3 import smtplib 4 from email.mime.text import MIMEText 5 from email.mime.multipart import MIMEMultipart 6 from email.header import Header 7 import config.config as cf 8 9 10 def send_mail(sendto): 11 """ 12 發送郵件 13 :param sendto:收件人列表,如['22459496@qq.com'] 14 """ 15 mail_host = 'smtp.sohu.com' # 郵箱服務器地址 16 username = 'test@sohu.com' # 郵箱用戶名 17 password = 'test' # 郵箱密碼 18 receivers = sendto # 收件人 19 20 # 建立一個帶附件的實例 21 message = MIMEMultipart() 22 message['From'] = Header(u'UI自動化', 'utf-8') 23 message['subject'] = Header(u'UI自動化測試結果', 'utf-8') # 郵件標題 24 message.attach(MIMEText(u'測試結果詳見附件', 'plain', 'utf-8'))# 郵件正文 25 # 構造附件 26 report_root = cf.get_value('report_path') # 獲取報告路徑 27 report_file = 'report.html' # 報告文件名稱 28 att1 = MIMEText(open(report_root + report_file, 'rb').read(), 'base64', 'utf-8') 29 att1["Content-Type"] = 'application/octet-stream' 30 att1["Content-Disposition"] = 'attachment; filename={}'.format(report_file) 31 message.attach(att1) 32 33 try: 34 smtp = smtplib.SMTP() 35 smtp.connect(mail_host, 25) # 25爲 SMTP 端口號 36 smtp.login(username, password) 37 smtp.sendmail(username, receivers, message.as_string()) 38 print u'郵件發送成功' 39 except Exception, e: 40 print u'郵件發送失敗' 41 raise e
封裝的郵件模塊,報告HTML文件會作爲附件發送,這裏須要把最上面的4個變量全改爲你本身的。chrome
1 # coding=utf-8 2 3 from selenium.common.exceptions import TimeoutException 4 from selenium.webdriver.support.ui import WebDriverWait 5 from selenium.webdriver.common.keys import Keys 6 from selenium.webdriver.common.action_chains import ActionChains 7 import os 8 import inspect 9 import config.config as cf 10 import logging 11 import time 12 13 log = logging.getLogger('szh.BasePage') 14 15 16 class BasePage(object): 17 def __init__(self): 18 self.driver = cf.get_value('driver') # 從全局變量取driver 19 20 def split_locator(self, locator): 21 """ 22 分解定位表達式,如'css,.username',拆分後返回'css selector'和定位表達式'.username'(class爲username的元素) 23 :param locator: 定位方法+定位表達式組合字符串,如'css,.username' 24 :return: locator_dict[by], value:返回定位方式和定位表達式 25 """ 26 by = locator.split(',')[0] 27 value = locator.split(',')[1] 28 locator_dict = { 29 'id': 'id', 30 'name': 'name', 31 'class': 'class name', 32 'tag': 'tag name', 33 'link': 'link text', 34 'plink': 'partial link text', 35 'xpath': 'xpath', 36 'css': 'css selector', 37 } 38 if by not in locator_dict.keys(): 39 raise NameError("wrong locator!'id','name','class','tag','link','plink','xpath','css',exp:'id,username'") 40 return locator_dict[by], value 41 42 def wait_element(self, locator, sec=30): 43 """ 44 等待元素出現 45 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 46 :param sec:等待秒數 47 """ 48 by, value = self.split_locator(locator) 49 try: 50 WebDriverWait(self.driver, sec, 1).until(lambda x: x.find_element(by=by, value=value), 51 message='element not found!!!') 52 log.info(u'等待元素:%s' % locator) 53 return True 54 except TimeoutException: 55 return False 56 except Exception, e: 57 raise e 58 59 def get_element(self, locator, sec=60): 60 """ 61 獲取一個元素 62 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 63 :param sec:等待秒數 64 :return: 元素可找到返回element對象,不然返回False 65 """ 66 if self.wait_element(locator, sec): 67 by, value = self.split_locator(locator) 68 print by, value 69 try: 70 element = self.driver.find_element(by=by, value=value) 71 log.info(u'獲取元素:%s' % locator) 72 return element 73 except Exception, e: 74 raise e 75 else: 76 return False 77 78 def get_elements(self, locator): 79 """ 80 獲取一組元素 81 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 82 :return: elements 83 """ 84 by, value = self.split_locator(locator) 85 try: 86 elements = WebDriverWait(self.driver, 60, 1).until(lambda x: x.find_elements(by=by, value=value)) 87 log.info(u'獲取元素列表:%s' % locator) 88 return elements 89 except Exception, e: 90 raise e 91 92 def open(self, url): 93 """ 94 打開網址 95 :param url: 網址鏈接 96 """ 97 self.driver.get(url) 98 log.info(u'打開網址:%s' % url) 99 100 def clear(self, locator): 101 """ 102 清除元素中的內容 103 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 104 """ 105 self.get_element(locator).clear() 106 log.info(u'清空內容:%s' % locator) 107 108 def type(self, locator, text): 109 """ 110 在元素中輸入內容 111 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 112 :param text: 輸入的內容 113 """ 114 self.get_element(locator).send_keys(text) 115 log.info(u'向元素 %s 輸入文字:%s' % (locator, text)) 116 117 def enter(self, locator): 118 """ 119 在元素上按回車鍵 120 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 121 """ 122 self.get_element(locator).send_keys(Keys.ENTER) 123 log.info(u'在元素 %s 上按回車' % locator) 124 125 def click(self, locator): 126 """ 127 在元素上單擊 128 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 129 """ 130 self.get_element(locator).click() 131 log.info(u'點擊元素:%s' % locator) 132 133 def right_click(self, locator): 134 """ 135 鼠標右擊元素 136 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 137 """ 138 element = self.get_element(locator) 139 ActionChains(self.driver).context_click(element).perform() 140 log.info(u'在元素上右擊:%s' % locator) 141 142 def double_click(self, locator): 143 """ 144 雙擊元素 145 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 146 """ 147 element = self.get_element(locator) 148 ActionChains(self.driver).double_click(element).perform() 149 log.info(u'在元素上雙擊:%s' % locator) 150 151 def move_to_element(self, locator): 152 """ 153 鼠標指向元素 154 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 155 """ 156 element = self.get_element(locator) 157 ActionChains(self.driver).move_to_element(element).perform() 158 log.info(u'指向元素%s' % locator) 159 160 def drag_and_drop(self, locator, target_locator): 161 """ 162 拖動一個元素到另外一個元素位置 163 :param locator: 要拖動元素的定位 164 :param target_locator: 目標位置元素的定位 165 """ 166 element = self.get_element(locator) 167 target_element = self.get_element(target_locator) 168 ActionChains(self.driver).drag_and_drop(element, target_element).perform() 169 log.info(u'把元素 %s 拖至元素 %s' % (locator, target_locator)) 170 171 def drag_and_drop_by_offset(self, locator, xoffset, yoffset): 172 """ 173 拖動一個元素向右下移動x,y個偏移量 174 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 175 :param xoffset: X offset to move to 176 :param yoffset: Y offset to move to 177 """ 178 element = self.get_element(locator) 179 ActionChains(self.driver).drag_and_drop_by_offset(element, xoffset, yoffset).perform() 180 log.info(u'把元素 %s 拖至座標:%s %s' % (locator, xoffset, yoffset)) 181 182 def click_link(self, text): 183 """ 184 按部分連接文字查找並點擊連接 185 :param text: 連接的部分文字 186 """ 187 self.get_element('plink,' + text).click() 188 log.info(u'點擊鏈接:%s' % text) 189 190 def alert_text(self): 191 """ 192 返回alert文本 193 :return: alert文本 194 """ 195 log.info(u'獲取彈框文本:%s' % self.driver.switch_to.alert.text) 196 return self.driver.switch_to.alert.text 197 198 def alert_accept(self): 199 """ 200 alert點確認 201 """ 202 self.driver.switch_to.alert.accept() 203 log.info(u'點擊彈框確認') 204 205 def alert_dismiss(self): 206 """ 207 alert點取消 208 """ 209 self.driver.switch_to.alert.dismiss() 210 log.info(u'點擊彈框取消') 211 212 def get_attribute(self, locator, attribute): 213 """ 214 返回元素某屬性的值 215 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 216 :param attribute: 屬性名稱 217 :return: 屬性值 218 """ 219 value = self.get_element(locator).get_attribute(attribute) 220 log.info(u'獲取元素 %s 的屬性值 %s 爲:%s' % (locator, attribute, value)) 221 return value 222 223 def get_ele_text(self, locator): 224 """ 225 返回元素的文本 226 :param locator: 定位方法+定位表達式組合字符串,用逗號分隔,如'css,.username' 227 :return: 元素的文本 228 """ 229 log.info(u'獲取元素 %s 的文本爲:%s' % (locator, self.get_element(locator).text)) 230 return self.get_element(locator).text 231 232 def frame_in(self, locator): 233 """ 234 進入frame 235 :param locator: 定位方法+定位表達式組合字符串,如'css,.username' 236 """ 237 e = self.get_element(locator) 238 self.driver.switch_to.frame(e) 239 log.info(u'進入frame:%s' % locator) 240 241 def frame_out(self): 242 """ 243 返回主文檔 244 """ 245 self.driver.switch_to.default_content() 246 log.info(u'退出frame返回默認文檔') 247 248 def open_new_window_by_locator(self, locator): 249 """ 250 點擊元素打開新窗口,並將句柄切換到新窗口 251 :param locator: 定位方法+定位表達式組合字符串,如'css,.username' 252 """ 253 self.get_element(locator).click() 254 self.driver.switch_to.window(self.driver.window_handles[-1]) 255 log.info(u'點擊元素 %s 打開新窗口' % locator) 256 257 # old_handle = self.driver.current_window_handle 258 # self.get_element(locator).click() 259 # all_handles = self.driver.window_handles 260 # for handle in all_handles: 261 # if handle != old_handle: 262 # self.driver.switch_to.window(handle) 263 264 def open_new_window_by_element(self, element): 265 """ 266 點擊元素打開新窗口,並將句柄切換到新窗口 267 :param element: 元素對象 268 """ 269 element.click() 270 self.driver.switch_to.window(self.driver.window_handles[-1]) 271 log.info(u'點擊元素打開新窗口') 272 273 def js(self, script): 274 """ 275 執行JavaScript 276 :param script:js語句 277 """ 278 self.driver.execute_script(script) 279 log.info(u'執行JS語句:%s' % script) 280 281 def scroll_element(self, locator): 282 """ 283 拖動滾動條至目標元素 284 :param locator: 定位方法+定位表達式組合字符串,如'css,.username' 285 """ 286 script = "return arguments[0].scrollIntoView();" 287 element = self.get_element(locator) 288 self.driver.execute_script(script, element) 289 log.info(u'滾動至元素:%s' % locator) 290 291 def scroll_top(self): 292 """ 293 滾動至頂部 294 """ 295 self.js("window.scrollTo(document.body.scrollHeight,0)") 296 log.info(u'滾動至頂部') 297 298 def scroll_bottom(self): 299 """ 300 滾動至底部 301 """ 302 self.js("window.scrollTo(0,document.body.scrollHeight)") 303 log.info(u'滾動至底部') 304 305 def back(self): 306 """ 307 頁面後退 308 """ 309 self.driver.back() 310 log.info(u'頁面後退') 311 312 def forward(self): 313 """ 314 頁面向前 315 """ 316 self.driver.forward() 317 log.info(u'頁面向前') 318 319 def is_text_on_page(self, text): 320 """ 321 返回頁面源代碼 322 :return: 頁面源代碼 323 """ 324 if text in self.driver.page_source: 325 log.info(u'判斷頁面上有文本:%s' % text) 326 return True 327 else: 328 log.info(u'判斷頁面上沒有文本:%s' % text) 329 return False 330 331 def refresh(self): 332 """ 333 刷新頁面 334 """ 335 self.driver.refresh() 336 log.info(u'刷新頁面') 337 338 def screenshot(self, info='-'): 339 """ 340 截圖,起名爲:文件名-方法名-註釋 341 :param info: 截圖說明 342 """ 343 catalog_name = cf.get_value('screenshot_path') # 從全局變量取截圖文件夾位置 344 if not os.path.exists(catalog_name): 345 os.makedirs(catalog_name) 346 class_object = inspect.getmembers(inspect.stack()[1][0])[-3][1]['self'] # 得到測試類的object 347 classname = str(class_object).split('.')[1].split(' ')[0] # 得到測試類名稱 348 testcase_name = inspect.stack()[1][3] # 得到測試方法名稱 349 filepath = catalog_name + classname + "@" + testcase_name + info + ".png" 350 self.driver.get_screenshot_as_file(filepath) 351 log.info(u'截圖:%s.png' % info) 352 353 def close(self): 354 """ 355 關閉當前頁 356 """ 357 self.driver.close() 358 self.driver.switch_to.window(self.driver.window_handles[0]) 359 log.info(u'關閉當前Tab') 360 361 def sleep(self, sec): 362 time.sleep(sec) 363 log.info(u'等待%s秒' % sec)
二次封裝了selenium經常使用操做,作爲全部頁面類的基類。瀏覽器
本框架支持selenium全部的定位方法,爲了提升編寫速度,改進了使用方法,定義元素時方法名和方法值爲一個用逗號隔開的字符串,如:
- xpath定位:i_keyword = 'xpath,//input[@id="kw"]' # 關鍵字輸入框
- id定位:b_search = 'id,su' # 搜索按鈕
- 其餘定位方法同上,再也不一一舉例服務器
使用時如上面代碼中type()方法,是在如輸入框中輸入文字,調用時輸入type(i_keyword, "輸入內容")
type()中會調用get_element()方法,對輸入的定位表達式進行解析,而且會等待元素一段時間,當元素出現時當即進行操做。
另外能夠看到每一個基本操做都加入了日誌,下圖便是用例運行後報告中記錄的日誌
1 # coding=utf-8 2 3 from page_object.base_page import BasePage 4 5 6 class SearchPage(BasePage): 7 def __init__(self, driver): 8 self.driver = driver 9 10 # i=輸入框, l=連接, im=圖片, t=文字控件, d=div, lab=label 11 # 含_百度百科的搜索結果 12 l_baike = 'xpath,//a[(. = "星空物語_百度百科")]' 13 14 # 下一頁 15 b_next_page = 'link,下一頁>' 16 17 # 上一頁 18 b_up_page = 'xpath,//a[(. = "<上一頁")]' 19 20 # 點擊搜索結果的百科 21 def click_result(self): 22 self.open_new_window_by_locator(self.l_baike) 23 self.sleep(3) 24 25 # 點擊下一頁 26 def click_next_page(self): 27 self.click(self.b_next_page)
PO模式中封裝的百度的搜索頁,繼承了上面的BasePage類;每一個頁面類中上面定義各控件的表達式,下面將頁面上的各類操做封裝爲方法。這樣若是在多個用例中調用了控件或操做方法,未來更新維護只須要在頁面類中改一下,全部用例就都更新了。
1 # coding=utf-8 2 3 import sys 4 reload(sys) 5 sys.setdefaultencoding('utf8') 6 from page_object.home_page import HomePage 7 from page_object.search_page import SearchPage 8 import pytest 9 import config.config as cf 10 11 12 class TestSearch(): 13 """ 14 pytest: 15 測試文件以test_開頭 16 測試類以Test開頭,而且不能帶有__init__方法 17 測試函數以test_開頭 18 斷言使用assert 19 """ 20 driver = cf.get_value('driver') # 從全局變量取driver 21 home_page = HomePage(driver) 22 search_page = SearchPage(driver) 23 24 def test_click_result(self): 25 """搜索頁-點擊首個搜索結果""" 26 try: 27 self.home_page.open_homepage() 28 self.home_page.input_keyword(u'星空物語') # 輸入關鍵字 29 self.search_page.click_result() # 點擊百科 30 assert self.home_page.is_text_on_page(u'電視劇《一塊兒來看流星雨》片頭曲') # 驗證頁面打開 31 self.home_page.screenshot(u'打開搜索結果') 32 self.search_page.close() # 關閉百科頁面 33 except Exception, e: 34 self.home_page.screenshot(u'打開搜索結果失敗') 35 raise e 36 37 def test_click_next_page(self): 38 """搜索頁-搜索翻頁""" 39 try: 40 self.search_page.click_next_page() # 點下一頁 41 assert self.home_page.wait_element(self.search_page.b_up_page) # 上一頁出現 42 self.search_page.scroll_element(self.search_page.b_up_page) # 滾到上一頁 43 self.home_page.screenshot(u'搜索翻頁') 44 except Exception, e: 45 self.home_page.screenshot(u'搜索翻頁失敗') 46 raise e
百度搜索頁的測試用例,這裏我簡單寫了2個用例,第1個是搜索後點擊首個搜索結果可打開,第2個是搜索結果可翻頁。用例中的具體操做均是使用的上面頁面類中封裝好的操做方法。
1 # coding=utf-8 2 3 import pytest 4 from py._xmlgen import html 5 import config.config as cf 6 import logging 7 8 log = logging.getLogger('szh.conftest') 9 10 11 @pytest.mark.hookwrapper 12 def pytest_runtest_makereport(item): 13 """當測試失敗的時候,自動截圖,展現到html報告中""" 14 pytest_html = item.config.pluginmanager.getplugin('html') 15 outcome = yield 16 report = outcome.get_result() 17 extra = getattr(report, 'extra', []) 18 19 if report.when == 'call' or report.when == "setup": 20 xfail = hasattr(report, 'wasxfail') 21 if (report.skipped and xfail) or (report.failed and not xfail): 22 file_name = report.nodeid.replace("::", "_") + ".png" 23 driver = cf.get_value('driver') # 從全局變量取driver 24 screen_img = driver.get_screenshot_as_base64() 25 if file_name: 26 html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \ 27 'onclick="window.open(this.src)" align="right"/></div>' % screen_img 28 extra.append(pytest_html.extras.html(html)) 29 report.extra = extra 30 report.description = str(item.function.__doc__)#.decode('utf-8', 'ignore') # 不解碼轉成Unicode,生成HTML會報錯 31 # report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape") 32 33 34 @pytest.mark.optionalhook 35 def pytest_html_results_table_header(cells): 36 cells.insert(1, html.th('Description')) 37 cells.pop() # 刪除報告最後一列links 38 39 40 @pytest.mark.optionalhook 41 def pytest_html_results_table_row(report, cells): 42 cells.insert(1, html.td(report.description)) 43 cells.pop() # 刪除報告最後一列links
conftest.py是pytest提供數據、操做共享的文件,其文件名是固定的,不能夠修改。
conftest.py文件所在目錄必須存在__init__.py文件。
其餘文件不須要import導入conftest.py,pytest用例會自動查找
全部同目錄測試文件運行前都會執行conftest.py文件
我只在conftest.py中加入了報錯截圖的功能,若是你有須要在用例前、後執行一些操做,均可以寫在這裏。
1 # coding=utf-8 2 3 import pytest 4 import config.config as cf 5 from util.log import Logger 6 import argparse 7 from selenium import webdriver 8 from util.mail import send_mail 9 10 11 def get_args(): 12 """命令行參數解析""" 13 parser = argparse.ArgumentParser(description=u'可選擇參數:') 14 parser.add_argument('-e', '--environment', choices=['preview', 'product'], default='preview', help=u'測試環境preview,線上環境product') 15 args = parser.parse_args() 16 if args.environment in ('pre', 'preview'): 17 cf.set_value('environment', 'preview') 18 cf.set_value('site', 'http://www.baidu.com/') 19 elif args.environment in ('pro', 'product'): 20 cf.set_value('environment', 'preview') 21 cf.set_value('site', 'https://www.baidu.com/') 22 else: 23 print u"請輸入preview/product" 24 exit() 25 26 27 def set_driver(): 28 """設置driver""" 29 # 配置Chrome Driver 30 chrome_options = webdriver.ChromeOptions() 31 chrome_options.add_argument('--start-maximized') # 瀏覽器最大化 32 chrome_options.add_argument('--disable-infobars') # 不提醒chrome正在受自動化軟件控制 33 prefs = {'download.default_directory': cf.get_value('download_path')} 34 chrome_options.add_experimental_option('prefs', prefs) # 設置默認下載路徑 35 # chrome_options.add_argument(r'--user-data-dir=D:\ChromeUserData') # 設置用戶文件夾,可免登錄 36 driver = webdriver.Chrome('{}\\driver\\chromedriver.exe'.format(cf.get_value('root_path')), options=chrome_options) 37 cf.set_value('driver', driver) 38 39 40 def main(): 41 """運行pytest命令啓動測試""" 42 pytest.main(['-v', '-s', 'test_case/', '--html=report/report.html', '--self-contained-html']) 43 44 45 if __name__ == '__main__': 46 cf.init() # 初始化全局變量 47 get_args() # 命令行參數解析 48 log = Logger('szh') # 初始化log配置 49 set_driver() # 初始化driver 50 main() # 運行pytest測試集 51 cf.get_value('driver').quit() # 關閉selenium driver 52 53 # 先將util.mail文件send_mail()中的用戶名、密碼填寫正確,再啓用發送郵件功能!!! 54 send_mail(['22459496@qq.com']) # 將報告發送至郵箱
run.py用來作一些初始化的工做,運行測試,以及測試收尾,具體能夠看代碼中的註釋。
我將瀏覽器driver的初始化放在了這裏,並將driver存入全局變量,這樣瀏覽器只需打開一次便可運行全部的測試。若是你想每一個用例都打開、關閉一次瀏覽器,那能夠把定義driver的方法放在conftest.py中。
get_args()是封裝的命令行參數解析,方便集成Jenkins時快速定義運行內容。目前只定義了一個環境參數-e, 可設置測試環境preview,線上環境product,你能夠根據須要添加更多參數。
調用方法:python run.py -e product
main()封裝了pytest的命令行執行模式,你也能夠按需修改。
最後放一張運行後的測試報告的截圖,我故意將某個用例寫錯,能夠看到,報告中顯示了具體的報錯信息以及出錯時頁面的截圖
全部代碼可去GitHub獲取:https://github.com/songzhenhua/selenium_ui_auto