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

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

相关推荐
长和信泰光伏储能40 分钟前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂1 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study2 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境2 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境2 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1232 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102163 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb003 小时前
一个项目带你入门AI应用开发01
python
0566464 小时前
Python康复训练——常用标准库
开发语言·python·学习
昆曲之源_娄江河畔4 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql