UI自动化框架基础层开发实战(上)

一、什么是基础层

基础层承担了整个UI自动化框架的基础任务,后续框架开发的底层代码。

设计思路:本层主要对基本的方法如定位方法,断言基本类,混淆数据处理,驱动选择等方法提到的一个概念层,分一个主目录存放 目录设计:BasePage.py 基本类 封装基本的元素定位,元素操作类和方法 BaseAsert.py 断言类,异常处理类封装,驱动类封装等等。

1、基础层的作用

1、框架中设计基础层实际是为了应对后续繁杂的业务和逻辑代码,试想如果没有基础层的处理,框架后续交互使用时大量测试人员进入到脚本开发,每个人人都在代码里面写重复的代码或者说他们内部之间协定互相引用公共类,这样对于框架的健壮性可扩展性是不强的。如果最开始框架设计时就给与一个基础层和说明,后续交互后测试人员时都引用基础层的代码即可。

2、基于pageobject的代码设计思路,需要基础层的出现,分离公共代码和其他层代码的耦合

2、基础层有什么

当前的目录设计下我是写了这么几个py文件,如下

  1. BaseAssert.py 断言基类
  2. BaseLoggers.py 用例日志打印基类
  3. BasePage.py 页面对象基类
  4. BrowserPage.py 浏览器操作基类
  5. comsrc.py 公共方法基类
  6. decorator.py 公共装饰器类
  7. Driver.py 驱动以及运行选择基类
  8. exceptions.py 异常处理类
  9. AddCookies.py 鉴权基类

当前只是完成了这么几个基类,后续随着框架的完善,会新增部分代码

3、poium借鉴式开发的原因

  1. 我的代码思路实现在之前的书籍上也有大家可以看看,但是当我看到poium开源代码后,我发现这个框架里有很多我想要的东西,那么为什么不在巨人的肩膀上完善后续框架的开发呢。
  2. 不是说poium的代码就不需要任何修改直接照搬,实际还是要根据框架的需要来修改源代码,比如日志的输出,新增用例的运行和运行环境的选择等
  3. 这里还是非常感谢poium的作者,让我有学习提升的机会,特此声明其中BasePage.py、BrowserPage.py文件大体内容都是poium开源代码里面的。

二、基类文件BasePage.py开发

UI自动化实际大家接触的时候最开始学习到的就是元素定位,比如形如find_element_by_id()的几大定位方法,那么本文件中对这些操作都做了一次性的封装,

开发PageObject类

web端的页面对象处理需要使用到PageObject类,本类基本上处理了浏览器初始化时遇到的公共函数,以下对各个类进行代码演示

class PageObject(object):
    """
    Page Object pattern.
    """

    def __init__(self, driver, url=None):
        """
        :param driver: `selenium.webdriver.WebDriver` Selenium webdriver instance
        :param url: `str`
        Root URI to base any calls to the ``PageObject.get`` method. If not defined
        in the constructor it will try and look it from the webdriver object.
        """
        self.driver = driver
        self.root_uri = url if url else getattr(self.driver, 'url', None)

    def get(self, uri):
        """
        :param uri:  URI to GET, based off of the root_uri attribute.
        """
        root_uri = self.root_uri or ''
        self.driver.get(root_uri + uri)
        self.driver.implicitly_wait(4)

    def add_cookies(self, cookies_dict):
        self.driver.add_cookie(cookies_dict)

    def get_all_cookies(self):
        return self.driver.get_cookies()

    def delete_all_cookie(self):
        self.driver.delete_all_cookies()

    def get_one_cookies(self, name):
        return self.driver.get_cookie(name)

    def delete_one_cookies(self, name):
        self.driver.delete_cookie(name)

    def get_page_source(self):
        return self.driver.page_source


单独拿出get()函数来分析,此函数使用到的host+path格式的url拼接跳转,

    def get(self, uri):
        """
        :param uri:  URI to GET, based off of the root_uri attribute.
        """
        root_uri = self.root_uri or ''
        self.driver.get(root_uri + uri)
        self.driver.implicitly_wait(4)

开发Element类

接下来看一下另外一个主类Element,对元素定位进行了公共的封装,以前我再书籍中写到的定位元素封装使通过对yaml文件中形如这样的数据xpath->//button[contains(text(),‘确认支付’)],进行分割识别定位方式和定位的元素信息,而在这里换了另外一种更为简洁的方法来完成,依赖键值对的特性来完成。这里我截取了部分核心代码供参考

