第一個Pytest UI自動化測試實戰實例

前言

明天就放假了,4天小長假,是不是很開心!也許很多人要回老家幫家裏種地,幹農活。其實能陪陪家裏人,幫忙乾點農活還是挺開心的,希望大家有個愉快的假期!廢話不多說哈,今天再來說說pytest吧,經過幾周的時間學習,有收穫也有疑惑,總之最後還是搞個小項目出來證明自己的努力不沒有白費

環境準備

序號 庫/插件/工具 安裝命令
1 確保您已經安裝了python3.x  
2 配置python3+pycharm+selenium2開發環境  
3 安裝pytest庫 pip install pytest
4 安裝pytest -html 報告插件 pip install pytest-html
5 安裝pypiwin32庫(用來模擬按鍵) pip install pypiwin32
6 安裝openpyxl解析excel文件庫 pip install openpyxl
7 安裝yagmail發送報告庫 pip install yagmail
8 確保已配置火狐或谷歌瀏覽器及對應驅動  
9 確保已經正確配置好發送郵件的郵箱  

項目簡介

測試地址

https://mail.126.com

測試範圍

1.126電子郵箱登錄功能測試-驗證正確帳號密碼登錄成功-驗證錯誤用戶名密碼登錄失敗(有很多情況,用例裏面做了充分的校驗)

2.126電子郵箱添加聯繫人功能測試-驗證正確填寫必填項數據添加聯繫人成功-驗證缺省必填項數據添加聯繫人失敗-驗證必填項字段數據格式錯誤添加聯繫人失敗

3.126電子郵箱發送郵件功能測試-驗證普通郵件發送成功-驗證帶附件郵件發送成功

項目設計

1.python編程語言設計測試腳本

2.webdriver驅動瀏覽器並操作頁面元素

3.二次封裝webdriver Api 操作方法

4.採用PageObject設計模式,設計測試業務流程

5.通過UI對象庫存儲頁面操作元素

6.通過數據文件存儲數據,讀取數據,參數化測試用例並驅動測試執行

7.通過第三方插件pytest-html生成測試報告

8.通過yagmail第三方庫,編寫發送報告接口,測試工作完成後自動發送測試報告

代碼分析

目錄結構

 1 PytestAutoTestFrameWork
 2 |—|config
 3 |——|__init__.py
 4 |——|conf.py
 5 |——|config.ini
 6 |—|data
 7 |——|__init__.py
 8 |——|tcData.xlsx
 9 |—Page
10 |——|PageObject.py
11 |———|__init__.py
12 |———|ContactPage.py
13 |———|HomePage.py
14 |———|LoginPage.py
15 |———|SendMailPage.py
16 |——|__init__.py
17 |——|BasePage.py
18 |—|report
19 |—|TestCases
20 |——|__init__.py
21 |——|conftest.py
22 |——|test_confactCase.py
23 |——|test_loginCase.py
24 |——|test_sendMailCase.py
25 |—|util
26 |——|__init__.py
27 |——|clipboard.py
28 |——|keyboard.py
29 |——|parseConFile.py
30 |——|parseExcelFile.py
31 |——|sendMailForReport.py
32 |—|conftest.py
33 |—|pytest.ini
34 |—|RunTestCase.py
目錄結構

代碼實現

通過126郵箱測試範圍分析,我們需要通過設計剪切板,模擬鍵盤完成附件上傳操作,因此我們首先來編寫這兩個方法

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/15 12:04
 4 @Auth : linux超
 5 @File : clipboard.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import win32con
11 import win32clipboard as WC
12 
13 
14 class ClipBoard(object):
15     '''設置剪切板內容和獲取剪切板內容'''
16 
17     @staticmethod
18     def getText():
19         '''獲取剪切板的內容'''
20         WC.OpenClipboard()
21         value = WC.GetClipboardData(win32con.CF_TEXT)
22         WC.CloseClipboard()
23         return value
24 
25     @staticmethod
26     def setText(value):
27         '''設置剪切板的內容'''
28         WC.OpenClipboard()
29         WC.EmptyClipboard()
30         WC.SetClipboardData(win32con.CF_UNICODETEXT, value)
31         WC.CloseClipboard()
32 
33 
34 if __name__ == '__main__':
35     from selenium import webdriver
36 
37     value = 'python'
38     driver = webdriver.Firefox()
39     driver.get('http://www.baidu.com')
40     query = driver.find_element_by_id('kw')
41     ClipBoard.setText(value)
42     clValue = ClipBoard.getText()
43     query.send_keys(clValue.decode('utf-8'))
clipboard.py-操作剪切板
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/15 12:05
 4 @Auth : linux超
 5 @File : keyboard.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 
