在前面的文章中,我们分别介绍过PageObject模式在APP自动化测试和Web自动化测试中的实际应用:
- 《基于Selenium4+PageObject模式的Web自动化测试最佳实践》
- 《基于UiAutomator2+PageObject模式开展APP自动化测试实战》
里面具体描述了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模式更简洁、更具可读性和可维护性。如果你有更好的设计方案或心得,欢迎与我留言讨论!