课外活动:再次理解页面实例化PO对象的魔法方法__getattr__

课外活动:再次理解页面实例化PO对象的魔法方法__getattr__

一、动态属性访问机制解析

1.1 核心实现原理

class Page:
    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)  # 动态返回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] = element
        return 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 YamlReader


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 = 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']).data

    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')
    # })
    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 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()

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值