11 # 模擬按鍵
12 import win32api
13 import win32con
14 import time
15 
16 
17 class KeyBoard(object):
18     """模擬按鍵"""
19     # 鍵盤碼
20     vk_code = {
21         'enter' : 0x0D,
22         'tab' : 0x09,
23         'ctrl' : 0x11,
24         'v' : 0x56,
25         'a' : 0x41,
26         'x' : 0x58
27     }
28 
29     @staticmethod
30     def keyDown(key_name):
31         """按下鍵"""
32         key_name = key_name.lower()
33         try:
34             win32api.keybd_event(KeyBoard.vk_code[key_name], 0, 0, 0)
35         except Exception as e:
36             print('未按下enter鍵')
37             print(e)
38 
39     @staticmethod
40     def keyUp(key_name):
41         """擡起鍵"""
42         key_name = key_name.lower()
43         win32api.keybd_event(KeyBoard.vk_code[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)
44 
45     @staticmethod
46     def oneKey(key):
47         """模擬單個按鍵"""
48         key = key.lower()
49         KeyBoard.keyDown(key)
50         time.sleep(2)
51         KeyBoard.keyUp(key)
52 
53     @staticmethod
54     def twoKeys(key1, key2):
55         """模擬組合按鍵"""
56         key1 = key1.lower()
57         key2 = key2.lower()
58         KeyBoard.keyDown(key1)
59         KeyBoard.keyDown(key2)
60         KeyBoard.keyUp(key1)
61         KeyBoard.keyUp(key2)
62 
63 
64 if __name__ == '__main__':
65     from selenium import webdriver
66     driver = webdriver.Firefox()
67     driver.get('http://www.baidu.com')
68     driver.find_element_by_id('kw').send_keys('python')
69     KeyBoard.twoKeys('ctrl', 'a')
70     KeyBoard.twoKeys('ctrl', 'x')
keyboard.py-模擬鍵盤

通過測試項目設計,我們需要把測試數據存放在Excel文件中,把頁面操作元素存在UI對象庫中也就是一個配置文件,那麼我們需要對Excel 和 ini文件解析,因此我們開始編寫這兩個方法,設計UI對象庫和測試數據文件

  1 """
  2 ------------------------------------
  3 @Time : 2019/4/22 16:12
  4 @Auth : linux超
  5 @File : parseExcelFile.py
  6 @IDE  : PyCharm
  7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
  8 ------------------------------------
  9 """
 10 from openpyxl import load_workbook
 11 from config.conf import excelPath
 12 
 13 
 14 class ParseExcel(object):
 15 
 16     def __init__(self):
 17         self.wk = load_workbook(excelPath)
 18         self.excelFile = excelPath
 19 
 20     def getSheetByName(self, sheetName):
 21         """獲取sheet對象"""
 22         sheet = self.wk[sheetName]
 23         return sheet
 24 
 25     def getRowNum(self, sheet):
 26         """獲取有效數據的最大行號"""
 27         return sheet.max_row
 28 
 29     def getColsNum(self, sheet):
 30         """獲取有效數據的最大列號"""
 31         return sheet.max_column
 32 
 33     def getRowValues(self, sheet, rowNum):
 34         """獲取某一行的數據"""
 35         maxColsNum = self.getColsNum(sheet)
 36         rowValues = []
 37         for colsNum in range(1, maxColsNum + 1):
 38             value = sheet.cell(rowNum, colsNum).value
 39             if value is None:
 40                 value = ''
 41             rowValues.append(value)
 42         return tuple(rowValues)
 43 
 44     def getColumnValues(self, sheet, columnNum):
 45         """獲取某一列的數據"""
 46         maxRowNum = self.getRowNum(sheet)
 47         columnValues = []
 48         for rowNum in range(2, maxRowNum + 1):
 49             value = sheet.cell(rowNum, columnNum).value
 50             if value is None:
 51                 value = ''
 52             columnValues.append(value)
 53         return tuple(columnValues)
 54 
 55     def getValueOfCell(self, sheet, rowNum, columnNum):
 56         """獲取某一個單元格的數據"""
 57         value = sheet.cell(rowNum, columnNum).value
 58         if value is None:
 59             value = ''
 60         return value
 61 
 62     def getAllValuesOfSheet(self, sheet):
 63         """獲取某一個sheet頁的所有測試數據,返回一個元祖組成的列表"""
 64         maxRowNum = self.getRowNum(sheet)
 65         columnNum = self.getColsNum(sheet)
 66         allValues = []
 67         for row in range(2, maxRowNum + 1):
 68             rowValues = []
 69             for column in range(1, columnNum + 1):
 70                 value = sheet.cell(row, column).value
 71                 if value is None:
 72                     value = ''
 73                 rowValues.append(value)
 74             allValues.append(tuple(rowValues))
 75         return allValues
 76 
 77 
 78 if __name__ == '__main__':
 79     # excel = ParseExcel()
 80     # sheet = excel.getSheetByName('login')
 81     # print('行號:', excel.getRowNum(sheet))
 82     # print('列號:', excel.getColsNum(sheet))
 83     #
 84     # rowvalues = excel.getRowValues(sheet, 1)
 85     # columnvalues = excel.getColumnValues(sheet, 2)
 86     # valueofcell = excel.getValueOfCell(sheet, 1, 2)
 87     # allvalues = excel.getAllValuesOfSheet(sheet)
 88     #
 89     # print('第{}行數據{}'.format(1, rowvalues))
 90     # print('第{}列數據{}'.format(2, columnvalues))
 91     # print('{}{}單元格的內容{}'.format(1, 2, valueofcell))
 92     # print('login{}'.format(allvalues))
 93 
 94     excel = ParseExcel()
 95     sheet = excel.getSheetByName('mail')
 96     print('行號:', excel.getRowNum(sheet))
 97     print('列號:', excel.getColsNum(sheet))
 98 
 99     allvalues = excel.getAllValuesOfSheet(sheet)
100 
101     print('sendmail{}'.format(allvalues))
parseExcelFile.py-解析Excel文件
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/18 10:54
 4 @Auth : linux超
 5 @File : parseConFile.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import configparser
11 from config.conf import configDir
12 
13 
14 class ParseConFile(object):
15 
16     def __init__(self):
17         self.file = configDir
18         self.conf = configparser.ConfigParser()
19         self.conf.read(self.file, encoding='utf-8')
20 
21     def getAllSections(self):
22         """獲取所有的section,返回一個列表"""
23         return self.conf.sections()
24 
25     def getAllOptions(self, section):
26         """獲取指定section下所有的option, 返回列表"""
27         return self.conf.options(section)
28 
29     def getLocatorsOrAccount(self, section, option):
30         """獲取指定section, 指定option對應的數據, 返回元祖和字符串"""
31         try:
32             locator = self.conf.get(section, option)
33             if ('->' in locator):
34                 locator = tuple(locator.split('->'))
35             return locator
36         except configparser.NoOptionError as e:
37             print('error:', e)
38         return 'error: No option "{}" in section: "{}"'.format(option, section)
39 
40     def getOptionValue(self, section):
41         """獲取指定section下所有的option和對應的數據,返回字典"""
42         value = dict(self.conf.items(section))
43         return value
44 
45 
46 if __name__ == '__main__':
47     cf = ParseConFile()
48     print(cf.getAllSections())
49     print(cf.getAllOptions('126LoginAccount'))
50     print(cf.getLocatorsOrAccount('126LoginAccount', 'username'))
51     print(cf.getOptionValue('126LoginAccount'))
parseConFile.py-解析配置文件
 1 [126LoginAccount];126郵箱正確的登錄賬號和密碼;運行用例時請更換正確的用戶名和密碼
 2 username=linuxxiaochao
 3 password=xiaochao11520
 4 [HomePageElements];126郵箱首頁菜單欄元素
 5 homePage=id->_mail_tabitem_0_3text
 6 mailList=id->_mail_tabitem_1_4text
 7 applicationCenter=id->_mail_tabitem_2_5text
 8 inBox=id->_mail_tabitem_3_6text
 9 [LoginPageElements];126郵箱登錄頁面的元素
10 frame=xpath->//div[@id="loginDiv"]/iframe
11 username=xpath->//input[@name="email"]
12 password=xpath->//input[@name="password"]
13 loginBtn=xpath->//a[@id="dologin"]
14 ferrorHead=xpath->//div[@class="ferrorhead"]
15 [ContactPageElements];126郵箱添加聯繫人頁面元素
16 new_contact=xpath->//span[text()="新建聯繫人"]
17 name=id->input_N
18 mail=xpath->//div[@id="iaddress_MAIL_wrap"]//input[@class="nui-ipt-input"]
19 star=xpath->//span[@class="nui-chk-text"]/preceding-sibling::span/b
20 phone=xpath->//div[@id='iaddress_TEL_wrap']//input[@class='nui-ipt-input']
21 comment=id->input_DETAIL
22 commit=xpath->//span[text()='確 定']
23 tooltip=xpath->//span[text()='請正確填寫郵件地址。']
24 [SendMailPageElements];126郵箱發送郵件頁面元素
25 writeMail=xpath->//div[@id='dvNavContainer']//span[text()='寫 信']
26 addressee=xpath->//input[@aria-label='收件人地址輸入框,請輸入郵件地址,多人時地址請以分號隔開']
27 subject=xpath->//input[contains(@id, '_subjectInput')]
28 iframe=xpath->//iframe[@class="APP-editor-iframe"]
29 text=xpath->/html/body
30 sendBtn=xpath->//header//span[text()='發送']
31 expect=xpath->//h1[contains(@id,'_succInfo')]
32 uploadAttachment=xpath->//div[@title="點擊添加附件"]
33 delete=xpath->//a[text()='刪除']
config.ini
新建excel文件,分3個sheet,分別爲:login,contact,mail #每個sheet中數據可自行填寫,驅動測試用例執行不同的數據進行測試

login

contact

mail

數據,UI對象庫,解析方法都已經有了,接下來通過PageObject模式設計編寫每個頁面的操作及封裝126郵箱的功能,以便後續設計用例調用

  1 """
  2 ------------------------------------
  3 @Time : 2019/4/20 8:45
  4 @Auth : linux超
  5 @File : BasePage.py
  6 @IDE  : PyCharm
  7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
  8 ------------------------------------
  9 """
 10 import time
 11 from selenium.webdriver.support import expected_conditions as EC
 12 from selenium.webdriver.support.wait import WebDriverWait as wd
 13 from selenium.webdriver.common.by import By
 14 from selenium.common.exceptions import NoSuchWindowException, TimeoutException, \
 15     NoAlertPresentException, NoSuchFrameException
 16 from selenium import webdriver
 17 
 18 from util.clipboard import ClipBoard
 19 from util.keyboard import KeyBoard
 20 from util.parseConFile import ParseConFile
 21 from util.parseExcelFile import ParseExcel
 22 
 23 
 24 class BasePage(object):
 25     """
 26     結合顯示等待封裝一些selenium 內置方法
 27     """
 28     cf = ParseConFile()
 29     excel = ParseExcel()
 30 
 31     def __init__(self, driver, outTime=30):
 32         self.byDic = {
 33         'id': By.ID,
 34         'name': By.NAME,
 35         'class_name': By.CLASS_NAME,
 36         'xpath': By.XPATH,
 37         'link_text': By.LINK_TEXT
 38         }
 39         self.driver = driver
 40         self.outTime = outTime
 41 
 42     def findElement(self, by, locator):
 43         """
 44         find alone element
 45         :param by: eg: id, name, xpath, css.....
 46         :param locator: id, name, xpath for str
 47         :return: element object
 48         """
 49         try:
 50             print('[Info:Starting find the element "{}" by "{}"!]'.format(locator, by))
 51             element = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))
 52         except TimeoutException as t:
 53             print('error: found "{}" timeout!'.format(locator), t)
 54         except NoSuchWindowException as e:
 55             print('error: no such "{}"'.format(locator), e)
 56         except Exception as e:
 57             raise e
 58         else:
 59             # print('[Info:Had found the element "{}" by "{}"!]'.format(locator, by))
 60             return element
 61 
 62     def findElements(self, by, locator):
 63         """
 64         find group elements
 65         :param by: eg: id, name, xpath, css.....
 66         :param locator: eg: id, name, xpath for str
 67         :return: elements object
 68         """
 69         try:
 70             print('[Info:start find the elements "{}" by "{}"!]'.format(locator, by))
 71             elements = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))
 72         except TimeoutException as t:
 73             print(t)
 74         except NoSuchWindowException as e:
 75             print(e)
 76         except Exception as e:
 77             raise e
 78         else:
 79             # print('[Info:Had found the elements "{}" by "{}"!]'.format(locator, by))
 80             return elements
 81 
 82     def isElementExsit(self, by, locator):
 83         """
 84         assert element if exist
 85         :param by: eg: id, name, xpath, css.....
 86         :param locator: eg: id, name, xpath for str
 87         :return: if element return True else return false
 88         """
 89         if by.lower() in self.byDic:
 90             try:
 91                 wd(self.driver, self.outTime).\
 92                     until(EC.visibility_of_element_located((self.byDic[by], locator)))
 93             except TimeoutException:
 94                 print('Error: element "{}" time out!'.format(locator))
 95                 return False
 96             except NoSuchWindowException:
 97                 print('Error: element "{}" not exsit!'.format(locator))
 98                 return False
 99             return True
