1. 程式人生 > >基於Selenium的web自動化框架(python)

基於Selenium的web自動化框架(python)

1 什麼是selenium

Selenium 是一個基於瀏覽器的自動化工具,它提供了一種跨平臺、跨瀏覽器的端到端的web自動化解決方案。Selenium主要包括三部分:Selenium IDE、Selenium WebDriver 和Selenium Grid:

  • Selenium IDE:Firefox的一個擴充套件,它可以進行錄製回放,並可以把錄製的操作以多種語言(例如java,python等)的形式匯出成測試用例。
  • Selenium WebDriver:提供Web自動化所需的API,主要用作瀏覽器控制、頁面元素選擇和除錯。不同的瀏覽器需要不同的WebDriver。
  • Selenium Grid:提供了在不同機器的不同瀏覽器上執行selenium測試的能力

本文中主要使用python結合Selenium WebDriver庫進行自動化測試框架的搭建。

2 自動化測試框架

一個典型的自動化測試框架一般包括用例管理模組、自動化執行控制器、報表生成模組和日誌模組等,這些模組之間不是相互孤立的,而是相輔相成的。

 

下面來介紹下每個模組的邏輯單元:

  • 用例管理模組

用例管理模組包括用例的新增、修改、刪除等操作單元,這些單元也會涉及到用例書寫的模式,測試資料的管理、可複用庫等

  • 自動化執行控制器

控制器是自動化用例執行的組織模組,主要負責以什麼方式去執行用例。比較典型的控制器有使用者圖形介面(GUI)和“commandline+檔案”兩種。

  • 報表生成模組

報表生成模組主要負責執行完用例以後生成報表,報表一般以HTML格式居多,資訊主要包括用例的執行情況及相應的總結報告。另外還可以添加發送郵件功能。

  • 日誌模組

日誌模組主要用來記錄用例的執行情況,以便於更高效的調查用例失敗資訊及追蹤用例執行情況。

3 自動化框架的設計與實現

3.1       需求分析

測試物件是一個典型的後臺系統的Web展現平臺,基於此平臺設計的自動化框架要包含測試用例管理、測試執行控制、測試報表及測試日誌的生成,整體測試框架要輕量易用。

3.2       概要設計

概要設計包括了四個大的模組:公共庫模組(可複用函式、日誌管理、報表管理以及傳送郵件管理)、用例倉庫(具體用例的管理)、頁面管理(單獨對Web頁面進行抽象,封裝頁面元素和操作方法)以及執行模組。

概要設計類圖:

3.3       詳細設計與實現

3.3.1        頁面管理

                測試Web物件是一個典型的單頁面應用,因此採用頁面模式(page pattern)來進行組織:

 

頁面模式是頁面與測試用例之間的橋樑,它將每個頁面抽象成一個單獨的頁面類,為測試用例提供頁面元素的定位和操作。

頁面模式的類圖如下:

 

BasePage作為基類只包含一個driver成員變數,它用來標記Selenium中的WebDriver,以便在BasePage的派生類中定位頁面元素。LoginPage和PageN等作為派生類,可以提供相應頁面元素的定位和操作方法。比如測試物件的登入頁面:

 

從頁面可以看出,需要操作的頁面元素分別為:Username,Password,remember my username checkbox和Sign in按鈕,它們對應的操作為輸入使用者名稱和密碼,點選checkbox和點選Sign In按鈕,具體程式碼級別的實現如下:

頁面基類BasePage.py:

class BasePage(object):  
    """description of class"""  
  
    #webdriver instance  
    def __init__(self, driver):  
        self.driver = driver  

LoginPage頁面繼承自BasePage,並進行Login Page的元素定位及操作實現。程式碼中定位了username和password,並且添加了設定使用者名稱和密碼的操作。

複製程式碼
from BasePage import BasePage  
from selenium.webdriver.common.by import By  
from selenium.webdriver.common.keys import Keys  
  
class LoginPage(BasePage):  
    """description of class"""  
     #page element identifier  
    usename = (By.ID,'username')  
    password = (By.ID, 'password')  
    dialogTitle = (By.XPATH,"//h3[@class=\"modal-title ng-binding\"]")  
    cancelButton = (By.XPATH,'//button[@class=\"btn btn-warning ng-binding\"][@ng-click=\"cancel()\"]')  
    okButton = (By.XPATH,'//button[@class=\"btn btn-primary ng-binding\"][@ng-click=\"ok()\"]')  
  
    #Get username textbox and input username  
    def set_username(self,username):  
        name = self.driver.find_element(*LoginPage.usename)  
        name.send_keys(username)  
      
    #Get password textbox and input password, then hit return  
    def set_password(self, password):  
        pwd = self.driver.find_element(*LoginPage.password)  
        pwd.send_keys(password + Keys.RETURN)  
  
    #Get pop up dialog title  
    def get_DiaglogTitle(self):  
        digTitle = self.driver.find_element(*LoginPage.dialogTitle)  
        return digTitle.text  
  
    #Get "cancel" button and then click  
    def click_cancel(self):  
        cancelbtn = self.driver.find_element(*LoginPage.cancelButton)  
        cancelbtn.click()  
  
     #click Sign in  
    def click_SignIn(self):  
        okbtn = self.driver.find_element(*LoginPage.okButton)  
        okbtn.click()  
