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); |
@H_403_0@另一种选择是使用 Selenium 中的
waitForCondition
函数,一个 JavaScript 代码片段将被作为参数传递给该函数。一旦 Selenium 检测到条件返回为真,它将停止等待。您可以等待一些元素或文本出现或者不出现。JavaScript 可以运行在由 Selenium.browser.getCurrentWindow()
函数弹出的应用程序窗口中。清单 2 是检查窗口状态的样例代码。它只工作在 Firefox 中。
清单 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); |
图 1. Dojo 组合框
@H_403_0@使用 Selenium IDE 来记录图 1 中提供的组合框上选中的操作。单击向下箭头,会出现一个下拉列表。选中第三项 Stack(SWG)。记录的脚本提供在图 2 中。
图 2. Selenium IDE 记录的脚本
@H_403_0@有时,只会由 IDE 生成第二行脚本。在这种情况下,添加单击箭头按钮的操作。对于上面的脚本,如果第一行被重新播放,那么它应该生成下拉列表。但是它不执行任何操作。对于多个 Dojo 小部件,单击并不真正执行单击操作。将
click(locator)
更改为 clickAt(locator,coordString)
或者 MouseDown(locator)
和 MouseUp(locator)。
@H_403_0@对于下拉列表,等待时间应该相加。像图 2 中展示的脚本一样,选中项的单击操作将会刚好在单击向下箭头按钮之后执行。它可能会因为下拉列表没有出现而失败。简单地添加一个 pause
命令,或者使用 waitFor
命令等待菜单项元素出现,并继续执行下一个命令。
@H_403_0@修改后的将会自动化 Dojo 组合框上的选择的脚本展示在图 3 中。
图 3. 修改后的在 Dojo 组合框中进行选择的 IDE 脚本
@H_403_0@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. 日期选择器
@H_403_0@对于图 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']"); |
@H_403_0@如本例所示,Dojo 应用程序不能通过简单的 IDE 记录进行测试。这些脚本有可能不能通过测试。脚本中有一些丢失的操作,或者操作并不真正工作。脚本应该调整成能够在 IDE 和 RC 中顺利地执行。对于复杂的 Dojo 小部件,一种可能的解决方案是使用
runScript(String)
函数,因为 Selenium 对 JavaScript 提供很好的支持。清单 5 提供一个 JavaScript 语句来模拟组合框选择。
清单 5. 运行 JavaScript 语句在组合框上进行选择
selenium.runScript("dijit.byId(\"offeringType\").setValue(\"Stack(SWG)");"); |
classpath
是 TestNG.jar 文件的文件路径。
清单 6. TestNG Ant 任务
<taskdef resource="testngtasks" classpath="testng.jar"/> |
@H_403_0@主要的目标是启动服务器、运行测试,然后停止服务器。这些任务按照 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> |
@H_403_0@代码更可取的地方是使用
waitfor
任务来测试 Selenium 服务器是否已成功启动,而不是暂停一段固定的时间。如果 URL http://localhost:4444/selenium-server/driver/?cmd=testComplete
可用,就意味着 Selenium 已经成功启动。在清单 7 中,它最多等待两分钟,并且每 100 毫秒在本地主机上检查一次 Selenium 服务器,以提供完整的 URL。
@H_403_0@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> |
@H_403_0@
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> </target> |
@H_403_0@
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" /> </target> |
@H_403_0@上面列出了关键任务。将它们组合到您的构建文件,以便利用 Ant 完成良好集成的测试。
*iehta
运行模式,对于 Selenium-RC 1.0 beta 2 或更晚的版本,使用 *iexplore
运行模式。
@H_403_0@如果测试 HTTPS 网站时出现一个如下所示的安全警告,那么单击 View Certificate 并安装 HTTPS 网站的证书。如果继续弹出警告,那么考虑在 IE 中进行配置。打开 Tool > Internet Options > Advanced,并取消选择 security 分类下的 Warn about invalid site certificates 和 Check for publisher's certificate revocation。
图 5. 测试 HTTPS 网站时的安全警告
@H_403_0@创建新的 Firefox 配置文件 @H_403_0@对于 Firefox,遵循以下步骤创建定制的配置文件,然后重启服务器:
- 关闭任何正在运行的 Firefox 实例。
- 利用配置文件管理器
firefox -ProfileManager
启动 Firefox。 - 创建一个新的配置文件。出现提示时,为配置文件选择一个目录。将它放在项目文件夹里面。
- 选择配置文件并运行 Firefox。
- 利用您将用于测试的自签名证书导航到 HTTPS URL。出现提示时接受证书。这将在配置文件中创建一个异常。
- 关闭浏览器。
- 转到 Firefox 配置文件目录。
- 删除该目录中除
cert_override.txt
和cert8.db
文件之外的任何东西。
-firefoxProfileTemplate /path/to/profile/dir
启动服务器时,Selenium 将使用一个部分配置文件(带有证书异常)作为创建新配置文件的基础。这将提供证书异常,而避免了使用整个配置文件带来额外的混乱。注意一下在 Selenium RC 1.0 Beta 2 或更晚版本中以 *firefox
模式,以及在 Selenium RC 1.0 Beta 2 之前的版本中以 *chrome
模式启动 Firefox。
@H_403_0@对于运行模式,*chrome
或 *iehta
是较早版本 Selenium RC 中支持 HTTPS 和安全弹出处理的实验模式。自 Selenium-RC 1.0 beta 2 起,它们已经稳定成 *firefox
和 *iexplore
运行模式。请谨慎地根据所使用的 Selenium 版本而使用运行模式。
图 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"> <tbody> <tr> <td align="center" style="vertical-align: top;"> <div> <img alt="supported" src="supported.png"/> </div> </td> <td align="center" style="vertical-align: top;"> <div> <a href="test?name=test1">edit</a> </div> </td> ……. |
@H_403_0@Xpath 是一种找到不带特定 ID 或名称的元素的简单方式。
- 如果知道 ID 或名称之外的一个属性,那么直接使用
@attribute=value
定位元素。 - 如果只知道属性值的一些特定部分,那么使用
contains(attribute,value)
定位元素。 - 如果元素没有指定的属性,那么利用 Firebug 搜索最近的具有指定属性的父元素,然后使用 Xpath 从这个元素开始定位想要找到的那个元素。
表 1. 定位元素的 Xpath 表达式
定位元素 | Xpath 表达式 |
n 行的第一列 | //table[@id='test_table']//tr[n]/td |
n 行的图像 | //table[@id='test_table']//tr[n]//img |
‘Test 1’ 的编辑链接 | //a[contains(@href,test1)] |
@H_403_0@表 1 展示了定位元素的 Xpath 表达式。在 Firebug 的帮助下,Xpath 可以定位元素和复制的元素。在元素没有 ID 和名称时,Selenium IDE 将会采用 Xpath。尽管 Xpath 利用已经录的脚本,有助于保持一致性,但是它高度依赖于 web 页面的结构。这使得测试用例可读性差,增加了维护难度。此外,在 Internet Explorer 7 和 Internet Explorer 8 中运行具有多个复杂 Xpath 表达式的测试用例可能会太慢了。在这种情况下,将 Xpath 更换为 DOM,后者是另一种高效的定位策略。 @H_403_0@DOM 是 Document Object Model(文档对象模型)的缩写。Selenium 允许您利用 JavaScript 遍历 HTML DOM。Java 的灵活性允许在 DOM 表达式中有多个语句,用分号隔开,以及在语句中定义函数。
表 2. 定位元素的 DOM 表达式
定位元素 | DOM 表达式 | |
n 行的第一列 | dom=document.getElementById('test_table').rows[n-1].cells[0] |
|
n 行的图像 | dom=element=document.getElementById('test_table').rows[n-1].cells[1]; element.getElementsByTagName('IMG')[0] |
|
‘Test 1’ 的编辑链接 |
|
@H_403_0@表 2 展示了定位元素的 DOM 表达式。DOM 定位器在 Firefox 和 Internet Explorer 中也有很好的性能。组织 DOM 表达式需要一些 JavaScript 知识。有时,DOM 表达式对于复杂的元素来说太长了,难以看懂(参见表 2 中提到的 Test 1 的编辑链接的表达式)。 @H_403_0@CSS 定位器用于利用 CSS 选择器选择元素。当 HTML 代码具有良好的样式时,可以高效地利用 CSS 定位器。样例表达式展示在表 3 中。
表 3. 定位元素的 CSS 表达式
定位元素 | CSS 表达式 | |
n 行的第一列 | css=#test_table .test_class:nth-child(n) |
|
n 行的图像 |
|
|
‘Test 1’ 的编辑链接 | css=a[href*='test2'] |
@H_403_0@一般来说,选用熟悉的定位器表达式,并在脚本结构中保持一致。如果有多种表达式可执行,那么使用最高效的方式在 web 页面中定位元素。
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); |
@H_403_0@
windowname
是调用 window.open
函数的第二个参数。上面提到的 popupwindowIdentifier
是一个窗口标识符,可以是窗口 ID、窗口名称、title=the title of the window
或 var=javascript variable
。如果弹出窗口的属性未知,但是真的定义了,那么使用 getAllWindowIds()
、getAllWindowNames()
或 getAttributeFromAllWindows()
函数来检索弹出窗口的属性。
@H_403_0@在最新版的 Selenium RC 1.0.1 中,Selenium 添加了像 selectPopUp(String)
和 deselectPopUp()
这样的方法,它们的功能在以前版本中由 selectWindow(String)
提供。
清单 13. 处理弹出窗口的弹出函数
//wait for the popup window with timeout; selenium.waitForPopUp(“”,timeout); //same as selenium.selectWindow selenium.selectPopUp(“”); //perform action on popup window and close the window; .... //same as selenium.selectWindow(null); selenium.deselectPopUp(); |
@H_403_0@清单 13 展示了处理弹出窗口最简单的方式。您可以保留
waitForPopUp
和 selectPopUp
函数中的第一个参数为空。如果同时弹出多个窗口,请指定窗口属性。
图 7. 安全信息窗口
@H_403_0@跳过图 7 中安全信息窗口的解决方案是打开 Tools > Internet Options > Custom Level。然后启用 Display mixed content。 @H_403_0@配置 Internet Explorer 跳过非主要窗口会减少或消除运行测试用例时不必要的处理。但是如果配置了 Firefox,那么将它保存为新的配置文件,并利用定制的配置文件启动服务器。在关于测试 HTTPS 网站的一节中提到了这样做的原因。 @H_403_0@对于上载/下载窗口,最好是处理而不是跳过它们。为了避免 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 EndIf EndFunc |
@H_403_0@在 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(); } } |
@H_403_0@清单 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,$saveAsFileName) Sleep(1000) if FileExists ($SaveAsFileName) Then FileDelete($SaveAsFileName) EndIf ControlClick($save_title,"") Return TestFileExists($SaveAsFileName) Else Return False EndIf EndFunc |
@H_403_0@AutoIt 脚本很容易编写,但是依赖于浏览器类型和版本,因为不同的浏览器和版本中,窗口标题和窗口控件类是不相同的。
window.alert()
生成的警告对话框,使用 selenium.getAlert()
来检索前一操作期间生成的 JavaScript 警告的消息。如果没有警告,该函数将会失败。得到一个警告与手动单击 OK 的结果相同。
@H_403_0@对于由 window.confirmation()
生成的确认对话框,使用 selenium.getConfirmation()
来检索前一操作期间生成的 JavaScript 确认对话框的消息。默认情况下,该函数会返回 true,与手动单击 OK 的结果相同。这可以通过优先执行 chooseCancelOnNextConfirmation
命令来改变。
@H_403_0@对于由 window.prompt()
生成的提示对话框,使用 selenium.getPromt()
来检索前一操作期间生成的 JavaScript 问题提示对话框的消息。提示的成功处理需要优先执行 answerOnNextPrompt
命令。
@H_403_0@JavaScript 警告在 Selenium 中不会弹出为可见的对话框。处理这些弹出对话框失败会导致异常,指出没有未预料到的警告。这会让测试用例失败。