100         else:
101             print('the "{}" error!'.format(by))
102 
103     def isClick(self, by, locator):
104         """判斷是否可點擊,返回元素對象"""
105         if by.lower() in self.byDic:
106             try:
107                 element = wd(self.driver, self.outTime).\
108                     until(EC.element_to_be_clickable((self.byDic[by], locator)))
109             except Exception:
110                 return False
111             return element
112         else:
113             print('the "{}" error!'.format(by))
114 
115     def isAlertAndSwitchToIt(self):
116         """
117         assert alert if exsit
118         :return: alert obj
119         """
120         try:
121             re = wd(self.driver, self.outTime).until(EC.alert_is_present())
122         except NoAlertPresentException:
123             return False
124         except Exception:
125             return False
126         return re
127 
128     def switchToFrame(self, by, locator):
129         """判斷frame是否存在,存在就跳到frame"""
130         print('info:switching to iframe "{}"'.format(locator))
131         if by.lower() in self.byDic:
132             try:
133                 wd(self.driver, self.outTime).\
134                     until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by], locator)))
135             except TimeoutException as t:
136                 print('error: found "{}" timeout!'.format(locator), t)
137             except NoSuchFrameException as e:
138                 print('error: no such "{}"'.format(locator), e)
139             except Exception as e:
140                 raise e
141         else:
142             print('the "{}" error!'.format(by))
143 
144     def switchToDefaultFrame(self):
145         """返回默認的frame"""
146         print('info:switch back to default iframe')
147         try:
148             self.driver.switch_to.default_content()
149         except Exception as e:
150             print(e)
151 
152     def getAlertText(self):
153         """獲取alert的提示信息"""
154         if self.isAlertAndSwitchToIt():
155             alert = self.isAlertAndSwitchToIt()
156             return alert.text
157         else:
158             return None
159 
160     def getElementText(self, by, locator, name=None):
161         """獲取某一個元素的text信息"""
162         try:
163             element = self.findElement(by, locator)
164             if name:
165                 return element.get_attribute(name)
166             else:
167                 return element.text
168         except:
169             print('get "{}" text failed return None'.format(locator))
170             return None
171 
172     def loadUrl(self, url):
173         """加載url"""
174         print('info: string upload url "{}"'.format(url))
175         self.driver.get(url)
176 
177     def getSource(self):
178         """獲取頁面源碼"""
179         return self.driver.page_source
180 
181     def sendKeys(self, by, locator, value=''):
182         """寫數據"""
183         print('info:input "{}"'.format(value))
184         try:
185             element = self.findElement(by, locator)
186             element.send_keys(value)
187         except AttributeError as e:
188             print(e)
189 
190     def clear(self, by, locator):
191         """清理數據"""
192         print('info:clearing value')
193         try:
194             element = self.findElement(by, locator)
195             element.clear()
196         except AttributeError as e:
197             print(e)
198 
199     def click(self, by, locator):
200         """點擊某個元素"""
201         print('info:click "{}"'.format(locator))
202         element = self.isClick(by, locator)
203         if element:
204             element.click()
205         else:
206             print('the "{}" unclickable!')
207 
208     def sleep(self, num=0):
209         """強制等待"""
210         print('info:sleep "{}" minutes'.format(num))
211         time.sleep(num)
212 
213     def ctrlV(self, value):
214         """ctrl + V 粘貼"""
215         print('info:pasting "{}"'.format(value))
216         ClipBoard.setText(value)
217         self.sleep(3)
218         KeyBoard.twoKeys('ctrl', 'v')
219 
220     def enterKey(self):
221         """enter 回車鍵"""
222         print('info:keydown enter')
223         KeyBoard.oneKey('enter')
224 
225     def waitElementtobelocated(self, by, locator):
226         """顯示等待某個元素出現,且可見"""
227         print('info:waiting "{}" to be located'.format(locator))
228         try:
229             wd(self.driver, self.outTime).until(EC.visibility_of_element_located((self.byDic[by], locator)))
230         except TimeoutException as t:
231             print('error: found "{}" timeout!'.format(locator), t)
232         except NoSuchWindowException as e:
233             print('error: no such "{}"'.format(locator), e)
234         except Exception as e:
235             raise e
236 
237     def assertValueInSource(self, value):
238         """斷言某個關鍵字是否存在頁面源碼中"""
239         print('info:assert "{}" in page source'.format(value))
240         source = self.getSource()
241         assert value in source, '關鍵字"{}"不存在源碼中!'.format(value)
242 
243     def assertStringContainsValue(self, String, value):
244         """斷言某段字符串包含另一個字符串"""
245         print('info:assert "{}" contains "{}"'.format(String, value))
246         assert value in String, '"{}"不包含"{}"!'.format(String, value)
247 
248 
249     @staticmethod
250     def getSheet(sheetName):
251         """獲取某個sheet頁的對象"""
252         sheet = BasePage.excel.getSheetByName(sheetName)
253         return sheet
254 
255 
256 if __name__ == "__main__":
257     driver = webdriver.Firefox()
258     frame = ('xpath', '//div[@id="loginDiv"]/ifram')
259     wait = BasePage(driver)
260     driver.get('https://mail.126.com/')
261     wait.switchToFrame(*frame)
262     username = wait.findElement('xpath', '//input[@name="email"]')
263     username.send_keys('賬號')
264     if wait.isElementExsit('xpath', '//input[@name="password"]'):
265         wait.findElement('xpath', '//input[@name="password"]').send_keys('xiaochao11520')
266     wait.click('xpath', '//a[@id="dologin"]')
BasePage.py-webdriver二次封裝
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 12:28
 4 @Auth : linux超
 5 @File : HomePage.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 from Page.BasePage import BasePage
