前军教程网

中小站长与DIV+CSS网页布局开发技术人员的首选CSS学习平台

PageFactory(页面工厂)模式在GUI自动化测试中的正确打开方式

在前面的文章中,我们分别介绍过PageObject模式在APP自动化测试和Web自动化测试中的实际应用:

里面具体描述了PageObject模式的概念、设计思想以及案例实践。而今天的主题是PageFactory模式,两者听起来似乎只有一字之差。那么什么是PageFactory模式?它们有什么区别与联系?PageFactory比PageObject好在哪里?如何在实际项目中应用?我们慢慢往下看。

一、PageObject模式

在正式介绍PageFactory前,我们不妨先来简单看一下PageObject模式的代码:

① 第一层:base_page(元素定位方法二次封装)层

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

DEFAULT_SECONDS = 5

class BasePage(object):
    def __init__(self, driver):
        self.driver = driver

    def by_id(self, id_name):
        locator = (By.ID, id_name)
        WebDriverWait(self.driver, DEFAULT_SECONDS).until(EC.visibility_of_element_located(locator))
        return self.driver.find_element(locator)

    def by_class(self, class_name):
        locator = (By.CLASS_NAME, class_name)
        WebDriverWait(self.driver, DEFAULT_SECONDS).until(EC.visibility_of_element_located(locator))
        return self.driver.find_element(locator)

    def by_css_selector(self, css_name):
        locator = (By.CSS_SELECTOR, css_name)
        WebDriverWait(self.driver, DEFAULT_SECONDS).until(EC.visibility_of_element_located(locator))
        return self.driver.find_element(locator)

② 第二层:业务中的页面元素层

封装各个页面元素对象,如手机号输入框、登录按钮

from common.base_page import BasePage


class LoginPage(BasePage):
    def username_input(self):
        """用户名输入框"""
        return self.by_css_selector(css_name="[placeholder='请输入账户/手机号']")

    def password_input(self):
        """密码输入框"""
        return self.by_css_selector(css_name="[placeholder='请输入你的密码']")

③ 测试用例层

调用页面元素对象,组成操作步骤,验证业务流。

def test_login():
    driver = webdriver.Chrome()
    driver.get('http://xxxxxx')
    login_page = LoginPage(driver)
    login_page.username_input().send_keys('15288888888')
    login_page.password_input().send_keys('123456')
    login_page.password_input().click()
    # 可以添加断言和其他测试逻辑
    driver.quit()

通过上述代码设计可以看出PageObject模式的:

优点:将元素的定位和操作分离,元素的定位信息(如 XPath、CSS 选择器)存储在页面对象中,而测试用例只调用页面对象的操作方法,不关心元素的定位细节。这样,就算是页面的元素发生变化(例如元素的 ID、XPath 改变),只需要在页面对象中修改元素的定位信息,而无需修改测试用例。

缺点:在base_page中,为了保持定位稳定性,每个元素定位方法中都加入了显式等待机制:WebDriverWait(self.driver, DEFAULT_SECONDS).until(
EC.visibility_of_element_located(locator)),产生了大量的代码冗余。

二、PageFactory模式

1.优化PageObject

既然每个方法中都包含WebDriverWait,那么完全可以将其提炼出来单独作为一个独立的方法。

① 设计base_page

  • 首先,定义初始化方法,并且在初始化方法中定义一个变量element_cache,用来缓存已经查找过的元素
  • 其次,提炼显式等待机制WebDriverWait代码,将其写在wait_for_element方法中
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.element_cache = {}  # 缓存已经查找过的元素

    def wait_for_element(self, by, value):
        locator = (by, value)
        if locator in self.element_cache:
            return self.element_cache[locator]
        element = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(locator))
        self.element_cache[locator] = element
        return element

② 封装页面对象

页面元素层,同样继承BasePage类,查找页面元素对象时,通过By.xx进行定位,并使用@property装饰器进行修饰。

from common.base_page import BasePage
from selenium.webdriver.common.by import By

class LoginPage(BasePage):
    def __init__(self, driver):
        super().__init__(driver)

    @property
    def username_input(self):
        return self.wait_for_element(By.CSS_SELECTOR, "[placeholder='请输入账户/手机号']")

    @property
    def password_input(self):
        return self.wait_for_element(By.CSS_SELECTOR, "[placeholder='请输入你的密码']")

    @property
    def click_login(self):
        return self.wait_for_element(By.XPATH, "xxxx")

③ 测试用例层

注意,因为login_page中的username_input、password_input等方法使用了@property装饰器装饰,已经将其转换为属性,所以后续在调用此方法时要像调用普通属性一样调用它们,即:login_page.username_input,而不是login_page.username_input()。

