簡介
利用 selenium 測試 ajax 應用程式
asynchronous javascript and xml (ajax) 是一種用于建立互動式 web 應用程式的 web 開發技術。ajax 應用程式的一個特征是,不會導緻一次重新加載整個頁面。相反,浏覽器将具有一個對伺服器的異步調用以獲得資料,并且隻重新整理目前頁面的特定部分。要提高 web 頁面的互動性、響應速度和可用性,測試 ajax 應用程式的過程需要一些改變。
我們首先重新整理 web 頁面,然後就是等待,直到異步調用完成。完成之後,可以繼續進行驗證。此時,出現适當等待時間的問題。
selenium 提供了更為高效的處理等待的方式。一種可能做法是,使用類 com.thoughtworks.selenium.wait 來等待一個元素或文本在頁面上出現或消失。可以在 until() 函數中定義等待的退出條件,或者擴充 wait 類來實作等待退出。清單 1 是使用 wait 類的樣例代碼。它将在條件滿足時停止等待,或者在超出最大等待時間時傳回一個逾時異常。
清單 1. 等待元素或文本出現
wait wait = new wait() {
public boolean until() {
return selenium.iselementpresent(locator);
// or selenium.istextpresent(pattern);
}
};
wait.wait("", timeoutinmilliseconds);
清單 2. 等待視窗就緒的狀态
string script = "var my_window = selenium.browserbot.getcurrentwindow();"
script += "var bool;";
script += "var readystate = (my_window.document.readystate);";
script += "if (readystate == 'complete'){";
script += "bool = 'true';";
script += "}";
script += "bool;";
selenium.waitforcondition(script, timeoutinmilliseconds);
如何支援 dojo 應用程式
dojo 是一個常用的 javascript 工具包,用于構造動态 web 界面。使用 selenium 測試 dojo 應用程式時的一個關鍵點是認識 dojo 小部件和記錄它們的操作。作者定義的 dojo 小部件處于抽象級别。頁面運作時,會将 dojo 小部件轉換成基本的 html 代碼。存在很多由 dojo 自動生成的 html 代碼,是以,dojo 小部件的認識可能與傳統 html 小部件有些不同。
dojo 小部件上執行的操作(包括文本字段、按鈕複選框和單選按鈕)可能與 html 小部件相同。但是,dojo 在組合框上提供的日期選擇器和其他額外的小部件可能需要特定的處理。

