• 谈谈Python中的pageobject和pagefactory的用法
  • 发布于 1个月前
  • 148 热度
    0 评论
我们做UI自动化中, page object用得比较多。其实这个也体现了我们古老的智慧: “物以类聚,人以群分”。什么是page factory呢?讲这方面的就比较少,我来谈谈我的看法。
我们先不谈它们的定义。

我给你先做个测试题:请你花10秒钟的时间,记住以下的20个数字:

好,我们再来试一组数字,还是花10秒钟来记住它:

其实这2组的20个数字是一样的,但是不是觉得第二组一下子就记住了?

为什么会这样?

因为第二组数据更符合我们大脑的使用习惯,数字与数字之间有清晰的逻辑和结构。在我们面向对象的编程语言中,不管是java,还是python。就是应用的这种逻辑。相同属性的构建成一个类。在UI自动化测试中,我们也可以运用这种思想,将UI按照页面模块化。方便我们查找,更新和维护。这就是page object主要思想。如果一个测试对象,比如某网站,页面很多,元素很多,而且需要封装的方法也多,如果不分门别类,你会发现case写得越多,会越发失控。


就像我们生活中,如果衣服乱放,找起来就很困难。如果将它们分门别类的安放好,组成一个结构分明的整体,方便日后的理解、存储、使用。

有了这个思想,实现起来也简单。

现在来简单说一说pagefactory, 其实就是对page object 模式的一种补充。如同一个标签。对每个对象有一个说明。如同购买商品,有个产品说明书一样。
但是如果手写的话,会很麻烦。如果能扫二维码,这是多么的简单。
我将page factory 理解成二维码。

我们知道,在java中很容易实现 page factory, 可以用annotation来写,跟写注释一样。
@FindBy(name = "q")
private WebElement searchBox;
定义的同时,将查找方式都写出来了,一看便知。python能不能实现呢?自己写一个装饰器函数,也能实现这个功能。
比如我定义一个pageobject_support.py文件:
__all__ = ['cacheable', 'callable_find_by', 'property_find_by']
def cacheable_decorator(lookup):        
    def func(self):        
        if not hasattr(self, '_elements_cache'):            
            self._elements_cache = {}  # {callable_id: element(s)}
        cache = self._elements_cache
        key = id(lookup)        
        if key not in cache:
            cache[key] = lookup(self)        
        return cache[key]
     return func
cacheable = cacheable_decorator
_strategy_kwargs = ['id_', 'xpath', 'link_text', 'partial_link_text','name', 'tag_name', 'class_name', 'css_selector']
def _callable_find_by(how, using, multiple, cacheable, context, driver_attr, **kwargs):    
    def func(self):        # context - driver or a certain element
        if context:
            ctx = context() if callable(context) else context.__get__(self)  # or property
        else:
            ctx = getattr(self, driver_attr)        # 'how' AND 'using' take precedence over keyword arguments
        if how and using:
            lookup = ctx.find_elements if multiple else ctx.find_element            
             return lookup(how, using)        
       if len(kwargs) != 1 or list(kwargs.keys())[0] not in _strategy_kwargs:
            raise ValueError(                "If 'how' AND 'using' are not specified, one and only one of the following "
                "valid keyword arguments should be provided: %s." % _strategy_kwargs)
        key = list(kwargs.keys())[0]
        value = kwargs[key]
        suffix = key[:-1] if key.endswith('_') else key  # find_element(s)_by_xxx
        prefix = 'find_elements_by' if multiple else 'find_element_by'
        lookup = getattr(ctx, '%s_%s' % (prefix, suffix))        
        return lookup(value)    
    return cacheable_decorator(func) if cacheable else func
def callable_find_by(how=None, using=None, multiple=False, cacheable=False, context=None, driver_attr='_driver',
                     **kwargs):    
    return _callable_find_by(how, using, multiple, cacheable, context, driver_attr, **kwargs)
def property_find_by(how=None, using=None, multiple=False, cacheable=False, context=None, driver_attr='_driver',
                     **kwargs):    
    return property(_callable_find_by(how, using, multiple, cacheable, context, driver_attr, **kwargs))
然后写一个测试函数来测试,这里以用百度查找为例子:
from pageobject_support import callable_find_by as find_byfrom selenium import webdriverclass BaiduSearchPage(object):
    def __init__(self, driver):
        self._driver = driver    #search_box = find_by(id_="kw")
    search_box = find_by(how="id",using="kw")

    search_button = find_by(id_='su')    def search(self, keywords):
        self.search_box().clear()
        self.search_box().send_keys(keywords)
        self.search_button().click()if __name__ == '__main__':
    driver = webdriver.Chrome()
    driver.get("https://www.baidu.com")
    BaiduSearchPage(driver).search("selenium")    #driver.close()
当然你可以用:“search_box = find_by(how="id",using="kw")”, 但是用:“search_box = find_by(id_="kw")” 更简单。这样的好处就是简单明了,不要到处写 driver.find_element_by_XXX.当然这只是一个非常简单的例子,你可以继续加工,写成一个框架。到时候直接拿来用就可以了。
用户评论