基於selenium+Python3.7+yaml+Robot Framework的UI自動化測試框架


前端自動化測試框架

項目說明

  • 本框架是一套基於selenium+Python3.7+yaml+Robot Framework而設計的數據驅動UI自動化測試框架,Robot Framework 作為執行器,本框架整體特點為:用例與代碼分離;用例驅動測試的執行;分層設計、腳本模塊化;自動准備測試條件和數據、事后清理;支持失敗多次重試、且在每次重試前都會還原一次測試環境,保證重試的成功率;支持郵件呈現測試結果;可靈活挑選項目和用例執行。

框架特點說明

  • 數據驅動
  1. 封裝一個測試腳本,通過不同的數據來驅動,保證測試覆蓋率
  2. 用例執行前初始化,執行后釋放資源
  3. 執行測試數據,並進行結果判斷,測試失敗或者異常時,會產生相關日志和截圖
  • 用例與代碼分離
  1. 代碼中不含有測試用例
  2. 測試用例采用robot格式撰寫
  3. 用例按功能點分類
  4. 每個用例都是獨立的,不互相依賴也不互相影響
  5. 可按項目、模塊、測試點挑選執行用例
  6. 運行結束后,還原測試環境,清理臟數據
  • 分層設計
  1. 操作對象、頁面元素、業務邏輯、測試數據相互剝離,靈活調用
  2. 封裝底層操作組件
  3. 按頁面封裝和分類頁面元素
  4. 封裝基礎邏輯,組合業務邏輯實現功能點
  • 腳本模塊化
  1. 不同功能腳本模塊化,各模塊間保持獨立性和可融合性
  2. 封裝基礎方法,如隨機郵箱等
  3. 封裝通用模塊,如文件讀寫、壓縮文件、郵件發送、日志等
  4. 模塊支持自主開啟和關閉
  • 失敗多次重試
  1. 可自主開啟和關閉失敗自動重試功能
  2. 重試開始在每輪測試結束之后
  3. 每次重試前后會初始化環境和還原環境
  4. 只重試失敗用例
  5. 每次重試結束會自動合並重試報告
  6. 重試結束會自動合並出總報告
  7. 報告可選擇自動壓縮並郵件發送
  8. 測試結束后,也可手動啟動失敗重試和合並報告

技術棧

  • selenium
  • Python3.7
  • Robot Framework
  • yaml
  • AutoItLibrary
  • logging
  • SSHLibrary
  • databaselibrary

環境部署

  • 安裝python3.7,並將Python的安裝目錄添加到系統環境變量的Path路徑中
  • 命令行窗口執行pip install -r requirements.txt 安裝工程所依賴的庫文件
  • AutoItLibrary安裝
  1. 安裝autoit-v3-setup.exe(V3.3.14.5),安裝過程中一定要選x64,不然加載AutoItLibrary還是會是紅色
  2. pip install robotframework-autoitlibrary
  3. 在Python安裝目錄(\Python37\Lib\site-packages\win32com\client)下,修改dynamic.py文件,在import pythoncom后,加 pythoncom.CoInitialize() 。(目的是解決在ride中導入AutoItLibrary時,報(-2147221008, '尚未調用 CoInitialize。', None, None)的錯誤。)
  • 將相關瀏覽器的驅動,比如谷歌瀏覽器的驅動chromedriver.exe放在python的安裝目錄(\Python37)下
  • 將自定義庫,比如randomGenerator.py放在python目錄(\Python37\Lib\site-packages)下
  • 在Python安裝目錄(\Python37\Lib\site-packages\robotide\contrib\testrunner)下,修改testrunner.py文件,將'latin1' if IS_WINDOWS修改為'mbcs' if IS_WINDOWS。(目的是解決RIDE控制台中文顯示亂碼的問題),修改后的內容和位置具體如下:

for _ in myqueuerng:
    try:
        # DEBUG result += self._queue.get_nowait()
        # .decode(utils.SYSTEM_ENCODING, 'replace')
        # .decode('UTF-8','ignore')
        result += encoding.console_decode(self._queue.get_nowait(),
                                          'mbcs' if IS_WINDOWS
                                          else 'UTF-8')

安裝UI元素定位工具(根據實際需要選裝)

  • 火狐瀏覽器插件
  1. Try XPath
  2. xPath Finder
  3. ChroPath
  • 谷歌瀏覽器插件
  1. ChroPath

框架目錄結構圖及相關說明

1、代碼目錄結構圖如下

