PO模式
1、為什么需要PO思想?
首先我們觀察和思考一下,目前我們寫的作業腳本的問題: 元素定位和操作動 作寫到一起了,這就就會用導致一個問題:
- UI的頁面元素比較容易變化的,所以元素定位和腳本操作寫到一起,一旦 元素發生了變化,這些腳本都需要修改
- 特別是登錄頁面在很多個頁面都會涉及到 被引用,那么就會導致: 維護成 本比較大。 所以,我們通過PO模式來解決這個問題。
2、什么是PO模式
Page Object Model(頁面對象模型), 或者也可稱之為POM。在UI自動化測試 廣泛使用的一種分層設計模式。
page就是網頁的頁面層,object就是封裝的LoginPage 類:每一個頁面一個 類,包含業務邏輯和測試對象
- 頁面元素元素定位: 定義為類的屬性
- 頁面的操作行為 : 定義為類的方法
- 業務邏輯: 對頁面的操作 為了得到實際結果的過程 和步驟 ,這是測試 對象
測試用例+ 測試數據層: 單獨維護。 包含測試邏輯步驟和測試用例
- 測試邏輯: 測試部分 ,預期結果和實際結果的對比 ,這是測試用例
PO核心是通過頁面層封裝所有的頁面元素及操作,測試用例層通過調用頁面層 操作組裝業務邏輯。
PO模式的核心思想:體現了業務邏輯和測試邏輯?的分離,測試用例和測試對象分離
以登錄操作為例:
- 獲取頁面登錄錯誤信息 登出 等操作都是業務邏輯,單獨進行PageObject 的封裝。
- 用例里只傳用例數據,不會出現元素定位的代碼。包括斷言里也不要出現 元素定位。
PO模式最終的實現效果:
頁面層:
- 頁面類A(A1 A2 A3)
- 頁面類B(B1 B2 B3) 頁面類C(C1 C2 C3)
用例層:
- 用例1 = A1 + B2 + C3
- 用例2 = A1 + B2 + C1
PO模式優點:
- 提高測試用例的可讀性
- 提高測試用例可維護性
- 減少代碼重復
我們現在以登錄頁面為例:登錄頁面類
因為這個頁面里有元素定位 + 元素操作: 把這些東西最好是封裝在一起,方便 被用例層調用。
- 頁面里的元素: 寫成類的屬性
- 用元組形式表示;因為后面顯示等待都是用元組調用的;調用用self調 用 ;
- 并不需要一次性把所有元素都寫出來,可以后續擴展
- 頁面的操作: 封裝成實例方法
- driver先沒有,
- 可以作為參數 一些變化的數據也參數化
三層PO思想【BasePage封裝思想】
但是這樣寫還有個問題:既然每個元素都用顯示等待操作,每個頁面都寫一 遍太麻煩,冗余度太高了;而且每個頁面都有一些其他的共同的操作,比如點 擊,輸入文本等;那么每個頁面都重復寫,是不是可以進一步優化呢?
- 這些每個頁面都會調用的方法【等待,點擊,輸入文本等】,就屬于公共 方法; BasePage 封裝思想
- 用三層PO模式: 公共頁面的內容單獨提取出來封裝:這也叫做 。封裝成為一個BasePage類。
- 其他頁面自己獨有的元素和方法依然放在單獨頁面類里封裝;需要用到 basepage類的內容的時候,如何實現?--- 類的繼承實現
所以新建一個common的目錄,建一個base_page的py文件,方所有的功能 方法。
- BasePage 用來存放所有頁面類的公共部分,一些公共的操作(顯式封裝)
- 封裝一個BasePage的類,里面的方法都是每個頁面都要用的公共的方法。
base_page.py
from time import sleepimport pyautogui
import pyperclip
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChainsclass BasePage:# driver每個方法都用,如果直接定位參數,沒有辦法實現實例方法共享,所以可以定義為實例屬性 實現共享def __init__(self,driver): # 初始化方法里定義了以參數 到時候實例化對象傳參"""初始化函數 定義driver作為實例屬性 實例實例方法共享這個實例屬性"""self.driver = driver# 操作和行為 行為為實例方法def wait_element_clickable(self, locator):web_element = WebDriverWait(self.driver, 8, 0.5).until(EC.element_to_be_clickable(locator))return web_elementdef wait_element_visible(self, locator):web_element = WebDriverWait(self.driver, 8, 0.5).until(EC.visibility_of_element_located(locator))return web_elementdef wait_element_presence(self, locator):web_element = WebDriverWait(self.driver, 8, 0.5).until(EC.presence_of_element_located(locator))return web_element# 1、普通點擊操作 --關鍵字def click_element(self,locator):self.wait_element_clickable(locator).click()# 2、鼠標點擊操作def mouse_click(self,locator):# 先找到元素element = self.wait_element_visible(locator)# 用鼠標點擊ActionChains(self.driver).click(element).perform()# 3、js點擊操作def js_click(self,locator):# 先找到元素element = self.wait_element_visible(locator)# 用js傳參點擊self.driver.execute_script("arguments[0].click()",element)# 4、輸入數據文本def input_text(self,locator,text):self.wait_element_visible(locator).send_keys(text)# 5、獲取元素的文本 : 拿到這個文本 返回這個文本def get_text(self,locator):return self.wait_element_visible(locator).text# 6、獲取元素的屬性:拿到這個屬性的值 設置為返回值def get_attribute(self,locator,attr_name):return self.wait_element_visible(locator).get_attribute(attr_name)# 7、窗口切換def switch_window(self,url):# 先拿到所有的窗口句柄handles = self.driver.window_handlesfor win in handles:if self.driver.current_url == url: # 判斷當前的頁面是否為想要的url地址breakelse:self.driver.switch_to.window(win) # 如果不是 繼續切換# 8、移動鼠標def move_mouse(self,locator):element = self.wait_element_presence(locator)ActionChains(self.driver).move_to_element(element).perform()# 9、文件上傳def file_upload(self,file_path):# 1、先復制路徑pyperclip.copy(file_path)# 2、粘貼 -- hotkey 通過熱鍵粘貼pyautogui.hotkey("ctrl", "v")pyautogui.press("enter", presses=2)
home_page_v1.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWaitclass HomePage:# 頁面元素元素定位: 定義為類的屬性 --元組loc_login = (By.XPATH, '//a[text()="登錄"]')# 操作和行為 行為為實例方法def wait_element_clickable(self,driver,locator):web_element = WebDriverWait(driver, 8, 0.5).until(EC.element_to_be_clickable(locator))return web_elementdef wait_element_visible(self,driver,locator):web_element = WebDriverWait(driver, 8, 0.5).until(EC.visibility_of_element_located(locator))return web_elementdef wait_element_presence(self,driver,locator):web_element = WebDriverWait(driver, 8, 0.5).until(EC.presence_of_element_located(locator))return web_element# 點擊首頁里的登錄鏈接按鈕 打開登錄頁面 操作-- 定義為實例方法def click_login_link(self,driver):self.wait_element_visible(driver,self.loc_login).click()
test_login.py
"""
編寫測試用例 執行測試用例 斷言 的框架 - pytest框架
"""
from page_object.login_page_v1 import LoginPage
from page_object.home_page_v1 import HomePage
from selenium import webdriver# pytest框架編寫測試用例
def test_login():driver = webdriver.Chrome()driver.maximize_window()driver.get("http://mall.lemonban.com:3344/")# 1、點擊homepage 登錄的鏈接 == 先實例化對象,再調用實例方法,實例方法要傳參driverHomePage().click_login_link(driver)# 2、調用LoginPage里的login實例方法 執行登錄操作== 先實例化對象,再調用實例方法,實例方法要傳參LoginPage().login(driver,"lemon_py","12345678")