本系列所有代碼?https://github.com/zhangting85/simpleWebtest
本文將介紹一個(gè)Java+TestNG+Maven+Selenium的web自動(dòng)化測(cè)試腳本環(huán)境下selenium頁(yè)面對(duì)象設(shè)計(jì)下的頁(yè)面模塊的寫(xiě)法,并提供全部代碼。
?
在一個(gè)頁(yè)面上,有的時(shí)候,會(huì)有一些需要重復(fù)利用的模塊。
比如,一個(gè)電子商務(wù)網(wǎng)站上,經(jīng)常會(huì)在頁(yè)面最頂上有一個(gè)搜索框。這個(gè)搜索框在幾乎所有頁(yè)面上都會(huì)出現(xiàn)。可以隨時(shí)用它搜索一些商品。
這里,有人用繼承,寫(xiě)一個(gè)父類,父類提供了這個(gè)搜索框的一些功能封裝。然后所有頁(yè)面類都繼承這個(gè)父類。
這樣寫(xiě)一開(kāi)始是沒(méi)問(wèn)題的。但是當(dāng)這類重用模塊增加了,變動(dòng)了,會(huì)造成整個(gè)測(cè)試代碼邏輯結(jié)構(gòu)亂成一團(tuán)。所以不推薦。
?
這里介紹一下我的寫(xiě)法:
把這些重復(fù)利用的部分作為頁(yè)面模塊。然后我對(duì)京東的首頁(yè)、搜索結(jié)果頁(yè)建立了頁(yè)面對(duì)象模型:
代碼如下:
?
首頁(yè)

1 /** 2 *京東首頁(yè) 3 */ 4 public class JDHomepage extends Page { 5 /** 6 *URL常量,很少用到,一般在起始頁(yè)用,有時(shí)放到配置文件里去統(tǒng)一管理 7 */ 8 private static final String URL="http://www.jd.com" ; 9 10 /** 11 *可供重用的頁(yè)面模塊,作為成員對(duì)象在顯示這個(gè)模塊的頁(yè)面中保存。 12 *這里用了組合的寫(xiě)法(composite),注意不要濫用繼承。 13 */ 14 public SearchHeaderModule searchHeader= new SearchHeaderModule(); 15 16 /** 17 * 只有homepage之類的起始頁(yè)才必要有這個(gè)init方法用來(lái)打開(kāi)URL。 18 * return this 表示執(zhí)行完畢之后頁(yè)面仍舊在本頁(yè)。 19 * 如果留在本頁(yè),并有頁(yè)面刷新,就要return new JDHomepage 20 * 如果沒(méi)有頁(yè)面刷新等頁(yè)面改變,就return this 21 * 如果跳轉(zhuǎn)到其他頁(yè)面,就return new xxxPage 22 * 這樣寫(xiě)的好處,是每個(gè)方法的return語(yǔ)句上明確了頁(yè)面跳轉(zhuǎn)的預(yù)期結(jié)果 23 * Only the start page of a test case should has this init method 24 * @return return this means no page refresh and stay on this page after this method 25 * return new JDHomepage means stay on this page and has a page refresh 26 * return new xxxPage means page redirects after this method 27 */ 28 public JDHomepage init(){ 29 DriverManager.driver.get(URL); 30 return this ; 31 } 32 33 34 35 }
在首頁(yè)里我其實(shí)沒(méi)有封裝什么業(yè)務(wù)邏輯,正常來(lái)說(shuō)如果實(shí)際去實(shí)現(xiàn)整個(gè)京東的測(cè)試用例,那么首頁(yè)這個(gè)類會(huì)變得比較龐大的。
這里我用下面這段代碼創(chuàng)建了SearchHeader這個(gè)頁(yè)面模塊
public SearchHeaderModule searchHeader= new SearchHeaderModule();
作為一個(gè)成員對(duì)象。這個(gè)對(duì)象的實(shí)例會(huì)在JDHomepage的構(gòu)造方法被調(diào)用前先被jvm調(diào)用。
所以,每個(gè)Homepage的實(shí)例都會(huì)包含一個(gè)SearchHeader,然后我們只使用時(shí)如下調(diào)用即可:
home.init().searchHeader.search("巧克力");
home是一個(gè)JDHomepage類的實(shí)例,init方法是去打開(kāi)這個(gè)page的URL,我只在首頁(yè)等起始頁(yè)上寫(xiě)init方法。
search是searchHeader提供的方法,這樣直接連點(diǎn)調(diào)用即可。
?
SearchHeader的實(shí)現(xiàn):