2、目錄結構說明

  • Config ===========> 配置文件
  • pycode ===========> python公共方法、模塊封裝,工具類等
  • rfcode ==========> 存放項目/系統的測試用例、公共配置、界面元素、業務邏輯操作等等
  • logs ==========> 日志文件
  • Report ==========> 測試報告
  • common_Run.py ===========> 測試用例總執行模塊
  • testfile ============> 存放測試過程的操作文件
  • requirements.txt ============> 相關依賴包文件
  • run_test.bat =============> 測試啟動按鈕,測試過程中,若有失敗則自動進行失敗重試
  • retry.bat ============> 測試結束后,再次進行失敗重試手動啟動按鈕

代碼設計與功能說明

1、定義運行配置文件 config.yml

該文件主要控制測試的執行方式、模塊的功能開關、測試用例的篩選、郵件的配置以及日志的配置,具體如下:


config.yml配置信息

---
#待執行的產品項目, Cloud -雲項目; rfcode -執行所有的項目
ProductItem: Cloud

#待執行的測試套件(標簽), all -所有的測試套件: debug -調試中套件; login -登錄功能 ; filter -賬號篩選: P1 -一級用例
testSuite: P1

#測試報告標題                                        
testReportTitle: Cloud_Report

#失敗重試開關, 0 -關, 1 -開 ,開啟后默認重試一次
retry_switch: 0
#失敗重試次數
retryTime: 1

#發送測試報告郵件開關, 0 -關, 1 -開                
emailSwitch: 1

#郵件配置
#發件郵箱
smtp_server: smtp.126.com
server_username: XXXtest@126.com
server_pwd: XXXXXX
#收件人(列表)
msg_to:
- XXX1@qq.com
- XXX2@126.com

#郵件主題
msg_subject: '[XX項目][測試環境-develop][jira號][自動化測試報告]'

#日志級別(字典),由高到低: CRITICAL 、 ERROR 、 WARNING 、 INFO 、 DEBUG
log:
    file_name: test.log
    backup: 5
    console_level: DEBUG           #控制台日志級別
    file_level: DEBUG              #文件日志級別
    pattern: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

2、測試用例的設計

測試用例以robot格式的文件保存,平時只需維護測試數據和期望結果,維護成本低。測試用例的數據格式如下:

3、用例執行腳本

按照測試用例,組合業務邏輯,實現功能點測試,以供測試用例循環調用

4、測試執行主程序common_Run.py(收集測試用例,批量執行並生成測試報告,並發送報告到郵件)


# coding: utf-8

import os,sys
import time
from config import BASE_PATH,Config
from global_model.log import logger
from global_model.format_change import formatChange
from email_model.run_sendEmail import sendEailMock


def batch_Call(basePath, product_item, testCase_tag):
    '''
    首次執行測試:
    param robot_testSuite: robot testSuite路徑;
    param product_item: 執行的待測產品項目;
    param testCase_tag: 執行的測試用例標簽;
    param testCaseReportPath: 測試報告路徑;
    '''
    try:
        logger.info(u'=======================首次測試執行開始!正在初始化數據庫!=============================')
        pybot_result_list = []        
        output_dir = "-d " + basePath + "/Report/primo_report"          
        output_xml = "-o output.xml"
        output_log = "-l log.html"
        output_report = "-r report.html"
        productItem = "-s " + product_item
        caseTag = "-i " + testCase_tag
        pybot_cmd = "pybot " + output_dir + " " + output_xml + " " + output_log + " " + output_report + " " + productItem + " " + caseTag + " " + basePath
        pybot_result=os.system(pybot_cmd)  
        return pybot_result

    except Exception as e:
        logger.exception(u'首次執行測試失敗. %s', e)

    finally:
        logger.info(u'測試項目根目錄為:{0}'.format(basePath))
        logger.debug(u'首次執行測試報告輸出目錄:{0}'.format(output_dir))
        logger.info(u'首次執行測試的pybot命令:{0}'.format(pybot_cmd))
        logger.info(u'======================首次執行測試已完成!用例失敗數為:{0} 個!========================'.format(pybot_result))  	