圖 1. dojo 組合框
使用 selenium ide 來記錄圖 1 中提供的組合框上選中的操作。單擊向下箭頭,會出現一個下拉清單。選中第三項 stack(swg)。記錄的腳本提供在圖 2 中。
圖 2. selenium ide 記錄的腳本
有時,隻會由 ide 生成第二行腳本。在這種情況下,添加單擊箭頭按鈕的操作。對于上面的腳本,如果第一行被重新播放,那麼它應該生成下拉清單。但是它不執行任何操作。對于多個 dojo 小部件,單擊并不真正執行單擊操作。将 click(locator) 更改為 clickat(locator, coordstring) 或者 mousedown(locator) 和 mouseup(locator)。
對于下拉清單,等待時間應該相加。像圖 2 中展示的腳本一樣,選中項的單擊操作将會剛好在單擊向下箭頭按鈕之後執行。它可能會因為下拉清單沒有出現而失敗。簡單地添加一個 pause 指令,或者使用 waitfor 指令等待菜單項元素出現,并繼續執行下一個指令。
修改後的将會自動化 dojo 組合框上的選擇的腳本展示在圖 3 中。
圖 3. 修改後的在 dojo 組合框中進行選擇的 ide 腳本
rc 代碼展示在清單 3 中。
清單 3. 自動化 dojo 組合框中選擇操作的 rc 代碼
selenium.clickat("//div[@id='widget_offeringtype']/div/div",””);
selenium.waitforcondition("selenium.iselementpresent(\"offeringtype_popup2\")", "2000");
selenium.clickat("offeringtype_popup2",””);
圖 4. 日期選擇器
對于圖 4 中的日期選擇器例子,執行的操作可能不會被 ide 記錄。編寫如下面清單 4 所示的 rc 代碼。
清單 4. 自動化選擇的 rc 代碼
//click on the date field by id you defined;
selenium.clickat("datebox","");
//wait for the drop down date box by id;
selenium.waitforcondition("selenium.iselementpresent(\"widget_datebox_dropdown\")", \
"2000");
//click previous year 2008;
selenium.clickat("//span[contains(@class,'dijitcalendarpreviousyear')]", "");
//click on the month increase;
//previous month would contains ‘dijitcalendarincrease’.
selenium.clickat("//img[contains(@class,'dijitcalendarincrease')]","");
//click on the date such as 28 of current month; if you do not specify
//the td with the attribute of current month class, it will click \
on the //first 28 of previous month;
selenium.click("//td[contains(@class,'dijitcalendarcurrentmonth')]/span[text()='28']");
如本例所示,dojo 應用程式不能通過簡單的 ide 記錄進行測試。這些腳本有可能不能通過測試。腳本中有一些丢失的操作,或者操作并不真正工作。腳本應該調整成能夠在 ide 和 rc 中順利地執行。對于複雜的 dojo 小部件,一種可能的解決方案是使用 runscript(string) 函數,因為 selenium 對 javascript 提供很好的支援。清單 5 提供一個 javascript 語句來模拟組合框選擇。
清單 5. 運作 javascript 語句在組合框上進行選擇
selenium.runscript("dijit.byid(\"offeringtype\").setvalue(\"stack(swg)");");
如何利用 ant 建構 selenium 測試
諸如 ant 這樣的內建工具可以友善地建構 selenium 測試和順暢地運作測試用例,無需單獨啟動 selenium 伺服器。如果 selenium 測試由 testng 驅動,那麼定義清單 6 所示 testng ant 任務。清單 6 中假設 classpath 是 testng.jar 檔案的檔案路徑。
清單 6. testng ant 任務
<taskdef resource="testngtasks" classpath="testng.jar"/>
主要的目标是啟動伺服器、運作測試,然後停止伺服器。這些任務按照 bulid.xml 中定義的順序實作在清單 7 中。
清單 7. 啟動伺服器、運作測試用例并停止伺服器的 ant 任務
<target name="run_test" description="start,run and stop" depends="dist">
<parallel>
<antcall target="start-server" />
<sequential>
<echo taskname="waitfor" message="waitforproxy server launch" />
<waitfor maxwait="2" maxwaitunit="minute" checkevery="100">
<http url="http://localhost:4444/selenium-server/driver/?cmd=testcomplete" />
</waitfor>
<antcall target="runtestng" />
<antcall target="stop-server" />
</sequential>
</parallel>
</target>
代碼更可取的地方是使用 waitfor 任務來測試 selenium 伺服器是否已成功啟動,而不是暫停一段固定的時間。如果 url http://localhost:4444/selenium-server/driver/?cmd=testcomplete 可用,就意味着 selenium 已經成功啟動。在清單 7 中,它最多等待兩分鐘,并且每 100 毫秒在本地主機上檢查一次 selenium 伺服器,以提供完整的 url。
start-server 任務的詳細内容定義在清單 8 中。firefox profile 模闆位置和其他參數可以指定在标記 <arg> 中。
清單 8. 詳細的啟動伺服器的 ant 任務
<target name="start-server">
<java jar="lib/selenium-server.jar" fork="true">
<arg line="-firefoxprofiletemplate ${selenium}/profile/" />
</java>
</target>
runtestng 任務的詳細内容定義在清單 9 中。testng 任務的常用屬性包括 outputdir 和 xmlfileset。屬性 outputdir 用于設定輸出報告位置。屬性 xmlfileset 用于包含啟動 xml 檔案。更多選項請參考 testng 正式網站。
清單 9. 運作測試用例的 ant 任務
<target name="runtestng">
<testng outputdir="${testng.report.dir}" sourcedir="${build}"
classpathref="run.cp" haltonfailure="true">
<xmlfileset dir="${build}" includes="testng.xml" />
</testng>
stop-server 任務的詳細内容定義在清單 10 中。
清單 10. 停止 selenium 伺服器的 ant 任務
<target name="stop-server">
<get taskname="selenium-shutdown"
src="http://localhost:4444/selenium-server/driver/?cmd=shutdown" ignoreerrors="true" />
<echo taskname="selenium-shutdown" message=" errors during shutdown are expected" />
上面列出了關鍵任務。将它們組合到您的建構檔案,以便利用 ant 完成良好內建的測試。
如何支援測試 https 網站
随着網際網路日益強調資訊安全,越來越多的 web 應用程式在使用 ssl 身份認證。selenium ide 預設支援 https,但是 selenium rc 不是這樣的。internet explorer 和 firefox 中的解決方案各不相同。
對于 ie,在 setup 目錄下的 ssl 支援檔案夾中在安裝一個證書。如果使用的版本早于 selenium-rc 1.0 beta 2,請使用 *iehta 運作模式,對于 selenium-rc 1.0 beta 2 或更晚的版本,使用 *iexplore 運作模式。
如果測試 https 網站時出現一個如下所示的安全警告,那麼單擊 view certificate 并安裝 https 網站的證書。如果繼續彈出警告,那麼考慮在 ie 中進行配置。打開 tool > internet options > advanced,并取消選擇 security 分類下的 warn about invalid site certificates 和 check for publisher's certificate revocation。
圖 5. 測試 https 網站時的安全警告
建立新的 firefox 配置檔案
對于 firefox,遵循以下步驟建立定制的配置檔案,然後重新開機伺服器:
關閉任何正在運作的 firefox 執行個體。
利用配置檔案管理器 firefox -profilemanager 啟動 firefox。
建立一個新的配置檔案。出現提示時,為配置檔案選擇一個目錄。将它放在項目檔案夾裡面。
選擇配置檔案并運作 firefox。
利用您将用于測試的自簽名證書導航到 https url。 出現提示時接受證書。這将在配置檔案中建立一個異常。
關閉浏覽器。
轉到 firefox 配置檔案目錄。
删除該目錄中除 cert_override.txt 和 cert8.db 檔案之外的任何東西。
預設情況下,selenium 将在啟動 firefox 的執行個體時建立一個新的配置檔案。當您利用參數 -firefoxprofiletemplate /path/to/profile/dir 啟動伺服器時,selenium 将使用一個部配置設定置檔案(帶有證書異常)作為建立新配置檔案的基礎。這将提供證書異常,而避免了使用整個配置檔案帶來額外的混亂。注意一下在 selenium rc 1.0 beta 2 或更晚版本中以 *firefox 模式,以及在 selenium rc 1.0 beta 2 之前的版本中以 *chrome 模式啟動 firefox。
對于運作模式,*chrome 或 *iehta 是較早版本 selenium rc 中支援 https 和安全彈出處理的實驗模式。自 selenium-rc 1.0 beta 2 起,它們已經穩定成 *firefox 和 *iexplore 運作模式。請謹慎地根據所使用的 selenium 版本而使用運作模式。
如何高效地認識不帶 id 屬性的 web 元素
使用一個有含義的 id 或名稱是一種高效且友善的定位元素的方式。它也可以改善測試用例的可讀性。但是為了每個元素具有一個有含義的、惟一的 id(尤其是動态元素),selenium 提供多種政策來認識元素,比如說 xpath、dom 和 css。
下面是一個樣例,使用三種政策來定位圖 6 中提供的動态表格中的一個元素。html 代碼在清單 11 中。
圖 6. 動态表格樣例
清單 11. 第一個表格列的 html 代碼
<table id="test_table" border="1">
<tbody>
<tr>
<td align="left">
<div class="test_class">test 1</div>
</td>
<td align="center" style="vertical-align: top;">
<table id="autogenbookmark_4">
<div>
<img alt="supported" src="supported.png"/>
</div>
<a href="test?name=test1">edit</a>
…….
xpath 是一種找到不帶特定 id 或名稱的元素的簡單方式。
如果知道 id 或名稱之外的一個屬性,那麼直接使用 @attribute=value 定位元素。
如果隻知道屬性值的一些特定部分,那麼使用 contains(attribute, value) 定位元素。
如果元素沒有指定的屬性,那麼利用 firebug 搜尋最近的具有指定屬性的父元素,然後使用 xpath 從這個元素開始定位想要找到的那個元素。
表 1. 定位元素的 xpath 表達式
表 1 展示了定位元素的 xpath 表達式。在 firebug 的幫助下,xpath 可以定位元素和複制的元素。在元素沒有 id 和名稱時,selenium ide 将會采用 xpath。盡管 xpath 利用已經錄的腳本,有助于保持一緻性,但是它高度依賴于 web 頁面的結構。這使得測試用例可讀性差,增加了維護難度。此外,在 internet explorer 7 和 internet explorer 8 中運作具有多個複雜 xpath 表達式的測試用例可能會太慢了。在這種情況下,将 xpath 更換為 dom,後者是另一種高效的定位政策。
dom 是 document object model(文檔對象模型)的縮寫。selenium 允許您利用 javascript 周遊 html dom。java 的靈活性允許在 dom 表達式中有多個語句,用分号隔開,以及在語句中定義函數。
表 2. 定位元素的 dom 表達式
表 2 展示了定位元素的 dom 表達式。dom 定位器在 firefox 和 internet explorer 中也有很好的性能。組織 dom 表達式需要一些 javascript 知識。有時,dom 表達式對于複雜的元素來說太長了,難以看懂(參見表 2 中提到的 test 1 的編輯連結的表達式)。
css 定位器用于利用 css 選擇器選擇元素。當 html 代碼具有良好的樣式時,可以高效地利用 css 定位器。樣例表達式展示在表 3 中。
表 3. 定位元素的 css 表達式
一般來說,選用熟悉的定位器表達式,并在腳本結構中保持一緻。如果有多種表達式可執行,那麼使用最高效的方式在 web 頁面中定位元素。
如何處理彈出視窗
一般來說,操作都是在由 selenium 啟動的主視窗中執行。如果您想在一個由 window.open 函數生成的新視窗中執行操作,那麼将焦點更換到新視窗。在彈出視窗中執行操作之後,焦點傳回到主視窗。處理彈出視窗的過程定義在清單 12 中。
清單 12. 處理彈出視窗的樣例代碼
//wait for the popup window with timeout;
selenium.waitforpopup(windowname, timeout);
//select the pop up window
selenium.selectwindow(popupwindowidentifier);
//perform action on popup window and close the window;
....
//return to the main window use 'null'
selenium.selectwindow(null);
windowname 是調用 window.open 函數的第二個參數。上面提到的 popupwindowidentifier 是一個視窗辨別符,可以是視窗 id、視窗名稱、title=the title of the window 或 var=javascript variable。如果彈出視窗的屬性未知,但是真的定義了,那麼使用 getallwindowids()、getallwindownames() 或 getattributefromallwindows() 函數來檢索彈出視窗的屬性。
在最新版的 selenium rc 1.0.1 中,selenium 添加了像 selectpopup(string) 和 deselectpopup() 這樣的方法,它們的功能在以前版本中由 selectwindow(string) 提供。
清單 13. 處理彈出視窗的彈出函數
selenium.waitforpopup(“”, timeout);
//same as selenium.selectwindow
selenium.selectpopup(“”);
//same as selenium.selectwindow(null);
selenium.deselectpopup();
清單 13 展示了處理彈出視窗最簡單的方式。您可以保留 waitforpopup 和 selectpopup 函數中的第一個參數為空。如果同時彈出多個視窗,請指定視窗屬性。
如何處理上載/下載下傳檔案視窗
selenium 使用 javascript 來模拟操作。是以,它不支援諸如上載視窗、下載下傳視窗或身份認證視窗之類的浏覽器元素。對于非主要視窗,配置浏覽器跳過彈出視窗。
圖 7. 安全資訊視窗
跳過圖 7 中安全資訊視窗的解決方案是打開 tools > internet options > custom level。然後啟用 display mixed content。
配置 internet explorer 跳過非主要視窗會減少或消除運作測試用例時不必要的處理。但是如果配置了 firefox,那麼将它儲存為新的配置檔案,并利用定制的配置檔案啟動伺服器。在關于測試 https 網站的一節中提到了這樣做的原因。
對于上載/下載下傳視窗,最好是處理而不是跳過它們。為了避免 selenium 的局限性,一種建議是使用 java 機器人 autoit 來處理檔案上載和下載下傳問題。autoit 被設計來自動化 window gui 操作。它可以認識大多數 window gui,提供很多 api,并且很容易轉換為 .exe 檔案,這樣的檔案可以直接運作或者在 java 代碼中調用。清單 14 示範了處理檔案上載的腳本。這些腳本的步驟是:
根據浏覽器類型确定上載視窗标題。
激活上載視窗。
将檔案路徑放入編輯框中。
送出。
清單 14. 處理上載的 autoit 腳本
;first make sure the number of arguments passed into the scripts is more than 1
if $cmdline[0]<2 then exit endif
handleupload($cmdline[1],$cmdline[2])
;define a function to handle upload
func handleupload($browser, $uploadfile)
dim $title ;declare a variable
;specify the upload window title according to the browser
if $browser="ie" then ; stands for ie;
$title="select file"
else ; stands for firefox
$title="file upload"
endif
if winwait($title,"",4) then ;wait for window with
title attribute for 4 seconds;
winactivate($title) ;active the window;
controlsettext($title,"","edit1",$uploadfile) ;put the
file path into the textfield
controlclick($title,"","button2") ;click the ok
or save button
else
return false
endfunc
在 java 代碼中,定義一個函數來執行 autoit 編寫的 .exe 檔案,并在單擊 browse 之後調用該函數。
清單 15. 執行 autoit 編寫的 .exe 檔案
public void handleupload(string browser, string filepath) {
string execute_file = "d:\\scripts\\upload.exe";
string cmd = "\"" + execute_file + "\"" + " " + "\"" + browser + "\""
+ " " + "\"" + filepath + "\""; //with arguments
try {
process p = runtime.getruntime().exec(cmd);
p.waitfor(); //wait for the upload.exe to complete
} catch (exception e) {
e.printstacktrace();
清單 16 是處理 internet explorer 中下載下傳視窗的 autoit 腳本。internet explorer 和 firefox 中的下載下傳腳本各不相同。
清單 16. 處理 internet explorer 中下載下傳的 autoit 腳本
if $cmdline[0]<1 then exit endif
handledownload($cmdline[1])
func handledownload($saveasfilename)
dim $download_title="file download"
if winwait($download_title,"",4) then
winactivate($download_title)
sleep (1000)
controlclick($download_title,"","button2","")
dim $save_title="save as"
winwaitactive($save_title,"",4)
controlsettext($save_title,"","edit1", $saveasfilename)
sleep(1000)
if fileexists ($saveasfilename) then
filedelete($saveasfilename)
controlclick($save_title, "","button2","")
return testfileexists($saveasfilename)
autoit 腳本很容易編寫,但是依賴于浏覽器類型和版本,因為不同的浏覽器和版本中,視窗标題和視窗控件類是不相同的。
如何驗證警告/确認/提示資訊
對于由 window.alert() 生成的警告對話框,使用 selenium.getalert() 來檢索前一操作期間生成的 javascript 警告的消息。如果沒有警告,該函數将會失敗。得到一個警告與手動單擊 ok 的結果相同。
對于由 window.confirmation() 生成的确認對話框,使用 selenium.getconfirmation() 來檢索前一操作期間生成的 javascript 确認對話框的消息。預設情況下,該函數會傳回 true,與手動單擊 ok 的結果相同。這可以通過優先執行 choosecancelonnextconfirmation 指令來改變。
對于由 window.prompt() 生成的提示對話框,使用 selenium.getpromt() 來檢索前一操作期間生成的 javascript 問題提示對話框的消息。提示的成功處理需要優先執行 answeronnextprompt 指令。
javascript 警告在 selenium 中不會彈出為可見的對話框。處理這些彈出對話框失敗會導緻異常,指出沒有未預料到的警告。這會讓測試用例失敗。
最新内容請見作者的github頁:http://qaseven.github.io/