自動(dòng)化測(cè)試(10) | Selenium Java 封裝

WebDriver 封裝

歡迎閱讀WebDriver封裝講義。本篇講義將會(huì)重點(diǎn)介紹Selenium WebDriver API的封裝的概念和方法,以及使用封裝進(jìn)行自動(dòng)化測(cè)試的設(shè)計(jì)。

WebDriver API 封裝

封裝的概念

從之前的講義和學(xué)習(xí)中,我們知道,WebDriver API的調(diào)用以及自動(dòng)化測(cè)試,我們也初步接觸了線性測(cè)試、以及模塊化自動(dòng)化測(cè)試和數(shù)據(jù)驅(qū)動(dòng)測(cè)試,那么回顧之前的內(nèi)容,我們不只是可以利用WebDriver提供的一系列的定位符以便使用元素定位方法,我們這里開(kāi)始嘗試封裝后調(diào)用。首先,我們從封裝的概念開(kāi)始。

封裝是一個(gè)面向?qū)ο缶幊痰母拍?,是面向?qū)ο缶幊痰暮诵膶傩?,通過(guò)將代碼內(nèi)部實(shí)現(xiàn)進(jìn)行密封和包裝,從而簡(jiǎn)化編程。

所謂“對(duì)象”,形象地說(shuō),我們可以把它理解為一塊積木。設(shè)計(jì)積木的人需要設(shè)計(jì)積木的外觀與形狀,還有內(nèi)部的材質(zhì)。堆積木的人對(duì)于內(nèi)部的材質(zhì)并不關(guān)心,他們只需要根據(jù)不同的外觀與形狀來(lái)決定堆放的位置。因此,對(duì)于開(kāi)發(fā)者而言,要設(shè)計(jì)面向?qū)ο蟮某绦?,同時(shí)會(huì)是兩個(gè)迥然不同的身份:設(shè)計(jì)者與使用者。

先談?wù)勈褂谜?。使用者的身份,就是利用已?jīng)提供給你的所有對(duì)象,根據(jù)需求,設(shè)計(jì)出自己需要實(shí)現(xiàn)的程序。就如堆積木的過(guò)程。這恰恰是面向?qū)ο缶幊痰膬?yōu)勢(shì)所在,那就是“對(duì)象的重用”。已經(jīng)設(shè)計(jì)好的對(duì)象,可以被不同的使用者調(diào)用,這些功能既然已經(jīng)實(shí)現(xiàn),對(duì)于使用者而言,當(dāng)然就免去了自己去設(shè)計(jì)的過(guò)程。正如堆積木那樣,既然有了現(xiàn)成設(shè)計(jì)好的積木,使用者所要做的工作就是把這些積木最后組合起來(lái),堆成不同的形狀。WebDriver所提供的類庫(kù),就是這樣的積木。那么我們以下的操作將會(huì)基于上述的定位符進(jìn)行定位操作。

前面說(shuō)到對(duì)象好比是一個(gè)積木,設(shè)計(jì)者需要定義好這個(gè)積木的外觀和形狀,也要考慮積木內(nèi)部的制作,例如選用的材質(zhì),以及是空心還是實(shí)心。如果將這個(gè)積木剖開(kāi)來(lái)看,實(shí)際上該對(duì)象應(yīng)分為內(nèi)、外兩層。由于使用者只關(guān)心外部的實(shí)現(xiàn),因此設(shè)計(jì)者就需要考慮,哪些實(shí)現(xiàn)應(yīng)暴露在外,哪些實(shí)現(xiàn)應(yīng)隱藏于內(nèi)。這就體現(xiàn)了對(duì)象的封裝的思想。

簡(jiǎn)而言之,封裝就是把原始和原生的方法進(jìn)行再包裝。將原始的代碼用心的代碼包裝起來(lái),通過(guò)對(duì)新代碼的調(diào)用,來(lái)使用原始的代碼的過(guò)程。

封裝的好處

對(duì)Selenium進(jìn)行封裝的好處主要有如下三個(gè)方面:

  • 使用成本低
    1. 不需要要求所有的測(cè)試工程師會(huì)熟練使用Selenium,而只需要會(huì)使用封裝以后的代碼
    2. 不需要對(duì)所有的測(cè)試工程師進(jìn)行完整培訓(xùn)。也避免工作交接的成本。
    3. 測(cè)試人員使用統(tǒng)一的代碼庫(kù)
  • 維護(hù)成本低
    1. 通過(guò)封裝,在代碼發(fā)生大范圍變化和遷移的時(shí)候,不需要維護(hù)所有代碼,只需要變更封裝的部分即可
    2. 維護(hù)代碼不需要有大量的工程師,只需要有核心的工程師進(jìn)行封裝的維護(hù)即可
  • 代碼安全性
    1. 對(duì)作為第三方的Selenium進(jìn)行封裝,是代碼安全的基礎(chǔ)。
    2. 對(duì)于任何的代碼的安全隱患,必須由封裝來(lái)解決,使得風(fēng)險(xiǎn)可控。
    3. 使用者并不知道封裝內(nèi)部的代碼結(jié)構(gòu)。