11 
12 
13 class HomePage(BasePage):
14     # 配置文件讀取元素
15     homePage = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'homePage')
16     mailList = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'mailList')
17     applicationCenter = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'applicationCenter')
18     inBox = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'inBox')
19     '''首頁菜單選項'''
20     def selectMenu(self, Menu='mailList'):
21         """郵箱首頁選擇菜單"""
22         if Menu == 'mailList':
23             self.click(*HomePage.mailList)
24         elif Menu == 'homePage':
25             self.click(*HomePage.homePage)
26         elif Menu == 'applicationCenter':
27             self.click(*HomePage.applicationCenter)
28         elif Menu == 'inBox':
29             self.click(*HomePage.inBox)
30         else:
31             raise ValueError('''
32             菜單選擇錯誤!
33             homePage->首頁
34             mailList->通訊錄
35             applicationCenter->應用中心
36             inBox->收件箱''')
37 
38 if __name__=='__main__':
39     from selenium import webdriver
40     from Page.PageObject.LoginPage import LoginPage
41     driver = webdriver.Firefox()
42     login = LoginPage(driver)
43     login.login('賬號', 'xiaochao11520')
44 
45     home = HomePage(driver)
46     home.selectMenu()
HomePage.py-郵箱首頁選擇菜單
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 12:28
 4 @Auth : linux超
 5 @File : LoginPage.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 from Page.BasePage import BasePage