複製程式碼

採用頁面模式來管理頁面和測試用例有很多好處,主要體現在:

  • 簡單並且清晰

每個頁面都有單獨的類來封裝頁面元素和操作,讓頁面操作更加具體化,而不是相對獨立的。

比如未使用頁面模式,測試用例的輸入使用者名稱和密碼的程式碼:

 #enter username and password  
driver.find_element_by_id("username").clear()  
driver.find_element_by_id("username").send_keys("sbxadmin")  
driver.find_element_by_id("password").clear()  
driver.find_element_by_id("password").send_keys("password"+Keys.RETURN) 

使用頁面模式之後,輸入使用者名稱和密碼的程式碼:

#Step2: Open Login page  
login_page = BasePage.LoginPage(self.driver)  
#Step3: Enter username  
login_page.set_username("username")  
#Step4: Enter password  
login_page.set_password("password")  

通過對比我們不難發現,未使用頁面模式的程式碼組織比較混亂,步驟多,可讀性非常差,不難想象,一個通篇都是find_element_by_id或者send_Keys的測試用例到底有多糟糕!而使用了頁面模式之後,在哪個頁面做什麼操作都非常清晰,非常接近測試用例的步驟,易讀性非常好。

  • 可複用性好

由於頁面操作都被封裝在了頁面類中,所以頁面方法和容易呼叫,可複用性非常好。而未使用頁面模式的用例只能每次都實現一遍。

  • 可維護性好

由於測試目標頁面的多變性,頁面元素的定位經常需要改變,利用了頁面模式後,只需要修改一遍其頁面類中的定位就可以對所用用到該元素的測試用例生效;而在未使用該模式的情況下,必須修改每一個用到該元素的測試用例,非常容易遺漏,工作量也非常大。

綜合以上頁面模式的各種優點,我們在以後的web自動化中可以多使用該模式來組織頁面。

3.3.2        公共庫模組

                公共庫模組是為建立測試用例服務的,它主要包括常量、公共函式、日誌管理、報表管理以及傳送郵件管理等。

                公共庫模組涉及到的功能一般多而雜,在設計的時候只要遵循高內聚低耦合就可以了。比如常量、變數和一些公共函式可以放在同一個檔案中Common.py:

複製程式碼
from datetime import datetime  
  
def driverPath():  
    return r'C:\Users\xua\Downloads\chromedriver_win32\chromedriver.exe'  
def baseUrl():  
    return "https://xxx.xxx.xxx.xxx:9000"  
#change time to str  
def getCurrentTime():  
    format = "%a %b %d %H:%M:%S %Y"  
    return datetime.now().strftime(format) 
# Get time diff  
def timeDiff(starttime,endtime):  
    format = "%a %b %d %H:%M:%S %Y"  
    return datetime.strptime(endtime,format) - datetime.strptime(starttime,format) 
複製程式碼

測試用例資訊類用來標識測試用例,並且包括執行用例執行結果資訊,主要包括以下欄位:

複製程式碼
class TestCaseInfo(object):  
    """description of class"""  
    def __init__(self, id="",name="",owner="",result="Failed",starttime="",endtime="",secondsDuration="",errorinfo=""):  
        self.id = id  
        self.name = name  
        self.owner = owner  
        self.result = result  
        self.starttime = starttime  
        self.endtime = endtime  
  self.secondsDuration = secondsDuration  
  self.errorinfo = errorinfo  
複製程式碼

測試用例資訊需要在每個測試用例中例項化,以便對測試用例進行標記,並最終體現在測試報告中。

日誌主要用來記錄測試用例執行步驟及產生的錯誤資訊,不同的資訊有不同的日誌級別,比如Information,Warning,Critical和Debug。由於每個測試用例產生的日誌條目比較少,所以在測試框架中只利用了最高級別的日誌列印,即Debug級別,該級別也會將其他所有的日誌級別的資訊同樣打印出來。在具體的實現中引用了Python標準庫中的logging類庫,以便更方便的控制日誌輸出:

複製程式碼
import logging  
import ResultFolder  
  
logger = logging.getLogger()  
logger.setLevel(logging.DEBUG)  
  
  
def CreateLoggerFile(filename):  
    try:  
        fulllogname = ResultFolder.GetRunDirectory()+"\\"+filename+".log"  
        fh = logging.FileHandler(fulllogname)  
        fh.setLevel(logging.DEBUG)  
        formatter = logging.Formatter('%(asctime)s [line:%(lineno)d] %(message)s')  
        fh.setFormatter(formatter)  
        logger.addHandler(fh)  
    except Exception as err:  
        logger.debug("Error when creating log file, error message: {}".format(str(err)))  
  
  
def Log(message):  
    logger.debug(message)
複製程式碼