封裝的目的

封裝,最終為了實(shí)現(xiàn)自動(dòng)化測(cè)試框架。在自動(dòng)化測(cè)試領(lǐng)域,有一個(gè)經(jīng)典的問(wèn)題:“既然可以編寫或者通過(guò)record & playback可以做一個(gè)腳本,那么為什么還需要自動(dòng)化測(cè)試框架呢?”簡(jiǎn)而言之,就是為什么需要如此這般麻煩的設(shè)計(jì)和編寫自動(dòng)化測(cè)試框架,不是原本已經(jīng)可以做自動(dòng)化測(cè)試了么?

這個(gè)回答可以很“官方”,從維護(hù)性、重用性、安全性和成本等角度可以回答。

在這點(diǎn),就好比是建造房子。在沒(méi)有設(shè)計(jì)框架的基礎(chǔ)上,我們或許可以建造一個(gè)樓房,最多也就三兩層吧;但是對(duì)于一份經(jīng)過(guò)完整設(shè)計(jì)的圖紙,人們可以建造出高樓大廈。

封裝,就是把基礎(chǔ)的石頭等建材,通過(guò)我們的個(gè)性化的方法,將地基可用而安全的搭建好。是自動(dòng)化測(cè)試框架的基石。

自動(dòng)化測(cè)試模型

自動(dòng)化測(cè)試模型.png
  1. 封裝 Selenium 為 BoxDriver
  2. 在 測(cè)試用例中,實(shí)例化 BoxDriver,產(chǎn)生 bd 對(duì)象
  3. 使用 bd 對(duì)象,構(gòu)造 業(yè)務(wù)模塊的實(shí)例化對(duì)象,產(chǎn)生 common
  4. 使用 common 在測(cè)試用例中,構(gòu)建測(cè)試步驟
  5. 使用數(shù)據(jù)驅(qū)動(dòng)的外部數(shù)據(jù),通過(guò)讀取,進(jìn)行測(cè)試
  6. 執(zhí)行整個(gè)用例

使用封裝進(jìn)行自動(dòng)化測(cè)試

封裝技術(shù)

  1. 面向?qū)ο蟮木幊?/li>
  • 類:模板,設(shè)計(jì)
  • 構(gòu)造方法
  • 成員變量
  • 成員方法
  • 類的實(shí)例化產(chǎn)生對(duì)象
  • 實(shí)例化過(guò)程: new 關(guān)鍵字
  • 實(shí)例化的方法: 類名(參數(shù))--> 構(gòu)造方法
  • 實(shí)例化過(guò)程相當(dāng)于 類執(zhí)行了構(gòu)造方法,產(chǎn)生了 this
  • 實(shí)例化出來(lái)的對(duì)象,可以訪問(wèn)成員方法
  1. 封裝:做一個(gè)模板,這個(gè)模板有webdriver所有的功能
  • 封裝 webdriver,自動(dòng)化 WebUI 測(cè)試
  • 封裝 Appium,自動(dòng)化 APP UI 測(cè)試
  • 封裝 自定義的 Web接口 Driver
  1. 其他公司的開(kāi)源框架

封裝示例

測(cè)試腳本:以下的腳本

  • 找到一個(gè)指定輸入框(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);
    }
    

    ?

  • 找到一個(gè)可以點(diǎn)擊的元素(selector),并且點(diǎn)擊(click)

    click(selector)

    public void click(String selector) {
      this.locateElement(selector).click();
    }
    

    ?

  • 找到一個(gè)指定的frame,并且切換進(jìn)去

    switchToFrame(selector)

    public void switchToFrame(String selector) {
      WebElement we = this.locateElement(selector);
      this.baseDriver.switchTo().frame(we);
    }
    
  • 找到一個(gè)指定的select,并且通過(guò)index進(jìn)行選擇

    selectByIndex(selector, index)

    public void selectByIndex(String selector, int index) {
      WebElement we = this.locateElement(selector);
      Select s = new Select(we);
      s.selectByIndex(index);
    }
    

    ?

