WebDriver 進(jìn)階
歡迎閱讀WebDriver進(jìn)階講義漓帚。本篇講義將會(huì)重點(diǎn)介紹Selenium WebDriver API的重點(diǎn)使用方法,以及使用模塊化和參數(shù)化進(jìn)行自動(dòng)化測(cè)試的設(shè)計(jì)午磁。
WebDriver API 進(jìn)階使用
元素定位
從之前的講義和學(xué)習(xí)中尝抖,我們知道,WebDriver API的調(diào)用以及自動(dòng)化測(cè)試迅皇,務(wù)必從頁(yè)面元素的定位開(kāi)始昧辽,那么回顧之前的內(nèi)容,WebDriver提供了一系列的定位符以便使用元素定位方法登颓。常見(jiàn)的定位符有以下幾種:
- id
- name
- class name
- tag
- link text
- partial link text
- xpath
- css selector
那么我們以下的操作將會(huì)基于上述的定位符進(jìn)行定位操作搅荞。
對(duì)于元素的定位,WebDriver API可以通過(guò)定位簡(jiǎn)單的元素和一組元素來(lái)操作框咙。在這里咕痛,我們需要告訴Selenium如何去找元素,以至于他可以充分的模擬用戶行為喇嘱,或者通過(guò)查看元素的屬性和狀態(tài)茉贡,以便我們執(zhí)行一系列的檢查。
在Selenium2中者铜,WebDriver提供了多種多樣的findElement(by.)
方法在一個(gè)網(wǎng)頁(yè)里面查找元素腔丧。這些方法通過(guò)提供過(guò)濾標(biāo)準(zhǔn)來(lái)定位元素。當(dāng)然WebDriver也提供了同樣多種多樣的findElements(by.)
的方式去定位多個(gè)元素作烟。
Selenium2提供的8個(gè)findElement(by.)
方法去定位元素愉粤。在這里我們來(lái)具體查看每個(gè)方法的詳細(xì)使用方式。下面的表格將會(huì)列出這些具體的方法:
方法Method | 描述Description | 參數(shù)Argument | 示例Example |
---|---|---|---|
id |
該方法通過(guò)ID的屬性值去定位查找單個(gè)元素 | id: 需要被查找的元素的ID | findElement(by.id("search")) |
name |
該方法通過(guò)name的屬性值去定位查找單個(gè)元素 | name: 需要被查找的元素的名稱 | findElement(by.name("q")) |
class name |
該方法通過(guò)class的名稱值去定位查找單個(gè)元素 | class_name: 需要被查找的元素的類名 | findElement(by.className("input-text")) |
tag_name |
該方法通過(guò)tag的名稱值去定位查找單個(gè)元素 | tag: 需要被查找的元素的標(biāo)簽名稱 | findElement(by.tagName("input")) |
link_text |
該方法通過(guò)鏈接文字去定位查找單個(gè)元素 | link_text: 需要被查找的元素的鏈接文字 | findElement(by.linkText("Log In")) |
partial_link_text |
該方法通過(guò)部分鏈接文字去定位查找單個(gè)元素 | link_text: 需要被查找的元素的部分鏈接文字 | findElement(by.partialLinkText("Long")) |
xpath |
該方法通過(guò)XPath的值去定位查找單個(gè)元素 | xpath: 需要被查找的元素的xpath | findElement(by.xpath("http://*[@id='xx']/a")) |
css_selector |
該方法通過(guò)CSS選擇器去定位查找單個(gè)元素 | css_selector: 需要被查找的元素的ID | findElement(by.cssSelector("#search")) |
接下來(lái)的列表將會(huì)詳細(xì)展示find_elements_by
的方法集合俗壹。這些方法依據(jù)匹配的具體標(biāo)準(zhǔn)返回一系列的元素科汗。
方法Method | 描述Description | 參數(shù)Argument | 示例Example |
---|---|---|---|
id |
該方法通過(guò)ID的屬性值去定位查找多個(gè)元素 | id: 需要被查找的元素的ID | findElements(by.id("search")) |
name |
該方法通過(guò)name的屬性值去定位查找多個(gè)元素 | name: 需要被查找的元素的名稱 | findElements(by.name("q")) |
class_name |
該方法通過(guò)class的名稱值去定位查找多個(gè)元素 | class_name: 需要被查找的元素的類名 | findElements(by.className("input-text")) |
tag_name |
該方法通過(guò)tag的名稱值去定位查找多個(gè)元素 | tag: 需要被查找的元素的標(biāo)簽名稱 | findElements(by.tagName("input")) |
link_text |
該方法通過(guò)鏈接文字去定位查找多個(gè)元素 | link_text: 需要被查找的元素的鏈接文字 | findElements(by.linkText("Log In")) |
partial_link_text |
該方法通過(guò)部分鏈接文字去定位查找多個(gè)元素 | link_text: 需要被查找的元素的部分鏈接文字 | findElements(by.partialLinkText("Long")) |
xpath |
該方法通過(guò)XPath的值去定位查找多個(gè)元素 | xpath: 需要被查找的元素的xpath | findElements(by.xpath("http://div[contains(@class,'list')]")) |
css_selector |
該方法通過(guò)CSS選擇器去定位查找多個(gè)元素 | css_selector: 需要被查找的元素的ID | findElements(by.cssSelector(".input_class")) |
依據(jù)ID查找
請(qǐng)查看如下HTML的代碼藻烤,以便實(shí)現(xiàn)通過(guò)ID的屬性值去定義一個(gè)查找文本框的查找:
<input id="search" type="text" name="q" value=""
class="input-text" maxlength="128" autocomplete="off"/>
根據(jù)上述代碼绷雏,這里我們使用findElement(By.id())
的方法去查找搜索框并且檢查它的最大長(zhǎng)度maxlength
屬性头滔。我們通過(guò)傳遞ID的屬性值作為參數(shù)去查找,參考如下的代碼示例:
void testSearchTextFieldMaxLength(){
// get the search textbox
WebElement searchField = driver.findElement(By.id("search"))
// check maxlength attribute is set to 128
assertEqual("128", searchField.getAttribute("maxlength"))
}
如果使用findElement(By.id())
方法涎显,將會(huì)返回所有的具有相同ID屬性值的一系列元素坤检。
依據(jù)名稱name查找
這里還是根據(jù)上述ID查找的HTML代碼,使用findElement(By.id())
的方法進(jìn)行查找期吓。參考如下的代碼示例:
// get the search textbox
searchField = self.driver.findElement(By.name("q"))
同樣早歇,如果使用findElements(By.name())
方法,將會(huì)返回所有的具有相同name屬性值的一系列元素讨勤。
依據(jù)class name查找
除了上述的ID和name的方式查找箭跳,我們還可以使用class name的方式進(jìn)行查找和定位。
事實(shí)上潭千,通過(guò)ID谱姓,name或者類名class name查找元素是最提倡推薦的和最快的方式。當(dāng)然Selenium2 WebDriver也提供了一些其他的方式刨晴,在上述三類方式條件不足屉来,查找無(wú)效的時(shí)候,可以通過(guò)這些其他方式來(lái)查找狈癞。這些方式將會(huì)在后續(xù)的內(nèi)容中講述茄靠。
請(qǐng)查看如下的HTML代碼,通過(guò)改代碼進(jìn)行練習(xí)和理解.
<button type="submit" title="Search" class="button">
<span><span>Search</span></span>
</button>
根據(jù)上述代碼蝶桶,使用findElement(By.cssName())
方法去定位元素慨绳。
void testSearchButtonEnabled(){
// get Search button
searchButton = self.driver.findElement(By.cssName("button"))
// check Search button is enabled
assertTrue(searchButton.isEnabled())
}
同樣的如果使用findElements(By.cssName())
方法去定位元素,將會(huì)返回所有的具有相同name屬性值的一系列元素真竖。
依據(jù)標(biāo)簽名tag name查找
利用標(biāo)簽的方法類似于利用類名等方法進(jìn)行查找儡蔓。我們可以輕松的查找出一系列的具有相同標(biāo)簽名的元素。例如我們可以通過(guò)查找表中的<tr>
來(lái)獲取行數(shù)疼邀。
下面有一個(gè)HTML的示例喂江,這里在無(wú)序列表中使用了<img>
標(biāo)簽。
<ul class="promos">
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
01B.png" alt="Physical & Virtual Gift Cards">
</a>
</li>
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
02.png" alt="Shop Private Sales - Members Only">
</a>
</li>
<li>
<a href="http://demo.magentocommerce.com/accessories/
bags-luggage.html">
<img src="/media/wysiwyg/homepage-three-columnpromo-
03.png" alt="Travel Gear for Every Occasion">
</a>
</li>
</ul>
這里面我們使用findElements(By.tag())
的方式去獲取全部的圖片旁振,在此之前获询,我們將會(huì)使用findElement(By.tag())
去獲取到指定的<ul>
。
具體代碼如下:
void testCountOfPromoBannersImages(){
// get promo banner list
bannerList = driver.findElement(By.className("promos"))
// get images from the banner_list
banners = bannerList.findElements(By.tagName("img"))
// check there are 20 tags displayed on the page
assertEqual(20, banners.length)
}
依據(jù)鏈接文字link Text查找
鏈接文字查找通常比較簡(jiǎn)單拐袜。使用findElement(By.linkText())
請(qǐng)查看以下示例
<a href="#header-account" class="skip-link skip-account">
<span class="icon"></span>
<span class="label">Account</span>
</a>
測(cè)試代碼如下:
void testMyAccountLinkIsDisplayed(){
// get the Account link
accountLink =
driver.findElement(By.linkText("ACCOUNT"))
// check My Account link is displayed/visible in
// the Home page footer
self.assertTrue(accountLink.isDisplayed)
}
依據(jù)部分鏈接文字partial text查找
這里依舊使用上述的列子進(jìn)行代碼編寫(xiě):
void test_account_links(){
// get the all the links with Account text in it
accountLinks = self.driver.findElements(By.partialLinkText("ACCOUNT"))
// check Account and My Account link is
displayed/visible in the Home page footer
assertTrue(2, accountLinks.length)
}
依據(jù)XPath進(jìn)行查找
XPath是一種在XML文檔中搜索和定位節(jié)點(diǎn)node的一種查詢語(yǔ)言吉嚣。所有的主流Web瀏覽器都支持XPath。Selenium2可以用強(qiáng)大的XPath在頁(yè)面中查找元素蹬铺。
XPath 使用路徑表達(dá)式來(lái)選取 XML 文檔中的節(jié)點(diǎn)或者節(jié)點(diǎn)集尝哆。這些路徑表達(dá)式和我們?cè)诔R?guī)的電腦文件系統(tǒng)中看到的表達(dá)式非常相似。
常用的XPath的方法有starts-with()
甜攀,contains()
和ends-with()
等
若想要了解更多關(guān)于XPath的內(nèi)容秋泄,請(qǐng)查看http://www.w3school.com.cn/xpath/index.asp
如下有一段HTML代碼琐馆,其中里面的<img>
沒(méi)有使用ID,name或者類屬性恒序,所以我們無(wú)法使用之前的方法瘦麸。亞這里我們可以通過(guò)<img>
的alt
屬性,定位到指定的tag歧胁。
<ul class="promos">
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
01B.png" alt="Physical & Virtual Gift Cards">
</a>
</li>
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
02.png" alt="Shop Private Sales - Members Only">
</a>
</li>
<li>
<a href="http://demo.magentocommerce.com/accessories/
bags-luggage.html">
<img src="/media/wysiwyg/homepage-three-columnpromo-
03.png" alt="Travel Gear for Every Occasion">
</a>
</li>
</ul>
具體代碼如下:
void testVipPromo(){
// get vip promo image
vipPromo = driver.findElement(By.xpath("http://img[@alt='Shop Private Sales - Members Only']"))
// check vip promo logo is displayed on home page
assertTrue(vipPromo.isDisplayed)
// click on vip promo images to open the page
vipPromo.click()
// check page title
assertEqual("VIP", driver.title)
}
當(dāng)然滋饲,如果使用find_elements_by_xpath()
的方法,將會(huì)返回所有匹配了XPath查詢的元素喊巍。
依據(jù)CSS選擇器進(jìn)行查找
CSS是一種設(shè)計(jì)師用來(lái)描繪HTML文檔的視覺(jué)的層疊樣式表屠缭。一般來(lái)說(shuō)CSS用來(lái)定位多種多樣的風(fēng)格,同時(shí)可以用來(lái)是同樣的標(biāo)簽使用同樣的風(fēng)格等崭参。類似于XPath勿她,Selenium2也可以使用CSS選擇器來(lái)定位元素。
請(qǐng)查看如下的HTML文檔阵翎。
<div class="minicart-wrapper">
<p class="block-subtitle">Recently added item(s)
<a class="close skip-link-close" href="#" title="Close">×</a>
</p>
<p class="empty">You have no items in your shopping cart.
</p>
</div>
我們來(lái)創(chuàng)建一個(gè)測(cè)試逢并,驗(yàn)證這些消息是否正確。
void testShoppingCartStatus(){
// check content of My Shopping Cart block on Home page
// get the Shopping cart icon and click to open the
// Shopping Cart section
shoppingCartIcon = driver.findElement(By.cssSelector("div.header-minicartspan.icon")
shoppingCartIcon.click()
// get the shopping cart status
shoppingCartStatus = driver.findElement(By.cssSelector("p.empty")).text
self.assertEqual("You have no items in your shopping cart.",
shoppingCartStatus)
// close the shopping cart section
closeButton = driver.findElement(By.cssSelector("div.minicart-wrappera.close")
closeButton.click()
}
鼠標(biāo)事件
Web測(cè)試中郭卫,有關(guān)鼠標(biāo)的操作砍聊,不只是單擊,有時(shí)候還要做右擊贰军、雙擊玻蝌、拖動(dòng)等操作。這些操作包含在ActionChains類中词疼。
常用的鼠標(biāo)方法:
- contextClick() //右擊
- douchClick() //雙擊
- dragAndDrop() //拖拽
- moveToElement() //鼠標(biāo)停在一個(gè)元素上
- clickAndHold() // 按下鼠標(biāo)左鍵在一個(gè)元素上
鍵盤事件
鍵盤操作經(jīng)常處理的如下:
代碼 | 描述 |
---|---|
sendKeys(Keys.BACK_SPACE) |
刪除鍵(BackSpace) |
sendKeys(Keys.SPACE) |
空格鍵(Space) |
sendKeys(Keys.TAB) |
制表鍵(Tab) |
sendKeys(Keys.ESCAPE) |
回退鍵(Esc) |
sendKeys(Keys.ENTER) |
回車鍵(Enter) |
sendKeys(Keys.CONTROL,'a') |
全選(Ctrl+A) |
sendKeys(Keys.CONTROL,'c') |
復(fù)制(Ctrl+C) |
特殊元素定位
iframe 操作
iframe 元素會(huì)創(chuàng)建包含另外一個(gè)文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)俯树。
在一個(gè)<html>中,包含了另一個(gè)<html>
示例
<html>
<head>
<title>iframe示例</title>
</head>
<body>
<h1>
這里是H1贰盗,標(biāo)記了標(biāo)題
</h1>
<p>
這里是段落许饿,標(biāo)記一個(gè)段落,屬于外層
</p>
<div>
<iframe id="iframe-1">
<html>
<body>
<p>
這里是個(gè)段落舵盈,屬于內(nèi)層陋率,內(nèi)聯(lián)框架中的
</p>
<div id="div-1">
<p class="hahahp">
這里是div中的段落,需要被定位
</p>
</div>
</body>
</html>
</iframe>
</div>
</body>
</html>
需要定位上面示例中的<p>
:這里是div中的段落秽晚,需要被定位
如下是selenium WebDiriver的代碼
Java代碼
// 這里涉及了iframe操作
// 1. 找到 iframe 元素對(duì)應(yīng)的 css selector瓦糟,存到變量中
// 2. 調(diào)用driver.switchTo().frame(剛剛被找到的iframe元素)
WebElement frameElement = driver.findElement(By.cssSelector("#iframe-1"));
driver.switchTo().frame(frameElement);
// 進(jìn)入紫禁城以后,正常操作
driver.findElement(By.cssSelector("XXX")).click();
Thread.sleep(2000);
// TODO:繼續(xù)填寫(xiě)表單
// 在紫荊城結(jié)束以后必須退出來(lái)
// switchTo().frame()和 switchTo().defaultContent() 成對(duì)出現(xiàn)
driver.switchTo().defaultContent();
Python代碼
## 查找并定位 iframe
element_frame = driver.find_element_by_css_selector('#iframe-1')
## 切換到剛剛查找到的 iframe
driver.switch_to.frame(element_frame)
## 定位 <p>
driver.find_element_by_css_selector('#div-1 > p')
## TODO....
## 退出剛剛切換進(jìn)去的 iframe
driver.switch_to.default_content()
Select 操作
<select>
是選擇列表Select 是個(gè)selenium的類
selenium.webdriver.support.select.Select
Select 類的路徑:
C:\Python35\Lib\site-packages\selenium\webdriver\support\select.py
<select id="brand">
<option value ="volvo">Volvo</option>
<option value ="saab">Saab</option>
<option value="opel">Opel</option>
<option value="audi">Audi</option>
</select>
示例赴蝇,選擇 Audi
Java代碼
// 遇到 <select>
// 1. 定位 select菩浙,保存這個(gè)select 元素到變量中
// 2. 對(duì)變量進(jìn)行操作,把變量作為一個(gè)參數(shù),用new Select(變量)產(chǎn)生一個(gè)可操作的對(duì)象
// 3. 調(diào)用對(duì)象的方法
WebElement elementSelect = driver.findElement(By.cssSelector("#type"));
Select objectSelect = new Select(elementSelect);
objectSelect.selectByIndex(2);
Python代碼
## 查找并定位到 select
element_select = driver.find_element_by_css_selector('#brand')
## 用Select類的構(gòu)造方法劲蜻,實(shí)例化一個(gè)對(duì)象 object_select
object_select = Select(element_select)
## 操作 object_select
object_select.select_by_index(3)
## 也可以這樣
object_select.select_by_value('audi')
## 還可以這樣
object_select.select_by_visible_text('Audi')
高級(jí)用戶交互API
高級(jí)用戶交互API提供了一個(gè)更新更完善的機(jī)制來(lái)定義并描述用戶在一個(gè)網(wǎng)頁(yè)上的各種操作陆淀。這些操作包括:拖拽、按住CTRL鍵選擇多個(gè)元素等等斋竞。
快速上手
為了生成一連串的動(dòng)作,我們使用Actions來(lái)建立秃殉。首先坝初,我們先配置操作:
Actions builder = new Actions(driver);
builder.keyDown(Keys.CONTROL)
.click(someElement)
.click(someOtherElement)
.keyUp(Keys.CONTROL);
然后,獲得操作(Action):
Action selectMultiple = builder.build();
最后钾军,執(zhí)行這個(gè)動(dòng)作:
selectMultiple.perform();
這一系列的動(dòng)作應(yīng)該盡量的短鳄袍。在使用中最好在執(zhí)行一個(gè)簡(jiǎn)短的動(dòng)作后驗(yàn)證頁(yè)面是否處于正確的狀態(tài),然后再執(zhí)行下面的動(dòng)作吏恭。下一節(jié)將會(huì)列出所有可用的動(dòng)作(Action)拗小,并且說(shuō)明它們?nèi)绾芜M(jìn)行擴(kuò)展。
鍵盤交互(Keyboard interactions)
鍵盤交互是發(fā)生在一個(gè)特定的頁(yè)面元素的樱哼,而webdriver會(huì)確保這個(gè)頁(yè)面元素在執(zhí)行鍵盤動(dòng)作時(shí)處于正確的狀態(tài)哀九。這個(gè)正確的狀態(tài),包括頁(yè)面元素滾動(dòng)到可視區(qū)域并定位到這個(gè)頁(yè)面元素搅幅。
既然這個(gè)新的API是面向用戶(user-oriental)的接口阅束,那么對(duì)于一個(gè)用戶,在對(duì)一個(gè)元素輸入文本前做顯式的交互就更加的符合邏輯茄唐。這意味著息裸,當(dāng)想定位到相鄰的頁(yè)面元素時(shí),可能需要點(diǎn)擊一下元素或按下Tab(Keys.TAB
)鍵沪编。
The new interactions API will (first) support keyboard actions without a provided element. The additional work to focus on an element before sending it keyboard events will be added later on.
鼠標(biāo)交互(Mouse interactions)
鼠標(biāo)操作有一個(gè)上下文-鼠標(biāo)的當(dāng)前位置呼盆。因此,當(dāng)為幾個(gè)鼠標(biāo)操作設(shè)定一個(gè)上下文時(shí)蚁廓,第一個(gè)操作的上下文就是元素的相對(duì)位置访圃,下一個(gè)操作的上下文就上一個(gè)操作后的鼠標(biāo)相對(duì)位置。
單個(gè)動(dòng)作
所有的動(dòng)作都實(shí)現(xiàn)了Action
接口相嵌,這個(gè)接口只有一個(gè)方法:perform()
挽荠。每個(gè)動(dòng)作所需要的信息都通過(guò)Constructor傳入。當(dāng)調(diào)用這個(gè)動(dòng)作的時(shí)候平绩,動(dòng)作知道如何與頁(yè)面交互(如圈匆,找到活動(dòng)的元素并輸入文本或者計(jì)算出在屏幕上的點(diǎn)擊坐標(biāo))并且調(diào)用底層實(shí)現(xiàn)來(lái)實(shí)現(xiàn)這個(gè)交互。
下面是一些動(dòng)作:
- ButtonReleaseAction - 釋放鼠標(biāo)左鍵
- ClickAction - 相當(dāng)于 WebElement.click()
- ClickAndHoldAction - 按下鼠標(biāo)左鍵并保持
- ContextClickAction - 一般就是鼠標(biāo)右鍵捏雌,通常調(diào)出右鍵菜單用跃赚。
- DoubleClickAction - 雙擊某個(gè)元素
- KeyDownAction - 按下修飾鍵(SHIFT,CTRL,ALT纬傲,WIN)
- KeyUpAction - 釋放修飾鍵
- MoveMouseAction - 移動(dòng)鼠標(biāo)從當(dāng)前位置到另外的元素.
- MoveToOffsetAction - 移動(dòng)鼠標(biāo)到一個(gè)元素的偏移位置(偏移可以為負(fù)满败,元素是鼠標(biāo)剛移動(dòng)到的那個(gè)元素)。
- SendKeysAction - 相當(dāng)于 WebElement.sendKey(...)
CompositeAction
包含一系列的動(dòng)作叹括,當(dāng)被調(diào)用的時(shí)候算墨,它會(huì)調(diào)用它所包含的所有動(dòng)作的perform方法。通常汁雷,這個(gè)動(dòng)作通常都不是直接建立的净嘀,一般是使用ActionChainsGenerator
。
生成動(dòng)作鏈
Actions
鏈生成器實(shí)現(xiàn)了創(chuàng)建者模式來(lái)新建一個(gè)包含一組動(dòng)作的CompositeAction
侠讯。使用Actions生成器可以很容易的生成動(dòng)作并調(diào)用build()
方法來(lái)獲得復(fù)雜的操作挖藏。
Actions builder = new Actions(driver);
Action dragAndDrop = builder.clickAndHold(someElement)
.moveToElement(otherElement)
.release(otherElement)
.build();
dragAndDrop.perform();
有一個(gè)對(duì)Actions
進(jìn)行擴(kuò)展的計(jì)劃,給Actions
類添加一個(gè)方法厢漩,這個(gè)方法可以追加任何動(dòng)作到其擁有的動(dòng)作列表上膜眠。這將允許添加擴(kuò)展的動(dòng)作,而不用人工創(chuàng)建CompositeAction溜嗜。關(guān)于擴(kuò)展Actions
,請(qǐng)往下看宵膨。
擴(kuò)展Action接口的指導(dǎo)
Action接口只有一個(gè)方法-perform()
。除了實(shí)際的交互本身炸宵,所有的條件判斷也都應(yīng)該在這個(gè)這個(gè)方法里實(shí)現(xiàn)柄驻。在動(dòng)作創(chuàng)建和動(dòng)作實(shí)際執(zhí)行這段時(shí)間內(nèi),很可能頁(yè)面的狀態(tài)已經(jīng)發(fā)生了變化焙压,比如元素的可視情況已經(jīng)坐標(biāo)已經(jīng)不能找到了鸿脓。
實(shí)現(xiàn)細(xì)節(jié)
為了達(dá)到每個(gè)操作的執(zhí)行與具體實(shí)現(xiàn)的分離,所有的動(dòng)作都依賴2個(gè)接口:Mouse
和Keyboard
涯曲。這些接口被所有支持高級(jí)用戶接口API的driver實(shí)現(xiàn)了野哭。需要注意的是,這些接口是為了讓動(dòng)作使用的幻件,而不是最終用戶拨黔。本節(jié)的信息,主要是針對(duì)想擴(kuò)展WebDriver的開(kāi)發(fā)者的绰沥。
一字警告
Keyboard
和Mouse
接口是設(shè)計(jì)用來(lái)支持各種Action類的篱蝇。有鑒于此,它們的API沒(méi)有Actions鏈生成器API穩(wěn)定徽曲。直接使用這些接口可能達(dá)不到期望的結(jié)果零截,因?yàn)锳ction類做了額外的工作來(lái)確保在實(shí)際事件觸發(fā)時(shí)處于正確的環(huán)境條件。這些準(zhǔn)備工作包括定位在正確的元素上或者鼠標(biāo)交互前保證元素是可見(jiàn)的秃臣。
Keyboard接口
Keyboard接口包含3個(gè)方法:
- void sendKeys(CharSequence... keysToSend) - 與 sendKeys(...)相似.
- void pressKey(Keys keyToPress) - 按下一個(gè)鍵并保持涧衙。鍵僅限于修飾鍵(Control, Alt and Shift).
- void releaseKey(Keys keyToRelease) - 釋放修飾鍵.
至于如何在調(diào)用之間保存修飾鍵的狀態(tài)是Keyboard接口實(shí)現(xiàn)類的職責(zé)哪工。只有活躍的元素才會(huì)接收到這些事件。
Mouse接口
Mouse
接口包含以下方法(有可能不久之后會(huì)有變化):
- void click(WebElement onElement) - 同click()方法一樣.
- void doubleClick(WebElement onElement) - 雙擊一個(gè)元素.
- void mouseDown(WebElement onElement) - 在一個(gè)元素上按下左鍵并保持 Action selectMultiple = builder.build();
- void mouseUp(WebElement onElement) - 在一個(gè)元素上釋放左鍵.
- void mouseMove(WebElement toElement) - 從當(dāng)前位置移動(dòng)到一個(gè)元素
- void mouseMove(WebElement toElement, long xOffset, long yOffset) - 從當(dāng)前位置移動(dòng)到一個(gè)元素的偏移坐標(biāo)
- void contextClick(WebElement onElement) - 在一個(gè)元素上做一個(gè)右鍵操作
Native events(原生事件) VS synthetic events(合成事件)
WebDriver提供的高級(jí)用戶接口弧哎,要么是直接模擬的Javascript事件(即合成事件)雁比,要么就是讓瀏覽器生成Javascript事件(即原生事件)。原生事件能更好的模擬用戶交互撤嫩,而合成事件則是平臺(tái)獨(dú)立的偎捎,這使得使用了替代的窗口管理器的linux系統(tǒng)顯得尤其重要,具體參加native events on Linux序攘。原生事件無(wú)論什么時(shí)候總是應(yīng)該盡可能的使用茴她。
下面的表格展示了瀏覽器對(duì)事件的支持情況。
瀏覽器 | 操作系統(tǒng) | 原生事件 | 合成事件 |
---|---|---|---|
Firefox | Linux | 支持 | 支持(默認(rèn)) |
Firefox | Windows | 支持(默認(rèn)) | 支持 |
Internet Explorer | Windows | 支持(默認(rèn)) | 不支持 |
Chrome | Linux/Windows | 支持* | 不支持 |
Opera | Linux/Windows | 支持(默認(rèn)) | 不支持 |
HtmlUnit | Linux/Windows | 支持(默認(rèn)) | 不支持 |
ChromeDriver提供了2種模式來(lái)支持原生事件:Webkit事件和原始事件两踏。其中Webkit事件是使用Webkit函數(shù)來(lái)觸發(fā)的Javascript事件败京,而原始事件模式則使用的是操作系統(tǒng)級(jí)別的事件兜喻。
FirefoxDriver中梦染,原生事件可以使用FirefoxProfile來(lái)進(jìn)行開(kāi)關(guān)控制。
FirefoxProfile profile = new FirefoxProfile();
profile.setEnableNativeEvents(true);
FirefoxDriver driver = new FirefoxDriver(profile);
例子
以下是原生事件與合成事件表現(xiàn)不同的一些例子:
- 使用合成事件朴皆,點(diǎn)擊隱藏在其他元素下面的元素是可以的帕识。使用原生事件,瀏覽器會(huì)將點(diǎn)擊事件作用在所給坐標(biāo)最外層的元素上遂铡,就像是用戶點(diǎn)擊在特定的位置一樣肮疗。
- 當(dāng)一個(gè)用戶,按下TAB鍵希望焦點(diǎn)從當(dāng)前元素定位到下一個(gè)元素扒接,瀏覽器是可以做到的看蚜。使用合成事件的話歉糜,瀏覽器并不知道TAB鍵被按下了,因此也不會(huì)改變焦點(diǎn)。而使用原生事件梨撞,瀏覽器則會(huì)表現(xiàn)正確。
模塊化與類庫(kù)
線性測(cè)試
至此之前臂容,我們介紹的測(cè)試腳本嗅剖,盡管使用了unittest測(cè)試框架,但是測(cè)試是按照指定的線路進(jìn)行的矾利,是線性的測(cè)試姑裂,完全遵循了腳本的執(zhí)行順序。
測(cè)試腳本1
driver = FirefoxDriver()
driver.get("http://wwww.xxx.com")
driver.findElement(By.id("tbUserName")).sendKeys("username")
driver.findElement(By.id(("tbPassword")).sendKeys("123456")
driver.findElement(By.id(("btnLogin")).click()
#執(zhí)行具體用例操作
......
driver.quit ()
如上圖男旗,其實(shí)登錄的模塊可以共用舶斧。
模塊化
- 使用模塊化的業(yè)務(wù)組裝測(cè)試
- 業(yè)務(wù)從測(cè)試用例抽離
- 具體步驟:
用IDEA新建maven項(xiàng)目
修改pom.xml未指定的內(nèi)容
右上角(或者右下角)點(diǎn)擊
Enable Auto-import
在
src/main/java
新建Java Class(測(cè)試用例)-
新建的測(cè)試用例中寫(xiě)三個(gè)方法:(需要testNg的注解)
-
public void setUp
(@BeforeTest) -
public void tearDown
(@AfterTest) -
public void testXxx
(@Test)
-
在
src/main/java
新建Java Class2(測(cè)試業(yè)務(wù))-
在新建的測(cè)試業(yè)務(wù)類中寫(xiě)業(yè)務(wù)的方法:
- openUrl
- changeLanguage
- logIn
-
在新建的測(cè)試業(yè)務(wù)類中寫(xiě)
構(gòu)造方法
注意需要傳遞
driver
和url
public RanzhiCommon(WebDriver driver, String url){ this.baseDriver = driver; this.baseUrl = url; }
模塊話是自動(dòng)化測(cè)試的第一個(gè)延伸和基礎(chǔ)。需要對(duì)自動(dòng)化重復(fù)編寫(xiě)的腳本進(jìn)行重構(gòu)(refactor)察皇,將重復(fù)的腳本抽取出來(lái)捧毛,放到指定的代碼文件中,作為共用的功能模塊。
測(cè)試腳本1:RanzhiCommon.java
/**
* 實(shí)際上的登錄方法
*
* @param account:用戶名
* @param password:密碼
*/
public void logIn(String account, String password) {
WebDriver driver = this.driver;
// 輸入用戶名
WebElement accountElement = driver.findElement(By.id("account"));
accountElement.clear();
accountElement.sendKeys(account);
// 輸入密碼
WebElement passwordElement = driver.findElement(By.id("password"));
passwordElement.clear();
passwordElement.sendKeys(password);
// 點(diǎn)擊登錄按鈕
driver.findElement(By.id("submit")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
另一份文件 RanzhiCommon.java
/**
* 登出系統(tǒng)
*/
public void logOut() {
WebDriver driver = this.driver;
driver.findElement(By.id("start")).click();
driver.findElement(By.linkText("退出")).click();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
自動(dòng)化的測(cè)試:代碼如下
package com.hello;
import com.hello.library.RanzhiCommon;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
/**
* Created by Linty on 9/11/2016.
* 使用模塊化的測(cè)試用例
*/
public class RanzhiTestCase02 {
// 成員變量
private WebDriver driver;
private String baseUrl;
private RanzhiCommon common;
@Before
public void setUp() {
this.driver = new FirefoxDriver();
this.baseUrl = "http://172.31.95.220/ranzhi/www";
this.common = new RanzhiCommon(this.driver, this.baseUrl);
}
@After
public void tearDown() {
this.driver.quit();
this.common = null;
}
@Test
public void testLoginWithModule() {
WebDriver driver = this.driver;
RanzhiCommon common = this.common;
// 步驟一:打開(kāi)頁(yè)面
common.openWebPage("/");
Assert.assertEquals("登錄頁(yè)面打開(kāi)錯(cuò)誤",
this.baseUrl + "/sys/index.php?m=user&f=login&referer=L3JhbnpoaS93d3cvc3lzL2luZGV4LnBocA==",
driver.getCurrentUrl());
// 步驟二:切換語(yǔ)言
String actualLanguage = common.changeChinese();
Assert.assertEquals("系統(tǒng)語(yǔ)言切換失敗", "簡(jiǎn)體", actualLanguage);
// 步驟三:進(jìn)行登錄
common.logIn("admin", "123456");
Assert.assertEquals("登錄頁(yè)面登錄跳轉(zhuǎn)失敗",
this.baseUrl + "/sys/index.php?m=index&f=index",
driver.getCurrentUrl());
// 步驟四:退出系統(tǒng)
common.logOut();
Assert.assertEquals("登錄頁(yè)面退出跳轉(zhuǎn)失敗",
this.baseUrl + "/sys/index.php?m=user&f=login",
driver.getCurrentUrl());
}
}
參數(shù)化驅(qū)動(dòng)
數(shù)據(jù)驅(qū)動(dòng)
如果說(shuō)模塊化是自動(dòng)化測(cè)試的第一步呀忧,那么數(shù)據(jù)驅(qū)動(dòng)是自動(dòng)化的第二步师痕,從本意上來(lái)講。數(shù)據(jù)改變更新驅(qū)動(dòng)自動(dòng)化的執(zhí)行而账。從而引起測(cè)試結(jié)果的改變胰坟。其實(shí)類似于參數(shù)化。
-
使用數(shù)據(jù)驅(qū)動(dòng)測(cè)試
- 測(cè)試數(shù)據(jù)從用例抽離
- 常見(jiàn)的測(cè)試數(shù)據(jù)的形式:
- 外部文件(文本文件泞辐、Excel(帶有格式笔横,不容易讀))
- csv(默認(rèn)是用"
,
"隔開(kāi)每一列) - txt
- csv(默認(rèn)是用"
- 數(shù)據(jù)庫(kù)的方式
- MySQL (輕便)
- Oracle/ SQL Server
-
具體進(jìn)行數(shù)據(jù)驅(qū)動(dòng)的方式:
- 找一個(gè)類,或者編寫(xiě)一個(gè)類
- 找讀取csv的類:maven的方式
- 找一個(gè)類,或者編寫(xiě)一個(gè)類
示例代碼
@Test
public void testAddBatchUserByCsv() {
// 讀取 csv 文件到FilerReader中
// 用捕獲異常的方式 進(jìn)行文件讀取咐吼,防止出現(xiàn)“文件不存在”的異常
Reader in = null;
try {
in = new FileReader("src/main/resources/team_member.csv");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 讀取 csv 到 records中
Iterable<CSVRecord> records = null;
try {
records = CSVFormat.EXCEL.parse(in);
} catch (IOException e) {
e.printStackTrace();
}
// 遍歷 records吹缔,循環(huán)添加 userToAdd
for (CSVRecord record : records) {
RanzhiUser userToAdd = new RanzhiUser(
record.get(0), record.get(1),
Integer.parseInt(record.get(3)),
Integer.parseInt(record.get(4)),
record.get(2).toCharArray()[0],
record.get(5),
record.get(0) + record.get(6)
);
baseRanzhiCommon.login("admin", "123456");
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedMainUrl = baseUrl + "sys/index.php?m=index&f=index";
Assert.assertEquals("登錄成功主頁(yè)跳轉(zhuǎn)失敗", expectedMainUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.selectApp(RanzhiApp.Admin);
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedAdminUrl = baseUrl + "sys/index.php?m=admin&f=index";
Assert.assertEquals("后臺(tái)管理主頁(yè)跳轉(zhuǎn)失敗", expectedAdminUrl, baseDriver.getCurrentUrl());
baseDriver.switchTo().frame("iframe-superadmin");
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
baseRanzhiCommon.selectSubMenuForAdmin(AdminSubMenu.Organization);
String expectedOrganizationUrl = baseUrl + "sys/index.php?m=admin&f=index";
Assert.assertEquals("后臺(tái)管理組織跳轉(zhuǎn)失敗", expectedOrganizationUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.clickAddUserButton();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedAddUserUrl = baseUrl + "sys/index.php?m=user&f=create";
Assert.assertEquals("添加成員主頁(yè)跳轉(zhuǎn)失敗", expectedAddUserUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.addNewUser(userToAdd);
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
try {
sleep(5000);
} catch (InterruptedException ignored) {
}
String expectedUserSavedUrl = baseUrl + "sys/index.php?m=user&f=admin";
Assert.assertEquals("用戶保存跳轉(zhuǎn)失敗", expectedUserSavedUrl, baseDriver.getCurrentUrl());
baseDriver.switchTo().defaultContent();
baseRanzhiCommon.logout();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedLogoutUrl = baseUrl + "sys/index.php?m=user&f=login";
Assert.assertEquals("退出登錄頁(yè)面跳轉(zhuǎn)錯(cuò)誤", expectedLogoutUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.login(userToAdd.getAccount(), userToAdd.getPassword());
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedMainUrl2 = baseUrl + "sys/index.php?m=index&f=index";
Assert.assertEquals("登錄成功主頁(yè)跳轉(zhuǎn)失敗", expectedMainUrl2, baseDriver.getCurrentUrl());
baseRanzhiCommon.logout();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedLogoutUrl2 = baseUrl + "sys/index.php?m=user&f=login";
Assert.assertEquals("退出登錄頁(yè)面跳轉(zhuǎn)錯(cuò)誤", expectedLogoutUrl2, baseDriver.getCurrentUrl());
}
}
關(guān)于參數(shù)化驅(qū)動(dòng),我們可以將數(shù)據(jù)放到csv中锯茄,然后通過(guò)讀取csv的數(shù)據(jù)進(jìn)行自動(dòng)化測(cè)試厢塘。同時(shí)也可以使用數(shù)據(jù)庫(kù)嘗試,代碼如下:
@Test
public void testAddBatchUserByDb() {
Statement stmt = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/test?" +
"user=root&password=");
} catch (SQLException e) {
e.printStackTrace();
}
stmt = conn.createStatement();
String sql = "SELECT \n" +
" `account`,\n" +
" `realname`,\n" +
" `gender`,\n" +
" `dept`,\n" +
" `role`,\n" +
" `password`,\n" +
" `email` \n" +
"FROM\n" +
" `test`.`userlist` \n" +
"LIMIT 0, 1000 ;";
rs = stmt.executeQuery(sql);
// or alternatively, if you don't know ahead of time that
// the query will be a SELECT...
if (stmt.execute(sql)) {
rs = stmt.getResultSet();
System.out.println(rs.next());
}
if (rs != null) {
while (rs.next()) {
RanzhiUser userToAdd = new RanzhiUser(
rs.getString("account"),
rs.getString("realname"),
Integer.parseInt(rs.getString("role")),
Integer.parseInt(rs.getString("dept")),
rs.getString("gender").toCharArray()[0],
rs.getString("password"),
rs.getString("account") + rs.getString("email")
);
baseRanzhiCommon.login("admin", "123456");
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedMainUrl = baseUrl + "sys/index.php?m=index&f=index";
Assert.assertEquals("登錄成功主頁(yè)跳轉(zhuǎn)失敗", expectedMainUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.selectApp(RanzhiApp.Admin);
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedAdminUrl = baseUrl + "sys/index.php?m=admin&f=index";
Assert.assertEquals("后臺(tái)管理主頁(yè)跳轉(zhuǎn)失敗", expectedAdminUrl, baseDriver.getCurrentUrl());
baseDriver.switchTo().frame("iframe-superadmin");
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
baseRanzhiCommon.selectSubMenuForAdmin(AdminSubMenu.Organization);
String expectedOrganizationUrl = baseUrl + "sys/index.php?m=admin&f=index";
Assert.assertEquals("后臺(tái)管理組織跳轉(zhuǎn)失敗", expectedOrganizationUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.clickAddUserButton();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedAddUserUrl = baseUrl + "sys/index.php?m=user&f=create";
Assert.assertEquals("添加成員主頁(yè)跳轉(zhuǎn)失敗", expectedAddUserUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.addNewUser(userToAdd);
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
try {
sleep(5000);
} catch (InterruptedException ignored) {
}
String expectedUserSavedUrl = baseUrl + "sys/index.php?m=user&f=admin";
Assert.assertEquals("用戶保存跳轉(zhuǎn)失敗", expectedUserSavedUrl, baseDriver.getCurrentUrl());
baseDriver.switchTo().defaultContent();
baseRanzhiCommon.logout();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedLogoutUrl = baseUrl + "sys/index.php?m=user&f=login";
Assert.assertEquals("退出登錄頁(yè)面跳轉(zhuǎn)錯(cuò)誤", expectedLogoutUrl, baseDriver.getCurrentUrl());
baseRanzhiCommon.login(userToAdd.getAccount(), userToAdd.getPassword());
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedMainUrl2 = baseUrl + "sys/index.php?m=index&f=index";
Assert.assertEquals("登錄成功主頁(yè)跳轉(zhuǎn)失敗", expectedMainUrl2, baseDriver.getCurrentUrl());
baseRanzhiCommon.logout();
baseDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
String expectedLogoutUrl2 = baseUrl + "sys/index.php?m=user&f=login";
Assert.assertEquals("退出登錄頁(yè)面跳轉(zhuǎn)錯(cuò)誤", expectedLogoutUrl2, baseDriver.getCurrentUrl());
}
}
// Now do something with the ResultSet ....
} catch (SQLException ex) {
// handle any errors
System.out.println("SQLException: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("VendorError: " + ex.getErrorCode());
} finally {
// it is a good idea to release
// resources in a finally{} block
// in reverse-order of their creation
// if they are no-longer needed
if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) {
} // ignore
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException sqlEx) {
} // ignore
stmt = null;
}
}
}
Git的使用
- git
git vs svn
配置管理工具
手工測(cè)試:文檔(測(cè)試計(jì)劃肌幽、用例晚碾、測(cè)試報(bào)告、需求喂急、用戶手冊(cè))
開(kāi)發(fā):源代碼管理
-
安裝Git的步驟
教室內(nèi)網(wǎng)安裝包:
\\172.31.95.220\share\01-新教學(xué)軟件\01-自動(dòng)化測(cè)試\git安裝
- 安裝git程序
git官網(wǎng):https://git-scm.com
git本身是命令行工具
官網(wǎng)推薦git桌面工具:SourceTree - 安裝TortoiseGit客戶端
- 安裝git程序
-
選擇Git倉(cāng)庫(kù)
- 最有名的 https://github.com格嘁,開(kāi)源代碼的主戰(zhàn)場(chǎng)
- 國(guó)內(nèi)的
- coding.net
- 碼云:git.oschina.net
- code.csdn.net
- 選擇 coding.net
-
具體使用步驟:
- 在coding.net 初始化Git倉(cāng)庫(kù)(先注冊(cè)用戶,登錄)
- 創(chuàng)建項(xiàng)目(啟用README.md初始化項(xiàng)目)
- md文件:Markdown文件
- 在coding.net的項(xiàng)目左側(cè)廊移,找到
代碼
糕簿,復(fù)制旁邊的路徑 - 在非系統(tǒng)盤創(chuàng)建文件夾 git
- 進(jìn)入git,右鍵選擇
git clone
- 彈出的對(duì)話框中狡孔,默認(rèn)就有剛剛復(fù)制的路徑懂诗,檢查文件存放的位置
- 比如 git\selenium_weekend
- 確定,輸入用戶名 + 密碼
- 初始化成功
-
修改文件并提交(用TortoiseGit操作)
- 從Git倉(cāng)庫(kù)更新當(dāng)前的文件
- TortoiseGit右鍵步氏,選擇Git Sync
- 彈出的對(duì)話框中响禽,選擇pull:拉取文件
- 如果是私有Git倉(cāng)庫(kù),需要輸入用戶名 + 密碼
- 編輯需要的文件
- 提交所處理的變動(dòng)
- TortoiseGit右鍵荚醒,選擇Git Commit
- 選擇push:提交文件
- 如果是私有Git倉(cāng)庫(kù)芋类,需要輸入用戶名 + 密碼
- 從Git倉(cāng)庫(kù)更新當(dāng)前的文件
-
使用編程工具提交代碼(用IDEA或者PyCharm)
- 在本地的Git項(xiàng)目文件夾中創(chuàng)建項(xiàng)目
- 比如:git\selenium_weekend
- 用IDEA 創(chuàng)建
Maven
項(xiàng)目- 注意
project location
務(wù)必在git\selenium_weekend
- 比如 項(xiàng)目名字
HelloSelenium
- 項(xiàng)目路徑:
git\selenium_weekend\HelloSelenium
- 注意
- 覆蓋
pom.xml
- 編寫(xiě) 代碼
- 選擇 IDEA的 VCS |
Enable Version Control Integration
- 彈出的窗口選擇 Git
- 所有的代碼文件名字變成紅色
- 右鍵 左側(cè)的項(xiàng)目名字
HelloSelenium
- 選擇
Git
|Add
- 所有的代碼文件名字變成綠色
- 選擇
- 右鍵 左側(cè)的項(xiàng)目名字
HelloSelenium
- 選擇
Git
|Commit Directory
- 左側(cè)填寫(xiě) 說(shuō)明
- 右側(cè)勾選
Reformat Code
- 選擇右下角
Commit And Push
- "XX文件已經(jīng)committed"以后,點(diǎn)擊
Push
- 輸入用戶名 + 密碼界阁,勾選 remember password
- push successful
- 選擇
- 在本地的Git項(xiàng)目文件夾中創(chuàng)建項(xiàng)目
階段小結(jié)
這里的數(shù)據(jù)換成了特別的數(shù)據(jù)侯繁,就是關(guān)鍵字。
Selenium IDE 的作用
通過(guò)錄制頁(yè)面元素的定位和操作泡躯,進(jìn)行自定義命令的編輯贮竟,(Select定位頁(yè)面元素)丽焊,導(dǎo)出指定的帶單元測(cè)試框架的腳本(自動(dòng)化測(cè)試用例),輔助代碼編寫(xiě)咕别,以及快速入門技健。-
Selenium Java 環(huán)境搭建
使用maven的方式,管理依賴項(xiàng)惰拱,進(jìn)行環(huán)境搭建 新建maven的項(xiàng)目雌贱,編輯項(xiàng)目中的 pom.xml,*.jar jar包
- 依賴項(xiàng)要添加對(duì)偿短,包名和版本
- 電腦聯(lián)網(wǎng)欣孤,需要連接訪問(wèn)maven倉(cāng)庫(kù)
-
Selenium WebDriver 的元素定位
需要注意iFrame-
單一元素定位
id, name, class name(不靠譜), tag name(不靠譜)
xpath, css selector, link text, partial link textclass是以
.
開(kāi)頭id 是以
#
開(kāi)頭css selector:
#langs > button
xpath:
//*[@id="langs"]/button
-
xpath(絕對(duì)路徑):
/html/body/div/div/div[1]/div/div/button
解釋
<html> <body> <div> <div> <div> <div> <div> <button></button> </div> </div> </div> <div></div> </div> <div> </body> </html>
?
-
定位一系列元素
id name (不靠譜)
class name
css selectorxpath
-
-
Selenium 使用 單元測(cè)試框架
Java: junit4/TestNG Python: unittest
項(xiàng)目 Java Python 方法名 小駱駝 testRanzhiLogin() 類C的命名test_ranzhi_login() 用例的步驟 @Test(方法以test開(kāi)頭) test_開(kāi)頭 用例的前置條件 @Before(方法 setUp) 重寫(xiě)setUp() 用例的清場(chǎng)方式 @After(方法 tearDown) 重寫(xiě)tearDown() 依賴的處理 添加junit依賴項(xiàng)到pom.xml import unittest 寫(xiě)一個(gè)測(cè)試用例類,繼承unittest.TestCase -
Selenium 使用 模塊化 用例設(shè)計(jì)
library文件夾
RanzhiCommon.java-
面向?qū)ο螅簩?shí)例化一個(gè)對(duì)象(注意構(gòu)造方法)昔逗,調(diào)用對(duì)象的成員降传,包括普通方法,變量
Java實(shí)體類
Java枚舉類型設(shè)計(jì)模式:自動(dòng)化測(cè)試框架勾怒,頁(yè)面對(duì)象模式
python 面向?qū)ο?/p>
dict類型
-
Selenium 使用 數(shù)據(jù)驅(qū)動(dòng) 進(jìn)行用例設(shè)計(jì)和執(zhí)行
- 讀文件進(jìn)行測(cè)試(準(zhǔn)備的用例數(shù)據(jù))
- 讀數(shù)據(jù)庫(kù)進(jìn)行測(cè)試(不推薦)
- 代碼中婆排,注意關(guān)閉數(shù)據(jù)庫(kù)連接
- 代碼中,注意SQL腳本的效率控硼,
- 替代方案泽论,導(dǎo)出數(shù)據(jù)庫(kù)的數(shù)據(jù)到文件中艾少,然后讀文件卡乾,或者把該文件導(dǎo)入本地的數(shù)據(jù)庫(kù),注意類型轉(zhuǎn)換缚够,尤其是日期類型幔妨。
- 新建一個(gè)
CsvUtility.java
,新建一個(gè)DbUtility.java
-
Selenium 工具的使用匯總 IDEA 和 Git
`IDEA` `PyCharm` 打開(kāi)對(duì)的文件夾(作為項(xiàng)目) Reformat Code (Ctrl + Alt + L) Refactor | rename 重構(gòu)谍椅,修改名字误堡,批量關(guān)聯(lián)的級(jí)聯(lián)修改 按下ctrl+鼠標(biāo)左鍵,導(dǎo)航定位的聲明處 紅燈處理雏吭,選中錯(cuò)誤的行锁施,然后等紅燈出來(lái),點(diǎn)右上角的箭頭 Java里面 用 Alt + Enter 鍵 Git的集成: github.io coding.net git.oschina.net bitbucket.org code.csdn.net(騰訊的測(cè)試工具 apt)
- 在線建倉(cāng)庫(kù)
- git clone 下載到本地(需要新建非系統(tǒng)盤的 git文件夾)
- git pull 獲取遠(yuǎn)程變化到本地
- commit 提交到本地
- push 提交到遠(yuǎn)程
集成在IDEA中
菜單 VCS | Enable ……杖们,里面選擇git
提交git 需要先add
導(dǎo)出一份 完全沒(méi)有版本管理信息的代碼悉抵。 export
- 在線建倉(cāng)庫(kù)