本系列所有代碼?https://github.com/zhangting85/simpleWebtest
本文將介紹一個Java+TestNG+Maven+Selenium的web自動化測試腳本環境下selenium頁面對象設計下的頁面模塊的寫法,并提供全部代碼。
?
在一個頁面上,有的時候,會有一些需要重復利用的模塊。
比如,一個電子商務網站上,經常會在頁面最頂上有一個搜索框。這個搜索框在幾乎所有頁面上都會出現。可以隨時用它搜索一些商品。
這里,有人用繼承,寫一個父類,父類提供了這個搜索框的一些功能封裝。然后所有頁面類都繼承這個父類。
這樣寫一開始是沒問題的。但是當這類重用模塊增加了,變動了,會造成整個測試代碼邏輯結構亂成一團。所以不推薦。
?
這里介紹一下我的寫法:
把這些重復利用的部分作為頁面模塊。然后我對京東的首頁、搜索結果頁建立了頁面對象模型:
代碼如下:
?
首頁
1
/**
2
*京東首頁
3
*/
4
public
class
JDHomepage
extends
Page {
5
/**
6
*URL常量,很少用到,一般在起始頁用,有時放到配置文件里去統一管理
7
*/
8
private
static
final
String URL="http://www.jd.com"
;
9
10
/**
11
*可供重用的頁面模塊,作為成員對象在顯示這個模塊的頁面中保存。
12
*這里用了組合的寫法(composite),注意不要濫用繼承。
13
*/
14
public
SearchHeaderModule searchHeader=
new
SearchHeaderModule();
15
16
/**
17
* 只有homepage之類的起始頁才必要有這個init方法用來打開URL。
18
* return this 表示執行完畢之后頁面仍舊在本頁。
19
* 如果留在本頁,并有頁面刷新,就要return new JDHomepage
20
* 如果沒有頁面刷新等頁面改變,就return this
21
* 如果跳轉到其他頁面,就return new xxxPage
22
* 這樣寫的好處,是每個方法的return語句上明確了頁面跳轉的預期結果
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
}
在首頁里我其實沒有封裝什么業務邏輯,正常來說如果實際去實現整個京東的測試用例,那么首頁這個類會變得比較龐大的。
這里我用下面這段代碼創建了SearchHeader這個頁面模塊
public
SearchHeaderModule searchHeader=
new
SearchHeaderModule();
作為一個成員對象。這個對象的實例會在JDHomepage的構造方法被調用前先被jvm調用。
所以,每個Homepage的實例都會包含一個SearchHeader,然后我們只使用時如下調用即可:
home.init().searchHeader.search("巧克力");
home是一個JDHomepage類的實例,init方法是去打開這個page的URL,我只在首頁等起始頁上寫init方法。
search是searchHeader提供的方法,這樣直接連點調用即可。
?
SearchHeader的實現:
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
* 頁面模塊。此處表示京東各頁面上方共享的搜索條
12
* 他本身也可以看做是一個頁面
13
* 并以組合(composite)的形式嵌入外部網頁,注意不要濫用繼承
14
* this page module is composite to the outer page
15
*/
16
public
class
SearchHeaderModule
extends
Page {
17
18
/**
19
* PageFactory的寫法,用標簽來定義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
* 搜索一個關鍵字,先輸入文字,再按搜索按鈕 search a keyword
30
*
31
*
@param
keyword
32
* :搜索關鍵字
33
*
@return
返回一個JDItemlistPage
34
*/
35
public
JDItemlistPage search(String keyword) {
36
searchInput.sendKeys(keyword);
37
searchButton.click();
38
return
new
JDItemlistPage();
39
}
40
}
這個SearchHader就是一個普通的頁面對象。
注意所有的頁面對象里的封裝方法我都讓他返回類似 new JDItemlistPage()之類的頁面對象。
這樣我們在test case里可以連點。比如
home.init().searchHeader.search("巧克力").getProduct(1).getText();
至于連點造成調試困難?不,由于我們有事件監聽和自動log功能,調試不會很困難。
并且我通常是先寫線性代碼再重構成頁面對象,寫成這種的都是已經執行通過的代碼。
另外,我們不是每次都需要返回新的頁面對象實例,因為有時比做一個操作,頁面不會跳轉也不會變動。這時,return this;返回當前頁的實例就行了。
?
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
*京東搜索商品結果頁
12
*/
13
public
class
JDItemlistPage
extends
Page {
14
15
16
/**
17
*先找所有商品的父親節點plist
18
*/
19
@FindBy(id = "plist"
)
20
public
WebElement productList;
21
22
/**
23
*直接找第一個商品,XPATH表達式過長,無法閱讀。(你會看得頭疼嗎?我會。。。)
24
*注意這個xpath是由firepath自動生成的,冗余過度。如果你要用xpath,一定要會自己寫
25
*插件太傻,別依賴他。
26
*/
27
@FindBy(xpath = ".//*[@id='plist']/ul/li[1]/div/div[2]/a"
)
28
public
WebElement firstproduct;
29
30
/**
31
*預先定位所有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,讓父親來找兒子,這種寫法也是可以的,但是也不是特別好(這一定不是強迫癥)
39
*但是這個方法只能找第一個商品,想找第二個商品要再寫一個方法。不推薦。
40
*/
41
public
String getFirstProductName() {
42
return
productList.findElement(By.xpath("http://div[@class='p-name'][1]//a"
)).getText();
43
}
44
45
/**
46
*先找父親plist,讓父親來找兒子,但是加了一個傳入參數告訴父親要找第幾個兒子,也就是第幾個商品。(圣斗士嗎,這么多兒子)
47
*這樣我寫一次可以找到這個頁面上任意一個商品了,京東的網頁設計特別適合自動化,可能你要測的網站不是這么工整。
48
*這里的重點是:Xpath表達式是一個字符串,你可以隨意拼接。所以傳入參數number可以插進去。
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,然后取第幾個,我喜歡從1開始所以number-1,僅個人喜好。
57
*接著對找到的product執行getProductNameOf方法來獲取名字
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
}
這個頁面就是一個標準的頁面對象了
為了擴展一下,我增加了一些內容,比如尋找第一個商品的四種方法:
JDHomepage home =
new
JDHomepage();
//
結果頁面the expected result page
JDItemlistPage resultPage=home.init().searchHeader.search("巧克力"
);
//
actual result: 用四種方法找出第一個商品名字,作為實際結果.(回字有五種寫法:P)
String product_1
= resultPage.firstproduct.getText();
//
不推薦,但偶爾有適用場景
String product_2= resultPage.getFirstProductName();
//
不推薦,但偶爾有適用場景
String product_3= resultPage.getProductNameByIndexMethodOne(1);
//
推薦寫法,但你方法名字不要這么長
String product_4= resultPage.getProductNameByIndexMethodTwo(1);
//
推薦寫法,但你方法名字不要這么長
?
如上代碼中,(和JDItemlistPage的代碼結合起來看)
方法1直接用PageObject.WebElement來獲取商品,缺點是每個商品我都要定義一個WebElement
方法2先找到product list,再用一句寫死的Xpath來尋找第一個商品,缺點是個商品我都要寫一段寫死的Xpath
方法3先找到product list,再通過傳入參數來組合一段可用的Xpath,優點是我只要寫一次Xpath
方法4先找到所有product:
@FindBy(xpath = ".//*[@id='plist']//li"
)
public
List<WebElement> products;
然后再蔥存放WebElement的List里取第一個元素。我同樣要寫一次By.className定位。
?
對于尋找商品這樣的例子來說,推薦用方法3或4。對于一般的頁面元素推薦用方法1。對于一些其他特殊的場景,看情況使用方法2。
另外,JDItemListPage里也可以像首頁一樣加入一個SearchHeader的定義,這里沒加只是因為目前我用到的test case里沒有這個需要。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

