Python PageFactory-use configuration file to dynamically generate page PageObject

need

In the PageObject mode of Python Selenium, generally each page needs to write a class, and a PageObject is written as follows:

class BaiduPageObject(object):
    def __init__(self, driver):   # 一般通过继成BasePage类实现该方法
        self.driver = driver

    # 页面元素 -----
    search_ipt_loc = ('id', 'kw')
    search_btn_loc = ('id', 'su')

    # 页面操作 -----
    def input_search_ipt(self, keyword):
        search_ipt = self.driver.find_element(*search_ipt_loc)
        search_ipt.clear()
        search_ipt.send_keys(keyword):

    def click_search_btn(self):
        self.driver.find_element(*search_btn_loc).click()

    def search(self, keyword):
        self.input_search_ipt(keyword)
        self.click_search_btn()

For a well-formed UI project, before writing use cases, it is often necessary to write dozens or hundreds of such PageObject classes. Since the format of each PageObject class is similar, you can use
Page Factory (page factory) + configuration file (yaml ) -> PageObject
to dynamically generate each page object

PageFactory is a PageObject-based design pattern in the Selenium Java SDK. It uses @FindBy(id = "kw")syntactic sugar to mark page elements, and supports using @CacheLookupto cache page elements (so that elements only need to be searched once)

Given the dynamic nature of Python, the implementation of Page Factory here is completely different from that in Selenium Java SDK

design and implementation

First design and agree on the format of the page configuration file as follows:

# filename: baidu.yaml
url: https://www.baidu.com  # 可选字段, 用于打开网页
title: 百度一下,你就知道   # 可选字段, 用于判断是否在当前网页
elements:   # 页面元素
  search_ipt: [id, kw]  # 支持id, name, class name, tag name, link text
  search_btn: [id, su]  # partial link text, xpath, css selector

actions:  # 可选字段, 页面操作
  search:
    input: [search_ipt, 默认值]  # input: 固定字段, 第一参数为上面的元素, 第二个为输入的文本 
    click: search_btn  # click: 固定字段, 参数为上面配置的元素

 page_factory.py

# page_factory.py
from types import MethodType  # 用于为实例动态绑定方法
from collections import OrderedDict  # 有序字典, 用于按顺序遍历actions中的操作步骤
import yaml
from selenium import webdriver


class Page(object):
    def __init__(self, driver, from_yaml=None, **kwargs):
        self.driver = driver
        self.url = kwargs.get('url', None)
        self.title = kwargs.get('title', None)
        self.elements = kwargs.get('elements', {})
        self.actions = kwargs.get('url', OrderedDict())
        if from_yaml is not None:
            self.init_page(from_yaml)

    def init_page(self, from_yaml):
        with open(from_yaml, encoding='utf-8') as f:
            data = yaml.load(f)
            self.url = data.get('url', None)
            self.title = data.get('title', None)
            self.elements = data.get('elements', {})
            self.actions = OrderedDict(data.get('actions', {}))

    def open(self):  # 打开页面url
        if self.url is not None:
            self.driver.get(self.url)

    def on_page(self):  # 通过title判断是否在当前页面上
        if self.title is not None:
            return self.title in self.driver.title

    def input(self, element, text):  # 输入操作, 指定配置中的元素名及文本
        element = self.__getattr__(element)  #
        element.clear()
        element.send_keys(text)

    def click(self, element):  # 点击操作, 指定元素名
        element = self.__getattr__(element)
        element.click()

    def _handle_element(self, item):  # 处理要查找的页面元素
        element_loc = self.elements.get(item, None)
        if element_loc is not None:
            element = self.driver.find_element(*element_loc)  # todo try
            setattr(Page, item, element)  # 缓存元素作为页面属性
            return element

    def _handle_action(self, item):   # 处理要查找的操作方法
        action = self.actions.get(item, None)
        if action is not None:

            def pack_action(self, text=None):  # 解析约定的action格式, 组装操作方法
                for step, params in action.items():
                    if step == 'input':
                        if text is not None and isinstance(params, list) and len(params)>1:
                            params[1] = text  # 使用调用时的text参数替换input: [search_ipt, $keyword]的第2个参数
                        self.input(*params)
                    elif step == 'click':
                        self.click(params)

            return MethodType(pack_action, self)  # 为实例绑定操作方法并返回

    def __getattr__(self, item):   # 查找页面属性和操作方法
        if not hasattr(Page, item):  # 如无该属性/方法则动态为页面添加元素属性和操作方法
            return self._handle_element(item) or self._handle_action(item)


if __name__ == '__main__':   # 使用方法
    driver = webdriver.Chrome()
    baidu = Page(driver, 'baidu.yaml')  
    baidu.open()
    baidu.search('临渊')

ALL

  • exception capture
  • Loading of a single url frame page, the return page of each action, and the flow between pages
  • Cache page objects, such as calling baidu = PageFactory(driver, ...) multiple times in different use cases, using the same retirement
  • Add more action support, such as select, hover, switch, etc.
  • Independent template parsing parser method, supports more variables, such $1, $2/$keyword, $func(a)as
  • Add hooks method to Page to make it more flexible

PS:
Using the Page Factory (page factory) method has not been relieved yet. Dozens of hundreds of page writing work is just changed from writing Python classes to writing yaml configuration files. However, static configuration files are relatively different from Python classes. That said, there are the following benefits:

  1. Realized the separation of data (page element locator) and code
  2. PageFactory supports importing configuration parameters in dictionary format to generate pages. The data can come from yaml files, page forms/databases, or even crawled by crawlers, which is convenient for integrating front-end web pages, or crawlers can automatically generate Pages

Practical case

Optical theory is useless, you have to learn to follow along, and you have to do it yourself, so that you can apply what you have learned to practice. At this time, you can learn from some practical cases.

If it is helpful to you, please like and collect it to give the author an encouragement. It is also convenient for you to quickly find it next time.

If you don’t understand, please consult the small card below. The blogger also hopes to learn and progress with like-minded testers

At the right age, choose the right position, and try to give full play to your own advantages.

My road of automated test development is inseparable from the plan of each stage along the way, because I like planning and summarizing,

Test and develop video tutorials, study notes and receive portals! ! !

Guess you like

Origin blog.csdn.net/m0_59868866/article/details/130183965