一步一步實現混合驅動自動化測試框架的搭建
混合驅動自動化測試框架,是一個非常高級的框架,非常好用,但也很難,不好掌握,需要多練習,就像搭建數據驅動框架一樣,需要自己去一點一點的寫,一邊搭建一邊做思路整理,包括遇到的一些問題和處理方法,遇到卡住的地方,就去看下老師是咋處理的,然后結合自己的思路繼續寫,感覺經過了漫長的時間,終於弄完了,還是把過程和總結列出來,做個筆記,另外也作為一份結果,給自己的付出做個即時反饋和激勵~
實現功能:
登錄126郵箱,添加聯系人,然后發送郵件,帶附件
框架結構:
Action:
封裝的操作元素的函數,如login,添加聯系人。。。
conf:
日志配置文件
定位元素配置文件
數據庫配置文件
PageObject:
一個頁面是一個類,類的方法可以獲取頁面上的相關元素
ProjectVar:
工程路徑
工程相關的全局變量
TestData:(文件或excel)
測試用例
測試數據
TestScript:
運行測試框架的主程序:入口,主要讀取測試數據的文件,記錄測試結果。
Util-工具類
讀取配置文件
excel工具類
時間類
查找元素的方法
讀取定位元素配置文件的方法
日志方法
日志操作
截圖
報告模板
思路和目標:
混合驅動模式是指包含數據驅動和關鍵字驅動,數據驅動就是實現數據和程序的分離,把數據放到配置文件中,關鍵字驅動模式中的關鍵字指的是某個動作,說白了就是一個函數的名稱,測試程序通過識別出這個關鍵字來作為方法名,然后去調用這個方法執行相關的操作,關鍵字驅動的思路就是把方法的名稱進行和程序的分離,把方法名放在配置文件中,然后從配置文件中讀出函數的名稱以及函數對應的參數,組合成函數調用表達式來進行函數的調用;
了解了數據驅動和關鍵字驅動的原理后,混合驅動就好理解了,就是數據驅動和關鍵字驅動的聯合,既用到數據驅動模式也用到關鍵字驅動模式,進而可以確定混合驅動的核心就是實現關鍵字(函數名)、數據(包括函數需要的參數)和程序的分離,讓測試人員在文件中維護好關鍵字和數據信息就可以實現框架程序的運行過程;
了解了這個核心之后,我的目的也就確定了,就是搭建一個測試框架,實現數據和程序的分離,同時也要實現函數名和程序的分離,通過在文件中讀取函數名信息(關鍵字)和數據信息,執行關鍵字所映射的函數,進行網頁元素的各種操作,來實現目標功能。
帶着這個思路,我來試着一步一步地實現這個框架的搭建。
步驟1:直接在一個文件中羅列代碼實現
新建一個工程hybrid_version2,在該工程下新建一個TestScipts包,在TestScripts包先新建TestScript.py文件
代碼:
#encoding=utf-8
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
#print u"啟動瀏覽器..."
print "start browser..."
#創建Firefox瀏覽器實例
driver=webdriver.Firefox(executable_path="c:\\geckodriver")
#最大化瀏覽器窗口
driver.maximize_window()
#print u"啟動瀏覽器成功..."
print "start browser done..."
#print u"訪問126郵箱登頁。。。"
print "access 126 mail login page..."
driver.get("http://mail.126.com")
#暫停5秒鍾,一遍郵箱登錄頁面加載完成
time.sleep(5)
assert u"126網易免費郵--你的專業電子郵局" in driver.title
print "access 126 mail login page done"
wait=WebDriverWait(driver,30)
wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID,"x-URS-iframe")))
username=driver.find_element_by_xpath("//input[@name='email']")
username.send_keys("xiaxiaoxu1987")
pwd=driver.find_element_by_xpath("//input[@name='password']")
pwd.send_keys("gloryroad")
pwd.send_keys(Keys.RETURN)
print "user login..."
time.sleep(5)
time1=time.time()
driver.switch_to.default_content()
print "total time:",time.time()-time1
assert u"網易郵箱" in driver.title
print "login done"
address_book_link = wait.until(lambda x: x.find_element_by_xpath("//div[text()='通訊錄']"))
address_book_link.click()
add_contact_button = wait.until(lambda x: x.find_element_by_xpath("//span[text()='新建聯系人']"))
add_contact_button.click()
contact_name = wait.until(lambda x: x.find_element_by_xpath("//a[@title='編輯詳細姓名']/preceding-sibling::div/input"))
contact_name.send_keys(u"徐鳳釵")
contact_email = wait.until(lambda x: x.find_element_by_xpath("//*[@id='iaddress_MAIL_wrap']//input"))
contact_email.send_keys("593152023@qq.com")
contact_is_star = wait.until(lambda x: x.find_element_by_xpath("//span[text()='設為星標聯系人']/preceding-sibling::span/b"))
contact_is_star.click()
contact_mobile = wait.until(lambda x: x.find_element_by_xpath("//*[@id='iaddress_TEL_wrap']//dd//input"))
contact_mobile.send_keys('18141134488')
contact_other_info = wait.until(lambda x: x.find_element_by_xpath("//textarea"))
contact_other_info.send_keys('my wife')
contact_save_button = wait.until(lambda x: x.find_element_by_xpath("//span[.='確 定']"))
contact_save_button.click()
print u"進入首頁。。。"
time.sleep(3)
#mainPage=wait.until(EC.visibility_of_element_located((By.XPATH,"//div[.='首頁']")))
mainPage=wait.until(EC.visibility_of_element_located((By.XPATH,"//div[.='首頁']")))#也好用
#mainPage=wait.until(lambda x: x.find_element(by='xpath', value = "//div[.='首頁']"))#好用
#mainPage=driver.find_element_by_xpath("//div[.='首頁']")#好用
mainPage.click()
assert u"已發送" in driver.page_source
print u"進入首頁成功"
print "write message..."
writeMessage=wait.until(lambda x:x.find_element_by_xpath("//span[text()='寫 信']"))
writeMessage.click()
#收件人
receiver=wait.until(lambda x:x.find_element_by_xpath("//div[contains(@id,'_mail_emailinput')]/input"))
receiver.send_keys("367224698@qq.com")
#主題
theme=wait.until(lambda x:x.find_element_by_xpath("//div[@aria-label='郵件主題輸入框,請輸入郵件主題']/input"))
theme.send_keys(u"測試郵件")
#添加附件
attachment=wait.until(lambda x:x.find_element_by_xpath("//div[@title='點擊添加附件']/input[@size='1' and @type='file']"))
attachment.send_keys("d:\\test.txt")
#切入正文iframe
driver.switch_to.frame(driver.find_element_by_xpath("//iframe[@tabindex=1]"))
editBox=driver.find_element_by_xpath('/html/body')
editBox.send_keys(u"發給夏曉旭的一封信")
driver.switch_to.default_content()
#print u"寫信完成"
print "write message done"
driver.find_element_by_xpath("//header//span[text()='發送']").click()
#print u"開始發送郵件"
print "start to send email.."
time.sleep(3)
assert u"發送成功" in driver.page_source
#print u"郵件發送成功"
print "send emial done"
driver.quit()
結果:添加聯系人、發郵件帶附件成功
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/TestScript.py
start browser...
start browser done...
access 126 mail login page...
access 126 mail login page done
user login...
total time: 0.00800013542175
login done
進入首頁。。。
進入首頁成功
write message...
write message done
start to send email..
send emial done
Process finished with exit code 0
至此,我在一個文件中實現了登錄郵箱、添加聯系人、寫郵件的功能,現在來看下,下一步需要做什么呢?
如果不考慮關鍵字驅動的方式,直觀感覺是把主要的功能進行封裝,例如把登錄,添加聯系人,寫郵件這三個大塊封裝成函數,然后分別調用這三個函數,但是現在要按照關鍵字驅動的思路去考慮每一步的優化和封裝,關鍵字驅動的最終實現形式是通過從excel里讀出關鍵字信息,來運行關鍵字所映射的函數,關鍵字的設置又依賴於測試用例的設計,測試用例又依賴於需求,測試用例的設計反映了需求中需要驗證的測試點,那么關鍵字的設置就體現了測試用例的步驟,貼一下關鍵字excel文件來看一下具體形式:
從上圖可以看到,關鍵字的設置就是測試用例具體的步驟,每一個步驟所執行的動作用關鍵字來標識,這里的動作包括輸入、斷言、等待等環節,通過程序中和這些動作對應的函數來進行web元素的操作,最終完成測試任務。
通過以上的整理現在可以確定,文件中的關鍵字和數據的設置就是這個框架的需求,這個測試框架是為了這個文件中的關鍵字和數據服務的,那搭建這個框架的過程應該是一邊進行文件布局、內容的設計,一邊進行程序的調試、優化,最終完成一個實現目標功能的測試框架,接下來的封裝和優化就需要按照關鍵字驅動的核心來進行了,說白了,封裝是要掌握一個度,這個度就是關鍵字,關鍵字的顆粒度有多細,那么封裝函數的顆粒度就有多細,這樣才會使關鍵字和函數相互映射。
確定了這個思路后,下面把非關鍵字指定的工具類先封裝一下,方便后續調用。
步驟2:封裝查找對象部分 ObjectMap.py
在工程下新建util包,在該包下新建ObjectMap.py文件,封裝查找元素的方法
ObjectMap.py:
#encoding=utf-8
from selenium.webdriver.support.ui import WebDriverWait
#獲取單個元素對象
def getElement(driver,locatorType,locatorExpression):
wait = WebDriverWait(driver, 20)
try:
element=wait.until(lambda x:x.find_element(by=locatorType,value=locatorExpression))
return element
except Exception,e:
raise e
#獲取多個相同頁面元素對象,以list返回
def getElements(driver,locatorType,locatorExpression):
wait=WebDriverWait(driver,20)
try:
elements=wait.until(lambda x:x.find_elements(by=locatorType,value=locatorExpression))
return elements
except Exception,e:
raise e
if __name__=='__main__':
#測試代碼
from selenium import webdriver
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http://www.baidu.com")
searchBox=getElement(driver,'id','kw')
print searchBox.tag_name
aList=getElements(driver,'tag name','a')
print len(aList)
driver.quit()
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/util/ObjectMap.py
input
33
Process finished with exit code 0
修改主程序TestCript.py來調用查找元素方法
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
print "start browser..."
driver=webdriver.Firefox(executable_path="c:\\geckodriver")
driver.maximize_window()
print "start browser done..."
print "access 126 mail login page..."
driver.get("http://mail.126.com")
wait=WebDriverWait(driver,20)
time.sleep(5)
assert u"126網易免費郵--你的專業電子郵局" in driver.title
print "access 126 mail login page done"
wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID,"x-URS-iframe")))
username=getElement(driver,'xpath',"//input[@name='email']")
username.send_keys("xiaxiaoxu1987")
pwd=getElement(driver,'xpath',"//input[@name='password']")
pwd.send_keys("gloryroad")
pwd.send_keys(Keys.RETURN)
print "user login..."
time.sleep(5)
time1=time.time()
driver.switch_to.default_content()
print "total time:",time.time()-time1
assert u"網易郵箱" in driver.title
print "login done"
address_book_link = getElement(driver,'xpath',"//div[text()='通訊錄']")
address_book_link.click()
add_contact_button = getElement(driver,'xpath',"//span[text()='新建聯系人']")
add_contact_button.click()
contact_name = getElement(driver,'xpath',"//a[@title='編輯詳細姓名']/preceding-sibling::div/input")
contact_name.send_keys(u"徐鳳釵")
contact_email = getElement(driver,'xpath',"//*[@id='iaddress_MAIL_wrap']//input")
contact_email.send_keys("593152023@qq.com")
contact_is_star = getElement(driver,'xpath',"//span[text()='設為星標聯系人']/preceding-sibling::span/b")
contact_is_star.click()
contact_mobile = getElement(driver,'xpath',"//*[@id='iaddress_TEL_wrap']//dd//input")
contact_mobile.send_keys('18141134488')
contact_other_info = getElement(driver,'xpath',"//textarea")
contact_other_info.send_keys('my wife')
contact_save_button = getElement(driver,'xpath',"//span[.='確 定']")
contact_save_button.click()
print u"進入首頁。。。"
time.sleep(3)
mainPage=getElement(driver,'xpath',"//div[.='首頁']")
mainPage.click()
assert u"已發送" in driver.page_source
print u"進入首頁成功"
print "write message..."
writeMessage=getElement(driver,'xpath',"//span[text()='寫 信']")
writeMessage.click()
#收件人
receiver=getElement(driver,'xpath',"//div[contains(@id,'_mail_emailinput')]/input")
receiver.send_keys("367224698@qq.com")
#主題
theme=getElement(driver,'xpath',"//div[@aria-label='郵件主題輸入框,請輸入郵件主題']/input")
theme.send_keys(u"測試郵件")
#添加附件
attachment=getElement(driver,'xpath',"//div[@title='點擊添加附件']/input[@size='1' and @type='file']")
attachment.send_keys("d:\\test.txt")
#切入正文iframe
driver.switch_to.frame(getElement(driver,'xpath',"//iframe[@tabindex=1]"))
#正文
editBox=getElement(driver,'xpath','/html/body')
editBox.send_keys(u"發給夏曉旭的一封信")
driver.switch_to.default_content()
#print u"寫信完成"
print "write message done"
#點擊發送按鈕
getElement(driver,'xpath',"//header//span[text()='發送']").click()
print "start to send email.."
time.sleep(3)
assert u"發送成功" in driver.page_source
print "send emial done"
driver.quit()
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/TestScript.py
start browser...
start browser done...
access 126 mail login page...
access 126 mail login page done
user login...
total time: 0.00999999046326
login done
進入首頁。。。
進入首頁成功
write message...
write message done
start to send email..
send emial done
Process finished with exit code 0
下面來封裝一下excel操作方法,用來后續讀寫excel使用
步驟3:封裝excel方法,進行excel文件的讀寫
在util包下新建ParseExcel.py文件,把之前自己封裝的execl操作拿過來,針對openpyxl 2.5.4版本
ParseExcel.py:
# encoding=utf-8
from openpyxl import load_workbook
from openpyxl.styles import Border, Side, Font
import time
class parseExcel(object):
def __init__(self, excelPath):
self.excelPath = excelPath
self.workbook = load_workbook(excelPath) # 加載excel
self.sheet = self.workbook.active # 獲取第一個sheet
self.font = Font(color=None)
self.colorDict = {"red": 'FFFF3030', "green": 'FF008B00'}
# 設置當前要操作的sheet對象,使用index來獲取相應的sheet
def get_sheet_by_index(self, sheet_index):
sheet_name = self.workbook.sheetnames[sheet_index]
self.sheet = self.get_sheet_by_name(sheet_name)
return self.sheet
# 獲取當前默認sheet的名字
def get_default_sheet(self):
return self.sheet.title
# 設置當前要操作的sheet對象,使用sheet名稱來獲取相應的sheet
def get_sheet_by_name(self, sheet_name):
self.sheet = self.workbook[sheet_name]
return self.sheet
# 獲取默認sheet中最大的行數
def get_max_row_no(self):
return self.sheet.max_row
# 獲取默認 sheet 的最大列數
def get_max_col_no(self):
return self.sheet.max_column
# 獲取默認sheet的最小(起始)行號
def get_min_row_no(self):
return self.sheet.min_row
# 獲取默認sheet的最小(起始)列號
def get_min_col_no(self):
return self.sheet.min_column
# 獲取默認 sheet 的所有行對象,
def get_all_rows(self):
return list(self.sheet.iter_rows())
# return list(self.rows)也可以
# 獲取默認sheet中的所有列對象
def get_all_cols(self):
return list(self.sheet.iter_cols())
# return list(self.sheet.columns)也可以
# 從默認sheet中獲取某一列,第一列從0開始
def get_single_col(self, col_no):
return self.get_all_cols()[col_no]
# 從默認sheet中獲取某一行,第一行從0開始
def get_single_row(self, row_no):
return self.get_all_rows()[row_no]
# 從默認sheet中,通過行號和列號獲取指定的單元格,注意行號和列號從1開始
def get_cell(self, row_no, col_no):
return self.sheet.cell(row=row_no, column=col_no)
# 從默認sheet中,通過行號和列號獲取指定的單元格中的內容,注意行號和列號從1開始
def get_cell_content(self, row_no, col_no):
return self.sheet.cell(row=row_no, column=col_no).value
# 從默認sheet中,通過行號和列號向指定單元格中寫入指定內容,注意行號和列號從1開始
# 調用此方法的時候,excel不要處於打開狀態
def write_cell_content(self, row_no, col_no, content, font=None):
self.sheet.cell(row=row_no, column=col_no).value = content
self.workbook.save(self.excelPath)
return self.sheet.cell(row=row_no, column=col_no).value
# 從默認sheet中,通過行號和列號向指定單元格中寫入當前日期,注意行號和列號從1開始
# 調用此方法的時候,excel不要處於打開狀態
def write_cell_current_time(self, row_no, col_no):
time1 = time.strftime("%Y-%m-%d %H:%M:%S")
self.sheet.cell(row=row_no, column=col_no).value = str(time1)
self.workbook.save(self.excelPath)
return self.sheet.cell(row=row_no, column=col_no).value
def save_excel_file(self):
self.workbook.save(self.excelPath)
if __name__ == '__main__':
p = parseExcel(u'D:\\testdata.xlsx')
print u"獲取默認行:", p.get_default_sheet()
print u"設置sheet索引為1", p.get_sheet_by_index(1)
print u"獲取默認sheet:", p.get_default_sheet()
print u"設置sheet索引為0", p.get_sheet_by_index(0)
print u"獲取默認sheet:", p.get_default_sheet()
print u"最大行數:", p.get_max_row_no()
print u"最大列數:", p.get_max_col_no()
print u"最小起始行數:", p.get_min_row_no()
print u"最小起始列數:", p.get_min_col_no()
print u"所有行對象:", p.get_all_rows()
print u"所有列對象:", p.get_all_cols()
print u"獲取某一列(2):", p.get_single_col(2)
print u"獲取某一行(4):", p.get_single_row(4)
print u"取得行號和列號(2,2)單元格:", p.get_cell(2, 2)
print u"取得行號和列號單元格的內容(2,2)", p.get_cell_content(2, 2)
print u"行號和列號寫入內容(11,11):'xiaxiaoxu'", p.write_cell_content(11, 11, 'xiaxiaoxu')
print u"行號和列號寫入當前日期(13,13):", p.write_cell_current_time(13, 13)
結果:ok
現在可以進行excel文件的讀寫操作了,來看下文件中的關鍵字(步驟函數)指向哪些操作,直接用老師給的excel關鍵字設計:
文件中需要的關鍵字去重后一共15個:
open_browser
visit_url
maximize_browser
sleep
assert_string_in_pagesource
waitFrameToBeAvailableAndSwitchToIt
clear
input_string
click
switch_to_default_content
assert_title
waitVisibilityOfElementLocated
paste_string
press_enter_key
close_browser
就是說在登錄、添加聯系人、發送郵件這個過程中需要最少15個函數來實現,下面就把主程序中實現的整個過程的代碼按照這個結構進行封裝,基本上每個方法用幾行代碼就搞定了。
從上邊的關鍵字中可以看到有兩個比較特別的方法:paste_string和press_enter_key,paste_string是用在打開附件添加窗進行輸入附件路徑的,press_enter_key是用來按enter鍵的,所以這兩個是要操作剪貼板和鍵盤的,先把這兩個給搞定。
步驟4:封裝剪貼板和鍵盤操作
在util包下新建clipboard.py和keyboard.py文件
clipboard.py:
#encoding=utf-8
import win32clipboard as w
import win32con
import time
class Clipboard(object):
#模擬Windows設置剪貼板
#讀取剪貼板
@staticmethod
def getText():
#打開剪貼板
w.OpenClipboard()
#獲取剪貼板中的數據
content=w.GetClipboardData(win32con.CF_TEXT)
#關閉剪貼板
w.CloseClipboard()
#返回剪貼板數據
return content
#設置剪貼板內容
@staticmethod
def setText(aString):
#打開剪貼板
w.OpenClipboard()
#清空剪貼板
w.EmptyClipboard()
#將數據aString寫入剪貼板
w.SetClipboardData(win32con.CF_UNICODETEXT,aString)
#關閉剪貼板
w.CloseClipboard()
if __name__=='__main__':
Clipboard.setText(u'hey buddy!')
time.sleep(3)
print Clipboard.getText()
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/util/keyboard.py
hey buddy!
Process finished with exit code 0
Keyboard.py:
#encoding=utf-8
import win32api
import win32con
class KeyboardKeys(object):
#模擬鍵盤按鍵類
VK_CODE={
'enter':0x0D,
'ctrl':0x11,
'v':0x56}
@staticmethod
def keyDown(keyName):
#按下按鍵
win32api.keybd_event(KeyboardKeys.VK_CODE[keyName],0,0,0)
@staticmethod
def keyUp(keyName):
#釋放按鍵
win32api.keybd_event(KeyboardKeys.VK_CODE[keyName],0,win32con.KEYEVENTF_KEYUP,0)
@staticmethod
def oneKey(key):#對前兩個方法的調用
#模擬單個按鍵
KeyboardKeys.keyDown(key)
KeyboardKeys.keyUp(key)
@staticmethod
def twoKeys(key1,key2):#對前面函數的調用
#模擬兩個組合鍵
KeyboardKeys.keyDown(key1)
KeyboardKeys.keyDown(key2)
KeyboardKeys.keyUp(key2)
KeyboardKeys.keyUp(key1)
if __name__=='__main__':
from util.clipboard import *
from selenium import webdriver
import time
Clipboard.setText(u"hello world")
time.sleep(3)
# driver=webdriver.Firefox(executable_path=r'c:\\geckodriver')
# driver.get('http://www.baidu.com')
# driver.find_element_by_xpath("//input[@id='kw']").click()
# KeyboardKeys.twoKeys("ctrl","v")
KeyboardKeys.twoKeys('ctrl','v')
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/util/keyboard.py
hello world
Process finished with exit code 0
准備工作差不多了,現在開始封裝關鍵字映射的方法:
open_browser
visit_url
maximize_browser
sleep
assert_string_in_pagesource
waitFrameToBeAvailableAndSwitchToIt
clear
input_string
click
switch_to_default_content
assert_title
waitVisibilityOfElementLocated
paste_string
press_enter_key
close_browser
步驟5:封裝關鍵字對應的方法。
在工程下新建action包,在該包下新建pageAction.py文件用來封裝關鍵字對應的方法
在封裝open_browser()函數時發現瀏覽器驅動文件的路徑需要在配置文件中配置一下
在工程下新建config包,在該包下新建config.py存放工程所在目錄和數據文件目錄
config.py:
#encoding=utf-8
import os
firefoxDriverPath='c:\\geckodriver'
chromeDriverPath='c:\\chromedriver'
ieDriverPath='d:\\IEDriverServer'
#當前工程所在目錄的絕對路徑
projectPath=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#測試數據文件的絕對路徑
dataFilePath=projectPath + u"\\testData\\126郵箱創建聯系人並發郵件.xlsx"
if __name__ == '__main__':
print projectPath
print dataFilePath
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/config/config.py
D:\test\hybrid_version2
D:\test\hybrid_version2\testData\126郵箱創建聯系人並發郵件.xlsx
Process finished with exit code 0
pageAction.py:
#encoding=utf-8
from util.ObjectMap import *
from util.clipboard import *
from util.keyboard import *
from selenium.webdriver.chrome.options import Options
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from config.config import *
#定義全局driver變量
driver=None
wait=None
#之所以括號中后邊寫*arg,是因為拼接數據的時候,可能會拼成有參數的,這時候做個容錯
#傳了參數也不會報錯。
def open_browser(browserName,*arg):
global driver,wait
try:
if browserName.lower() == 'firefox':
driver=webdriver.Firefox(executable_path=firefoxDriverPath)#稍后路徑放到config文件中
elif browserName.lower() == 'chrome':
#創建chrome瀏覽器的一個options實例對象
chrome_options=Options()
#添加屏蔽ignore-certificate-errors提示信息的設置參數項
chrome_options.add_experimental_option("excludeSwitches",["ignore-certificate-errors"])
driver=webdriver.Chrome(executable_path=chromeDriverPath, chrome_options=chrome_options)# 稍后把路徑放到config文件中
else:#剩余的就是ie了
driver = webdriver.Ie(executable_path=ieDriverPath) # 稍后把路徑放到config文件中
#driver對象確定之后,就可以確定wait了
wait=WebDriverWait(driver,20)
except Exception,e:
raise e
def visit_url(url,*arg):
#訪問某個網址
global driver
try:
driver.get(url)
except Exception,e:
raise e
def close_browser(*arg):
#關閉瀏覽器
global driver
try:
driver.quit()
except Exception,e:
raise e
def sleep(seconds,*arg):
try:
time.sleep(int(seconds))
except Exception,e:
raise e
def clear(locatorType,locatorExpression,*arg):
#找到輸入框元素對象,然后清楚輸入框內容
global driver
try:
getElement(driver,locatorType,locatorExpression).clear()
except Exception,e:
raise e
def input_string(locatorType,locatorExpression,content):
global driver
try:
getElement(driver,locatorType,locatorExpression).send_keys(content)
except Exception,e:
raise e
def click(locatorType,locatorExpression,*arg):
global driver
try:
getElement(driver,locatorType,locatorExpression).click()
except Exception,e:
raise e
def assert_string_in_pagesource(assertString,*arg):
#斷言頁面源碼中是否包含要斷言的字符串
global driver
try:
assert assertString in driver.page_source,u"%s not found in page source!"%assertString
except AssertionError,e:
#raise AssertionError(e)
raise e
except Exception,e:
raise e
def assert_title(titleStr,*arg):
#斷言頁面標題是否是給定的字符串
global driver
try:
assert titleStr in driver.title,u"%s not found in title!" %titleStr
except AssertionError,e:
raise e
except Exception,e:
raise e
def getTitle(*arg):
#獲取頁面標題
global driver
try:
return driver.title
except Exception,e:
raise e
def getPageSource(*arg):
#獲取頁面源碼,這個沒有用到
global driver
try:
return driver.page_source
except Exception,e:
raise e
def switch_to_frame(locatorType,frameLocatorExpression,*arg):
#查找到frame,並切進frame
global driver
try:
driver.switch_to.frame(getElement(driver,locatorType,frameLocatorExpression))
except Exception,e:
print "switch to frame error"
raise e
def switch_to_default_content(*arg):
#切換頁面從frame到默認窗口中
global driver
try:
driver.switch_to.default_content()
except Exception,e:
raise e
def paste_string(pasteContent,*arg):
#模擬ctrl + v操作
try:
Clipboard.setText(pasteContent)#Clipboard類的靜態函數
#等待2秒,防止剪貼板沒設置好內容就粘貼,會失敗
time.sleep(2)
KeyboardKeys.twoKeys('ctrl','v')#Keyboardkeys類的靜態函數
except Exception,e:
raise e
def press_tab_key(*arg):#沒用到
#模擬tab按鍵
try:
KeyboardKeys.oneKey('tab')
except Exception,e:
raise e
def press_enter_key(*arg):
#模擬enter按鍵
try:
KeyboardKeys.oneKey('enter')
except Exception,e:
raise e
def maximize_browser():
#窗口最大化
global driver
try:
driver.maximize_window()
except Exception,e:
raise e
def waitFrameToBeAvailableAndSwitchToIt(locatorType,locatorExpression,*arg):
#檢查frmae是否存在,存在則切進frame中
global wait
try:
wait.until(EC.frame_to_be_available_and_switch_to_it(locatorType,locatorExpression))
except Exception,e:
raise e
def waitVisibilityOfElementLocated(locatorType,locatorExpression,*arg):
#等待頁面元素出現在DOM中並且可見,存在則返回該元素
global wait
try:
element=wait.until(EC.visibility_of_element_located(locatorType,locatorExpression))
return element
except Exception,e:
raise e
if __name__ == '__main__':
from selenium import webdriver
open_browser('firefox')
visit_url('http:\\126.com')
switch_to_frame("id", "x-URS-iframe")
input_string('xpath', "//input[@name='email']","xiaxiaoxu1987")
input_string('xpath', "//input[@name='password']","gloryroad")
click("id","dologin")
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/action/pageAction.py
Process finished with exit code 0
這一步中,把關鍵字對應的函數都進行了封裝,下面嘗試在主程序中把代碼都用封裝起來的函數來實現。
TestScript.py:
#encoding=utf-8
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
print "start browser..."
open_browser('firefox')
maximize_browser()
print "start browser done..."
print "access 126 mail login page..."
visit_url("http://mail.126.com")
sleep(5)
assert_title(u"126網易免費郵--你的專業電子郵局")
print "access 126 mail login page done"
switch_to_frame('id',"x-URS-iframe")
#wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID,"x-URS-iframe")))
input_string('xpath',"//input[@name='email']",'xiaxiaoxu1987')
input_string('xpath',"//input[@name='password']",'gloryroad')
press_enter_key()
#pwd.send_keys(Keys.RETURN)
print "user login..."
sleep(5)
switch_to_default_content()
assert_title(u"網易郵箱")
print "login done"
click('xpath',"//div[text()='通訊錄']")
click('xpath',"//span[text()='新建聯系人']")
input_string('xpath',"//a[@title='編輯詳細姓名']/preceding-sibling::div/input",u"徐鳳釵")
input_string('xpath',"//*[@id='iaddress_MAIL_wrap']//input","593152023@qq.com")
click('xpath',"//span[text()='設為星標聯系人']/preceding-sibling::span/b")
input_string('xpath',"//*[@id='iaddress_TEL_wrap']//dd//input",'18141134488')
input_string('xpath',"//textarea",'my wife')
click('xpath',"//span[.='確 定']")
print u"進入首頁。。。"
sleep(3)
click('xpath',"//div[.='首頁']")
assert_string_in_pagesource(u"已發送")
print u"進入首頁成功"
print "write message..."
click('xpath',"//span[text()='寫 信']")
input_string('xpath',"//div[contains(@id,'_mail_emailinput')]/input","367224698@qq.com")
input_string('xpath',"//div[@aria-label='郵件主題輸入框,請輸入郵件主題']/input",u"測試郵件")
input_string('xpath',"//div[@title='點擊添加附件']/input[@size='1' and @type='file']","d:\\test.txt")
switch_to_frame('xpath',"//iframe[@tabindex=1]")
input_string('xpath','/html/body',u"發給夏曉旭的一封信")
switch_to_default_content()
#print u"寫信完成"
print "write message done"
#點擊發送按鈕
click('xpath',"//header//span[text()='發送']")
print "start to send email.."
time.sleep(3)
assert_string_in_pagesource(u"發送成功")
print "send emial done"
close_browser()
結果:非常好用!
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/TestScript.py
start browser...
start browser done...
access 126 mail login page...
access 126 mail login page done
user login...
login done
進入首頁。。。
進入首頁成功
write message...
write message done
start to send email..
send emial done
Process finished with exit code 0
不得不說,封裝了函數之后再調用,真的很方便,代碼看起來非常整潔、清晰,運行了這個代碼,心情都好了。
在上邊主程序中調用的一個個的函數其實就是關鍵字的調用,這個框架最后實現的原理就是從文件中取出關鍵字和關鍵字需要的參數,組合成一個表達式,然后運行這個表達式,最后實現的其實就是上面主程序的代碼結構,只不過我們把函數的調用通過不同的形式來運行而已
我剛開始搭建這個框架的時候,有一個問題一直很模糊,就是為什么要這樣做,為什么要把關鍵字、數據放在文件中,然后寫程序去讀這個文件,為啥要通過文件的配置來運行程序,其實站在實際項目測試的角度來看就清楚了,這個框架就好像一輛車,我們可以駕駛這輛車去各種地方,但是我們可能不了解這個車內部的構造是什么,它是怎么工作的,但是只要我們會掛擋、踩油門、打轉向等簡單的操作,就可以讓車按我們的想法去行駛,這個框架也是一樣,在代碼中我們把相對復雜的邏輯實現了,給測試人員留出一個入口(存關鍵字和數據的文件),測試人員通過對有限的關鍵字進行組合、搭配,就可以運行這個框架,實現對目標系統的測試,在文件中對關鍵字和數據的組合來實現對測試系統的運行和斷言以及輸出測試結果,就是測試框架帶來的好處,它可以重復使用,隨意更換輸入的數據,更換瀏覽器,調整關鍵字順序等等,好處顯而易見。
下面就嘗試一下從文件中來讀取關鍵字和數據信息實現用例的執行。
步驟6:修改主程序,在主程序中讀取文件中的關鍵字和數據信息實現登錄的功能
再看一下文件的結構:
在測試用例sheet中划分了三個用例,分別是登錄,創建聯系人,發郵件,先看第一條登錄的用例,后邊是否執行列的y用來標識需要執行,其中”用例步驟sheet名”列的內容指向用例具體步驟的sheet頁名稱,第一個用例的步驟sheet是登錄,在登錄sheet頁中列出了步驟所對應的關鍵字(動作),關鍵字步驟所需要的參數在后邊的列,例如定位方式、定位表達式、操作值(關鍵字函數需要的參數),再后邊是測試時間,測試結果等信息。
先來試下從“測試用例”sheet中讀取第一條需要執行的用例,實現登錄的操作
在TestScript包下新建一個login.py來實現登錄
login.py:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
pe=parseExcel(dataFilePath)
pe.get_sheet_by_name(u"測試用例")
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[2].value
caseStepSheet=row[3].value
dataDrivenSourceSheet=row[4].value
ifExecute=row[5].value
if ifExecute.lower() == 'y':
# print u"用例名稱:", row[0].value
# print u"用例描述:", row[1].value
# print u"調用框架類型:", frameWorkType
# print u"用例步驟sheet名:", caseStepSheet
# print u"數據驅動的數據源sheet名:", dataDrivenSourceSheet
# print u"是否執行:", ifExecute
if frameWorkType ==u"關鍵字":
pe.get_sheet_by_name(caseStepSheet)
stepRows=pe.get_all_rows()
for idx1,row1 in enumerate(stepRows[1:]):
caseStepDescripion=row1[0].value
keyWord=row1[1].value
locatorType=row1[2].value
locatorExpression=row1[3].value
operateValue=int(row1[4].value) if isinstance(row1[4].value,long) else row1[4].value
# print u"測試步驟描述:",caseStepDescripion
# print u"關鍵字:",keyWord
# print u"操作元素的定位方式:",locatorType
# print u"操作元素的定位表達式:",locatorExpression
# print u"操作值:",operateValue
if (locatorType and locatorExpression):
command="%s('%s','%s',u'%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""),operateValue) if operateValue else "%s('%s','%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""))
elif operateValue :
command ="%s(u'%s')"%(keyWord,operateValue)
else:
command="%s()"%keyWord
print caseStepDescripion
print "command:",command
try:
eval(command)
except Exception,e:
raise e
elif frameWorkType == u"數據":
pass
else:
pass #不是y
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/login.py
當前sheet: 測試用例
打開瀏覽器
command: open_browser(u'firefox')
訪問被測試網址http://www.126.com
command: visit_url(u'http://www.126.com')
最大化窗口
command: maximize_browser()
等待126郵箱登錄主頁加載完成
command: sleep(u'5')
斷言當前活動頁面源碼中是否包含“126網易免費郵--你的專業電子郵局”
command: assert_string_in_pagesource(u'126網易免費郵--你的專業電子郵局')
顯示等待id屬性值為x-URS-iframe的frame框的出現,然后切換進入該frame框中
command: switch_to_frame('id','x-URS-iframe')
清除用戶名輸入框中的緩存用戶性
command: clear('xpath','//input[@name="email"]')
輸入登錄用戶名
command: input_string('xpath','//input[@name="email"]',u'xiaxiaoxu1987')
輸入登錄密碼
command: input_string('xpath','//input[@name="password"]',u'gloryroad')
點擊登錄按鈕
command: click('id','dologin')
等待
command: sleep(u'5')
切回默認會話窗體
command: switch_to_default_content()
斷言登錄成功后的頁面標題是否包含“網易郵箱6.0版”關鍵內容
command: assert_title(u'網易郵箱6.0版')
Process finished with exit code 0
至此,實現了測試用例sheet中的第一條用例的執行,過程包括判斷用例是否要執行,識別測試用例需要的步驟sheet是誰,然后進入這個步驟sheet,讀取每一行的關鍵字信息,進行函數的執行,最后這條測試用例就運行完了,下面看下還需要做什么,有什么可以優化,
從用例文件的內容來看,在執行用例之后,需要填寫執行結束時間和執行結果:
步驟sheet中,需要記錄執行時間,測試結果,錯誤信息,錯誤截圖,下一步來做這個。
步驟7:記錄執行時間、結果、錯誤信息,錯誤截圖
先來看測試結果,在測試用例sheet中的測試結果是根據它對應的步驟sheet中是否所有的關鍵字步驟都運行成功來確定的,如果都成功了,那這個用例就成功了,否則就是失敗,
在步驟sheet中,每一個關鍵字函數運行都會有一個結果和時間等信息,先把這兩塊搞定。
修改程序login.py:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
pe=parseExcel(dataFilePath)
pe.get_sheet_by_name(u"測試用例")
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[2].value
caseStepSheet=row[3].value
dataDrivenSourceSheet=row[4].value
ifExecute=row[5].value
if ifExecute.lower() == 'y':
# print u"用例名稱:", row[0].value
# print u"用例描述:", row[1].value
# print u"調用框架類型:", frameWorkType
# print u"用例步驟sheet名:", caseStepSheet
# print u"數據驅動的數據源sheet名:", dataDrivenSourceSheet
# print u"是否執行:", ifExecute
if frameWorkType ==u"關鍵字":
pe.get_sheet_by_name(caseStepSheet)
stepRows=pe.get_all_rows()
totalStepNum=len(stepRows)-1
print "totalStepNum:",totalStepNum
successStepNum=0
for idx1,row1 in enumerate(stepRows[1:]):
caseStepDescripion=row1[0].value
keyWord=row1[1].value
locatorType=row1[2].value
locatorExpression=row1[3].value
operateValue=int(row1[4].value) if isinstance(row1[4].value,long) else row1[4].value
# print u"測試步驟描述:",caseStepDescripion
# print u"關鍵字:",keyWord
# print u"操作元素的定位方式:",locatorType
# print u"操作元素的定位表達式:",locatorExpression
# print u"操作值:",operateValue
if (locatorType and locatorExpression):
command="%s('%s','%s',u'%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""),operateValue) if operateValue else "%s('%s','%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""))
elif operateValue :
command ="%s(u'%s')"%(keyWord,operateValue)
else:
command="%s()"%keyWord
print caseStepDescripion
print "command:",command
try:
eval(command)
except Exception,e:
pe.write_cell_content(idx1 + 2, 7, u"fail")
pe.write_cell_current_time(idx1 + 2, 6)
pe.write_cell_content(idx1+2,8,traceback.format_exc())
#寫入錯誤截圖需要封裝函數,返回圖片地址
else:#沒有報錯
successStepNum+=1
print "successStepNum:",successStepNum
pe.write_cell_content(idx1+2,7,u"pass")
pe.write_cell_current_time(idx1+2,6)
if totalStepNum == successStepNum:#此條用例執行成功
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"成功") # 在結果列中寫入忽略
pe.write_cell_current_time(idx+2,7)#寫入執行時間
else:
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"失敗") # 在結果列中寫入忽略
pe.write_cell_current_time(idx + 2, 7) # 寫入執行時間
elif frameWorkType == u"數據":
pass
else:#不是y
pe.get_sheet_by_name(u"測試用例")#把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx+2,8,u"忽略")#在結果列中寫入忽略
pe.write_cell_content(idx+2,7,"")#清空時間列的值
結果:實現了寫結果,寫時間,寫錯誤信息
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/login.py
當前sheet: 測試用例
totalStepNum: 13
打開瀏覽器
command: open_browser(u'firefox')
successStepNum: 1
訪問被測試網址http://www.126.com
command: visit_url(u'http://www.126.com')
successStepNum: 2
最大化窗口
command: maximize_browser()
successStepNum: 3
等待126郵箱登錄主頁加載完成
command: sleep(u'5')
successStepNum: 4
斷言當前活動頁面源碼中是否包含“126網易免費郵--你的專業電子郵局”
command: assert_string_in_pagesource(u'126網易免費郵--你的專業電子郵局')
successStepNum: 5
顯示等待id屬性值為x-URS-iframe的frame框的出現,然后切換進入該frame框中
command: switch_to_frame('id','x-URS-iframe1')
switch to frame error
清除用戶名輸入框中的緩存用戶性
command: clear('xpath','//input[@name="email"]1')
輸入登錄用戶名
command: input_string('xpath','//input[@name="email"]',u'xiaxiaoxu1987')
輸入登錄密碼
command: input_string('xpath','//input[@name="password"]',u'gloryroad')
點擊登錄按鈕
command: click('id','dologin')
等待
command: sleep(u'5')
successStepNum: 6
切回默認會話窗體
command: switch_to_default_content()
successStepNum: 7
斷言登錄成功后的頁面標題是否包含“網易郵箱6.0版”關鍵內容
command: assert_title(u'網易郵箱6.0版')
Process finished with exit code 0
寫入錯誤截圖地址這里,需要單獨封裝一個函數,然后在主程序中調用
在action包下pageAction.py中增加captureScreen方法:
在config.py中增加存放錯誤截圖的路徑:
# 異常圖片存放目錄
screenPicturesDir = projectPath + "\\errorScreenShots\\"
pageAction,py:#增加captureScreen方法
def captureScreen(*arg):
global driver
currentDate=time.strftime("%Y-%m-%d")
currentTime=datetime.now().strftime('%H-%M-%S-%f')#時分秒毫秒
dirName=os.path.join(screenPicturesDir,currentDate)
if not os.path.exists(dirName):
os.makedirs(dirName)
print "dirName:",dirName
screenShotNameAndPath="%s\\%s.png"%(dirName,currentTime)
print "screenPicturesDir:",screenPicturesDir
try:
driver.get_screenshot_as_file(screenShotNameAndPath.replace('\\',r'\\'))
except Exception,e:
raise e
else:
return screenShotNameAndPath
測試代碼:
if __name__ == '__main__':
from selenium import webdriver
open_browser('firefox')
visit_url('http:\\126.com')
# switch_to_frame("id", "x-URS-iframe")
# input_string('xpath', "//input[@name='email']","xiaxiaoxu1987")
# input_string('xpath', "//input[@name='password']","gloryroad")
# click("id","dologin")
Print captureScreen()
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/action/pageAction.py
dirName: D:\test\hybrid_version2\errorScreenShots\2018-07-24
screenPicturesDir: D:\test\hybrid_version2\errorScreenShots\
D:\test\hybrid_version2\errorScreenShots\2018-07-24\22-36-14-430000.png
Process finished with exit code 0
修改login.py,加入截圖的處理
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
pe=parseExcel(dataFilePath)
pe.get_sheet_by_name(u"測試用例")
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[2].value
caseStepSheet=row[3].value
dataDrivenSourceSheet=row[4].value
ifExecute=row[5].value
if ifExecute.lower() == 'y':
# print u"用例名稱:", row[0].value
# print u"用例描述:", row[1].value
# print u"調用框架類型:", frameWorkType
# print u"用例步驟sheet名:", caseStepSheet
# print u"數據驅動的數據源sheet名:", dataDrivenSourceSheet
# print u"是否執行:", ifExecute
if frameWorkType ==u"關鍵字":
pe.get_sheet_by_name(caseStepSheet)
stepRows=pe.get_all_rows()
totalStepNum=len(stepRows)-1
print "totalStepNum:",totalStepNum
successStepNum=0
for idx1,row1 in enumerate(stepRows[1:]):
caseStepDescripion=row1[0].value
keyWord=row1[1].value
locatorType=row1[2].value
locatorExpression=row1[3].value
operateValue=int(row1[4].value) if isinstance(row1[4].value,long) else row1[4].value
# print u"測試步驟描述:",caseStepDescripion
# print u"關鍵字:",keyWord
# print u"操作元素的定位方式:",locatorType
# print u"操作元素的定位表達式:",locatorExpression
# print u"操作值:",operateValue
if (locatorType and locatorExpression):
command="%s('%s','%s',u'%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""),operateValue) if operateValue else "%s('%s','%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""))
elif operateValue :
command ="%s(u'%s')"%(keyWord,operateValue)
else:
command="%s()"%keyWord
print caseStepDescripion
print "command:",command
try:
eval(command)
except Exception,e:
pe.write_cell_content(idx1 + 2, 7, u"fail")
pe.write_cell_current_time(idx1 + 2, 6)
pe.write_cell_content(idx1+2,8,traceback.format_exc())
pe.write_cell_content(idx1+2,9,captureScreen())
#寫入錯誤截圖需要封裝函數,返回圖片地址
else:#沒有報錯
successStepNum+=1
print "successStepNum:",successStepNum
pe.write_cell_content(idx1+2,7,u"pass")
pe.write_cell_current_time(idx1+2,6)
if totalStepNum == successStepNum:#此條用例執行成功
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"成功") # 在結果列中寫入忽略
pe.write_cell_current_time(idx+2,7)#寫入執行時間
else:
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"失敗") # 在結果列中寫入忽略
pe.write_cell_current_time(idx + 2, 7) # 寫入執行時間
elif frameWorkType == u"數據":
pass
else:#不是y
pe.get_sheet_by_name(u"測試用例")#把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx+2,8,u"忽略")#在結果列中寫入忽略
pe.write_cell_content(idx+2,7,"")#清空時間列的值
結果:ok
至此,我已經實現了關鍵字的步驟執行,記錄執行時間、結果、錯誤信息和錯誤截圖信息,下一步,實現數據驅動的執行。
步驟8:實現數據驅動用例的執行
看一下數據驅動對應的用例,數據驅動在這里其實是混合了關鍵字驅動和數據驅動兩部分的,用例步驟sheet名是創建聯系人,創建聯系人sheet里包含的是關鍵字信息,以及關鍵字對應的參數,在執行創建聯系人動作的過程中又從聯系人sheet中取出需要添加的聯系人信息,相當於用關鍵字執行相關的函數,到聯系人sheet里去執行關鍵字函數所需的數據,所以是關鍵字驅動和數據驅動的混合模式,說白了,就是在把函數的名稱和函數需要的參數分別放在文件中了,我們要做的就是如何正確的從文件中把這些可配置的數據給取出來,組合成正確的表達式來運行,這么做最大的好處是測試人員可以通過文件中數據和關鍵字的配置就可以運行整個框架,來測試被測系統。
貌似我一直在給自己確認這么做的目的。。。
思路是先在測試用例中,讀取出測試步驟的sheet名,需要的數據源sheet名,然后到測試步驟sheet中讀取出關鍵字信息以及參數信息,然后像關鍵字驅動那樣寫測試結果,時間信息、錯誤信息、截圖信息等。
在用坐標(如”A1”)的方式獲取單元格內容時,發現沒有對應的方法,需要在ParseExcel.py中添加一個方法,來實現用坐標取數據:
def get_cell_content_by_coordinate(self,coordinate):
return self.sheet[coordinate].value
測試代碼:
print u"通過坐標獲取單元格內容(D4):",p.get_cell_content_by_coordinate('D4')
結果:ok
通過坐標獲取單元格內容(D4): 5
在執行waitVisibilityOfElementLocated這個關鍵字的時候,發現個問題:
它的第一個參數在文件中設置的是xpath,如圖:
但是在pageAction.py中封裝這個函數時是這樣定義的:
element=wait.until(EC.visibility_of_element_located(locatortype,locatorExpression))
這樣在拼接參數執行command的時候,就成了這個樣子:
element=wait.until(EC.visibility_of_element_located(‘xpath’,’//span[xx]’))
但是通過EC.后邊接期望場景的時候,是跟find_element_by不一樣的,而是通過By模塊的By.XPATH,By.ID之類的方式來查找元素的,而且沒有引號,這就麻煩了,需要把這個方法改造一下,怎么改造呢,要做一個轉化,把文件中取出的字符串”xpath”進行一個映射,映射成By.XPATH,可以通過字典的方式,比如:
locatorTypeDict={
"xpath": By.XPATH,
"id": By.ID,
"name": By.NAME,
"css_selector": By.CSS_SELECTOR,
"class_name": By.CLASS_NAME,
"tag_name": By.TAG_NAME,
"link_text": By.LINK_TEXT,
"partial_link_text": By.PARTIAL_LINK_TEXT}
然后element=wait.until(EC.visibility_of_element_located(locatortype,locatorExpression))改成這樣:
element=wait.until(EC.visibility_of_element_located((locatorTypeDict[locatorType.lower()],locatorExpression)))
這樣轉成執行命令的時候就是:
element=wait.until(EC.visibility_of_element_located((By.XPATH, ‘//span[text()="新建聯系人"]’)))
這樣就符合語法規范了
然后在pageAction.py中修改waitVisibilityOfElementLocated()方法:
def waitVisibilityOfElementLocated(locatorType,locatorExpression,*arg):
#等待頁面元素出現在DOM中並且可見,存在則返回該元素
global wait
locatorTypeDict={
"xpath": By.XPATH,
"id": By.ID,
"name": By.NAME,
"css_selector": By.CSS_SELECTOR,
"class_name": By.CLASS_NAME,
"tag_name": By.TAG_NAME,
"link_text": By.LINK_TEXT,
"partial_link_text": By.PARTIAL_LINK_TEXT}
try:
element=wait.until(EC.visibility_of_element_located((locatorTypeDict[locatorType.lower()],locatorExpression)))
return element
except Exception,e:
raise e
if __name__ == '__main__':
from selenium import webdriver
open_browser('firefox')
visit_url('http:\\126.com')
switch_to_frame("id", "x-URS-iframe")
input_string('xpath', '//input[@name="email"]', u'xiaxiaoxu1987')
input_string('xpath', '//input[@name="password"]', u'gloryroad')
click('id', 'dologin')
sleep(u'5')
switch_to_default_content()
click('xpath','//div[text()="通訊錄"]')
waitVisibilityOfElementLocated('xpath', '//span[text()="新建聯系人"]')
click('xpath', '//span[text()="新建聯系人"]')
waitVisibilityOfElementLocated('xpath', '//a[@title="編輯詳細姓名"]/preceding-sibling::div/input')
測試結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/action/pageAction.py
Process finished with exit code 0
在測試用例中還有一個方法waitFrameToBeAvailableAndSwitchToIt()也用到了期望場景:EC.visibility_of_element_located()也需要像上邊那樣處理,寫一段locatorTypeDict={…}的定義,然后再在期望方法中查找元素:
locatorTypeDict={
"xpath": By.XPATH,
"id": By.ID,
"name": By.NAME,
"css_selector": By.CSS_SELECTOR,
"class_name": By.CLASS_NAME,
"tag_name": By.TAG_NAME,
"link_text": By.LINK_TEXT,
"partial_link_text": By.PARTIAL_LINK_TEXT}
如果用例中還有其他的期望場景,也要這樣重復的寫這個字典的定義,那我們就可以把這個字典提出來,作為global的變量存在,然后再函數中像調用driver一樣,用global 聲明一下就可以了
pageAction.py:
#encoding=utf-8
from util.ObjectMap import *
from util.clipboard import *
from util.keyboard import *
from config.config import *
from selenium.webdriver.chrome.options import Options
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from config.config import *
from selenium import webdriver
from datetime import datetime
from selenium.webdriver import ActionChains
import os
#定義全局driver變量
driver=None
wait=None
locatorTypeDict = {
"xpath": By.XPATH,
"id": By.ID,
"name": By.NAME,
"css_selector": By.CSS_SELECTOR,
"class_name": By.CLASS_NAME,
"tag_name": By.TAG_NAME,
"link_text": By.LINK_TEXT,
"partial_link_text": By.PARTIAL_LINK_TEXT}
def waitFrameToBeAvailableAndSwitchToIt(locatorType,locatorExpression,*arg):
#檢查frmae是否存在,存在則切進frame中
global wait
global locatorTypeDict
try:
element = wait.until(
EC.frame_to_be_available_and_switch_to_it((locatorTypeDict[locatorType.lower()], locatorExpression)))
return element
except Exception, e:
raise e
def waitVisibilityOfElementLocated(locatorType,locatorExpression,*arg):
#等待頁面元素出現在DOM中並且可見,存在則返回該元素
global wait
global locatorTypeDict
try:
element=wait.until(EC.visibility_of_element_located((locatorTypeDict[locatorType.lower()],locatorExpression)))
return element
except Exception,e:
raise e
其他函數定義省略。。。
在testScript包下新建一個dataAndKeywordDriven.py,把之前的login中關鍵字驅動的內容拿過來,然后接着寫數據驅動的部分
dataAndKeywordDriven.py:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
pe=parseExcel(dataFilePath)
pe.get_sheet_by_name(u"測試用例")
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[2].value
caseStepSheet=row[3].value
dataDrivenSourceSheet=row[4].value
ifExecute=row[5].value
if ifExecute.lower() == 'y':
# print u"用例名稱:", row[0].value
# print u"用例描述:", row[1].value
# print u"調用框架類型:", frameWorkType
# print u"用例步驟sheet名:", caseStepSheet
# print u"數據驅動的數據源sheet名:", dataDrivenSourceSheet
# print u"是否執行:", ifExecute
if frameWorkType ==u"關鍵字":
print "-----執行關鍵字驅動框架-----"
pe.get_sheet_by_name(caseStepSheet)
stepRows=pe.get_all_rows()
totalStepNum=len(stepRows)-1
print "totalStepNum:",totalStepNum
successStepNum=0
for idx1,row1 in enumerate(stepRows[1:]):
caseStepDescripion=row1[0].value
keyWord=row1[1].value
locatorType=row1[2].value
locatorExpression=row1[3].value
operateValue=str(row1[4].value) if isinstance(row1[4].value,long) else row1[4].value
# print u"測試步驟描述:",caseStepDescripion
# print u"關鍵字:",keyWord
# print u"操作元素的定位方式:",locatorType
# print u"操作元素的定位表達式:",locatorExpression
# print u"操作值:",operateValue
if (locatorType and locatorExpression):
command="%s('%s','%s',u'%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""),operateValue) if operateValue else "%s('%s','%s')"%(keyWord,locatorType,locatorExpression.replace("'","\""))
elif operateValue :
command ="%s(u'%s')"%(keyWord,operateValue)
else:
command="%s()"%keyWord
print caseStepDescripion
print "command:",command
try:
eval(command)
except Exception,e:
pe.write_cell_content(idx1 + 2, 7, u"fail",color='red')
pe.write_cell_current_time(idx1 + 2, 6)
pe.write_cell_content(idx1+2,8,traceback.format_exc())
pe.write_cell_content(idx1+2,9,captureScreen())
#寫入錯誤截圖需要封裝函數,返回圖片地址
else:#沒有報錯
successStepNum+=1
print "successStepNum:",successStepNum
pe.write_cell_content(idx1+2,7,u"pass",color="green")
pe.write_cell_current_time(idx1+2,6)
pe.write_cell_content(idx1 + 2, 8, "")
pe.write_cell_content(idx1 + 2, 9, "")
if totalStepNum == successStepNum:#此條用例執行成功
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"成功",color="green") # 在結果列中寫入忽略
pe.write_cell_current_time(idx+2,7)#寫入執行時間
else:
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"失敗",color="red") # 在結果列中寫入忽略
pe.write_cell_current_time(idx + 2, 7) # 寫入執行時間
elif frameWorkType == u"數據":
print "-----執行數據驅動框架-----"
print u"步驟sheet:",caseStepSheet
print u"數據源sheet",dataDrivenSourceSheet
pe.get_sheet_by_name(dataDrivenSourceSheet)
requiredContactNum = 0
successfullyAddedContactNum=0
isExecuteCol=pe.get_single_col(5)
emailCol=pe.get_single_col(1)
print "isExecuteCol:",isExecuteCol
print "emailCol:",emailCol
pe.get_sheet_by_name(caseStepSheet)
stepRows = pe.get_all_rows()
totalStepNum = len(stepRows) - 1
print "totalStepNum:", totalStepNum
for idx1,cell in enumerate(isExecuteCol[1:]):
if cell.value =='y':
successStepNum = 0
requiredContactNum+=1#記錄一次需要添加的聯系人
for row in stepRows[1:]:
caseStepDescripion = row[0].value
keyWord = row[1].value
locatorType = row[2].value
locatorExpression = row[3].value
operateValue = str(row[4].value) if isinstance(row[4].value, long) else row[4].value
# print "caseStepDescripion:",caseStepDescripion
# print "keyWord:",keyWord
# print "locatorType:",locatorType
# print "locatorExpression:",locatorExpression
# print "operateValue:",operateValue
if operateValue and operateValue.isalpha():
pe.get_sheet_by_name(dataDrivenSourceSheet)
operateValue=pe.get_cell_content_by_coordinate(operateValue+str(idx1+2))
#print "operateValue:",operateValue
if (locatorType and locatorExpression):
command = "%s('%s','%s',u'%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""),
operateValue) if operateValue else "%s('%s','%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""))
elif operateValue:
command = "%s(u'%s')" % (keyWord, operateValue)
print caseStepDescripion
print "command:",command
try:
if operateValue != u"否":
eval(command)
except Exception,e:#某個步驟執行失敗
print u"執行步驟-%s-失敗"%caseStepDescripion
else:#執行步驟成功
print u"執行步驟-%s-成功" % caseStepDescripion
successStepNum +=1
print "successStepNum:", successStepNum
#print "successStepNum:",successStepNum
if totalStepNum == successStepNum:#說明一條聯系人添加成功
#寫結果
print u"添加聯系人-%s-成功"%emailCol[idx1+2].value
successfullyAddedContactNum +=1#記錄一次添加一條聯系人成功
pe.get_sheet_by_name(dataDrivenSourceSheet)
pe.write_cell_current_time(idx1+2,7)
pe.write_cell_content(idx1+2,8,u"pass",color='green')
else:#說明添加聯系人沒成功,寫fail
print u"添加聯系人-%s-失敗" % emailCol[idx1 + 2].value
pe.get_sheet_by_name(dataDrivenSourceSheet)
pe.write_cell_current_time(idx1 + 2, 7)
pe.write_cell_content(idx1 + 2, 8, u"fail",color="red")
else: #忽略
pe.get_sheet_by_name(dataDrivenSourceSheet)
pe.write_cell_content(idx1+2,7,'')
pe.write_cell_content(idx1+2,8,u"忽略")
if requiredContactNum == successfullyAddedContactNum:#說明需要添加的聯系人和添加成功的聯系人數量一樣,那這條用例就成功了
pe.get_sheet_by_name(u"測試用例")
pe.write_cell_content(idx+2,8,u"成功",color="green")
pe.write_cell_current_time(idx+2,7)
else:#有聯系人添加失敗了,用例執行失敗,
pe.get_sheet_by_name(u"測試用例")
pe.write_cell_content(idx + 2, 8, u"失敗",color="red")
pe.write_cell_current_time(idx + 2, 7)
else:#不是y
pe.get_sheet_by_name(u"測試用例")#把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx+2,8,u"忽略")#在結果列中寫入忽略
pe.write_cell_content(idx+2,7,"")#清空時間列的值
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/dataAndKeywordDriven.py
當前sheet: 測試用例
-----執行關鍵字驅動框架-----
打印日志部分省略。。。
Process finished with exit code 0
看下excel寫入結果情況:
創建聯系人,不涉及寫入
在這塊兒,我把文件中用於上傳附件的關鍵字給改了,不是通過用點擊按鈕彈窗,然后調用鍵盤事件粘貼文件路徑的方式,因為在實踐過程中,發現點擊上傳附件按鈕,始終沒辦法彈出windows窗口,卡在這兒好久,最后妥協了,換了直接用sendKeys()的方式了。
至此,讀取關鍵字、數據、執行、寫入結果都已經可以了,在這一步發費的調試時間是最長的,難點是取出數據要看是否正確,關鍵字對應的函數是否執行正確,寫入文件的結果是否正確,以及判斷一條聯系人是否添加成功的邏輯,寫結果的顏色等等,每一個地方有問題可能都要調試一會兒。
在寫入測試結果的時候,需要修改一下ParseExce.py中的write_cell_content方法,把顏色的處理加進去。
def write_cell_content(self, row_no, col_no, content, font=None,color=None):
try:
self.sheet.cell(row=row_no, column=col_no).value = content
if color is not None:
self.sheet.cell(row=row_no, column=col_no).value = content
self.sheet.cell(row=row_no, column=col_no).font=Font(color=self.colorDict[color])
self.workbook.save(self.excelPath)
else:
self.sheet.cell(row=row_no, column=col_no).font = self.font
return self.sheet.cell(row=row_no, column=col_no).value
except Exception,e:
raise e
加入一個color參數,在寫入內容的時候,如果指定了顏色,那么就會進行顏色的處理。
從主程序來看,我們直接把關鍵字驅動,數據驅動的處理和測試結果的寫入,都羅列在了一個文件中,代碼看起來很長,讀起來不太清晰、簡潔,下面就試着把可以封裝出來的進行封裝。
步驟9:封裝主程序中的關鍵字驅動和數據驅動部分功能
在TestScripts包下新建keyWord.py用於封裝關鍵字方法
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
def keyWordFunction(excelObj,caseStepSheet):
excelObj.get_sheet_by_name(caseStepSheet)
stepRows = excelObj.get_all_rows()
totalStepNum = len(stepRows) - 1
#print "totalStepNum:", totalStepNum
successStepNum = 0
for idx1, row1 in enumerate(stepRows[1:]):
caseStepDescripion = row1[0].value
keyWord = row1[1].value
locatorType = row1[2].value
locatorExpression = row1[3].value
operateValue = str(row1[4].value) if isinstance(row1[4].value, long) else row1[4].value
if (locatorType and locatorExpression):
command = "%s('%s','%s',u'%s')" % (keyWord, locatorType, locatorExpression.replace("'", "\""),
operateValue) if operateValue else "%s('%s','%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""))
elif operateValue:
command = "%s(u'%s')" % (keyWord, operateValue)
else:
command = "%s()" % keyWord
print caseStepDescripion
#print "command:", command
try:
eval(command)
except Exception, e:
excelObj.write_cell_content(idx1 + 2, 7, u"fail", color='red')
excelObj.write_cell_current_time(idx1 + 2, 6)
excelObj.write_cell_content(idx1 + 2, 8, traceback.format_exc())
excelObj.write_cell_content(idx1 + 2, 9, captureScreen())
# 寫入錯誤截圖需要封裝函數,返回圖片地址
else: # 沒有報錯
successStepNum += 1
#print "successStepNum:", successStepNum
excelObj.write_cell_content(idx1 + 2, 7, u"pass", color="green")
excelObj.write_cell_current_time(idx1 + 2, 6)
excelObj.write_cell_content(idx1 + 2, 8, "")
excelObj.write_cell_content(idx1 + 2, 9, "")
if totalStepNum == successStepNum: # 此條用例執行成功
return 1#代表成功
else:
return 0#代表失敗
在TestScripts包下新建dataDriven.py用於封裝數據驅動方法
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
def dataDrivenFunction(excelObj,caseStepSheet,dataDrivenSourceSheet):
print u"步驟sheet:", caseStepSheet
print u"數據源sheet", dataDrivenSourceSheet
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
requiredContactNum = 0
successfullyAddedContactNum = 0
isExecuteCol = excelObj.get_single_col(5)
emailCol = excelObj.get_single_col(1)
#print "isExecuteCol:", isExecuteCol
#print "emailCol:", emailCol
excelObj.get_sheet_by_name(caseStepSheet)
stepRows = excelObj.get_all_rows()
totalStepNum = len(stepRows) - 1
#print "totalStepNum:", totalStepNum
for idx1, cell in enumerate(isExecuteCol[1:]):
if cell.value == 'y':
successStepNum = 0
requiredContactNum += 1 # 記錄一次需要添加的聯系人
for row in stepRows[1:]:
caseStepDescripion = row[0].value
keyWord = row[1].value
locatorType = row[2].value
locatorExpression = row[3].value
operateValue = str(row[4].value) if isinstance(row[4].value, long) else row[4].value
# print "caseStepDescripion:",caseStepDescripion
# print "keyWord:",keyWord
# print "locatorType:",locatorType
# print "locatorExpression:",locatorExpression
# print "operateValue:",operateValue
if operateValue and operateValue.isalpha():
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
operateValue = excelObj.get_cell_content_by_coordinate(operateValue + str(idx1 + 2))
# print "operateValue:",operateValue
if (locatorType and locatorExpression):
command = "%s('%s','%s',u'%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""),
operateValue) if operateValue else "%s('%s','%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""))
elif operateValue:
command = "%s(u'%s')" % (keyWord, operateValue)
print caseStepDescripion
#print "command:", command
try:
if operateValue != u"否":
eval(command)
except Exception, e: # 某個步驟執行失敗
print u"執行步驟-%s-失敗" % caseStepDescripion
else: # 執行步驟成功
print u"執行步驟-%s-成功" % caseStepDescripion
successStepNum += 1
#print "successStepNum:", successStepNum
# print "successStepNum:",successStepNum
if totalStepNum == successStepNum:#說明一條聯系人添加成功
#寫結果
print u"添加聯系人-%s-成功"%emailCol[idx1+2].value
successfullyAddedContactNum +=1#記錄一次添加一條聯系人成功
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
excelObj.write_cell_current_time(idx1+2,7)
excelObj.write_cell_content(idx1+2,8,u"pass",color='green')
else:#說明添加聯系人沒成功,寫fail
print u"添加聯系人-%s-失敗" % emailCol[idx1 + 2].value
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
excelObj.write_cell_current_time(idx1 + 2, 7)
excelObj.write_cell_content(idx1 + 2, 8, u"fail",color="red")
else: # 忽略
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
excelObj.write_cell_content(idx1 + 2, 7, '')
excelObj.write_cell_content(idx1 + 2, 8, u"忽略")
if requiredContactNum == successfullyAddedContactNum:#說明需要添加的聯系人和添加成功的聯系人數量一樣,那這條用例就成功了
return 1
else:#有聯系人添加失敗了,用例執行失敗,
return 0
在主程序中調用dataDriven方法和keyWord方法
dataAndKeywordDriven.py:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
from TestScripts.keyWord import *
from TestScripts.dataDriven import *
pe=parseExcel(dataFilePath)
pe.get_sheet_by_name(u"測試用例")
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
#print "caseRows:",caseRows
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[2].value
caseStepSheet=row[3].value
dataDrivenSourceSheet=row[4].value
ifExecute=row[5].value
#print "ifExecute",ifExecute
if ifExecute.lower() == 'y':
# print u"用例名稱:", row[0].value
# print u"用例描述:", row[1].value
# print u"調用框架類型:", frameWorkType
# print u"用例步驟sheet名:", caseStepSheet
# print u"數據驅動的數據源sheet名:", dataDrivenSourceSheet
# print u"是否執行:", ifExecute
if frameWorkType ==u"關鍵字":
print "#####執行關鍵字驅動框架#####"
result =keyWordFunction(pe,caseStepSheet)
print "result:",result
if result:#此條用例執行成功
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"成功",color="green") # 在結果列中寫入忽略
pe.write_cell_current_time(idx+2,7)#寫入執行時間
else:
pe.get_sheet_by_name(u"測試用例") # 把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx + 2, 8, u"失敗",color="red") # 在結果列中寫入忽略
pe.write_cell_current_time(idx + 2, 7) # 寫入執行時間
elif frameWorkType == u"數據":
print "#####執行數據驅動框架#####"
result=dataDrivenFunction(pe,caseStepSheet,dataDrivenSourceSheet)
if result:#說明需要添加的聯系人和添加成功的聯系人數量一樣,那這條用例就成功了
pe.get_sheet_by_name(u"測試用例")
pe.write_cell_content(idx+2,8,u"成功",color="green")
pe.write_cell_current_time(idx+2,7)
else:#有聯系人添加失敗了,用例執行失敗,
pe.get_sheet_by_name(u"測試用例")
pe.write_cell_content(idx + 2, 8, u"失敗",color="red")
pe.write_cell_current_time(idx + 2, 7)
else:#不是y
pe.get_sheet_by_name(u"測試用例")#把默認sheet獲取到測試用例sheet上
pe.write_cell_content(idx+2,8,u"忽略")#在結果列中寫入忽略
pe.write_cell_content(idx+2,7,"")#清空時間列的值
結果:ok
C:\Python27\python.exe D:/test/hybrid_version2/TestScripts/dataAndKeywordDriven.py
當前sheet: 測試用例
#####執行關鍵字驅動框架#####
打開瀏覽器
訪問被測試網址http://www.126.com
最大化窗口
等待126郵箱登錄主頁加載完成
斷言當前活動頁面源碼中是否包含“126網易免費郵--你的專業電子郵局”
顯示等待id屬性值為x-URS-iframe的frame框的出現,然后切換進入該frame框中
清除用戶名輸入框中的緩存用戶性
輸入登錄用戶名
輸入登錄密碼
點擊登錄按鈕
等待
切回默認會話窗體
斷言登錄成功后的頁面標題是否包含“網易郵箱6.0版”關鍵內容
result: 1
#####執行數據驅動框架#####
步驟sheet: 創建聯系人
數據源sheet 聯系人
點擊“通訊錄”鏈接,進入通訊錄頁面
執行步驟-點擊“通訊錄”鏈接,進入通訊錄頁面-成功
顯示等待“新建聯系人”按鈕在頁面上可見
執行步驟-顯示等待“新建聯系人”按鈕在頁面上可見-成功
點擊“新建聯系人”按鈕
執行步驟-點擊“新建聯系人”按鈕-成功
顯示等待輸入聯系人姓名框是否在頁面上可見
執行步驟-顯示等待輸入聯系人姓名框是否在頁面上可見-成功
輸入聯系人姓名
執行步驟-輸入聯系人姓名-成功
輸入電子郵箱
執行步驟-輸入電子郵箱-成功
設為星標聯系人
執行步驟-設為星標聯系人-成功
輸入手機號
執行步驟-輸入手機號-成功
輸入聯系人備注信息
執行步驟-輸入聯系人備注信息-成功
點擊“確認”按鈕保存新聯系人
執行步驟-點擊“確認”按鈕保存新聯系人-成功
等待2秒
執行步驟-等待2秒-成功
斷言聯系人是否成功添加
執行步驟-斷言聯系人是否成功添加-成功
添加聯系人-wcx@qq.com-成功
點擊“通訊錄”鏈接,進入通訊錄頁面
執行步驟-點擊“通訊錄”鏈接,進入通訊錄頁面-成功
顯示等待“新建聯系人”按鈕在頁面上可見
執行步驟-顯示等待“新建聯系人”按鈕在頁面上可見-成功
點擊“新建聯系人”按鈕
執行步驟-點擊“新建聯系人”按鈕-成功
顯示等待輸入聯系人姓名框是否在頁面上可見
執行步驟-顯示等待輸入聯系人姓名框是否在頁面上可見-成功
輸入聯系人姓名
執行步驟-輸入聯系人姓名-成功
輸入電子郵箱
執行步驟-輸入電子郵箱-成功
設為星標聯系人
執行步驟-設為星標聯系人-成功
輸入手機號
執行步驟-輸入手機號-成功
輸入聯系人備注信息
執行步驟-輸入聯系人備注信息-成功
點擊“確認”按鈕保存新聯系人
執行步驟-點擊“確認”按鈕保存新聯系人-成功
等待2秒
執行步驟-等待2秒-成功
斷言聯系人是否成功添加
執行步驟-斷言聯系人是否成功添加-成功
添加聯系人-amy@qq.com-成功
#####執行關鍵字驅動框架#####
點擊“首頁”鏈接,進入郵箱首頁
判斷“寫信”按鈕是否在頁面上可見
點擊“寫信”按鈕
輸入收件人地址
輸入郵件主題
點擊“上傳附件”鏈接
顯示等待附件上傳完成
如果郵件正文的frame框是否可見,切換進該frame中
輸入郵件正文
退出郵件正文的frame
點擊郵件發送按鈕
等待郵件發送成功,返回結果
斷言頁面源碼中是否出現“發送成功”關鍵內容
關閉瀏覽器
result: 1
Process finished with exit code 0
至此,我把數據驅動、關鍵字驅動部分封裝成了方法,和主程序進行了分離,但是程序中寫入結果的代碼比較占篇幅,也比較零散,下面嘗試把寫結果部分進行下一下封裝
步驟10:把寫測試結果的功能進行封裝
在util包下新建writeResult.py文件:
#encoding=utf-8
from util.ParseExcel import *
from config.config import *
import traceback
from util.ParseExcel import *
from config.config import *
def writeResult(excelObj,sheetName,sheetType,rowNo,result,errorInfo=None,captureScreenPath=None):
colorOfResult = {"pass": "green", "fail": "red", u"成功": "green",u"失敗": "red", u"忽略": None, "": None}
print "當前所在sheet:",excelObj.get_default_sheet()
print excelObj, sheetName, sheetType, rowNo, result, errorInfo,captureScreenPath
sheetTypeDict={
"testCase":[testCase_runTime, testCase_testResult],#測試用例sheet名,時間列和結果列的列號
"caseStep":[testStep_runTime, testStep_testResult,testStep_errorInfo,testStep_errorPic],#關鍵字驅動測試步驟sheet名,時間列和結果列號
"dataSheet":[dataSource_runTime, dataSource_result]}#數據驅動的數據源sheet名,時間、結果列號
try:
excelObj.get_sheet_by_name(sheetName)
if sheetType != "caseStep":#sheetType不是關鍵字步驟sheet,說明是用例sheet或者數據源sheet
print "sheetType1:",sheetType
print "sheetTypeDict[sheetType][1]:",sheetTypeDict[sheetType][1]
#直接把結果寫上去
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][1], result, color=colorOfResult[result])
if result != "" and result != u"忽略":
print "result-1:",result
excelObj.write_cell_current_time(rowNo,sheetTypeDict[sheetType][0])
else:#聯系人被忽略,時間列清空
print "result-2:", result
excelObj.write_cell_content(rowNo,sheetTypeDict[sheetType][0],"",color=colorOfResult[result])
else :#sheetType是步驟sheet
print "sheetType2:", sheetType
print "result:",result
#先把結果列和時間列給寫上
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][1], result, color=colorOfResult[result])
excelObj.write_cell_current_time(rowNo, sheetTypeDict[sheetType][0])
if errorInfo and captureScreenPath:#有錯誤信息和截圖信息,把錯誤信息和截圖信息寫進去
print "errorInfo:",errorInfo
print "captureScreenPath:",captureScreenPath
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][2], errorInfo)
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][3], captureScreenPath)
else:#沒有截圖信息和錯誤信息,把截圖信息和錯誤信息清空
print "sheetTypeDict[sheetType][3]",sheetTypeDict[sheetType][3]
print "sheetTypeDict[sheetType][2]",sheetTypeDict[sheetType][2]
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][3],"")#是成功的,沒有錯誤信息
excelObj.write_cell_content(rowNo, sheetTypeDict[sheetType][2], "")#是成功的,沒有截圖信息
except Exception,e:
raise e
keyword.py中調用:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
from util.writeResult import *
def keyWordFunction(excelObj,caseStepSheet):
excelObj.get_sheet_by_name(caseStepSheet)
stepRows = excelObj.get_all_rows()
totalStepNum = len(stepRows) - 1
#print "totalStepNum:", totalStepNum
successStepNum = 0
for idx1, row1 in enumerate(stepRows[1:]):
caseStepDescripion = row1[testStep_testStepDescribe-1].value
keyWord = row1[testStep_keyWords-1].value
locatorType = row1[testStep_locationType-1].value
locatorExpression = row1[testStep_locatorExpression-1].value
operateValue = str(row1[testStep_operateValue-1].value) if isinstance(row1[testStep_operateValue-1].value, long) else row1[testStep_operateValue-1].value
if (locatorType and locatorExpression):
command = "%s('%s','%s',u'%s')" % (keyWord, locatorType, locatorExpression.replace("'", "\""),
operateValue) if operateValue else "%s('%s','%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""))
elif operateValue:
command = "%s(u'%s')" % (keyWord, operateValue)
else:
command = "%s()" % keyWord
print caseStepDescripion
#print "command:", command
try:
eval(command)
except Exception, e:
errorInfo=traceback.format_exc()
captureScreenPath=captureScreen()
writeResult(excelObj, caseStepSheet,"caseStep", idx1 + 2, u"fail",errorInfo=errorInfo,captureScreenPath=captureScreenPath)
else: # 沒有報錯
successStepNum += 1
writeResult(excelObj, caseStepSheet,"caseStep", idx1 + 2, u"pass")
if totalStepNum == successStepNum: # 此條用例執行成功
return 1#代表成功
else:
return 0#代表失敗
dataDriven.py中調用:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
from util.writeResult import *
def dataDrivenFunction(excelObj,caseStepSheet,dataDrivenSourceSheet):
print u"步驟sheet:", caseStepSheet
print u"數據源sheet", dataDrivenSourceSheet
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
requiredContactNum = 0
successfullyAddedContactNum = 0
isExecuteCol = excelObj.get_single_col(5)
emailCol = excelObj.get_single_col(1)
excelObj.get_sheet_by_name(caseStepSheet)
stepRows = excelObj.get_all_rows()
totalStepNum = len(stepRows) - 1
#print "totalStepNum:", totalStepNum
for idx1, cell in enumerate(isExecuteCol[1:]):
if cell.value == 'y':
successStepNum = 0
requiredContactNum += 1 # 記錄一次需要添加的聯系人
for row in stepRows[1:]:
caseStepDescripion = row[testStep_testStepDescribe-1].value
keyWord = row[testStep_keyWords-1].value
locatorType = row[testStep_locationType-1].value
locatorExpression = row[testStep_locatorExpression-1].value
operateValue = str(row[testStep_operateValue-1].value) if isinstance(row[testStep_operateValue-1].value, long) else row[testStep_operateValue-1].value
if operateValue and operateValue.isalpha():
excelObj.get_sheet_by_name(dataDrivenSourceSheet)
operateValue = excelObj.get_cell_content_by_coordinate(operateValue + str(idx1 + 2))
# print "operateValue:",operateValue
if (locatorType and locatorExpression):
command = "%s('%s','%s',u'%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""),
operateValue) if operateValue else "%s('%s','%s')" % (
keyWord, locatorType, locatorExpression.replace("'", "\""))
elif operateValue:
command = "%s(u'%s')" % (keyWord, operateValue)
print caseStepDescripion
#print "command:", command
try:
if operateValue != u"否":
eval(command)
except Exception, e: # 某個步驟執行失敗
print u"執行步驟-%s-失敗" % caseStepDescripion
else: # 執行步驟成功
print u"執行步驟-%s-成功" % caseStepDescripion
successStepNum += 1
#print "successStepNum:", successStepNum
# print "successStepNum:",successStepNum
if totalStepNum == successStepNum:#說明一條聯系人添加成功
#寫結果
print u"添加聯系人-%s-成功"%emailCol[idx1+2].value
successfullyAddedContactNum +=1#記錄一次添加一條聯系人成功
writeResult(excelObj,dataDrivenSourceSheet,"dataSheet",idx1+2,u"pass",)
else:#說明添加聯系人沒成功,寫fail
print u"添加聯系人-%s-失敗" % emailCol[idx1 + 2].value
writeResult(excelObj, dataDrivenSourceSheet, "dataSheet", idx1 + 2, u"fail", )
else: # 忽略
writeResult(excelObj, dataDrivenSourceSheet, "dataSheet", idx1 + 2, u"忽略", )
if requiredContactNum == successfullyAddedContactNum:#說明需要添加的聯系人和添加成功的聯系人數量一樣,那這條用例就成功了
return 1
else:#有聯系人添加失敗了,用例執行失敗,
return 0
主程序中調用:
#encoding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from util.ObjectMap import *
from action.pageAction import *
from util.ParseExcel import *
import traceback
from TestScripts.keyWord import *
from TestScripts.dataDriven import *
from util.writeResult import *
pe=parseExcel(dataFilePath)
caseSheetName=u"測試用例"#測試用例sheet名
pe.get_sheet_by_name(caseSheetName)
print u"當前sheet:",pe.get_default_sheet()
caseRows=pe.get_all_rows()
#print "caseRows:",caseRows
for idx,row in enumerate(caseRows[1:]):
frameWorkType=row[testCase_frameWorkName-1].value
caseStepSheet=row[testCase_testStepSheetName-1].value
dataDrivenSourceSheet=row[testCase_dataSourceSheetName-1].value
ifExecute=row[testCase_isExecute-1].value
#print "ifExecute",ifExecute
if ifExecute.lower() == 'y':
if frameWorkType ==u"關鍵字":
print "#####執行關鍵字驅動框架#####"
result =keyWordFunction(pe,caseStepSheet)
print "result:",result
if result:#此條用例執行成功
writeResult(pe,caseSheetName,"testCase",idx+2,u"成功")
else:
writeResult(pe,caseSheetName,"testCase", idx + 2, u"失敗")
elif frameWorkType == u"數據":
print "#####執行數據驅動框架#####"
result=dataDrivenFunction(pe,caseStepSheet,dataDrivenSourceSheet)
if result:#說明需要添加的聯系人和添加成功的聯系人數量一樣,那這條用例就成功了
writeResult(pe, caseSheetName,"dataSheet", idx + 2, u"成功")
else:#有聯系人添加失敗了,用例執行失敗,
writeResult(pe, caseSheetName,"dataSheet", idx + 2, u"失敗")
else:#不是y
writeResult(pe,caseSheetName, "testCase", idx + 2, u"忽略")
結果:ok
說實話,封裝這個寫結果的邏輯,大概用了我一個人天的工時,而且很多地方是參考了老師的實現方式,但還是很費勁,感覺要處理的邏輯總是梳理不清楚,而且又不能抄老師的代碼,那樣就不會形成自己的思路,我先是按照自己的理解一點一點地實現,然后有卡住的地方再去看看老師代碼的思路,最終總算按照自己的想法把它實現了。
之前寫結果的過程都是在具體的地方羅列了往哪個行哪個列寫入具體的值,雖然步驟較多,但是看起來比較直觀,好理解,缺點就是重復的代碼有點兒多,不利於復用,所以給封裝出來,但是封裝的時候要考慮的邏輯點較多,簡單列一下:
1-要考慮寫結果的時候所在的sheet位置,不同sheet頁布局不一樣,要區分開來做具體的處理,比如用例sheet和數據源sheet結構相同,都有結果列和時間列,可以用相同方式處理,步驟sheet比前兩種sheet多了錯誤信息列和截圖信息列,這個可以單獨處理一下;
2-在處理用例sheet和數據源sheet時,結果有4種:成功、失敗、忽略、空,在結果這一列無非寫入這4個結果之一,所以可以直接把結果給寫上去,然后針對忽略或者空的情況,把時間這一列給清空掉,因為忽略的case是不用寫執行時間的,不為空或者不為忽略的就在執行時間列寫入當前的時間,這樣用例sheet和數據源sheet就處理完了;
3-然后是步驟sheet,因為步驟sheet是執行的步驟,只有成功和失敗兩種情況,不存在忽略的情況,區別就是成功時不需要寫錯誤信息和截圖信息,失敗時要寫入,那就先把執行時間和結果列給寫上去,然后針對失敗的情況寫一下錯誤信息列和截圖信息列,這樣寫結果的邏輯就都覆蓋全了;
4-然后一個難點就是怎么區分不同的sheet,怎么把結果寫到對應的列上去呢,可以把不同的sheet對應不同的列信息放到一個字典里,key是sheet類型,value是一個列表,用來放需要寫結果的列號,然后調用寫結果函數時把sheet的類型傳進來,然后用切片去取對應的列信息,就可以了;
5-然后是顏色處理,可以把所有可能的結果以及對應的顏色放到一個字典里(key是結果,value是顏色),在寫結果的時候,把color的值用字典取一下就可以。
以上5點就是封裝寫結果要考慮的邏輯,看起來挺簡單的,但是我用了好長時間才理清楚,
感覺還是得多練啊,有點像打怪,不多打點怪,經驗值還上不去呢~~
在封裝寫結果方法的過程中,優化了一個地方,之前在讀取文件中不同列號的值是直接用數字來寫的,我改成用變量的形式,這樣好理解一些,也方便維護
在config文件中,把不同sheet中的各個列的序號用變量存起來:
#測試用例sheet中列號信息
testCase_testCaseName = 1
testCase_frameWorkName = 3
testCase_testStepSheetName = 4
testCase_dataSourceSheetName = 5
testCase_isExecute = 6
testCase_runTime = 7
testCase_testResult = 8
#測試步驟對應的文件中后邊的關鍵字的列號
testStep_testStepDescribe = 1
testStep_keyWords = 2
testStep_locationType = 3
testStep_locatorExpression = 4
testStep_operateValue = 5
testStep_runTime = 6
testStep_testResult = 7
testStep_errorInfo = 8
testStep_errorPic = 9
# 數據源表中,是否執行列對應的數字編號
#聯系人的sheet
dataSource_isExecute = 6
dataSource_email = 2
dataSource_runTime = 7
dataSource_result = 8
至此,這個混合驅動的框架基本已經搭建完成了,還有一個地方需要優化,就是把程序中打印日志的print語句用日志模塊來代替,可以把更多的信息體現在日志中。
步驟11:封裝日志模塊
在config包下新建Logger.conf文件存日志配置信息
#logger.conf
###############################################
[loggers]
keys=root,example01,example02
[logger_root]
level=DEBUG
handlers=hand01,hand02
[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0
[logger_example02]
handlers=hand01,hand03
qualname=example02
propagate=0
###############################################
[handlers]
keys=hand01,hand02,hand03
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stderr,)
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('DataDrivenFrameWork.log', 'a')
[handler_hand03]
class=handlers.RotatingFileHandler
level=INFO
formatter=form01
args=('DataDrivenFrameWork.log', 'a', 10*1024*1024, 5)
###############################################
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
[formatter_form02]
format=%(name)-12s: %(levelname)-8s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
在util包下新建log.py文件,定義日志函數
#encoding=utf-8
import logging
import logging.config
from config.config import *
#讀取日志的配置文件
logging.config.fileConfig(projectPath+"\\config\\Logger.conf")
#選擇一個日志格式
logger=logging.getLogger("example02")
def error(message):
#打印debug級別的信息
logger.error(message)
def info(message):
#打印info級別的信息
logger.info(message)
def warning(message):
#打印warning級別的信息
logger.warning(message)
if __name__=="__main__":
info("hi")
print "config file path:",projectPath+"\\config\\Logger.conf"
error("world!")
warning("gloryroad!")
把文件中用print打印日志的地方,換成logging.info(string)的方式
需要注意兩個地方,一個是logging.info()函數中只能輸入一個參數,如果需要顯示逗號相隔的多個信息,可以用模板字符串的方式,另一個是最好使用u’%s’的方式,因為直接用%s的話,如果字符串中要替換的字符有中文,調用logging.info()會報錯,會提示沒有把所有的字符進行轉換。
下面是整個框架的結構:
此時此刻,“總結”已經顯得有些蒼白無力,知道了原理只是剛剛開始,實踐才是硬道理,多練吧,要走的路還很長~~