自動(dòng)化測(cè)試(9) | Selenium Java 進(jìn)階

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 &amp; 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 &amp; 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è)接口:MouseKeyboard涯曲。這些接口被所有支持高級(jí)用戶接口API的driver實(shí)現(xiàn)了野哭。需要注意的是,這些接口是為了讓動(dòng)作使用的幻件,而不是最終用戶拨黔。本節(jié)的信息,主要是針對(duì)想擴(kuò)展WebDriver的開(kāi)發(fā)者的绰沥。

一字警告

KeyboardMouse接口是設(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í)登錄的模塊可以共用舶斧。

模塊化

  1. 使用模塊化的業(yè)務(wù)組裝測(cè)試
    • 業(yè)務(wù)從測(cè)試用例抽離
    • 具體步驟:
      1. 用IDEA新建maven項(xiàng)目

      2. 修改pom.xml未指定的內(nèi)容

      3. 右上角(或者右下角)點(diǎn)擊 Enable Auto-import

      4. src/main/java 新建Java Class(測(cè)試用例)

      5. 新建的測(cè)試用例中寫(xiě)三個(gè)方法:(需要testNg的注解)

        • public void setUp(@BeforeTest)
        • public void tearDown(@AfterTest)
        • public void testXxx(@Test)
      6. src/main/java 新建Java Class2(測(cè)試業(yè)務(wù))

      7. 在新建的測(cè)試業(yè)務(wù)類中寫(xiě)業(yè)務(wù)的方法:

        • openUrl
        • changeLanguage
        • logIn
      8. 在新建的測(cè)試業(yè)務(wù)類中寫(xiě)構(gòu)造方法

        注意需要傳遞driverurl

        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ù)的形式:
    1. 外部文件(文本文件泞辐、Excel(帶有格式笔横,不容易讀))
      • csv(默認(rèn)是用","隔開(kāi)每一列)
      • txt
    2. 數(shù)據(jù)庫(kù)的方式
      • MySQL (輕便)
      • Oracle/ SQL Server
  • 具體進(jìn)行數(shù)據(jù)驅(qū)動(dòng)的方式:

    1. 找一個(gè)類,或者編寫(xiě)一個(gè)類
      • 找讀取csv的類:maven的方式

示例代碼