def mergeReport(basePath, report_title, match_report, copy_source_match, copy_target_match, merge_report_output):
    '''
    合並報告:
    param testCaseReportPath: 測試報告路徑;
    param report_title: 合並報告的標題;
    return:
    '''
    try:
        logger.info(u'==========================合並測試報告開始!==================================')
        output_dir = "-d " + basePath + "/Report/{0}".format(merge_report_output)  # 輸出合並報告目錄
        output_xml = "-o output.xml"
        output_log = "-l log.html"
        output_report = "-r report.html"

        # 被合並的報告
        merge_report =  "--merge " + basePath + "/Report/{0}".format(match_report) + "/output.xml"
        title = "--ReportTitle " + report_title
        rebot_cmd = r"rebot " + output_dir + " " + output_xml + " " + output_log + " " + output_report + " " + title + " "  + merge_report
        mergeReport_result = os.system(rebot_cmd)  
        logger.debug(u'合並報告的結果為:{0}'.format(mergeReport_result))

        # 復制截圖
        testCaseReportPath = basePath + "/Report"
        source_files = testCaseReportPath + "/{0}/*.png".format(copy_source_match)
        target_dir = testCaseReportPath + "/{0}".format(copy_target_match)
        cp_cmd = r"copy " + source_files + " " + target_dir 
        cp_cmd = cp_cmd.replace("/", "\\")
        cope_result = os.system(cp_cmd)
        logger.debug(u'復制截圖的結果為:{0}'.format(cope_result))
        return mergeReport_result

    except Exception as e:
        logger.exception(u'合並報告異常. %s', e)

    finally:
        logger.debug(u'輸出合並報告目錄為:{0}'.format(output_dir))
        logger.info(u'合並報告rebot命令:{0}'.format(rebot_cmd))
        if mergeReport_result != 0:
            logger.info(u'======================合並報告中有失敗用例!========================')
        else:
            logger.info(u'======================沒有失敗重試的報告需合並,或者合並報告中的用例已全通過!========================')
        logger.info(u'復制截圖命令為:{0}'.format(cp_cmd))
        if cope_result == 0:
            logger.info(u'======================截圖已成功復制到合並報告中!========================')
        else:
            logger.info(u'======================報告中沒有需要復制的截圖!========================')


def cleanLogs(basePath):
    '''
    刪除硬盤中合並前的測試報告:
    param testCaseReportPath: 測試報告路徑;
    return:
    '''
    try:
        logger.info(u'==========================清理磁盤報告開始!==================================')
        testCaseReportPath = basePath + "/Report"
        primo_report_files = testCaseReportPath + "/primo_report/*"
        retry_report_files = testCaseReportPath + "/retry_report/*"
        merge_files = testCaseReportPath + "/merge/*"
        zip_merges_files = testCaseReportPath + "/zip_merges/*"
        retry_times_files = testCaseReportPath + "/retry_times/*"
        del_cmd = "del " + primo_report_files + " " + retry_report_files + " " + merge_files + " " + zip_merges_files + " " + retry_times_files 
        del_cmd = del_cmd.replace("/", "\\")
        del_cmd = del_cmd + " " + "/s /q /f"
        clean_result = os.system(del_cmd)

    except Exception as e:
       logger.exception(u'清理磁盤報告異常. %s', e)

    finally:
        logger.info(u'清理磁盤報告命令為:{0}'.format(del_cmd))
        logger.debug(u'清理磁盤報告結果為:{0}'.format(clean_result))
        if clean_result == 0:
            logger.info(u'======================測試報告清理成功!========================')      
        else:
            logger.info(u'======================測試報告清理失敗!========================')  