11 
12 
13 class LoginPage(BasePage):
14 
15     # 配置文件讀取元素
16     frame = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'frame')
17     username = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'username')
18     password = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'password')
19     loginBtn = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'loginBtn')
20     ferrorHead = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'ferrorHead')  # 登錄失敗提示
21 
22     def login(self, userName, passWord):
23         '''登錄'''
24         print('-------staring login-------')
25         self.loadUrl('https://mail.126.com')
26         self.switchToFrame(*LoginPage.frame)
27         self.clear(*LoginPage.username)
28         self.sendKeys(*LoginPage.username, userName)
29         self.clear(*LoginPage.password)
30         self.sendKeys(*LoginPage.password, passWord)
31         self.click(*LoginPage.loginBtn)
32         self.switchToDefaultFrame()
33         print('---------end login---------')
34 
35     # add at 2019/04/19
36     def assertTextEqString(self, expected, name = None):
37         '''斷言提示信息是否與期望的值相等'''
38         self.switchToFrame(*LoginPage.frame)
39         text = self.getElementText(*LoginPage.ferrorHead, name)
40         self.switchToDefaultFrame()
41         print('info: assert "{}" == "{}"'.format(text, expected))
42         assert text == expected, '{} != {}'.format(text, expected)
43 
44 if __name__=="__main__":
45     from selenium import webdriver
46     driver = webdriver.Firefox()
47     login = LoginPage(driver, 30)
48     login.login('lin', '')
49     login.assertTextEqString('請輸入密碼')
LoginPage.py-封裝登錄功能
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 12:29
 4 @Auth : linux超
 5 @File : ContactPage.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 from Page.BasePage import BasePage
