19. 結合Selenium和YAML對頁面實例化PO對象改造
一、架構升級核心思路
1.1 改造核心目標
# 原始PO模式:顯式定義元素定位
username = ('id', 'ctl00_MainContent_username')# 改造后PO模式:動態屬性訪問
self.username.send_keys('Tester') # 自動觸發元素定位
1.2 關鍵技術實現
- 元編程技術:通過
__getattr__
實現動態屬性訪問 - 配置驅動模式:YAML文件存儲元素定位策略
- 鏈式繼承體系:實現跨頁面元素復用
二、核心類改造解析
2.1 頁面基類增強
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核心配置
# 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 配置加載方式
class CommonLoginPage(Page):locators = YamlReader(YAML_ELEMENT['cp']).data # 動態加載登錄頁配置class MainPage(CommonLoginPage):locators.update(YamlReader(YAML_ELEMENT['op']).data) # 繼承并擴展配置
四、頁面類實現模式
4.1 登錄頁面實現
class CommonLoginPage(Page):url = PROJECT_Oder_URLdef login(self, username='Tester'):self.driver.get(self.url)self.username.send_keys(username) # 動態屬性訪問self.password.send_keys('test')self.loginBtn.click()
4.2 主頁面擴展
class MainPage(CommonLoginPage):def search_bug(self):self.clickOrder.click() # 繼承父類配置self.orderInput.send_keys('Tom') # 新增子類配置
五、執行流程優化
5.1 元素定位流程
5.2 瀏覽器管理優化
def __init__(self, page=None):if page: # 支持頁面間共享driverself.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規范建議
loginBtn:- id # 定位類型- ctl00_login_button # 定位值- desc: 登錄按鈕 # 元數據擴展- timeout: 10 # 顯式等待參數
7.2 異常處理增強
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 :3.13.3
Selenium: 4.31.0po_2.py
"""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()
「小貼士」:點擊頭像→【關注】按鈕,獲取更多軟件測試的晉升認知不迷路! 🚀