WebDriver 封裝
歡迎閱讀WebDriver封裝講義猾愿。本篇講義將會重點介紹Selenium WebDriver API的封裝的概念和方法幌衣,以及使用封裝進行自動化測試的設(shè)計喇闸。
WebDriver API 封裝
封裝的概念
從之前的講義和學(xué)習(xí)中沧竟,我們知道短纵,WebDriver API的調(diào)用以及自動化測試远舅,我們也初步接觸了線性測試闰蛔、以及模塊化自動化測試和數(shù)據(jù)驅(qū)動測試,那么回顧之前的內(nèi)容图柏,我們不只是可以利用WebDriver提供的一系列的定位符以便使用元素定位方法序六,我們這里開始嘗試封裝后調(diào)用。首先蚤吹,我們從封裝的概念開始例诀。
封裝是一個面向?qū)ο缶幊痰母拍睿敲嫦驅(qū)ο缶幊痰暮诵膶傩圆米牛ㄟ^將代碼內(nèi)部實現(xiàn)進行密封和包裝繁涂,從而簡化編程。
所謂“對象”二驰,形象地說扔罪,我們可以把它理解為一塊積木。設(shè)計積木的人需要設(shè)計積木的外觀與形狀桶雀,還有內(nèi)部的材質(zhì)矿酵。堆積木的人對于內(nèi)部的材質(zhì)并不關(guān)心妆兑,他們只需要根據(jù)不同的外觀與形狀來決定堆放的位置相种。因此,對于開發(fā)者而言圈驼,要設(shè)計面向?qū)ο蟮某绦虬擦疲瑫r會是兩個迥然不同的身份:設(shè)計者與使用者障般。
先談?wù)勈褂谜咝伞J褂谜叩纳矸荼醒保褪抢靡呀?jīng)提供給你的所有對象,根據(jù)需求哪自,設(shè)計出自己需要實現(xiàn)的程序丰包。就如堆積木的過程禁熏。這恰恰是面向?qū)ο缶幊痰膬?yōu)勢所在壤巷,那就是“對象的重用”。已經(jīng)設(shè)計好的對象瞧毙,可以被不同的使用者調(diào)用胧华,這些功能既然已經(jīng)實現(xiàn),對于使用者而言宙彪,當然就免去了自己去設(shè)計的過程矩动。正如堆積木那樣,既然有了現(xiàn)成設(shè)計好的積木释漆,使用者所要做的工作就是把這些積木最后組合起來悲没,堆成不同的形狀。WebDriver
所提供的類庫男图,就是這樣的積木示姿。那么我們以下的操作將會基于上述的定位符進行定位操作。
前面說到對象好比是一個積木逊笆,設(shè)計者需要定義好這個積木的外觀和形狀栈戳,也要考慮積木內(nèi)部的制作,例如選用的材質(zhì)难裆,以及是空心還是實心子檀。如果將這個積木剖開來看,實際上該對象應(yīng)分為內(nèi)乃戈、外兩層褂痰。由于使用者只關(guān)心外部的實現(xiàn),因此設(shè)計者就需要考慮症虑,哪些實現(xiàn)應(yīng)暴露在外脐恩,哪些實現(xiàn)應(yīng)隱藏于內(nèi)。這就體現(xiàn)了對象的封裝的思想侦讨。
簡而言之驶冒,封裝就是把原始和原生的方法進行再包裝苟翻。將原始的代碼用心的代碼包裝起來,通過對新代碼的調(diào)用骗污,來使用原始的代碼的過程崇猫。
封裝的好處
對Selenium進行封裝的好處主要有如下三個方面:
- 使用成本低
- 不需要要求所有的測試工程師會熟練使用Selenium,而只需要會使用封裝以后的代碼
- 不需要對所有的測試工程師進行完整培訓(xùn)需忿。也避免工作交接的成本诅炉。
- 測試人員使用統(tǒng)一的代碼庫
- 維護成本低
- 通過封裝,在代碼發(fā)生大范圍變化和遷移的時候屋厘,不需要維護所有代碼涕烧,只需要變更封裝的部分即可
- 維護代碼不需要有大量的工程師,只需要有核心的工程師進行封裝的維護即可
- 代碼安全性
- 對作為第三方的Selenium進行封裝汗洒,是代碼安全的基礎(chǔ)议纯。
- 對于任何的代碼的安全隱患,必須由封裝來解決溢谤,使得風(fēng)險可控瞻凤。
- 使用者并不知道封裝內(nèi)部的代碼結(jié)構(gòu)。
封裝的目的
封裝世杀,最終為了實現(xiàn)自動化測試框架阀参。在自動化測試領(lǐng)域,有一個經(jīng)典的問題:“既然可以編寫或者通過record & playback可以做一個腳本瞻坝,那么為什么還需要自動化測試框架呢蛛壳?”簡而言之,就是為什么需要如此這般麻煩的設(shè)計和編寫自動化測試框架所刀,不是原本已經(jīng)可以做自動化測試了么衙荐?
這個回答可以很“官方”,從維護性勉痴、重用性赫模、安全性和成本等角度可以回答。
在這點蒸矛,就好比是建造房子瀑罗。在沒有設(shè)計框架的基礎(chǔ)上,我們或許可以建造一個樓房雏掠,最多也就三兩層吧斩祭;但是對于一份經(jīng)過完整設(shè)計的圖紙,人們可以建造出高樓大廈乡话。
封裝摧玫,就是把基礎(chǔ)的石頭等建材,通過我們的個性化的方法,將地基可用而安全的搭建好诬像。是自動化測試框架的基石屋群。
自動化測試模型
- 封裝 Selenium 為 BoxDriver
- 在 測試用例中,實例化 BoxDriver坏挠,產(chǎn)生 bd 對象
- 使用 bd 對象芍躏,構(gòu)造 業(yè)務(wù)模塊的實例化對象,產(chǎn)生 common
- 使用 common 在測試用例中降狠,構(gòu)建測試步驟
- 使用數(shù)據(jù)驅(qū)動的外部數(shù)據(jù)对竣,通過讀取,進行測試
- 執(zhí)行整個用例
使用封裝進行自動化測試
封裝技術(shù)
- 面向?qū)ο蟮木幊?/li>
- 類:模板榜配,設(shè)計
- 構(gòu)造方法
- 成員變量
- 成員方法
- 類的實例化產(chǎn)生對象
- 實例化過程: new 關(guān)鍵字
- 實例化的方法: 類名(參數(shù))--> 構(gòu)造方法
- 實例化過程相當于 類執(zhí)行了構(gòu)造方法否纬,產(chǎn)生了 this
- 實例化出來的對象,可以訪問成員方法
- 封裝:做一個模板蛋褥,這個模板有webdriver所有的功能
- 封裝 webdriver临燃,自動化 WebUI 測試
- 封裝 Appium,自動化 APP UI 測試
- 封裝 自定義的 Web接口 Driver
- 其他公司的開源框架
封裝示例
測試腳本:以下的腳本
-
找到一個指定輸入框(selector)壁拉,并且輸入指定的字符(text)
type(selector, text)
不用在業(yè)務(wù)邏輯中谬俄,使用多次的
findElement(By.id(...))
public void type(String selector, String text) { WebElement we = this.locateElement(selector); we.clear(); we.sendKeys(text); }
?
-
找到一個可以點擊的元素(selector)柏靶,并且點擊(click)
click(selector)
public void click(String selector) { this.locateElement(selector).click(); }
?
-
找到一個指定的frame弃理,并且切換進去
switchToFrame(selector)
public void switchToFrame(String selector) { WebElement we = this.locateElement(selector); this.baseDriver.switchTo().frame(we); }
-
找到一個指定的select,并且通過index進行選擇
selectByIndex(selector, index)
public void selectByIndex(String selector, int index) { WebElement we = this.locateElement(selector); Select s = new Select(we); s.selectByIndex(index); }
?
以上的代碼是封裝了locateElement()
的幾種方法屎蜓,在具體使用封裝過的代碼的時候痘昌,只需要簡單的調(diào)用即可。接下來的重點炬转,是介紹 locateElement(selector)
的封裝方式辆苔。
- 查找元素:
findElement(By...)
- 支持各種的查找:8種方式都需要支持,必須通過
selector
顯示出分類-
selector
中需要包含一個特殊符號 - 實例化 封裝好的類的時候扼劈,需要約定好是什么特殊符號
- 強制性用
硬編碼 hard code
來實例化驻啤,例如,
或者?
或者 其他非常用字符=>
- 或者,構(gòu)造方法中荐吵,傳遞
this.byChar
- 強制性用
-
- 要把查找到元素的返回給調(diào)用的地方:必須要有返回值骑冗,類型是
WebElement
private WebElement locateElement(String selector) {
WebElement we;
// 如果定位符中 有 分隔符,那么就從分隔符處分成兩段
// 第一段是By
// 第二段是真正的定位符
// 如果沒有分隔符先煎,就默認用 id 定位
if (!selector.contains(this.byChar)) {
// 用 id 定位
we = this.baseDriver.findElement(By.id(selector));
} else {
// 用 分隔符 分成兩個部分
String by = selector.split(this.byChar)[0];
String value = selector.split(this.byChar)[1];
we = findElementByChar(by, value);
}
return we;
}
- 接下來的重點贼涩,是實現(xiàn)
findElementByChar(by, value)
private WebElement findElementByChar(String by, String value) {
WebElement we = null;
switch (by.toLowerCase()) {
case "id":
case "i":
we = this.baseDriver.findElement(By.id(value));
break;
case "css_selector":
case "css":
case "cssselector":
case "s":
we = this.baseDriver.findElement(By.cssSelector(value));
break;
case "xpath":
case "x":
we = this.baseDriver.findElement(By.xpath(value));
break;
case "link_text":
case "link":
case "text":
case "linktext":
case "l":
we = this.baseDriver.findElement(By.linkText(value));
break;
//TODO: other by type
}
return we;
}
使用上面的封裝類,就需要指定特定的 selector
類型 | 示例(分隔符以逗號, 為例) |
描述 |
---|---|---|
id | "account" 或者 "i,account" 或者 "id,account" | 分隔符左右兩側(cè)不可以空格 |
xpath | "x,//*[@id="s-menu-dashboard"]/button/i" | |
css selector | "s,#s-menu-dashboard > button > i" | |
link text | "l,退出" | |
partial link text | "p,退" | |
name | "n,name1" | |
tag name | "t,input" | |
class name | "c,dock-bottom |
調(diào)用示例
void logIn(String account, String password) throws InterruptedException {
BoxDriver driver = this.baseDriver;
driver.type("account", account);
driver.type("password", password);
driver.click("submit");
// 點擊登錄按鈕后薯蝎,需要等待瀏覽器刷新
Thread.sleep(2000);
}
自動化的測試代碼示例
@Test
public void test01Login() {
common.openPage();
common.logIn(member.getAccount(), member.getPassword());
expectedUrl = this.baseUrl + "sys/index.html";
Assert.assertEquals(driver.getCurrentUrl(), expectedUrl, "新用戶登錄失敗");
common.logOut(lang);
}
封裝需要注意的部分
-
方法有無返回值遥倦、和參數(shù)
-
如果有返回值:確保代碼所有的路徑都會有返回值
public WebElement getElement(){ if (xxx) { return yyy; } } // 上述代碼會報錯,如果xxx條件不符合占锯,就沒有返回值的路徑了袒哥。
-
如果有參數(shù)缩筛,確保非值類型的參數(shù)是非空的 不是 null
public void type(Driver driver){ driver.xxx() } // 如果driver是null,那么xxx會直接報錯堡称。 NullPointerException錯誤異常
-
如果有較多的分支語句歪脏,需要注意 break的使用
public WebElement getElement(String selector){ switch(selector.toLowerCase()){ case "id": case "i": driver.findElement(By.id(xxx)); break; case "x": xxx } } // 注意需要填寫break
在python中,不支持switch語句粮呢,python使用的是 if ... elif
def get_element(selector): if selector == "id" or selecor == "i": driver.find_element_by_id(xxx) elif selector == "x": xxx
?
-
-
封裝中婿失,WebDriver對象的傳遞
-
WebDriver應(yīng)該是在封裝的類中,唯一存在啄寡。
class AutomateDriver(){ public click(String selector){ WebDriver driver = new FirefoxDriver(); driver.... } public type(String selector, String text){ WebDriver driver = new FirefoxDriver(); driver.... } } // 上述代碼中豪硅,會打開多個火狐瀏覽器 // 正確的做法:聲明一個全局的類的成員變量,進行賦值使用挺物。
?
-
關(guān)于Firefox Profile的使用
在做自動化測試的時候懒浮,經(jīng)常會發(fā)現(xiàn)Firefox 被初始化,提示收藏夾等识藤,遮住元素窗口砚著。因為Firefox每次都以默認的Profile 加載全新的Firefox,例如如下圖片:
在這樣的情況下痴昧,我們需要專門準備一份獨立的Firefox Profile進行使用稽穆。步驟如下
-
在命令行中進入Firefox的安裝目錄,然后輸入
firefox -ProfileManager -no-remote
Snap2.jpg -
然后在彈出的窗口中赶撰,新建一個Profile舌镶,記錄地址。
e5792a15-e7ed-39fa-bf46-5505c54923a5.png -
根據(jù)剛剛的地址豪娜,打開Profile所在的文件夾:
C:\Users\Linty\AppData\Roaming\Mozilla\Firefox\Profiles
Snap3.jpg -
復(fù)制
einy9uds.selenium
文件夾到base
目錄中餐胀。Snap4.jpg -
修改代碼
automate_driver.py
def __init__(self, by_char=",", firefox_profile=None): """ 構(gòu)造方法:實例化 BoxDriver 時候使用 :param by_char: 分隔符 :param firefox_profile: 可選擇的參數(shù),如果不傳遞瘤载,就是None 如果傳遞一個 profile否灾,就會按照預(yù)先的設(shè)定啟動火狐 去掉遮擋元素的提示框等 """ if firefox_profile is not None: firefox_profile = FirefoxProfile(firefox_profile) driver = webdriver.Firefox(firefox_profile=firefox_profile) try: self.base_driver = driver self.by_char = by_char except Exception: raise NameError("Firefox Not Found!")
-
修改代碼
xxx_test_cases.py
profile = "base\\einy9uds.selenium" self.base_driver = AutomateDriver(firefox_profile=profile)
完畢。