上一篇博客,我寫了python自動化框架的一些知識和粗淺的看法,在上一篇中我也給自己提出一個需求:如果記錄在測試過程中接口的調用情況?提出這個需求,我覺得是有意義的。你在測試過程中肯定會遇到一些莫名其妙的問題,比如:web某個頁面一直在刷進度條,導致你定位元素失敗,但是,你再手動操作一遍可能無法復現....對於我們來說,肯定會遇到許多類似的問題。你會發現有時候僅僅靠一張截圖,你遠遠找不到bug的原因。這時候,我在想如果我能拿到這一系列操作所調用的接口信息多好,我就能明白為什么發生這種問題了。比如一直在刷進度條我覺得有幾種情況:1.后端一直在等待某個接口的響應信息。2.網絡原因導致,接口響應很慢(局域網一般很少出現這類問題)、3.前端工程師沒有動態的把這個進度條display="None"....不論何種原因,我拿到相關的接口信息,就能對錯誤逐個排除。比如我發現某個接口的響應時間很長.....或者所有接口的響應的時間多是大於1s的,又或者都正常響應,原來js沒有動態改變進度的屬性?反正無論如何我拿到自動化操作的接口信息是沒有壞處的吧?(小小的缺點我后面提到)
那么問題是,我們如何精准的拿到這些信息?我開始的想法是通過firebug去拿,firebug我們平時用的也比較多,可以方便的看到控制台信息(js的執行情況)和網絡信息(接口調用情況),但是我查了很多資料都沒有辦法完整的把這些信息給導出來....但是,我很快的想到了Fiddler。Fiddler是目前為止我用的最好最順手的一款http抓包工具(不要和我提什么wireshark,雖然經過網卡的信息它都能抓但是僅對http協議來說,真不如fiddler牛逼,誰用誰知道),更重要的是由於它是個代理服務器,所以能抓任何設置其為代理的終端,包括手機...想到這,心中一陣竊喜。下面我先說說思路,然后再詳細的說明,我是怎么做的。我的思路如下:
1.設置fiddler過濾一下抓取信息,如:只抓取host為:*.csdn.net的接口信息。
2.測試執行開始前,打開fiddler。
3.當執行一個test時,先在fiddler控制台輸入cls,清空當前sessions,防止接口信息過多或混在一起不方便排查錯誤。
4.當執行test完畢,如果有錯誤,則保存此test執行過程中的所有sessions至一個文件夾。無錯誤不做操作(如果你非要保存也是可以的)
5.重復2-3的步驟,直至所有測試結束。
6.測試執行結束后,關閉fiddler。
上面的想法,其實也是很簡單的,我們再一個個看看如何實現:
對於步驟1/2/5 用python調用控制台打開fiddler是有問題的(主進程會阻塞,其他應用程序沒問題),改用AutoIt的run方法,關閉沒問題。
對於3/4是要想想辦法的。對於自動化人員來說AutoIt您應該是接觸過了,如果沒有就去看看吧!AutoIt有弊端有優點,最大的優點就是編寫簡單、腳本能轉換成exe.最大的缺點:windows非標准控件無法獲取。萬幸的是Fiddler的控制台輸入框能被AutoIt識別!還有就是如何改寫Fiddler的Scripts。(我們的需求很簡單,別被嚇到了)
所以第一步:我們編寫清除fiddler session的腳本,轉換成C_interface.exe。腳本簡單到不能簡單了,如下:
Example() Func Example() Local $hWnd = WinWait("[Title:Telerik Fiddler Web Debugger]", "", 10) WinActivate($hWnd);激活當前窗口 ControlFocus($hWnd, "","[CLASS:WindowsForms10.EDIT.app.0.141b42a_r6_ad1;NAME:txtExec]") ControlSetText($hWnd, "", "[CLASS:WindowsForms10.EDIT.app.0.141b42a_r6_ad1;NAME:txtExec]","cls") Send("{ENTER}") EndFunc ;
照顧一下,剛開始看AutoIt的同學,Title中的Telerik Fiddler Web Debugger與ControlFocus中的CLASS、NAME是通過AutoIt Window info這個工具捕捉的,我們的可能不一樣貼圖一張:
我們寫完了了清除session后,再來寫下保存接口信息的腳本,也很簡單保存為D_interface.exe:
Example()
Func Example()
Local $parment=$CmdLine[1];接受控制台數據,$parment為fiddler接口保存路徑
Local $hWnd = WinWait("[Title:Telerik Fiddler Web Debugger]", "", 10)
WinActivate($hWnd);激活當前窗口
ControlFocus($hWnd, "","[CLASS:WindowsForms10.EDIT.app.0.141b42a_r6_ad1;NAME:txtExec]")
ControlSetText($hWnd, "", "[CLASS:WindowsForms10.EDIT.app.0.141b42a_r6_ad1;NAME:txtExec]","dump "&$parment)
Send("{ENTER}")
IF WinActive("[Title:Cannot Save SAZ]") Then
ControlClick("[Title:Cannot Save SAZ]","","Button1")
EndIf
EndFunc ;
標紅部分的解釋是:當Fiddler沒有session時(雖然不太可能出現這種情況),執行dump命令會彈出個對話框,這時候要關閉對話框!!如果不關閉的話下面對fiddler的操作會出現問題,因為這時候彈出框是fiddler的頂級窗口,可能導致腳本中使用Enter鍵無效...
其次,由於python調用控制台啟動Fiddler有問題(具體問題原因未知),所以我們也用AutoIt編寫,並轉換成S_interface.exe:
Example() Func Example() Local $parment=$CmdLine[1] Run($parment) EndFunc ;
最后,我們改下Fiddler的Script的,從菜單的Rules->Customer Rules打開腳本剪輯器,直接拉到script的末端修改方法OnExecAction如下:
...... case "dump": UI.actSelectAll(); var bpMethod = sParams[1] //UI.actSaveSessionsToZip(CONFIG.GetPath("Captures") + "dump.saz"); UI.actSaveSessionsToZip(bpMethod) FiddlerObject.StatusText = "Dumped all sessions to " +bpMethod; //FiddlerObject.alert(bpMethod); UI.actRemoveAllSessions(); return true;
修改case 'dump'的情況,bpMethod是由命令bump空格后的參數。對應於上文我們AutoIt腳本中的$parment參數(由控制台輸入)。
上面我們的准備工作的做的差不多了,總結一下,干了下面的幾個事情:
1.用AutoIt生成了清除Fiddler session的一個exe
2.用AutoIt生成了保存Fiddler session的一個exe
3.修改了Fiddler的Script接受一個保存session路徑的一個參數
在完成了以上工作后,我們來進行測試!注意:在此之前我們要明白一些事情:
1.用Fiddler做代理后,可能影響接口的加載速度,畢竟有個第三者。但是我覺得速度影響在web自動化上不是那么重要的事情,畢竟現實中的訪問速度肯定比你公司內部訪問速度更差。(缺點之一)
2.用Fiddler做代理后,我們知道在訪問https的時候比如訪問百度,可能顯示非安全鏈接,我們平常的做法是把fiddler的證書導入瀏覽器(具體百度上有說明),但是我們webdriver啟動的是個空白的瀏覽器,如何能自動加載Fiddler證書?
3.用Fiddler做代理后,如果Fiddler崩潰或者沒啟動起來造成無法聯網導致所有腳本無法運行,這個風險我們如何規避?
第一個問題跳過,我們看看第二個問題:
在路徑C:\Python27\Lib\site-packages\selenium\webdriver\firefox\firefox_profile.py下定義了一個FirefoxProfile類,這個類我們平時可能不太用的上,但是用不上不代表他不重要,這個類是個管理瀏覽器插件的類。我們說明一下:
1.其構造函數傳火狐瀏覽器的插件路徑。火狐瀏覽器的插件一般在C:\Users\***\AppData\Roaming\Mozilla\Firefox\Profiles\****.default-*****"這個路徑下面。構造函數會把這個路徑下的東西copy到c:\\users\\pf-211x3\\appdata\\local\\temp\\***\\webdriver-py-profilecopy這個文件夾下。
2.encoded函數。這個函數的文檔屬性這樣解釋:"A zipped, base64 encoded string of profile directory for use with remote WebDriver JSON wire protocol"具體很么意思呢?就是這個函數會把上文中我們提到的c:\\users\\pf-211x3\\appdata\\local\\temp\\***\\webdriver-py-profilecopy這個文件夾壓縮成ZIP格式文件,然后對這個文件進行base64的編碼,當啟動瀏覽器的時候,會將這個編碼一同發給服務器,服務器再對他base64解碼、解壓縮將您本地火狐插件完完整整的復制到新啟動的空白瀏覽器上,那么我們新啟動的瀏覽器就擁有了本地瀏覽器所有的插件了。
3.set_preference。傳遞一個鍵值對,就是設置火狐瀏覽器的選項,比如設置代理等等....
4.add_extension。傳遞一個***.xpi的路徑,就是設置瀏覽器加載的插件,比如啟動瀏覽器加載firebug,把firebug插件路徑傳遞給add_extension即可
經過我對FirefoxProfile類的說明,您大概知道了問題二的解決辦法了吧,對的就是向FirefoxProfile類中傳遞插件的路徑。但是C:\Users\***\AppData\Roaming\Mozilla\Firefox\Profiles\****.default-*****"這個文件是比較大的反正我的是50M,將這樣一個大的文件經過步驟2的操作,是個費事費力的事情。所以你們會發現,如果把完整的插件路徑傳遞給FirefoxProfile,經過一系列的壓縮、傳遞,啟動本地瀏覽器會非常非常慢!經過排除和嘗試法,我發現火狐對證書的控制是由插件文件夾下的cert8.db控制的,所有我們把這個文件給拷貝出來放在一個文件夾中,單獨傳這個文件夾路徑即可。
第三個問題:
瀏覽器的代理有幾下幾種:1.不使用代理。2.自動檢測此網絡的代理設置。3.使用系統代理。4.手動配置代理。5,自動代理配置
對於1和4大家都明白;對於5也還好就是:寫一個腳本告訴瀏覽器什么樣的域名我要代理,其他的不使用代理(具體百度);對於2和3我多少有點不知道他怎么用,對於3使用系統代理我的實踐就是如果我啟動了fiddler它就使用了fiddler代理,如果沒有啟動就不使用代理,看起來挺智能了。我也不太清楚這樣為什么...所以對於問題三我也是糾結的:第一、如果設置手動代理看起來是沒問題的,就怕fiddler故障了,然后雪崩...第二、我着實不太了解我使用系統代理對不對?這個大家自己看好了。我反正就用系統代理了,至少能滿足我的想法:萬一fiddler故障了也沒啥,大不了就抓不到接口數據唄,其他的還能正常的跑....
最后,就是在我們上篇繼承unnitest的run方法里面,修改一點點代碼,也很簡單(紅色標識了):
......
def run(self, result=None): orig_result = result if result is None: result = self.defaultTestResult() startTestRun = getattr(result, 'startTestRun', None) if startTestRun is not None: startTestRun() self._resultForDoCleanups = result screenshot_path=getattr(result,"screenshot_path",False)
dir_name = os.path.dirname(__file__) # 當前腳本根目錄
#因為fiddler保存盡量要使用絕對路徑,如果使用相對路徑會保存到安裝目錄下,這是我們不希望的
sessiong_path = dir_name + "/" + "Error_session"#默認session保存路徑 if not screenshot_path: screenshot_path=self.__screenshot_path else: if os.path.dirname(screenshot_path):#如果是絕對路徑 sessiong_path=os.path.dirname(screenshot_path)+"/Error_session"#拿到運行test的根目錄+FiddlerSessions result.startTest(self) testMethod = getattr(self, self._testMethodName) if (getattr(self.__class__, "__unittest_skip__", False) or getattr(testMethod, "__unittest_skip__", False)): # If the class or method was skipped. try: skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') or getattr(testMethod, '__unittest_skip_why__', '')) self._addSkip(result, skip_why) finally: result.stopTest(self) return try: success = False try: self.setUp() except SkipTest as e: self._addSkip(result, str(e)) except KeyboardInterrupt: raise except: result.addError(self, sys.exc_info())#啟動setUp失敗直接判斷出錯 else: try: testMethod() except KeyboardInterrupt: raise except (self.failureException,exceptions.WebDriverException):#如果是斷言錯誤或WebDriverException,類型為fail,且增加截圖 #增加截圖 browser=self.getbrowser()#嘗試拿瀏覽器實例 if browser: filename=self.__class__.__name__+"_"+self._testMethodName+".png"#格式:類名+方面名稱 browser.get_screenshot_as_file(screenshot_path+"\\"+filename) reback_filename=filename else: reback_filename=None #保存sessions數據 sessionfile_name=self.__class__.__name__+"_"+self._testMethodName+"_err.saz"#注意格式是saz os.popen(dir_name+"/"+"Tools/D_interface.exe "+sessiong_path+"\\"+sessionfile_name)#控制台運行D_interface.exe由AutoIt生成,保存出錯的session result.addFailure(self, sys.exc_info(),reback_filename)#回傳截圖名稱給report,以便能顯示在報告中 except SkipTest as e:#如果為跳過的異常,類型為Skip異常
......
最后我的demo文檔結構大概是這樣的:
其中Error_session是保存錯誤的session;FireFox_profile是我們說到的火狐證書插件;Tools是我們轉換的3個簡單的exe程序;screen_shot存放錯誤截圖;IqunxingTest.py是我們改寫的unnitest類,我們新建測試demo腳本:
#coding=utf-8 import IqunxingTest import HTMLTestRunner import sys,os import unittest from selenium import webdriver from selenium.webdriver.firefox import firefox_profile import time dir_name = os.path.dirname(__file__) # 拿到根目錄 class Mydemo(IqunxingTest.IqunxingTest): u'''測試CSDN登錄''' @classmethod def setUpClass(cls): profile=firefox_profile.FirefoxProfile(profile_directory=dir_name+"/FireFox_profile") profile.set_preference("network.proxy.type", 5)#將瀏覽器代理設置為系統代理 cls.browser=webdriver.Firefox(firefox_profile=profile)#啟動帶插件的瀏覽器 cls.browser.implicitly_wait(10) @unittest.Myskip def test1(self): u'''打開csdn''' browser=self.browser browser.get("http://www.csdn.net/") @unittest.Myskip def test2(self): u'''csdn登錄''' os.popen(dir_name+"/"+"Tools/C_interface.exe")#在test開始前,清空Fiddler session信息 browser = self.browser browser.find_element_by_link_text(u"登錄").click() time.sleep(1) browser.find_element_by_id("username").send_keys("test") time.sleep(1) browser.find_element_by_id("password").send_keys("test") time.sleep(1) browser.find_element_by_class_name("logging").click()#點擊登錄 if not browser.find_element_by_class_name("phr_first").is_displayed():#如果沒有登錄成功是找不到這個控件會報錯 self.assertTrue(False,"login failed") if __name__ == '__main__': fiddler_path = "C:\Program Files (x86)\Fiddler2\Fiddler.exe"#您的Fiddler路徑 s = os.popen(dir_name + "/Tools/S_interface.exe " + "\"" + fiddler_path + "\"")#啟動Fidder module_name=os.path.basename(sys.argv[0]).split(".")[0] module=__import__(module_name) runner=HTMLTestRunner.HTMLTestRunner("reprot.html") all_suite=unittest.defaultTestLoader.loadTestsFromModule(module) runner.run(all_suite) os.popen('''taskkill /f /im "Fiddler.exe"''')#測試完成后關閉fiddler
因為我們要找一些有用的sesssion信息,可喜的是Fiddler能過濾一些你設置完的信息,我的過濾信息如下:
因為測試CSDN,所以我只展示域名為*.csdn.net的會話;另外,一些css,js,png等無用信息我也隱藏了(正則表達式隱藏)。好了萬事具備,我們運行下這個demo:最后在Error_session下保存了我們test2操作的所有http信息文件名為:Mydemo_test2_err.saz(過濾的除外),同時在screen_shot下保存錯誤的截圖。我們直接用fiddler打開這個saz文件:
從上面的截圖可以看出來,我們保存的session是完整的(過濾的除外)。而且我們看到了我們點擊登錄時,使用的接口以及傳遞的相關信息。當然,我們點擊Fiddler其他標簽事能獲取一切我們想獲取的信息。
這一節我的思路說完了...當然,你可能用不上這些,但是你至少了解到了AutoIt以及selenium的一些知識!還是那句:如果認為我說的有些道理,我的辛苦是值得的(畢竟寫了一天);如果認為沒用,請一笑而過~~
下個話題:selenium相關應用!!