報表管理及傳送郵件模組實現了報表(html格式)的生成及自動傳送郵件的功能。報表和郵件依附於當前測試的執行,每次執行都會獨立的觸發報表生成和郵件傳送。該模組主要運用了Python中的lxml、smtplib和email庫。

3.3.3        用例倉庫

                用例倉庫主要用來組織自動化測試用例。每條測試用例都被抽象成一個獨立的類,並且均繼承自unittest.TestCase類。 Python中的unittest庫提供了豐富的測試框架支援,包括測試用例的setUp和tearDown方法,在實現用例的過程中可以重寫。依託頁面管理和公共庫模組實現的頁面方法和公共函式,每一個測試用例指令碼的書寫都會非常清晰簡潔,一個簡單的Floor Manager Lite的登入用例如下:  

複製程式碼
class Test_TC_Login(unittest.TestCase):  
    def setUp(self):  
        self.driver = webdriver.Chrome(cc.driverPath())  
        self.base_url = cc.baseUrl()  
        self.testCaseInfo = TestCaseInfo(id=1,name="Test case name",owner='xua')  
        self.testResult = TestReport()  
        LogUtility.CreateLoggerFile("Test_TC_Login")  
    def test_A(self):  
        try:  
            self.testCaseInfo.starttime = cc.getCurrentTime()  
            #Step1: open base site  
            LogUtility.Log("Open Base site"+self.base_url)  
            self.driver.get(self.base_url)  
  
            #Step2: Open Login page  
            login_page = LoginPage(self.driver)  
  
            #Step3: Enter username & password  
            LogUtility.Log("Login web using username")  
            login_page.set_username("username")  
            login_page.set_password("password")  
  
            time.sleep(2)  
            #Checkpoint1: Check popup dialog title  
            LogUtility.Log("Check whether sign in dialog exists or not")  
            self.assertEqual(login_page.get_DiaglogTitle(),"Sign in")  
  
            #time.sleep(3)  
            #Step4: Cancel dialog  
            login_page.click_cancel()  
            self.testCaseInfo.result = "Pass"  
  
        except Exception as err:  
            self.testCaseInfo.errorinfo = str(err)  
            LogUtility.Log(("Got error: "+str(err)))  
        finally:  
            self.testCaseInfo.endtime = cc.getCurrentTime()  
            self.testCaseInfo.secondsDuration = cc.timeDiff(self.testCaseInfo.starttime,self.testCaseInfo.endtime)  
    def tearDown(self):  
        self.driver.close()  
        self.testResult.WriteHTML(self.testCaseInfo)  
  
if __name__ == '__main__':  
    unittest.main()  
複製程式碼

從這個測試用例中,我們可以看到

  1. Setup中定義了執行測試用例前的一些例項化工作
  2. tearDown對執行完測試做了清理和寫日誌檔案工作
  3. 測試步驟、測試資料和測試檢查點非常清晰,易修改(比如使用者名稱密碼)
  4. 日誌級別僅有Debug,所以寫日誌僅需用同一Log方法

3.3.4        用例執行模組(控制器)

                執行模組主要用來控制測試用例指令碼的批量執行,形成一個測試集。用例的執行引用了Python標準庫中的subprocess來執行nosetests的shell命令,從而執行給定測試用例集中的用例。測試用例集是一個簡單的純文字檔案,實現過程中利用了.txt檔案testcases.txt:

Test_Login_pass.py  
Test_Login_Fail.py  
#Test_MainPage_CheckSecurityTableInfo.py  
Test_MainPage_EditSecurityInfo.py

用例前沒有“#“標記的測試用例指令碼會被執行,而有”#“標記的則會被忽略,這樣可以很方便的控制測試集的執行,當然也可以建立不同的檔案來執行不同的測試集。

具體的呼叫程式碼如下:

複製程式碼
def LoadAndRunTestCases(self):  
    try:  
        f = open(self.testcaselistfile)  
        testfiles = [test for test in f.readlines() if not test.startswith("#")]  
        f.close()  
        for item in testfiles:  
            subprocess.call("nosetests "+str(item).replace("\\n",""),shell = True)  
    except Exception as err:  
        LogUtility.logger.debug("Failed running test cases, error message: {}".format(str(err)))  
    finally:  
        EmailUtils.send_report() 
複製程式碼

3.4       執行結果

測試用例執行完畢後主要有兩種輸出:日誌和測試報告。測試報告會html附件的形式通過郵件發出,例如:

 

4 需要改進的模組

     對於現有實現的測試框架,已經可以滿足web物件的自動化需求,但還是有些可以改進提高的地方,比如:

  1. 針對部分測試用例是否可以嘗試資料驅動
  2. 新增螢幕截圖功能
  3. 封裝selenium中By庫中的函式,以便更高效的定位頁面元素等
  4. 結合業界優秀的自動化框架和實踐持續改進

5 總結

         基於selenium實現的web自動化框架不僅輕量級而且靈活,可以快速的開發自動化測試用例。結合本篇中的框架設計以及一些好的實踐,希望對大家以後的web自動化框架的設計和實現有所幫助。