def test_login():
    driver = webdriver.Chrome()
    driver.get('http://xxxxxx')
    login_page = LoginPage(driver)
    login_page.username_input.send_keys('15288888888')
    login_page.password_input.send_keys('123456')
    login_page.password_input.click()
    # 可以添加断言和其他测试逻辑
    driver.quit()

2.什么是PageFactory模式

其实以上就是通过属性实现PageFactory的过程。那么,什么是PageFactory模式呢?

PageFactory模式是Selenium WebDriver中用于简化Page Object模式实现的一种设计模式,它提供了一种更简洁、更具可读性和可维护性的方式来实现页面元素的定位和操作。PageFactory 模式主要使用了装饰器和属性来管理页面元素的查找和初始化,并且通常结合 selenium 的 PageFactory 类(如果使用 Java 语言)或自定义的装饰器(在 Python 等语言中可以自定义实现)来完成。

核心概念

  • 元素定位和初始化:将页面元素的定位信息和元素的初始化延迟到使用元素时进行,而不是在页面对象的构造函数中完成,这样可以提高代码的可读性和可维护性。
  • 装饰器或注解的使用:使用装饰器或注解来标记元素,通过特定的机制(如 @FindBy 注解或自定义装饰器)将元素的定位信息和元素的查找逻辑关联起来。

适用场景

  • 需要管理大量页面元素的Web自动化测试。
  • 团队协作时,统一代码风格和元素管理方式。
  • 希望减少样板代码,提升可读性和维护性。

注意事项

  • 元素等待:在上述示例中使用 WebDriverWait 确保元素可见,但可以根据需要使用不同的 expected_conditions (如 element_to_be_clickable 等)。
  • 元素的唯一性:确保元素的定位信息能够唯一确定元素,避免定位到多个元素或错误元素。

3.PageFactory模式的另一种实现方式

除了使用属性“@property”,也可以通过“装饰器+__getattribute__”的方式来实现PageFactory设计:

# coding: utf-8
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.get_css_selector import *


class PageFactory:
    def __init__(self, driver):
        self.driver = driver
        self.element_cache = {}

    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if hasattr(attr, '__call__') and hasattr(attr, 'locator'):
            if name in self.element_cache:
                return self.element_cache[name]
            by, value = attr.locator
            element = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((by, value)))
            self.element_cache[name] = element
            return element
        return attr


def find_by(by, value):
    def decorator(func):
        func.locator = (by, value)
        return func

    return decorator


class LoginPage(PageFactory):
    def __init__(self, driver):
        super().__init__(driver)

    @find_by(By.CSS_SELECTOR, "[placeholder='请输入账户/手机号']")
    def username_input(self):
        pass

    @find_by(By.CSS_SELECTOR, "[placeholder='请输入你的密码']")
    def password_input(self):
        pass

    @find_by(By.XPATH, "//button[@class='login-btn']")
    def click_login(self):
        pass


def test_login():
    driver = webdriver.Chrome()
    driver.get('http://xxxxxxx')
    login_page = LoginPage(driver)
    login_page.username_input.send_keys('15288888888')
    login_page.password_input.send_keys('123456')
    login_page.password_input.click()
    # 可以添加断言和其他测试逻辑
    driver.quit()

代码解释

  • PageFactory 类:
    • __init__ 方法存储了 driver 对象。
    • __getattribute__ 方法是一个特殊的 Python 魔术方法,当访问一个属性时会被调用。如果该属性是一个可调用的函数且具有 locator 属性(由 find_by 装饰器添加),则使用 WebDriverWait 查找元素并返回。
  • find_by 装饰器:
    • 它接收元素的定位信息(by 和 value)。
    • 装饰器将 locator 属性添加到被装饰的函数中,存储定位信息。
  • LoginPage 类:
    • 继承自 PageFactory 类。
    • 每个元素定位方法(如 username_input、password_input 和 login_button)都使用 find_by 装饰器,添加元素的定位信息。
  • test_login 函数:
    • 初始化 Chrome 浏览器驱动并打开登录页面。
    • 创建 LoginPage 对象,通过调用元素定位方法获取元素并操作元素。

这样实现的优点是:

  • 简洁性:使用装饰器或注解将元素定位信息与元素查找逻辑分离,使代码更加简洁。
  • 可维护性:元素定位信息集中在装饰器或注解中,修改元素定位信息时只需要修改装饰器的参数,无需修改元素操作方法。
  • 延迟查找:元素的查找在使用时才进行,提高性能和稳定性,避免元素提前查找时可能出现的问题(如元素未加载)。
  • 避免重复查找:同时添加了元素缓存,避免多次查找相同元素。


以上就是GUI自动化测试中,PageFactory模式的两种设计实现。相较于PageObject模式,PageFactory模式更简洁、更具可读性和可维护性。如果你有更好的设计方案或心得,欢迎与我留言讨论!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言