课外活动:再次理解页面实例化PO对象的魔法方法__getattr__
课外活动:再次理解页面实例化PO对象的魔法方法__getattr__
一、动态属性访问机制解析
1.1 核心实现原理
class Page:def __getattr__(self, loc):"""魔法方法拦截未定义属性访问"""if loc not in self.locators.keys():raise Exceptionby, val = self.locators[loc] # 解包定位策略return self.driver.find_element(by, val) # 动态返回WebElement
执行流程分析:
二、self.username的生成路径
2.1 配置加载阶段
class CommonLoginPage(Page):locators = YamlReader(YAML_ELEMENT['cp']).data # 加载YAML配置
YAML配置文件示例:
username:- id- ctl00_MainContent_username
password:- id - ctl00_MainContent_password
loginBtn:- id- ctl00_MainContent_login_button
2.2 属性访问阶段
def login(self, username: str = 'Tester'):self.username.send_keys(username) # 触发__getattr__
具体执行步骤:
- 解释器检查
self
对象是否存在username
属性 - 未找到属性时调用
__getattr__('username')
- 在
locators
字典中查找’username’键 - 获取元组
('id', 'ctl00_MainContent_username')
- 执行
driver.find_element(id, 'ctl00_MainContent_username')
- 返回WebElement对象
- 调用该对象的
send_keys()
方法
三、关键技术点解析
3.1 延迟加载机制
# 仅在首次访问时加载配置
if loc not in self.locators.keys():raise Exception # 即时失败机制
优势特征:
- 避免启动时加载全部元素
- 减少不必要的资源消耗
- 支持动态配置更新
- 提升测试执行效率
3.2 异常处理流程
四、设计模式优势分析
4.1 与传统写法的对比
传统写法 | 实例化PO写法 |
---|---|
self.find_element(id, '...') | self.username |
显式维护定位器字典 | YAML配置自动加载 |
需要重复编写find_element | 通过魔法方法自动处理 |
修改定位策略需要修改代码 | 仅需修改YAML文件 |
4.2 工程化优势
- 关注点分离:测试逻辑与元素定位解耦
- 维护成本降低:修改元素定位无需改动代码
- 可读性提升:业务逻辑更直观
- 扩展性增强:支持多环境配置切换
五、注意事项与最佳实践
5.1 配置规范要求
# 正确写法(严格缩进)
loginBtn:- id- ctl00_login_button# 错误写法(缺少层级)
loginBtn: id, ctl00_login_button
5.2 异常处理建议
def __getattr__(self, loc):try:by, val = self.locators[loc]except KeyError:raise ElementNotFoundError(f"元素'{loc}'未配置") # 自定义异常except ValueError:raise InvalidLocatorError(f"定位器格式错误") # 配置校验return self.driver.find_element(by, val)
六、性能优化方向
6.1 元素缓存机制
class Page:_elements_cache = {} # 新增元素缓存def __getattr__(self, loc):if loc not in self._elements_cache:# ...定位逻辑...self._elements_cache[loc] = elementreturn self._elements_cache[loc]
6.2 预加载策略
def preload_elements(self):for loc in self.locators:getattr(self, loc) # 提前触发元素加载
七、完整代码
"""
Python :3.13.3
Selenium: 4.31.0
"""from chap3.ob import *
from setting import *
from chap5.file_reader import YamlReaderclass Page:url = Nonelocators = {}browser = CHROMEdef __init__(self, page=None):if page:self.driver = page.driverelse:self.driver = self.browser().start_chrome_browserdef __getattr__(self, loc):if loc not in self.locators.keys():raise Exceptionby, val = self.locators[loc]return self.driver.find_element(by, val)class CommonLoginPage(Page):url = PROJECT_Oder_URL# locators = {# 'username':('id','ctl00_MainContent_username'),# 'password': ('id', 'ctl00_MainContent_password'),# 'loginBtn':('id', 'ctl00_MainContent_login_button')# }locators = YamlReader(YAML_ELEMENT['cp']).datadef 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')# })CommonLoginPage.locators.update(YamlReader(YAML_ELEMENT['op']).data)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 sleepsleep(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()
「小贴士」:点击头像→【关注】按钮,获取更多软件测试的晋升认知不迷路! 🚀