当前位置: 首页 > news >正文

课外活动:再次理解页面实例化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
执行流程分析:
TestCase PageObject WebDriver YAML配置 WebElement self.username __getattr__('username') 查询locators['username'] ('id','ctl00_MainContent_username') find_element(id, ctl00...) WebElement对象 send_keys('Tester') TestCase PageObject WebDriver YAML配置 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__
具体执行步骤:
  1. 解释器检查self对象是否存在username属性
  2. 未找到属性时调用__getattr__('username')
  3. locators字典中查找’username’键
  4. 获取元组('id', 'ctl00_MainContent_username')
  5. 执行driver.find_element(id, 'ctl00_MainContent_username')
  6. 返回WebElement对象
  7. 调用该对象的send_keys()方法

三、关键技术点解析

3.1 延迟加载机制

# 仅在首次访问时加载配置
if loc not in self.locators.keys():raise Exception  # 即时失败机制
优势特征:
  • 避免启动时加载全部元素
  • 减少不必要的资源消耗
  • 支持动态配置更新
  • 提升测试执行效率

3.2 异常处理流程

访问self.username
存在locators配置?
返回WebElement
抛出异常
执行元素操作
测试立即失败

四、设计模式优势分析

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()

「小贴士」:点击头像→【关注】按钮,获取更多软件测试的晋升认知不迷路! 🚀

http://www.xdnf.cn/news/520399.html

相关文章:

  • 【免杀】C2免杀技术(五)动态API
  • C2S-Scale方法解读
  • [Android] 青木扫描全能文档3.0,支持自动扫描功能
  • 机器学习入门之朴素叶贝斯和决策树分类(四)
  • 【VMware】开启「共享文件夹」
  • 计算机系统的工作原理
  • 2.2.5
  • 进程间通信--信号量【Linux操作系统】
  • leetcode解题思路分析(一百六十四)1418 - 1424 题
  • [论文品鉴] DeepSeek V3 最新论文 之 MHA、MQA、GQA、MLA
  • 进程状态并详解S和D状态
  • C++学习:六个月从基础到就业——C++17:结构化绑定
  • 什么是dom?作用是什么
  • 产品周围的几面墙
  • C++高级用法--绑定器和函数对象
  • 垂直智能体:企业AI落地的正确打开方式
  • [人月神话_6] 另外一面 | 一页流程图 | 没有银弹
  • 三:操作系统线程管理之用户级线程与内核级线程
  • 大模型应用开发工程师
  • 从逻辑学视角探析证据学的理论框架与应用体系;《证据学》大纲参考
  • Java学习手册:服务熔断与降级
  • 朴素贝叶斯
  • 做什么, what to do?
  • 面试题总结二
  • atcoder C - ~
  • EmuEdit
  • 网页 H5 微应用接入钉钉自动登录
  • python29
  • 【从基础到模型网络】深度学习-语义分割-ROI
  • C++ - 网络编程之初始连接(Winsock2 概述、初始连接案例、初始连接案例解读)