@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的使用

  1. 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安裝

    1. 安裝git程序
      git官網(wǎng):https://git-scm.com
      git本身是命令行工具
      官網(wǎng)推薦git桌面工具:SourceTree
    2. 安裝TortoiseGit客戶端
  • 選擇Git倉(cāng)庫(kù)

    1. 最有名的 https://github.com格嘁,開(kāi)源代碼的主戰(zhàn)場(chǎng)
    2. 國(guó)內(nèi)的
      • coding.net
      • 碼云:git.oschina.net
      • code.csdn.net
    3. 選擇 coding.net
  • 具體使用步驟:

    1. 在coding.net 初始化Git倉(cāng)庫(kù)(先注冊(cè)用戶,登錄)
    2. 創(chuàng)建項(xiàng)目(啟用README.md初始化項(xiàng)目)
      • md文件:Markdown文件
    3. 在coding.net的項(xiàng)目左側(cè)廊移,找到代碼糕簿,復(fù)制旁邊的路徑
    4. 在非系統(tǒng)盤創(chuàng)建文件夾 git
    5. 進(jìn)入git,右鍵選擇 git clone
    6. 彈出的對(duì)話框中狡孔,默認(rèn)就有剛剛復(fù)制的路徑懂诗,檢查文件存放的位置
      • 比如 git\selenium_weekend
      • 確定,輸入用戶名 + 密碼
    7. 初始化成功
  • 修改文件并提交(用TortoiseGit操作)

    1. 從Git倉(cāng)庫(kù)更新當(dāng)前的文件
      • TortoiseGit右鍵步氏,選擇Git Sync
      • 彈出的對(duì)話框中响禽,選擇pull:拉取文件
      • 如果是私有Git倉(cāng)庫(kù),需要輸入用戶名 + 密碼
    2. 編輯需要的文件
    3. 提交所處理的變動(dòng)
      • TortoiseGit右鍵荚醒,選擇Git Commit
      • 選擇push:提交文件
      • 如果是私有Git倉(cāng)庫(kù)芋类,需要輸入用戶名 + 密碼
  • 使用編程工具提交代碼(用IDEA或者PyCharm)

    1. 在本地的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
    2. 覆蓋 pom.xml
    3. 編寫(xiě) 代碼
    4. 選擇 IDEA的 VCS | Enable Version Control Integration
    5. 彈出的窗口選擇 Git
      • 所有的代碼文件名字變成紅色
    6. 右鍵 左側(cè)的項(xiàng)目名字 HelloSelenium
      • 選擇 Git | Add
      • 所有的代碼文件名字變成綠色
    7. 右鍵 左側(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

階段小結(jié)

這里的數(shù)據(jù)換成了特別的數(shù)據(jù)侯繁,就是關(guān)鍵字。

  1. Selenium IDE 的作用
    通過(guò)錄制頁(yè)面元素的定位和操作泡躯,進(jìn)行自定義命令的編輯贮竟,(Select定位頁(yè)面元素)丽焊,導(dǎo)出指定的帶單元測(cè)試框架的腳本(自動(dòng)化測(cè)試用例),輔助代碼編寫(xiě)咕别,以及快速入門技健。

  2. 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ù)
  3. Selenium WebDriver 的元素定位
    需要注意iFrame

    • 單一元素定位

      id, name, class name(不靠譜), tag name(不靠譜)
      xpath, css selector, link text, partial link text

      • class是以開(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 selector

      xpath

  4. 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
  5. Selenium 使用 模塊化 用例設(shè)計(jì)

    1. library文件夾
      RanzhiCommon.java

    2. 面向?qū)ο螅簩?shí)例化一個(gè)對(duì)象(注意構(gòu)造方法)昔逗,調(diào)用對(duì)象的成員降传,包括普通方法,變量

      Java實(shí)體類
      Java枚舉類型

      設(shè)計(jì)模式:自動(dòng)化測(cè)試框架勾怒,頁(yè)面對(duì)象模式

      python 面向?qū)ο?/p>

      dict類型

  6. Selenium 使用 數(shù)據(jù)驅(qū)動(dòng) 進(jìn)行用例設(shè)計(jì)和執(zhí)行

    1. 讀文件進(jìn)行測(cè)試(準(zhǔn)備的用例數(shù)據(jù))
    2. 讀數(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)換缚够,尤其是日期類型幔妨。
    3. 新建一個(gè)CsvUtility.java,新建一個(gè)DbUtility.java
  7. 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市摘完,隨后出現(xiàn)的幾起案子姥饰,更是在濱河造成了極大的恐慌,老刑警劉巖孝治,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列粪,死亡現(xiàn)場(chǎng)離奇詭異审磁,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)岂座,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門态蒂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人费什,你說(shuō)我怎么就攤上這事吃媒。” “怎么了吕喘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵赘那,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我氯质,道長(zhǎng)募舟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任闻察,我火速辦了婚禮拱礁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辕漂。我一直安慰自己呢灶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布钉嘹。 她就那樣靜靜地躺著鸯乃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跋涣。 梳的紋絲不亂的頭發(fā)上缨睡,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音陈辱,去河邊找鬼奖年。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沛贪,可吹牛的內(nèi)容都是我干的陋守。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼利赋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼水评!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隐砸,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤之碗,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后季希,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體褪那,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幽纷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了博敬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片友浸。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖偏窝,靈堂內(nèi)的尸體忽然破棺而出收恢,到底是詐尸還是另有隱情,我是刑警寧澤祭往,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布伦意,位于F島的核電站,受9級(jí)特大地震影響硼补,放射性物質(zhì)發(fā)生泄漏驮肉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一已骇、第九天 我趴在偏房一處隱蔽的房頂上張望离钝。 院中可真熱鬧,春花似錦褪储、人聲如沸卵渴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浪读。三九已至,卻和暖如春宛裕,著一層夾襖步出監(jiān)牢的瞬間瑟啃,已是汗流浹背论泛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工揩尸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屁奏。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓岩榆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親坟瓢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勇边,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容