一、寫在前面的話
在使用WebDriver框架之前,我先后使用了其他兩款自動化測試框架,IBM Rational Robot(歷史悠久的老牌自動化工具)與TestComplete(功能強大,可支持Web、移動端和桌面程序自動化測試的付費工具),相比較于WebDrIver,它們對于自動化腳本開發者而言都不夠“開放”(可擴展)和“自由”(可封裝),或多或少都有一些局限性,當然這樣并不是說WebDriver就沒有局限性,對于GUI桌面程序界面WebDriver目前就束手無策,必須借助其他輔助工具,但這并不妨礙我對WebDriver的喜愛,因為對于Web端的自動化測試,使用WebDriver可以滿足我對頁面95%以上的覆蓋,并且在它的基礎上快速寫出一套符合自己項目的自動化測試框架。
二、準備工作
在開始自己項目的自動化測試之前,我們最好已經完成了下面的準備工作:
1、熟悉待測系統
對項目的待測系統整體功能和業務邏輯有比較清晰的認識。
2、編寫系統的自動化測試用例大綱
這一步主要是讓我們在編碼前,按優先級將系統可實施自動化測試的部分劃分出來。
3、選擇合適的工具和框架
對于WebDriver,我們可以選擇基于java或python,瀏覽器基于chrome或firefox,WebDriver版本、瀏覽器版本,用例管理選擇TestNG還是Junit等等都是考慮的因素,這里使用selenium-java 2.53.1/firefox 45/TestNG 6.8.8。
三、實現步驟
1>創建測試用例的父類BaseCase類:
每個測試用例類都繼承自BaseCase類,那么就可以將用例中的公共部分放到BaseCase類中去實現,從而簡化代碼結構和減少代碼冗余,比如:
a. 使用TestNG框架來管理用例,在BaseCase類中實現@BeforeSuite、@BeforeClass、@BeforeTest、@BeforeMethod及其對應的After方法等;
b. 一些常用的與用例相關的方法;
c. 公共變量等。
publicclassBaseCase{
publicstaticStringurl="";
publicstaticStringusername="";
publicstaticStringpassword="";
publicstaticWebDriverdriver;
@BeforeSuite
publicvoidinitSuite(){
//初始化整個項目,如配置數據同步
}
@BeforeClass
publicvoidinitTest(){
//初始化測試類,如打開瀏覽器
driver=newFirefoxDriver();
}
@AfterClass
publicvoidclose(){
//關閉瀏覽器等操作,當然你也可以放在@AfterTest或@AfterMethod
driver.close();
}
……
2>創建用例的操作類TestAction類:
TestAction類主要封裝一些界面動作,比如點擊、輸入、移動、刷新等,與界面用戶操作(動作)相關的都可以封裝在這個類里面。
publicclassTestAction{
privateWebDriverdriver;
publicTestAction(WebDriverdriver){
this.driver=driver;
}
publicvoidrefresh(){
driver.navigate().refresh();
Log.info("F5刷新頁面");
sleep(1);
}
publicvoidmoveToElement(WebElemente){
if(e!=null){
Actionsaction=newActions(driver);
Log.info("鼠標移動到:"+getText(e));
action.moveToElement(e).perform();//鼠標移動到元素上面
}else{
Log.error("未找到對象");
}
}
publicvoidclick(WebElemente){
if(e!=null){
Log.info("點擊對象:"+getText(e));
e.click();
}else{
Log.error("未找到對象");
}
}
publicvoidsetText(WebElementelement,Objectcontent,booleanisPrintLog){
if(element!=null){
element.clear();
element.sendKeys(String.valueOf(content));
if(isPrintLog){
assertEquals(getText(element),content+"","輸入");
}
}else{
Log.error("文本框元素未找到");
}
}
}
3>封裝常用基礎控件的Hanlder類:
這一步其實放到TestAction中也沒毛病,但是將一些常用的基礎控件的操作單獨封裝起來也是可以的(看個人習慣),比如:文本控件操作類TextHandler,表格操作類TableHandler,日期選擇控件操作類DatePickerHandler等等,下面以DatePickerHandler類舉例:
publicclassDatePickerHandler{
publicstaticWebElementgetDatePicker(){
Byby=By.xpath("http://*[@class='mz-datepicker']/input");
returnBaseCase.isElementExist(by)?Page.driver.findElement(by):null;
}
publicstaticList<WebElement>getDateLinks(){
Byby=By.xpath("http://*[@class='mz-calendar-top']/a");
returnBaseCase.isElementsExist(by)?Page.driver.findElements(by):null;
}
publicstaticStringsetDate(Stringtext){
TestAction.click(getDatePicker(),0.5);
if(getDateLinks()==null||getDateLinks().size()<1){
return"";
}
for(WebElementlink:getDateLinks()){
if(BaseCase.getText(link).equals(text)){
TestAction.click(link,0.2);
break;
}
}
Log.info("選擇日期范圍:"+BaseCase.getText(getDatePicker()));
returnBaseCase.getText(getDatePicker());
}
publicstaticStringsetDateText(StringdateRange){
((JavascriptExecutor)Page.driver).executeScript("arguments[0].removeAttribute(\"readOnly\");",getDatePicker());
getDatePicker().clear();
getDatePicker().sendKeys(dateRange);
if(!dateRange.equals(BaseCase.getText(getDatePicker()))){
Log.writeInfo("選擇日期范圍失敗,實際:"+BaseCase.getText(getDatePicker())+",期望:"+dateRange);
return"";
}
Log.writeInfo("選擇日期范圍:"+BaseCase.getText(getDatePicker()));
returnBaseCase.getText(getDatePicker());
}
}
4>元素對象管理:
前面已經封裝了BaseCase類和操作類,但是頁面的元素對象該如何管理呢?這可能要根據項目的大小和元素的多少來定,下面我提供幾種常用方式:
a. 直接將元素定位的id 、name或 xpath寫在代碼中;
b. 將元素定位的表達式提取出來存放在文本、XML、yaml或json中;
c. 將元素定位的表達式提取出來存放到數據庫中;
將元素定位寫在代碼中,好處就不言而喻了,方便調試嘛,但是對于頁面元素上萬這種就不推薦了,元素對象將會變得很難管理(估計代碼中會遺留很多無用對象),但是對于頁面元素不多的情況下還是推薦它的,下面就用PageObject的方式將元素定位寫在代碼中舉例:
publicclassHomePage{
privateWebDriverdriver;
publicHomePage(WebDriverdriver){
this.driver=driver;
}
publicWebElementgetLeftNavHome(){
Byby=By.xpath("http://a[text()='首頁']");
returnBaseCase.isElementExist(by)?driver.findElement(by):null;
}
publicWebElementgetUser(){
Byby=By.xpath("http://*[@class='name']/b");
returnBaseCase.isElementExist(by)?driver.findElement(by):null;
}
}
5>用例編寫:
根據頁面創建測試用例類,類中根據頁面功能點可以寫一個或多個@Test,用例的粒度自己把握。
publicclassTestHomePageextendsBaseCase{
privateHomePagehomePage;
@Test
publicvoidtestHeaderUsername(){
if(isLogged){
TestAction.refresh();
homePage=newHomePage();
if(assertEquals(getText(homePage.getUser()),user_name,"用戶名")){
TestAction.moveToElement(homePage.getUser());
TestAction.sleep(0.5);
if(homePage.getQuit().isDisplayed()){
TestAction.click(homePage.getQuit(),0.5);
if(driver.getTitle().contains("登錄")||driver.getTitle().contains("Login")){
Log.writeInfo("退出登陸成功");
}else{
Log.writeErrorInfo("退出登陸失敗");
}
}
}
}
}
@Test
publicvoidtestXXXXX1(){……}
@Test
publicvoidtestXXXXX2(){……}
……
6>用例管理:
采用TestNG的xml文件來管理用例
<suitename="WebAutomationTest"parallel="tests"thread-count="1">
<listeners>
<listenerclass-name="your Listener"/>
</listeners>
<testname="測試用例"preserve-order="true">
<classes>
<classname="com.alany.testcase.TestHomePage"></class>
用例類往后繼續添加
</classes>
</test>
</suite>