1 package simplewebtest.core.page.module.sample.jd; 2 3 import org.openqa.selenium.WebElement; 4 import org.openqa.selenium.support.FindBy; 5 6 import simplewebtest.core.Page; 7 import simplewebtest.core.page.sample.jd.JDItemlistPage; 8 9 10 /** 11 * 頁(yè)面模塊。此處表示京東各頁(yè)面上方共享的搜索條 12 * 他本身也可以看做是一個(gè)頁(yè)面 13 * 并以組合(composite)的形式嵌入外部網(wǎng)頁(yè),注意不要濫用繼承 14 * this page module is composite to the outer page 15 */ 16 public class SearchHeaderModule extends Page { 17 18 /** 19 * PageFactory的寫(xiě)法,用標(biāo)簽來(lái)定義web elment的查找 define how to find a webelment by 20 * annotation 21 */ 22 @FindBy(id = "key" ) 23 WebElement searchInput; 24 25 @FindBy(xpath = "http://input[@value='搜索']" ) 26 WebElement searchButton; 27 28 /** 29 * 搜索一個(gè)關(guān)鍵字,先輸入文字,再按搜索按鈕 search a keyword 30 * 31 * @param keyword 32 * :搜索關(guān)鍵字 33 * @return 返回一個(gè)JDItemlistPage 34 */ 35 public JDItemlistPage search(String keyword) { 36 searchInput.sendKeys(keyword); 37 searchButton.click(); 38 return new JDItemlistPage(); 39 } 40 }
這個(gè)SearchHader就是一個(gè)普通的頁(yè)面對(duì)象。
注意所有的頁(yè)面對(duì)象里的封裝方法我都讓他返回類似 new JDItemlistPage()之類的頁(yè)面對(duì)象。
這樣我們?cè)趖est case里可以連點(diǎn)。比如
home.init().searchHeader.search("巧克力").getProduct(1).getText();
至于連點(diǎn)造成調(diào)試?yán)щy?不,由于我們有事件監(jiān)聽(tīng)和自動(dòng)log功能,調(diào)試不會(huì)很困難。
并且我通常是先寫(xiě)線性代碼再重構(gòu)成頁(yè)面對(duì)象,寫(xiě)成這種的都是已經(jīng)執(zhí)行通過(guò)的代碼。
另外,我們不是每次都需要返回新的頁(yè)面對(duì)象實(shí)例,因?yàn)橛袝r(shí)比做一個(gè)操作,頁(yè)面不會(huì)跳轉(zhuǎn)也不會(huì)變動(dòng)。這時(shí),return this;返回當(dāng)前頁(yè)的實(shí)例就行了。
?
JDItemlistPage