以上的代碼是封裝了locateElement()的幾種方法,在具體使用封裝過(guò)的代碼的時(shí)候,只需要簡(jiǎn)單的調(diào)用即可。接下來(lái)的重點(diǎn),是介紹 locateElement(selector)的封裝方式。

  • 查找元素:findElement(By...)
  • 支持各種的查找:8種方式都需要支持,必須通過(guò) selector 顯示出分類
    • selector中需要包含一個(gè)特殊符號(hào)
    • 實(shí)例化 封裝好的類的時(shí)候,需要約定好是什么特殊符號(hào)
      1. 強(qiáng)制性用硬編碼 hard code來(lái)實(shí)例化,例如 , 或者 ? 或者 其他非常用字符 =>
      2. 或者,構(gòu)造方法中,傳遞 this.byChar
  • 要把查找到元素的返回給調(diào)用的地方:必須要有返回值,類型是 WebElement
private WebElement locateElement(String selector) {
  WebElement we;
  // 如果定位符中 有 分隔符,那么就從分隔符處分成兩段
  // 第一段是By
  // 第二段是真正的定位符
  // 如果沒(méi)有分隔符,就默認(rèn)用 id 定位
  if (!selector.contains(this.byChar)) {
    // 用 id 定位
    we = this.baseDriver.findElement(By.id(selector));
  } else {
    // 用 分隔符 分成兩個(gè)部分
    String by = selector.split(this.byChar)[0];
    String value = selector.split(this.byChar)[1];
    we = findElementByChar(by, value);
  }
  return we;
}
  • 接下來(lái)的重點(diǎn),是實(shí)現(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

類型 示例(分隔符以逗號(hào),為例) 描述
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");
  // 點(diǎn)擊登錄按鈕后,需要等待瀏覽器刷新
  Thread.sleep(2000);
}

自動(dòng)化的測(cè)試代碼示例

@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);
}

封裝需要注意的部分

  • 方法有無(wú)返回值、和參數(shù)

    • 如果有返回值:確保代碼所有的路徑都會(huì)有返回值

      public WebElement getElement(){
        if (xxx) {
          return yyy;
        }
      }
      // 上述代碼會(huì)報(bào)錯(cuò),如果xxx條件不符合,就沒(méi)有返回值的路徑了。
      
    • 如果有參數(shù),確保非值類型的參數(shù)是非空的 不是 null

      public void type(Driver driver){
        driver.xxx()
      }
      // 如果driver是null,那么xxx會(huì)直接報(bào)錯(cuò)。 NullPointerException錯(cuò)誤異常
      
    • 如果有較多的分支語(yǔ)句,需要注意 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語(yǔ)句,python使用的是 if ... elif

      def get_element(selector):
          if selector == "id" or selecor == "i":
              driver.find_element_by_id(xxx)
          elif selector == "x":
              xxx
      

      ?

  • 封裝中,WebDriver對(duì)象的傳遞

    • 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....
        }
      }
      // 上述代碼中,會(huì)打開(kāi)多個(gè)火狐瀏覽器
      // 正確的做法:聲明一個(gè)全局的類的成員變量,進(jìn)行賦值使用。
      

      ?

關(guān)于Firefox Profile的使用

在做自動(dòng)化測(cè)試的時(shí)候,經(jīng)常會(huì)發(fā)現(xiàn)Firefox 被初始化,提示收藏夾等,遮住元素窗口。因?yàn)镕irefox每次都以默認(rèn)的Profile 加載全新的Firefox,例如如下圖片:

Snap1.jpg

在這樣的情況下,我們需要專門準(zhǔn)備一份獨(dú)立的Firefox Profile進(jìn)行使用。步驟如下

  1. 在命令行中進(jìn)入Firefox的安裝目錄,然后輸入 firefox -ProfileManager -no-remote

    Snap2.jpg
  2. 然后在彈出的窗口中,新建一個(gè)Profile,記錄地址。

    e5792a15-e7ed-39fa-bf46-5505c54923a5.png
  3. 根據(jù)剛剛的地址,打開(kāi)Profile所在的文件夾:C:\Users\Linty\AppData\Roaming\Mozilla\Firefox\Profiles

    Snap3.jpg
  4. 復(fù)制 einy9uds.selenium文件夾到 base目錄中。

    Snap4.jpg
  5. 修改代碼 automate_driver.py

    def __init__(self, by_char=",", firefox_profile=None):
        """
        構(gòu)造方法:實(shí)例化 BoxDriver 時(shí)候使用
        :param by_char: 分隔符
        :param firefox_profile:
        可選擇的參數(shù),如果不傳遞,就是None
        如果傳遞一個(gè) profile,就會(huì)按照預(yù)先的設(shè)定啟動(dòng)火狐
        去掉遮擋元素的提示框等
        """
        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!")
    
  6. 修改代碼 xxx_test_cases.py

    profile = "base\\einy9uds.selenium"
    self.base_driver = AutomateDriver(firefox_profile=profile)
    
  7. 完畢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容