class Element(object):
    """
    元素对象
    """

    def __init__(self, timeout=5, describe="undefined", index=0, **kwargs):
        self.timeout = timeout
        self.index = index
        self.desc = describe
        if not kwargs:
            raise ValueError("Please specify a locator")
        if len(kwargs) > 1:
            raise ValueError("Please specify only one locator")
        self.kwargs = kwargs
        self.k, self.v = next(iter(kwargs.items()))

        if self.k not in LOCATOR_LIST.keys():
            raise FindElementTypesError("Element positioning of type '{}' is not supported.".format(self.k))

    def __get__(self, instance, owner):
        if instance is None:
            return None

        Browser.driver = instance.driver
        return self

    def __set__(self, instance, value):
        self.__get__(instance, instance.__class__)
        self.send_keys(value)

    @func_set_timeout(0.5)
    def __elements(self, key, vlaue):
        elems = Browser.driver.find_elements(by=key, value=vlaue)
        return elems

    def __find_element(self, elem):
        """
        Find if the element exists.
        """
        for i in range(self.timeout):
            try:
                elems = self.__elements(elem[0], elem[1])
            except FunctionTimedOut:
                elems = []

            if len(elems) == 1:
                # app.logger.info("✅ Find element: {by}={value} ".format(by=elem[0], value=elem[1]))
                logger.info("✅ Find element: {by}={value} ".format(by=elem[0], value=elem[1]))
                break
            elif len(elems) > 1:
                logger.info("❓ Find {n} elements through: {by}={value}".format(
                    n=len(elems), by=elem[0], value=elem[1]))
                break
            else:
                sleep(1)
        else:
            error_msg = "❌ Find 0 elements through: {by}={value}".format(by=elem[0], value=elem[1])
            logger.error(error_msg)
            print(error_msg)
            raise NoSuchElementException(error_msg)

    def __get_element(self, by, value):
        """
        根据传入的定位方式获取到页面对象供后续调用
        """

        # selenium
        if by == "id_":
            self.__find_element((By.ID, value))
            elem = Browser.driver.find_elements_by_id(value)[self.index]
        elif by == "name":
            self.__find_element((By.NAME, value))
            elem = Browser.driver.find_elements_by_name(value)[self.index]
        elif by == "class_name":
            self.__find_element((By.CLASS_NAME, value))
            elem = Browser.driver.find_elements_by_class_name(value)[self.index]
        elif by == "tag":
            self.__find_element((By.TAG_NAME, value))
            elem = Browser.driver.find_elements_by_tag_name(value)[self.index]
        elif by == "link_text":
            self.__find_element((By.LINK_TEXT, value))
            elem = Browser.driver.find_elements_by_link_text(value)[self.index]
        elif by == "partial_link_text":
            self.__find_element((By.PARTIAL_LINK_TEXT, value))
            elem = Browser.driver.find_elements_by_partial_link_text(value)[self.index]
        elif by == "xpath":
            self.__find_element((By.XPATH, value))
            elem = Browser.driver.find_elements_by_xpath(value)[self.index]
        elif by == "css":
            self.__find_element((By.CSS_SELECTOR, value))
            elem = Browser.driver.find_elements_by_css_selector(value)[self.index]

大概对文中的实现思路则是这样的,通过获取入参数据来识别定位方式和定位元素入xpath=//button[contains(text(),‘确认支付’)],则定位方式为xpath,元素信息是//button[contains(text(),‘确认支付’)],之所以说对比我之前的思路相对更优,是在于使用到了这两个方法__get__、set。便于了获取对象后直接使用方法如这个用法


user_input = Element(id_="j_username")

page.user_input="yuanbaojun"

开发Elements类

实际就是单个元素和一组元素定位的区别,不做概述了

其他类开发

这里面其他类的开发实际根据后面的实际项目开发需要来开发,比如当前能很容易想到的元素隐藏的查找类,元素等待类等等

三、基类文件BrowserPage.py开发

UI自动化框架的开发最开始的切入方向就是从web端自动化开始实现的,在web端自动化时浏览器相关的操作会被经常使用到的,所以基于web自动化的特性需要对浏览器相关的操作做一次封装,BrowserPage.py文件目前是主要的封装类文件了

开发Page类

在这里我把Page类继承自PageObject类,让page类拥有PageObject类的属性,page类的内容有fram的切换,浏览器标签页的切换,截图等功能。这里我罗列出了部分的代码供参考

class Page(PageObject):
    """
    Implement the APIs with javascript,
    and selenium/appium extension APIs。
    """

    def window_scroll(self, width=None, height=None):
        """
        JavaScript API, Only support css positioning
        Setting width and height of window scroll bar.
        """
        if width is None:
            width = "0"
        if height is None:
            height = "0"
        js = "window.scrollTo({w},{h});".format(w=str(width), h=height)
        self.driver.execute_script(js)

    @property
    def title(self):
        """
        JavaScript API
        Get page title.
        """
        js = 'return document.title;'
        return self.driver.execute_script(js)

    @property
    def url(self):
        """
        JavaScript API
        Get page URL.
        """
        js = "return document.URL;"
        return self.driver.execute_script(js)

    def switch_to_frame(self, frame_reference):
        """
        selenium API
        Switches focus to the specified frame, by id, name, or webelement.
        """
        self.driver.switch_to.frame(frame_reference)

    def switch_to_parent_frame(self

你可能感兴趣的:(测试,selenium自动化测试,python)