11 
12 
13 class ContactPage(BasePage):
14     # 配置文件讀取元素
15     new_contact = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'new_contact')
16     name = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'name')
17     mail = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'mail')
18     star = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'star')
19     phone = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'phone')
20     comment = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'comment')
21     commit = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'commit')
22     errortip = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'tooltip')  # 錯誤提示
23 
24     def newContact(self, Name, Mail, Star, Phone, Comment):
25         """添加聯繫人"""
26         print('--------string add contact--------')
27         self.click(*ContactPage.new_contact)
28         self.sendKeys(*ContactPage.name, Name)
29         self.sendKeys(*ContactPage.mail, Mail)
30         if Star == '1':
31             self.click(*ContactPage.star)
32         self.sendKeys(*ContactPage.phone, Phone)
33         self.sendKeys(*ContactPage.comment, Comment)
34         self.click(*ContactPage.commit)
35         print('--------end add contact--------')
36 
37     def assertErrorTip(self, excepted):
38         """斷言聯繫人添加失敗時是否有提示信息"""
39         text = self.getElementText(*ContactPage.errortip)
40         print('info: assert "{}"=="{}"'.format(text, excepted))
41         assert text == excepted
42 
43 if __name__ == '__main__':
44     from selenium import webdriver
45     from Page.PageObject.LoginPage import LoginPage
46     from Page.PageObject.HomePage import HomePage
47     driver = webdriver.Firefox()
48     home = HomePage(driver)
49     login = LoginPage(driver)
50     contact = ContactPage(driver)
51 
52     login.login('賬號', 'xiaochao11520')
53     home.selectMenu()
54     contact.newContact('[email protected]')
ContactPage.py-封裝添加聯繫人功能
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 9:16
 4 @Auth : linux超
 5 @File : SendMailPage.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 from Page.BasePage import BasePage
11 
12 
13 class SendMailPage(BasePage):
14     # 配置文件讀取元素
15     writeMail = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'writeMail')
16     addressee = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'addressee')
17     subject = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'subject')
18     iframe = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'iframe')
19     text = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'text')
20     sendBtn = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'sendBtn')
21     expect = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'expect')
22     uploadAttachment = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'uploadAttachment')
23     delete = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'delete')
24 
25     def sendMail(self, Address, Subject, Text, PFA=''):
26         """發送郵件功能"""
27         print('------------string send mail---------------------')
28         self.click(*SendMailPage.writeMail)
29         self.sendKeys(*SendMailPage.addressee, Address)
30         self.sendKeys(*SendMailPage.subject, Subject)
31         self.switchToFrame(*SendMailPage.iframe)
32         self.sendKeys(*SendMailPage.text, Text)
33         self.switchToDefaultFrame()
34         if PFA:
35             self.click(*SendMailPage.uploadAttachment)
36             self.ctrlV(PFA)
37             self.enterKey()
38             self.waitElementtobelocated(*SendMailPage.delete)
39         self.click(*SendMailPage.sendBtn)
40         print('------------end send mail---------------------')
41 
42 if __name__=='__main__':
43     from Page.PageObject.LoginPage import LoginPage
44     from selenium import webdriver
45     driver = webdriver.Firefox()
46 
47     login = LoginPage(driver)
48     login.login('賬號', 'xiaochao11520')
49     sendMail = SendMailPage(driver)
50     sendMail.sendMail('[email protected]', 'pytest', 'pytest實戰實例', 1, 'D:\KeyWordDriverTestFrameWork\geckodriver.log')
SendMailPage.py-封裝發送郵件功能

所有的準備工作都已經做好了,還有一個問題,我們的添加聯繫人和發送郵件應該是否應該在已經登錄的前提下測試呢?答案是肯定的。所以我們在用例同目錄下新建conftest.py文件並調用登錄功能(爲什麼這麼做,不明白的小夥伴可以去看一下我之前的文章,關於conftest.py的原理)

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 15:10
 4 @Auth : linux超
 5 @File : conftest.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12 
13 
14 # 從配置文件中獲取正確的用戶名和密碼
15 userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
16 passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
17 @pytest.fixture(scope='function')
18 def login(driver):
19     '''除登錄用例,每一個用例的前置條件'''
20     print('------------staring login------------')
21     loginFunc = LoginPage(driver, 30)
22     loginFunc.login(userName, passWord)
23     yield
24     print('------------end login------------')
25     driver.delete_all_cookies()
conftest.py-同用例目錄下,調用登錄功能

ok,開始編寫測試用例啦

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 14:10
 4 @Auth : linux超
 5 @File : test_loginCase.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12 