def run_test(basePath, product_item, testCase_tag, report_title, email_switch, smtp_server, server_username, server_pwd, msg_to, msg_subject, retry_switch, retry_time):
    '''
    失敗自動重試:
    param robot_testSuite: robot testSuite路徑;
    param testCase_tag: 執行的測試用例標簽;
    param testCaseReportPath: 測試報告路徑;
    param report_title: 合並報告的標題;
    '''
    #清理環境
    cleanLogs(basePath)

    #首次執行
    run_result = batch_Call(basePath, product_item, testCase_tag)
    
    #失敗重試
    retryFunction = retry_switch
    if retryFunction == 1:
        try:
            if  run_result != 0:
                logger.info(u'======================失敗用例重試第 1 次!========================')
                output_dir = "-d " + basePath + "/Report/retry_report"
                retry_xml = "-R " + basePath + "/Report/primo_report/output.xml"
                pybot_cmd = "pybot " + retry_xml + " " + output_dir + " " + basePath
                logger.info(u'第 1 次失敗重試命令為:{0}'.format(pybot_cmd))
                retry_result=os.system(pybot_cmd)
                logger.info(u'======================失敗用例重試第 1 次已完成!用例失敗數為:{0} 個!========================'.format(retry_result))

                retryTimes = retry_time
                n = 2
                while (retryTimes >=2 and retry_result != 0):
                    logger.info(u'======================失敗用例重試第 {0} 次!========================'.format(n))
                    output_dir = "-d " + basePath + "/Report/retry_times"
                    retry_xml = "-R " + basePath + "/Report/retry_report/output.xml"
                    pybot_cmd = "pybot " + retry_xml + " " + output_dir + " " + basePath
                    logger.info(u'第 {0} 次失敗重試命令為:{1}'.format(n, pybot_cmd))
                    retry_result=os.system(pybot_cmd)
                    logger.info(u'======================失敗用例重試第 {0} 次已完成!用例失敗數為:{1} 個!========================'.format(n, retry_result))
                    
                    logger.info(u'======================正在合並重試報告!========================')
                    merge_report_output = 'retry_report'
                    match_report = 'retry_*'
                    copy_source_match = 'retry_times'
                    copy_target_match = 'retry_report'
                    mergeReport(basePath, report_title, match_report, copy_source_match, copy_target_match, merge_report_output)
                    logger.info(u'======================失敗用例重試第 {0} 次合並報告已完成!========================'.format(n))
                    n = n + 1
                    retryTimes = retryTimes-1

        except Exception as e:
           logger.exception(u'失敗重試異常. %s', e)

    else:
        logger.info(u'======================本次測試的失敗重試功能已關閉!========================')

    #合並報告
    merge_report_output = 'merge'
    match_report = '*_report'
    copy_source_match = 'retry_report'
    copy_target_match = 'merge'
    report_result = mergeReport(basePath, report_title, match_report, copy_source_match, copy_target_match, merge_report_output)
    logger.debug(u'======================最終測試結果為: {0} !========================'.format(report_result))
    if report_result == 0:
        ReportResult = u'測試通過!'
    else:
        ReportResult = u'測試不通過!失敗用例數為:{0} 個'.format(report_result)

    #發送report到郵件
    emailFunction = email_switch
    if  emailFunction == 1:

        #將字符中的反斜杠轉成正斜杠
        fileUrl_PATH = basePath.replace('\\','/')
        logger.debug(u'基礎路徑的反斜杠轉成正斜杠為:{0}'.format(fileUrl_PATH))
         
        fileUrl='file:///{0}/Report/merge/report.html'.format(fileUrl_PATH)
        logger.info(u'html測試報告的url為:{0}'.format(fileUrl))
        save_fn=r'{0}\Report\zip_merges\report.png'.format(basePath)
        logger.debug(u'轉成圖片報告后保存的目標路徑為:{0}'.format(save_fn))
        formatChange_obj = formatChange()
        formatChange_obj.html_to_image(fileUrl, save_fn)

        email_folder_dir = basePath + "/Report/merge"       #待壓縮文件夾
        logger.debug(u'待壓縮文件夾為:{0}'.format(email_folder_dir))
        email_target_dir = basePath + "/Report/zip_merges"  #壓縮文件保存路徑
        sendEailMock_obj = sendEailMock()
        sendEailMock_obj.send_email(email_folder_dir, email_target_dir, smtp_server, server_username, server_pwd, msg_to, msg_subject, ReportResult, save_fn) 
    else:
        logger.info(u'======================本次測試的郵件功能已關閉!========================')


if __name__ == '__main__':
 
    c = Config()
    ProductItem = c.get('ProductItem')
    testSuite = c.get('testSuite')
    testReportTitle = c.get('testReportTitle')

    emailSwitch = c.get('emailSwitch')
    smtp_server = c.get('smtp_server')
    server_username = c.get('server_username')
    server_pwd = c.get('server_pwd')  
    msg_to = c.get('msg_to')
    msg_subject = c.get('msg_subject')

    retrySwitch = c.get('retry_switch')
    retryTime = c.get('retryTime')

    run_test(BASE_PATH, ProductItem, testSuite, testReportTitle, emailSwitch, smtp_server, server_username, server_pwd, msg_to, msg_subject, retrySwitch, retryTime)

5、測試報告呈現

  • 測試概要報告

  • 測試執行過程中若有失敗重試時,則報告中會呈現出前一次失敗的結果和原因,同時也會呈現出重試后成功的結果,並且最終的報告結果以最后一次的重試結果為准

  • 測試執行過程有失敗用例時,報告呈現失敗截圖

  • 報告發送至郵件時,郵件標題呈現測試通過與否,或失敗用例數

  • 郵件正文直接顯示本次測試概要報告,直觀顯示出執行用例數,失敗用例數,失敗的用例模塊等結果。

項目實戰演示




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM