zoukankan      html  css  js  c++  java
  • 基于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安装目录(Python37Libsite-packageswin32comclient)下,修改dynamic.py文件,在import pythoncom后,加 pythoncom.CoInitialize() 。(目的是解决在ride中导入AutoItLibrary时,报(-2147221008, '尚未调用 CoInitialize。', None, None)的错误。)
    • 将相关浏览器的驱动,比如谷歌浏览器的驱动chromedriver.exe放在python的安装目录(Python37)下
    • 将自定义库,比如randomGenerator.py放在python目录(Python37Libsite-packages)下
    • 在Python安装目录(Python37Libsite-packages obotidecontrib estrunner)下,修改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}Reportzip_merges
    eport.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、测试报告呈现

    • 测试概要报告

    • 测试执行过程中若有失败重试时,则报告中会呈现出前一次失败的结果和原因,同时也会呈现出重试后成功的结果,并且最终的报告结果以最后一次的重试结果为准

    • 测试执行过程有失败用例时,报告呈现失败截图

    • 报告发送至邮件时,邮件标题呈现测试通过与否,或失败用例数

    • 邮件正文直接显示本次测试概要报告,直观显示出执行用例数,失败用例数,失败的用例模块等结果。

    项目实战演示



  • 相关阅读:
    CSS定位属性
    CSS属性
    CSS基础
    HTML
    JDBC
    语言元素
    初识Python
    redis配置文件
    zabbix
    jumpserver
  • 原文地址:https://www.cnblogs.com/jun-zi/p/12055300.html
Copyright © 2011-2022 走看看