13 
14 @pytest.mark.loginTest
15 class TestLogin(object):
16 
17     # 測試數據
18     loginSheet = LoginPage.getSheet('login')
19     data = LoginPage.excel.getAllValuesOfSheet(loginSheet)
20 
21     # 正確的帳號和密碼
22     userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
23     passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
24 
25     @pytest.fixture()
26     def teardown_func(self, driver):
27         """
28         執行每個用例之後要清除一下cookie,
29         否則你第一個賬號登錄之後,重新加載網址還是登錄狀態,無法測試後面的賬號
30         """
31         yield
32         driver.delete_all_cookies()
33 
34     @pytest.mark.parametrize('username, password, expect', data)
35     def test_login(self, teardown_func, driver, username, password, expect):
36         """測試登錄"""
37         login = LoginPage(driver, 30)
38         login.login(username, password)
39         login.sleep(5)
40         # 增加登錄失敗時, 對提示信息的驗證
41         if username == TestLogin.userName and password == TestLogin.passWord:
42             login.assertValueInSource(expect)
43         elif username == '':
44             login.assertTextEqString(expect)
45         elif username != '' and password == '':
46             login.assertTextEqString(expect)
47         elif username == '' and password == '':
48             login.assertTextEqString(expect)
49         else:
50             login.assertTextEqString(expect)
51 
52 
53 if __name__ == "__main__":
54     pytest.main(['-v', 'test_loginCase.py'])
test_loginCase.py-登錄功能測試
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 16:15
 4 @Auth : linux超
 5 @File : test_contactCase.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import re
11 import pytest
12 from Page.PageObject.HomePage import HomePage
13 from Page.PageObject.ContactPage import ContactPage
14 
15 
16 @pytest.mark.conatctTest
17 class TestAddContact(object):
18 
19     # 測試數據
20     contactSheet = ContactPage.getSheet('contact')
21     data = ContactPage.excel.getAllValuesOfSheet(contactSheet)
22 
23     @pytest.mark.newcontact
24     @pytest.mark.parametrize('Name, Mail, Star, Phone, Comment, expect', data)
25     def test_NewContact(self, driver, login, Name, Mail, Star, Phone, Comment, expect):
26         """測試添加聯繫人"""
27         home_page = HomePage(driver)
28         contact_page = ContactPage(driver)
29         home_page.selectMenu()
30         contact_page.newContact(Name, Mail, Star, Phone, Comment)
31         home_page.sleep(5)
32         # 校驗錯誤的郵箱是否提示信息正確
33         if re.match(r'^.{1,}@[0-9a-zA-Z]{1,13}\..*$', Mail):
34             contact_page.assertValueInSource(expect)
35         else:
36             contact_page.assertErrorTip(expect)
37 
38 if __name__ == '__main__':
39     pytest.main(['-v', 'test_contactCase.py'])
test_contactCase.py-添加聯繫人功能測試
 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 10:04
 4 @Auth : linux超
 5 @File : test_sendMailCase.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import pytest
11 from Page.PageObject.SendMailPage import SendMailPage
12 
13 @pytest.mark.sendMailTest
14 class TestSendMail(object):
15 
16     sendMailSheet = SendMailPage.getSheet('mail')
17     data = SendMailPage.excel.getAllValuesOfSheet(sendMailSheet)
18 
19     @pytest.mark.sendmail
20     @pytest.mark.parametrize('Address, Subject, Text, PFA', data)
21     def test_sendMail(self, driver, login, Address, Subject, Text,PFA):
22         """測試發送郵件,包括帶附件的郵件"""
23         send_mail = SendMailPage(driver)
24         send_mail.sendMail(Address, Subject, Text, PFA)
25         send_mail.sleep(5)
26         assert send_mail.isElementExsit(*SendMailPage.expect)
27 
28 if __name__=='__main__':
29     pytest.main(['-v', 'test_sendMailCase.py'])
test_sendMailCase.py-發送郵件功能測試

問題

用例已經寫完了,有兩個問題

1.有沒有發現我們的報告怎麼生成的?也沒有失敗用例截圖?

2.我們貌似並沒有編寫驅動瀏覽器的代碼?

現在我們來解決這個兩個問題

根據pytest的conftest.py文件的原理,我們可以把驅動瀏覽器的代碼寫在一個全局的conftest.py文件裏面。報告生成其實是通過命令 pytest --html=‘report.html’ --self-contained-html生成的,但是這樣的報告對用例的描述不是很清晰,且沒有對失敗用例截圖,也不方便我們分析項目的缺陷,我們也可以填寫代碼放到這個文件裏面(關於報告修改的文章)

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/12 14:10
 4 @Auth : linux超
 5 @File : conftest.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import pytest
