页面对象开发的第二种实现方式:页面实例化
代码架构解析
1. Page基类设计
class Page:
url = None
locators = {}
browser = CHROME
def __init__(self, page=None):
if page:
self.driver = page.driver
else:
self.driver = self.browser().start_chrome_browser
def __getattr__(self, loc):
if loc not in self.locators.keys():
raise Exception
by, val = self.locators[loc]
return self.driver.find_element(by, val)
-
核心机制:
url
:存储页面地址的类属性locators
:元素定位器字典(需被子类覆盖)__init__
:支持通过已有页面实例共享driver__getattr__
:动态属性访问实现元素定位
-
设计亮点:
- 浏览器实例共享(实现页面跳转)
- 动态元素定位(通过属性名映射locators字典)
- 统一驱动管理(自动初始化或复用已有驱动)
2. CommonLoginPage登录模块
class CommonLoginPage(Page):
url = 'http://secure.smartbearsoftware.com/...'
locators = {
'username': ('id','ctl00_MainContent_username'),
'password': ('id', 'ctl00_MainContent_password'),
'loginBtn': ('id', 'ctl00_MainContent_login_button')
}
def get(self):
self.driver.get(self.url)
def login(self, username='Tester', password='test'):
self.username.send_keys(username)
self.password.send_keys(password)
self.loginBtn.click()
- 功能实现:
- 继承Page基类的基础能力
- 定义具体页面的元素定位器
- 封装页面访问和登录操作
- 通过属性访问元素(如self.username)
3. MainPage主界面模块
class MainPage(CommonLoginPage):
CommonLoginPage.locators.update({
'clickOrder': ('xpath', '//*[@id="ctl00_menu"]/li[3]/a'),
'orderInput': ('id', 'ctl00_MainContent_fmwOrder_txtName'),
'clickProcess': ('id', 'ctl00_MainContent_fmwOrder_InsertButton'),
'bug_label': ('id',"ctl00_MainContent_fmwOrder_RequiredFieldValidator3"),
'order_label': ('xpath','//*[@id="aspnetForm"]//td[1]/h1')
})
def search_bug(self, order_input='Tom'):
self.clickOrder.click()
self.orderInput.send_keys(order_input)
self.clickProcess.click()
- 扩展机制:
- 通过
locators.update
合并父类定位器 - 添加订单操作相关元素定位
- 实现缺陷搜索业务流程
- 继承复用登录功能
- 通过
4. TestMain测试执行类
class TestMain:
def test_login(self):
page = MainPage()
page.get()
page.login()
assert page.order_label.text == 'Web Orders'
page.driver.quit()
def test_search(self):
page = MainPage()
page.get()
page.login()
page.search_bug()
sleep(4)
assert page.bug_label.text == "Field 'Street' cannot be empty."
page.driver.quit()
- 测试流程:
- 创建页面实例时自动初始化浏览器
- 链式调用页面操作方法
- 通过页面属性访问元素进行断言
- 显式管理浏览器生命周期
类关系图示
关键设计模式
1. 动态属性映射
2. 页面实例传递
# 页面跳转示例
class OrderPage(Page):
def goto_detail(self):
return OrderDetailPage(page=self) # 传递当前页面实例
3. 定位器继承机制
class BasePage(Page):
locators = {'header': ('css', '.header')}
class HomePage(BasePage):
locators = BasePage.locators.copy()
locators.update({'banner': ('id', 'banner')})
理解上面代码,代码继续优化
1. 定位器管理改进
# 使用类属性合并代替直接修改
class MainPage(CommonLoginPage):
locators = {**CommonLoginPage.locators, **{
'clickOrder': ('xpath', '...'),
'orderInput': ('id', '...')
}}
2. 异常处理优化
def __getattr__(self, loc):
if loc not in self.locators:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{loc}'")
by, val = self.locators[loc]
try:
return self.driver.find_element(by, val)
except NoSuchElementException:
raise ElementNotFoundError(loc)
3. 等待机制集成
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def __getattr__(self, loc):
# ...
return WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((by, val))
方案优缺点对比
优势
- 减少样板代码:通过动态属性访问简化元素定位
- 增强可维护性:集中管理元素定位器
- 支持页面跳转:通过实例共享实现跨页面操作
注意事项
- 定位器字典需要严格维护
- 动态属性可能掩盖拼写错误
- 需要合理设计继承体系
全套代码
"""
Python :3.13.3
Selenium: 4.31.0
"""
from chap.ob import *
class Page:
url = None
locators = {}
browser = CHROME
def __init__(self, page=None):
if page:
self.driver = page.driver
else:
self.driver = self.browser().start_chrome_browser
def __getattr__(self, loc):
if loc not in self.locators.keys():
raise Exception
by, val = self.locators[loc]
return self.driver.find_element(by, val)
class CommonLoginPage(Page):
url = 'http://secure.smartbearsoftware.com/samples/testcomplete12/WebOrders/Login.aspx'
locators = {
'username':('id','ctl00_MainContent_username'),
'password': ('id', 'ctl00_MainContent_password'),
'loginBtn':('id', 'ctl00_MainContent_login_button')
}
def get(self):
"""
打开首页地址
:return:
"""
self.driver.get(self.url)
def login(self, username: str = 'Tester', password: str = 'test'):
self.username.send_keys(username)
self.password.send_keys(password)
self.loginBtn.click()
class MainPage(CommonLoginPage):
CommonLoginPage.locators.update({
'clickOrder': ('xpath', '//*[@id="ctl00_menu"]/li[3]/a'),
'orderInput': ('id', 'ctl00_MainContent_fmwOrder_txtName'),
'clickProcess': ('id', 'ctl00_MainContent_fmwOrder_InsertButton'),
'bug_label': ('id',"ctl00_MainContent_fmwOrder_RequiredFieldValidator3"),
'order_label': ('xpath','//*[@id="aspnetForm"]//td[1]/h1')
})
def search_bug(self, order_input: str = 'Tom'):
self.clickOrder.click()
self.orderInput.send_keys(order_input)
self.clickProcess.click()
class TestMain:
"""
测试登录和检索bug功能
"""
def test_login(self):
page = MainPage()
page.get()
page.login()
assert page.order_label.text == 'Web Orders'
print('test_login is passed')
page.driver.quit()
def test_search(self):
page = MainPage()
page.get()
page.login()
page.search_bug()
from time import sleep
sleep(4)
assert page.bug_label.text == "Field 'Street' cannot be empty."
print('test_search is passed')
page.driver.quit()
obj = TestMain()
obj.test_login()
obj.test_search()
“好的页面对象设计应该像使用真实网页一样自然”
「小贴士」:点击头像→【关注】按钮,获取更多软件测试的晋升认知不迷路! 🚀