zoukankan      html  css  js  c++  java
  • UI自动化框架搭建(四):完整UI自动化框架实现

    在第三节基础上多了下面5个层级(具体层级可参考下图)

    components层:  组件层,放置UI自动化公共组件(比如selenium的操作类)以及页面组件脚本(比如多个页面脚本相同,可以用组件形式存储,避免重复工作)

    config层:            配置层,管理系统配置

    log层:                日志层,放置UI自动化运行的日志信息

    page层:             页面层,放置UI自动化页面操作脚本

    screenshots层:  截图层,放置UI自动化运行中捕获的截图

    config、log、screenshots不难理解,主要解释下page层和components层怎么来方便我们UI自动化脚本编辑?

    1、代码维护层级清晰:
    page层: 维护UI脚本页面操作
    components层: 维护UI脚本组件
    testcases层: 维护测试用例
    2、减少重复工作:
    page层维护登录、目录层级选择这些公共页面操作,只需要编写一次
    components层维护页面操作的公共组件(比如多层目录选择),只需要编写一次

    接下来根据上节的test_aaa.py用例文件做下扩展

    首先page层,针对 test_aaa.py进行页面操作类封装

    aaa.py,具体代码如下

    #coding:utf-8
    from component.common.webdriver_base import WebDriverBase
    import time
    from utils.log_util import LogUtil
    
    logger = LogUtil("aaa").get_logger()
    class TestAaa(WebDriverBase):
    
        def login1(self):
            # 访问百度首页
            self.open_url(r"http://www.baidu.com")
            # self.driver.get(r"http://www.baidu.com")
            # 百度输入框输入
            self.loc_method("kw", "send_keys", method='id', text="懒勺")
            # self.driver.find_element_by_id("kw").send_keys("懒勺")
            # 点百度一下
            self.loc_method("su", "click", method='id')
            # self.driver.find_element_by_id("su").click()
            #等待时间只是为了让你可以看到目前效果,可以省略
            time.sleep(2)
    
        def login2(self):
            # 访问qq首页
            self.open_url(r"http://www.qq.com")
            # self.driver.get(r"http://www.qq.com")
            # 点新闻链接
            self.loc_method("//a[text()='新闻']", "click", method='xpath')
            # self.driver.find_element_by_xpath("//a[text()='新闻']").click()
            # 等待时间只是为了让你可以看到目前效果,可以省略
            time.sleep(3)
            logger.info("测试login2方法")

    test_aaa.py代码变更如下(为什么要把页面操作放到page层?分层方便代码维护,以及2个test类共用了相同的页面操作,可以直接调用,不需要重复维护):

    # -*- coding:utf-8 -*-
    import unittest
    from page.aaa import TestAaa
    import time
    
    #QingQing类的名字任意命名,但命名()里的unittest.TestCase就是去继承这个类,类的作用就是可以使runner.run识别
    class QingQing(unittest.TestCase):
        #unittest.TestCase类定义的setUpClass和tearDownClass方法前一定要加@classmethod,
        #setUpClass在这个类里面是第一个执行的方法
        #tearDownClass在这个类里面是最后一个执行的方法
        #中间的执行顺序是通过字符的大小进行顺序执行,命名必须test_开头
    
        #打开浏览器,获取配置
        @classmethod
        def setUpClass(self):
            self.aaa = TestAaa()
    
        def test_01_search_baidu(self):
            # 访问百度首页
            # 百度输入框输入
            # 点百度一下
            self.aaa.login1()
    
        #执行商品收费功能
        def test_02_search_qq_news(self):
            # 访问qq首页
            # 点新闻链接
            self.aaa.login2()
    
        #退出浏览器
        @classmethod
        def tearDownClass(self):
            self.aaa.quit_browser()
    
    if __name__ ==  "__main__":
        unittest.main()

    最后components层,对selenium做如下封装(为什么要封装,比如你点击和输入文本操作,一般前提还得考虑元素是否存在才能去点击或输入,这部分重复性工作可以省去)

    封装类webdriver_base.py,具体代码如下

    # -*- coding:utf-8 -*-
    from time import sleep
    
    import os
    from selenium.common.exceptions import *
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.select import Select
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium import webdriver
    from utils.datetime_util import DateTimeUtil
    
    from utils.log_util import LogUtil
    from utils.yaml_util import YamlUtil
    
    logger = LogUtil('webdriver_base').get_logger()
    
    driver = None
    class WebDriverBase(object):
    # 页面操作基础类
    
        def __init__(self):
            global driver
            # 如果driver不为空,直接使用原来的driver
            if driver !=  None:
                self.driver = driver
                return
    
            # 获取驱动
            chromeDriverPath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'driver',
                                                 'chromedriver.exe')
            option = webdriver.ChromeOptions()
            option.add_argument("disable-infobars")
            # 获取配置文件
            sysConfig = YamlUtil('sysconfig.yaml').read_yaml()
            # 找浏览器的名字
            browserName = sysConfig['browser']['browserName']
            if str(browserName).lower() == 'chrome':
                # 获取谷歌的驱动
                driver = webdriver.Chrome(executable_path=chromeDriverPath, chrome_options=option)
                self.driver = driver
            else:
                logger.error("暂不支持谷歌以外的驱动")
                raise Exception("暂不支持谷歌以外的驱动")
    
            if self.driver == None:
                logger.error("打开浏览器驱动失败")
                raise Exception("打开浏览器驱动失败")
    
            self.maximize_window()
    
        def open_url(self, url):
            # 访问浏览器地址
            self.driver.get(url)
    
        def get_driver(self):
            return self.driver
    
        def loc_method(self, eleLoc, action, method='CSS', text=None):
            """
            通用元素定位方法主入口
            :param eleLoc: 定位的元素路径
            :param action: 页面动作(输入文本,点击等等)
            :param method: 定位方式(css, path)提示:id、name、class属性都可以用css定位到,默认为CSS
            :param text: 如果是需要文本信息输入校验,才需要用到
            :return:
            """
    
            #loc放到selenium的driver.find_element方法就会自动识别元素
            if str(method).upper() == 'CSS':
                loc = (By.CSS_SELECTOR, eleLoc)
            elif str(method).upper() == 'XPATH':
                loc = (By.XPATH, eleLoc)
            elif str(method).upper() == 'ID':
                loc = (By.ID, eleLoc)
            elif str(method).upper() == 'NAME':
                loc = (By.NAME, eleLoc)
            elif str(method).upper() == 'CLASS':
                loc = (By.CLASS_NAME, eleLoc)
            else:
                loc = None
    
            try:
                if loc != None:
                    if action == 'click':
                        self.click(loc)
                    elif action == 'send_keys':
                        self.send_keys(text, loc)
                    elif action == 'select_by_text':
                        self.select_by_text(text, loc)
                    elif action == 'select_by_index':
                        self.select_by_index(text, loc)
                    elif action == 'select_by_value':
                        self.select_by_value(text, loc)
                    elif action == 'get_element_text':
                        return self.get_element_text(loc)
                    elif action == 'get_element_attribute':
                        return self.get_element_attribute(text, loc)
                    elif action == 'text_in_element':
                        return self.text_in_element(text, loc)
                    elif action == 'value_in_element':
                        return self.value_in_element(text, loc)
                    else:
                        logger.error("action错误:请确认action值:%s" % action)
                else:
                    logger.error("method错误:请确认method值:%s" % method)
            except Exception as e:
                logger.error(e)
    
        def send_keys(self, text, loc):
            # 输入框输入文本信息,先清除文本框内容后输入
            self.clear_input_box(loc)
            try:
                self.find_element(*loc).send_keys(text)
                sleep(1)
            except Exception as e:
                logger.error(e)
                self.get_screen_img()
                raise
    
        def clear_input_box(self, loc):
            # 清除输入框内容
            self.find_element(*loc).clear()
            sleep(1)
    
        def click(self, loc):
            # 点击
            try:
                self.find_element(*loc).click()
                sleep(2)
            except Exception as e:
                logger.error(e)
                self.get_screen_img()
                raise
    
        def move_to_element(self, *loc):
            # 鼠标悬停
            above = self.find_element(*loc)
            ActionChains(self.driver).move_to_element(above).perform()
    
        def close_single_window(self):
            # 关闭当前窗口(单个的)
            self.driver.close()
    
        def quit_browser(self):
            # 退出浏览器,关闭所有窗口
            self.driver.quit()
    
        def maximize_window(self):
            # 浏览器窗口最大化
            self.driver.maximize_window()
    
        def browser_forward(self):
            # 浏览器前进
            self.driver.forward()
    
        def browser_back(self):
            # 浏览器后退
            self.driver.back()
    
        def browser_refresh(self):
            # 浏览器刷新
            self.driver.refresh()
    
        def get_element_text(self, loc):
            # 获取元素的文本
            return self.find_element(*loc).text
    
        def get_element_attribute(self, attributeItem, loc):
            # 获取元素的属性,可以是id,name,type或其他任意属性
            return self.find_element(*loc).get_attribute(attributeItem)
    
        def implicitly_wait(self, seconds):
            # 隐式等待时间,最长等待seconds秒,超过抛出超时异常,常用于页面加载等待
            self.driver.implicitly_wait(seconds)
    
        def select_by_index(self, index, *loc):
            # 通过index 下标取select
            ele = self.find_element(*loc)
            Select(ele).select_by_index(index)
            sleep(1)
    
        def select_by_value(self, value, *loc):
            # 通过value值取select
            ele = self.find_element(*loc)
            Select(ele).select_by_value(value)
            sleep(1)
    
        def select_by_text(self, text, loc):
            # 通过文本text值取select
            ele = self.find_element(*loc)
            Select(ele).select_by_visible_text(text)
            sleep(1)
    
        def text_in_element(self, text, *loc, timeout=10):
            # 判断某个元素的text是否包含了预期的值
            # 没定位到元素返回False,定位到元素返回判断结果布尔值true
            try:
                ele = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element(*loc, text))
            except TimeoutException:
                logger.error("查找超时,%s不在元素的文本里面" % text)
                return False
            return ele
    
        def value_in_element(self, value, *loc, timeout=10):
            # 判断某个元素的value是否包含了预期的值
            # 没定位到元素返回False,定位到元素返回判断结果布尔值true
            try:
                ele = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element_value(*loc, value))
            except TimeoutException:
                logger.info("查找超时,%s不在元素的value里面" % value)
                return False
            return ele
    
        def find_element(self, *loc):
            """
            定位元素
            :param loc: 元组 示例:(By.CSS,'id')
            :return:
            """
            try:
                WebDriverWait(self.driver, 10).until(lambda driver: driver.find_element(*loc).is_displayed())
                return self.driver.find_element(*loc)
            except NoSuchElementException:
                logger.error("找不到定位的元素:%s" % loc[1])
                raise
            except TimeoutException:
                logger.error("元素查找超时:%s" % loc[1])
                raise
    
        def get_screen_img(self):
            #截图保存ui运行结果
            imgPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'screenshots')
            screenName = DateTimeUtil().get_current_time() + '.png'
            screenFile = os.path.join(imgPath, screenName)
            try:
                self.driver.get_screenshot_as_file(screenFile)
            except Exception as e:
                logger.error("没有成功截到图,原因是: %s" % e)
    
        def switch_to_next_window(self, currentHandle):
            # 当打开的窗口不是当前窗口,就切换
            allHandles = self.driver.window_handles
            for handle in allHandles:
                if handle != currentHandle:
                    self.driver.switch_to.window(handle)
                    break
    
        def switch_to_next_frame(self, iframe):
            # 表单切换到iframe,其中iframe是id
            self.driver.switch_to.frame(iframe)
    
        def execute_script(self, js):
            #执行js命令
            self.driver.execute_script(js)
    View Code

    截图中提到的工具类和配置代码如下

    log_util.py

    # -*- coding:utf-8 -*-
    import logging
    from datetime import datetime
    import os
    
    
    class LogUtil():
        def __init__(self, logname=None):
            # 日志名称
            self.logger = logging.getLogger(logname)
            # 日志级别
            self.logger.setLevel(logging.DEBUG)
            # 日志输出到控制台
            self.console = logging.StreamHandler()
            self.console.setLevel(logging.DEBUG)
            # 输出到文件
            self.date = datetime.now().strftime("%Y-%m-%d") + '.log'
            self.filename = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs', self.date)
            self.file = logging.FileHandler(self.filename, encoding='utf-8')
            self.file.setLevel(logging.DEBUG)
            # 日志显示内容
            self.formatstr = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s'
            self.format = logging.Formatter(self.formatstr)
            self.console.setFormatter(self.format)
            self.file.setFormatter(self.format)
            # 加入到hander
            self.logger.addHandler(self.console)
            self.logger.addHandler(self.file)
    
        def get_logger(self):
            return self.logger
    View Code

    datebase_util.py

    # -*- coding:utf-8 -*-
    from utils.log_util import LogUtil
    from utils.yaml_util import YamlUtil
    import pymysql
    import cx_Oracle
    
    logger = LogUtil('database_util').getLogger()
    
    class DataBase(object):
        def __init__(self):
            pass
    
        def queryDataBase(self, querySql):
            # 获取游标
            try:
                cursor = self.con.cursor()
                cursor.execute(querySql)
                return cursor.fetchone()[0]
            except Exception as e:
                logger.error(e)
            finally:
                self.con.close()
    
        def updateData(self, querySql):
            # 修改数据库数据
            try:
                cursor = self.con.cursor()
                cursor.execute(querySql)
                self.con.commit()
            except Exception as e:
                self.con.rollback()
                logger.error(e)
            finally:
                self.con.close()
    
    
    class OracleDataBase(DataBase):
        def __init__(self):
            sysConfig = YamlUtil('sysconfig.yaml').readYaml()
            host = sysConfig['oralceConfig']['host']
            port = sysConfig['oralceConfig']['port']
            user = sysConfig['oralceConfig']['username']
            pwd = sysConfig['oralceConfig']['password']
            database = sysConfig['oralceConfig']['database']
            self.con = cx_Oracle.connect("{}/{}@{}:{}/{}".format(user, pwd, host, port, 
                                                                 database).format(), encoding="UTF-8", nencoding="UTF-8")
    
    class MysqlDataBase(DataBase):
        def __init__(self):
            sysConfig = YamlUtil('sysconfig.yaml').readYaml()
            host = sysConfig['mysqlConfig']['host']
            port = sysConfig['mysqlConfig']['port']
            user = sysConfig['mysqlConfig']['username']
            pwd = sysConfig['mysqlConfig']['password']
            database = sysConfig['mysqlConfig']['database']
            self.con = pymysql.Connect(
                host=host,
                port=port,
                user=user,
                passwd=pwd,
                db=database,
                charset='utf8'
            )
    
    if __name__ == "__main__":
        pass
    View Code

    yaml_util.py

    # -*- coding:utf-8 -*-
    import os
    
    from ruamel import yaml
    
    from utils.log_util import LogUtil
    
    logger = LogUtil('yaml_util').get_logger()
    
    
    class YamlUtil(object):
        def __init__(self, file=None):
            try:
                if file != None:
                    self.configPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                                   'config', file)
                if self.configPath:
                    with open(self.configPath, 'r', encoding='utf-8') as f:
                        self.Yamlobject = yaml.safe_load(f)
            except Exception as e:
                logger.error(e)
    
        def read_yaml(self):
            return self.Yamlobject
    
        def write_yaml(self, name, value):
            self.Yamlobject[name] = value
            with open(self.file, 'w+', encoding='utf-8') as  fout:
                yaml.dump(self.Yamlobject, fout, default_flow_style=False, allow_unicode=True)
    
    
    if __name__ == '__main__':
        configPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                  'config', 'sysconfig.yaml')
        r = YamlUtil(configPath).read_yaml()
        print(r['browser']['browserName'])
    View Code

    datetime_util.py

    # -*- coding:utf-8 -*-
    from datetime import datetime
    
    
    class DateTimeUtil(object):
        def __init__(self):
            pass
    
        def get_current_time(self):
            return datetime.now().strftime("%Y%m%d%H%M%S")
    
        def get_current_date(self):
            return datetime.now().strftime("%Y-%m-%d")
    
    if __name__=="__main__":
        dateTime = DateTimeUtil()
        print(dateTime.get_current_time())
    View Code

    sysconfig.yaml

    browser:
        browserName:  chrome
    
    login:
        account: renlk24211
        passwd: '12345678'
        url: https://blade.com.cn
    
    mysqlConfig:
        host: 192.168.160.141
        port: 3306
        username: root
        password: 123456
        database: auto
    
    oracleConfig:
        host: 192.168.160.141
        port: 3306
        username: root
        password: 123456
        database: auto
    
    db2Config:
        host: 192.168.160.141
        port: 3306
        username: root
        password: 123456
        database: auto
    View Code

    主入口run_all_case.py封装

    # -*- coding:utf-8 -*-
    import unittest
    import os
    from utils.HTMLTestRunnerForPy3 import HTMLTestRunner
    from datetime import datetime
    
    
    class RunAllCase(object):
    
        def __init__(self):
            pass
    
        def add_cases(self):
            # 挑选用例,pattern='test_*.py'表示添加test_开头的py文件
            casePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'testcases')
            discover = unittest.defaultTestLoader.discover(
                start_dir=casePath,
                pattern='test_*.py'
            )
    
            return discover
    
        def get_report_file_path(self):
            # 指定生成报告地址
            report_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'reports')
            report_name = datetime.now().strftime("%Y%m%d%H%M%S") + '.html'
            report_file = os.path.join(report_path, report_name)
    
            return report_file
    
        def run_cases(self, report_file, discover, title, description):
            # 运行用例
            runner = HTMLTestRunner(
                stream=open(report_file, 'wb'),
                # 生成的html报告标题
                title=title,
                # 1是粗略的报告,2是详细的报告
                verbosity=2,
                # 生成的html描述
                description=description
            )
            runner.run(discover)
    
    if __name__ == "__main__":
        r = RunAllCase()
        discover = r.add_cases()
        report_file = r.get_report_file_path()
    
        title = '银行UI自动化测试报告'
        description = '银行UI自动化测试报告'
        r.run_cases(report_file, discover, title, description)
    View Code
  • 相关阅读:
    C复制字符串
    C语言分解数组
    perlCGI编程之测试环境
    linux下c语言 读取文件
    C++的组合(Composite)模式
    C#GDI+绘制多行文本和格式化文本
    shell中引号的应用
    perlCGI编程之Apache服务器安装配置
    求二叉树的深度
    perlCGI编程之页面参数传递
  • 原文地址:https://www.cnblogs.com/heng-xin/p/14119210.html
Copyright © 2011-2022 走看看