11 from selenium import webdriver
12 from py._xmlgen import html
13 
14 _driver = None
15 # 測試失敗時添加截圖和測試用例描述(用例的註釋信息)
16 
17 @pytest.mark.hookwrapper
18 def pytest_runtest_makereport(item):
19     """
20     當測試失敗的時候,自動截圖,展示到html報告中
21     :param item:
22     """
23     pytest_html = item.config.pluginmanager.getplugin('html')
24     outcome = yield
25     report = outcome.get_result()
26     extra = getattr(report, 'extra', [])
27 
28     if report.when == 'call' or report.when == "setup":
29         xfail = hasattr(report, 'wasxfail')
30         if (report.skipped and xfail) or (report.failed and not xfail):
31             file_name = report.nodeid.replace("::", "_")+".png"
32             screen_img = _capture_screenshot()
33             if file_name:
34                 html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
35                        'onclick="window.open(this.src)" align="right"/></div>' % screen_img
36                 extra.append(pytest_html.extras.html(html))
37         report.extra = extra
38         report.description = str(item.function.__doc__)
39         report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
40 
41 
42 @pytest.mark.optionalhook
43 def pytest_html_results_table_header(cells):
44     cells.insert(1, html.th('Description'))
45     cells.insert(2, html.th('Test_nodeid'))
46     cells.pop(2)
47 
48 
49 @pytest.mark.optionalhook
50 def pytest_html_results_table_row(report, cells):
51     cells.insert(1, html.td(report.description))
52     cells.insert(2, html.td(report.nodeid))
53     cells.pop(2)
54 
55 
56 def _capture_screenshot():
57     """
58     截圖保存爲base64
59     :return:
60     """
61     return _driver.get_screenshot_as_base64()
62 # 這裏我設置的級別是模塊級別,也就是每個測試文件運行一次
63 # 可以設置爲session,全部用例執行一次,但是針對126郵箱的話
64 # 登錄次數太多會叫你驗證,如果驗證就沒法執行用例了,我沒有對驗證處理(處理比較複雜)
65 
66 
67 @pytest.fixture(scope='module')
68 def driver():
69     global _driver
70     print('------------open browser------------')
71     _driver = webdriver.Firefox()
72 
73     yield _driver
74     print('------------close browser------------')
75     _driver.quit()
conftest.py-全局conftest.py文件

最後呢,爲了減小項目維護成本,我們把一些全局的配置項,放到我們的功能配置文件中共全局使用,包括運行用例的一些命令字符串,可以自行修改

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/20 16:50
 4 @Auth : linux超
 5 @File : conf.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 from datetime import datetime
11 import os
12 # 項目根目錄
13 projectDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 # 報告目錄
15 reportDir = os.path.join(projectDir, 'report')
16 # ui對象庫config.ini文件所在目錄
17 configDir = os.path.join(projectDir, 'config', 'config.ini')
18 # 測試數據所在目錄
19 excelPath = os.path.join(projectDir, 'data', 'tcData.xlsx')
20 # 當前時間
21 currentTime = datetime.now().strftime('%H_%M_%S')
22 
23 # 郵件配置信息
24 # 郵件服務器
25 smtpServer = 'smtp.qq.com'
26 # 發送者
27 fromUser = '賬號@qq.com'
28 # 發送者密碼
29 fromPassWord = 'mhxvqpewblldbjhf'
30 # 接收者
31 toUser = ['賬號@qq.com']# 可以同時發送給多人,追加到列表中
32 # 郵件標題
33 subject = 'xx項目自動化測試報告'
34 # 郵件正文
35 contents = '測試報告正文'
36 # 報告名稱
37 htmlName = r'{}\testReport{}.html'.format(reportDir, currentTime)
38 
39 # 腳本執行命令
40 args = r'pytest --html=' + htmlName+ ' ' + '--self-contained-html'
41 # modify by linuxchao at 2019/4/25
42 args_login = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'loginTest'+ ' --self-contained-html'
43 args_contact = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'contactTest'+ ' --self-contained-html'
44 args_sendmail = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'sendMailTest'+ ' --self-contained-html'
conf.py-全局配置文件

運行項目

通過命令運行

1.cmd切換到項目的根目錄,執行pytest --html=‘report.html’ --self-contained-html命令(此運行方式,無法發送測試報告郵件)

這種方式感覺有點low,我們換另外一種方式,可以通過os模塊自動執行相關命令,編寫運行用例代碼

 1 """
 2 ------------------------------------
 3 @Time : 2019/4/15 16:14
 4 @Auth : linux超
 5 @File : RunTestCase.py
 6 @IDE  : PyCharm
 7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
 8 ------------------------------------
 9 """
10 import sys
11 sys.path.append('.')
12 from config.conf import *
13 from util.sendMailForReprot import SendMailWithReport
14 
15 
16 def main():
17     # 判斷項目的根目錄是否在sys.path中,沒有就添加
18     if projectDir not in sys.path:
19         sys.path.append(projectDir)
20     # 執行用例
21     os.system(args)
22     # 發送郵件
23     SendMailWithReport.send_mail(
24         smtpServer, fromUser, fromPassWord,
25         toUser, subject, contents,
26         htmlName)
27 
28 
29 if __name__ == '__main__':
30     main()
RunTestCase.py-執行用例文件

我們可以直接執行這個文件執行所用的測試用例了!

其實我們運行用例往往不只是 使用pytest --html=‘report.html’ --self-contained-html 這樣一個簡單的命令運行,通常會添加很多的命令選項,比如-v,-q,-s等等,那麼怎麼辦呢?這時候就用到了pytest.ini配置文件了

只添加了幾個簡單的命令選項(pytest.ini具體使用及原理)

1 [pytest]
2 addopts=-vqs
3 testpaths=./TestCases
4 markers=
5     loginTest: Run login test cases
6     contactTest: Run add contact test cases
7     sendMailTest: Run send mail test cases
pytest.ini-pytest配置文件

測試輸出

1.自動生成html格式報告,其中報告裏面附帶用例執行日誌明細,及用例失敗自動截圖(部分報告展示)

2.自動發送測試郵件給指定用戶

項目源碼

源碼在本人github上,可自行下載!

PS:最後還是附上我們的QQ交流羣:878565760  真心希望所有對測試感興趣,想入門,想提升自己測試能力的小夥伴加入!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章