1 package simplewebtest.core.page.sample.jd; 2 3 import java.util.List; 4 5 import org.openqa.selenium.By; 6 import org.openqa.selenium.WebElement; 7 import org.openqa.selenium.support.FindBy; 8 9 import simplewebtest.core.Page; 10 /** 11 *京東搜索商品結(jié)果頁(yè) 12 */ 13 public class JDItemlistPage extends Page { 14 15 16 /** 17 *先找所有商品的父親節(jié)點(diǎn)plist 18 */ 19 @FindBy(id = "plist" ) 20 public WebElement productList; 21 22 /** 23 *直接找第一個(gè)商品,XPATH表達(dá)式過(guò)長(zhǎng),無(wú)法閱讀。(你會(huì)看得頭疼嗎?我會(huì)。。。) 24 *注意這個(gè)xpath是由firepath自動(dòng)生成的,冗余過(guò)度。如果你要用xpath,一定要會(huì)自己寫(xiě) 25 *插件太傻,別依賴他。 26 */ 27 @FindBy(xpath = ".//*[@id='plist']/ul/li[1]/div/div[2]/a" ) 28 public WebElement firstproduct; 29 30 /** 31 *預(yù)先定位所有product 32 *get all products, suggested to use this way 33 */ 34 @FindBy(xpath = ".//*[@id='plist']//li" ) 35 public List<WebElement> products; 36 37 /** 38 *先找父親plist,讓父親來(lái)找兒子,這種寫(xiě)法也是可以的,但是也不是特別好(這一定不是強(qiáng)迫癥) 39 *但是這個(gè)方法只能找第一個(gè)商品,想找第二個(gè)商品要再寫(xiě)一個(gè)方法。不推薦。 40 */ 41 public String getFirstProductName() { 42 return productList.findElement(By.xpath("http://div[@class='p-name'][1]//a" )).getText(); 43 } 44 45 /** 46 *先找父親plist,讓父親來(lái)找兒子,但是加了一個(gè)傳入?yún)?shù)告訴父親要找第幾個(gè)兒子,也就是第幾個(gè)商品。(圣斗士嗎,這么多兒子) 47 *這樣我寫(xiě)一次可以找到這個(gè)頁(yè)面上任意一個(gè)商品了,京東的網(wǎng)頁(yè)設(shè)計(jì)特別適合自動(dòng)化,可能你要測(cè)的網(wǎng)站不是這么工整。 48 *這里的重點(diǎn)是:Xpath表達(dá)式是一個(gè)字符串,你可以隨意拼接。所以傳入?yún)?shù)number可以插進(jìn)去。 49 *suggested 50 */ 51 public String getProductNameByIndexMethodOne( int number) { 52 return productList.findElement(By.xpath("http://div[@class='p-name']["+number+"]//a" )).getText(); 53 } 54 55 /** 56 *一次性找出所有product,然后取第幾個(gè),我喜歡從1開(kāi)始所以number-1,僅個(gè)人喜好。 57 *接著對(duì)找到的product執(zhí)行g(shù)etProductNameOf方法來(lái)獲取名字 58 *suggested 59 */ 60 public String getProductNameByIndexMethodTwo( int number) { 61 return getProductNameOf(products.get(number-1 )); 62 } 63 64 private String getProductNameOf(WebElement product) 65 { 66 return product.findElement(By.className("p-name" )).getText(); 67 68 } 69 70 71 }
這個(gè)頁(yè)面就是一個(gè)標(biāo)準(zhǔn)的頁(yè)面對(duì)象了
為了擴(kuò)展一下,我增加了一些內(nèi)容,比如尋找第一個(gè)商品的四種方法:
JDHomepage home = new JDHomepage(); // 結(jié)果頁(yè)面the expected result page JDItemlistPage resultPage=home.init().searchHeader.search("巧克力" ); // actual result: 用四種方法找出第一個(gè)商品名字,作為實(shí)際結(jié)果.(回字有五種寫(xiě)法:P) String product_1 = resultPage.firstproduct.getText(); // 不推薦,但偶爾有適用場(chǎng)景 String product_2= resultPage.getFirstProductName(); // 不推薦,但偶爾有適用場(chǎng)景 String product_3= resultPage.getProductNameByIndexMethodOne(1); // 推薦寫(xiě)法,但你方法名字不要這么長(zhǎng) String product_4= resultPage.getProductNameByIndexMethodTwo(1); // 推薦寫(xiě)法,但你方法名字不要這么長(zhǎng)
?
如上代碼中,(和JDItemlistPage的代碼結(jié)合起來(lái)看)
方法1直接用PageObject.WebElement來(lái)獲取商品,缺點(diǎn)是每個(gè)商品我都要定義一個(gè)WebElement
方法2先找到product list,再用一句寫(xiě)死的Xpath來(lái)尋找第一個(gè)商品,缺點(diǎn)是個(gè)商品我都要寫(xiě)一段寫(xiě)死的Xpath
方法3先找到product list,再通過(guò)傳入?yún)?shù)來(lái)組合一段可用的Xpath,優(yōu)點(diǎn)是我只要寫(xiě)一次Xpath
方法4先找到所有product:
@FindBy(xpath = ".//*[@id='plist']//li" ) public List<WebElement> products;
然后再蔥存放WebElement的List里取第一個(gè)元素。我同樣要寫(xiě)一次By.className定位。
?
對(duì)于尋找商品這樣的例子來(lái)說(shuō),推薦用方法3或4。對(duì)于一般的頁(yè)面元素推薦用方法1。對(duì)于一些其他特殊的場(chǎng)景,看情況使用方法2。
另外,JDItemListPage里也可以像首頁(yè)一樣加入一個(gè)SearchHeader的定義,這里沒(méi)加只是因?yàn)槟壳拔矣玫降膖est case里沒(méi)有這個(gè)需要。
?
selenium從入門到應(yīng)用 - 5,頁(yè)面對(duì)象設(shè)計(jì)模式下的頁(yè)面模塊
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
