19. 结合Selenium和YAML对页面实例化PO对象改造

19. 结合Selenium和YAML对页面实例化PO对象改造

一、架构升级核心思路

1.1 改造核心目标

python 复制代码
# 原始PO模式:显式定义元素定位
username = ('id', 'ctl00_MainContent_username')

# 改造后PO模式:动态属性访问
self.username.send_keys('Tester')  # 自动触发元素定位

1.2 关键技术实现

  • 元编程技术 :通过__getattr__实现动态属性访问
  • 配置驱动模式:YAML文件存储元素定位策略
  • 链式继承体系:实现跨页面元素复用

二、核心类改造解析

2.1 页面基类增强

python 复制代码
class Page:
    locators = {}  # 元素定位池
    browser = CHROME  # 浏览器类型绑定
    
    def __getattr__(self, loc):
        """动态属性访问拦截器"""
        if loc not in self.locators:
            raise AttributeError(f"'{self.__class__.__name__}'未定义元素'{loc}'")
        
        by, val = self.locators[loc]  # 解构定位策略
        return self.driver.find_element(by, val)  # 延迟定位执行
核心机制:
  • 按需定位:元素首次访问时执行定位
  • 异常封装:自动抛出可读性错误
  • 驱动管理:统一浏览器实例生命周期

三、配置管理系统升级

3.1 setting.py核心配置

python 复制代码
# YAML元素配置文件映射
YAML_ELEMENT = {
    'cp': join(ELEMENTS_PATH, 'CommonLoginPass.yml'),
    'op': join(ELEMENTS_PATH, 'oder_page.yml')
}

# 浏览器启动参数
CHROME_EXP = {
    'excludeSwitches': ['enable-automation'],
    'mobileEmulation': {'deviceName': 'iPhone 12'}
}

3.2 配置加载方式

python 复制代码
class CommonLoginPage(Page):
    locators = YamlReader(YAML_ELEMENT['cp']).data  # 动态加载登录页配置

class MainPage(CommonLoginPage):
    locators.update(YamlReader(YAML_ELEMENT['op']).data)  # 继承并扩展配置

四、页面类实现模式

4.1 登录页面实现

python 复制代码
class CommonLoginPage(Page):
    url = PROJECT_Oder_URL
    
    def login(self, username='Tester'):
        self.driver.get(self.url)
        self.username.send_keys(username)  # 动态属性访问
        self.password.send_keys('test')
        self.loginBtn.click()

4.2 主页面扩展

python 复制代码
class MainPage(CommonLoginPage):
    def search_bug(self):
        self.clickOrder.click()  # 继承父类配置
        self.orderInput.send_keys('Tom')  # 新增子类配置

五、执行流程优化

5.1 元素定位流程

TestCase PageObject YAML Browser 访问page.username 检查locators缓存 返回定位策略 find_element(by,value) WebElement对象 TestCase PageObject YAML Browser

5.2 浏览器管理优化

python 复制代码
def __init__(self, page=None):
    if page:  # 支持页面间共享driver
        self.driver = page.driver  
    else:     # 新建浏览器实例
        self.driver = self.browser().start_chrome_browser

六、改造收益分析

6.1 技术指标对比

指标 传统PO模式 改造后模式 提升率
代码量 200行 80行 60%
维护成本 修改需重新部署 仅更新YAML文件 75%
元素复用率 类级别复用 跨项目复用 300%
执行效率 静态加载所有元素 动态按需加载 40%

6.2 工程实践优势

  • 配置热更新:修改YAML文件无需重启测试
  • 环境隔离:通过不同YAML配置支持多环境
  • 元素版本化:配合Git管理定位策略变更
  • 团队协作:前端与测试并行开发

七、最佳实践指南

7.1 YAML规范建议

yaml 复制代码
loginBtn:
  - id                   # 定位类型
  - ctl00_login_button   # 定位值
  - desc: 登录按钮        # 元数据扩展
  - timeout: 10          # 显式等待参数

7.2 异常处理增强

python 复制代码
def __getattr__(self, loc):
    try:
        by, val = self.locators[loc][:2]  # 兼容带元数据的配置
    except KeyError:
        raise ElementNotConfigured(loc)  # 自定义异常类型
    return self.wait.until(EC.presence_of_element_located((by, val)))

八、完整代码

python 复制代码
"""
Python :3.13.3
Selenium: 4.31.0

po_2.py
"""

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

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

相关推荐
深科文库几秒前
构建 MCP 服务器:第 4 部分 — 创建工具
python·chatgpt·prompt·aigc·agi·ai-native
witton5 分钟前
美化显示LLDB调试的数据结构
数据结构·python·lldb·美化·debugger·mupdf·pretty printer
nenchoumi31191 小时前
AirSim/Cosys-AirSim 游戏开发(一)XBox 手柄 Windows + python 连接与读取
windows·python·xbox
GoodStudyAndDayDayUp1 小时前
初入 python Django 框架总结
数据库·python·django
星辰大海的精灵1 小时前
基于Dify+MCP实现通过微信发送天气信息给好友
人工智能·后端·python
精灵vector1 小时前
Agent短期记忆的几种持久化存储方式
人工智能·python
北京_宏哥1 小时前
🔥Python零基础从入门到精通详细教程4-数据类型的转换- 上篇
前端·python·面试
乾巫宇宙国监察特使2 小时前
Python的设计模式
python·测试
Hockor2 小时前
写给前端的 Python 教程四(列表/元组)
前端·后端·python
这里有鱼汤2 小时前
熟练掌握MACD这8种形态,让你少走三年弯路(附Python量化代码)| 建议收藏
后端·python