1.PO基本介紹
測試PO模式(Page Object Model)
測試頁面和測試腳本分離,即頁面封裝成類,供測試腳本進行調用。
優點
1.提高測試用例的可讀性;
2.減少了代碼的重複;
3.提高測試用例的可維護性,特别是針對UI頻繁變動的項目;
缺點
結構複雜: 基于流程做了子產品化的拆分。
2.Appium方法二次封裝
為什麼二次封裝方法?
減少過多使用類似方法,更友善管理定位元素,盡量将函數方法簡潔化、統一化。
系統已提供定位方法
driver.find_element_by_id()
driver.find_element_by_class_name()
driver.find_element_by_xpath()
driver.find_elements_by_id()
driver.find_elements_by_class_name()
driver.find_elements_by_xpath()
實際以上定位方法封裝以下方法:
find_element(by=By.XX, value=None)
find_elements(by=By.XX, value=None)
# by:定位類型(By.ID,By.CLASS_NAME,By.XPATH)
# value:定位元素的屬性值
封裝思路
為什麼做?
使用統一的方法來完成元素的定位.
怎麼去做?
借用:
find_element(by=By.XX, value=None)
find_elements(by=By.XX, value=None)
封裝實作(一)
# 點選元素
def click_element(self, location,location_vlaue):
# location: 定位類型
# location_vlaue: 定位元素屬性值
self.find_element(location,location_vlaue).click()
# 輸入内容
def input_element(self, location,location_vlaue,text):
# text: 需要輸入的值
self.find_element(location,location_vlaue).send_keys(text)
發現問題:
沒有簡化參數的傳遞,僅僅将點選和輸入做了封裝.
封裝實作(二)
# 點選元素
def click_element(self, location):
# location: 定位類型&定位屬性值,
⚠️ location類型為元祖 格式(By.ID,value),(By.CLASS_NAME,value),(By.XPATH,value)
self.find_element(*location).click()
# *location将元祖類型的值進行了一次解包操作,來滿足find_element的參數傳遞要求
# 輸入内容
def input_element(self, location,text):
# text: 需要輸入的值
self.fm = self.find_element(*location)
self.fm.clear() # 需要先清空輸入框,防止有預設内容
self.fm.send_keys(text)
Demo示例
業務場景:
1.進入設定
2.點選搜尋按鈕
3.輸入123
4.點選搜尋框傳回按鈕
封裝代碼:Base.py
from selenium.webdriver.support.wait import WebDriverWait
class Base(object):
def __init__(self,driver):
self.driver = driver
def find_element(self,loc,timeout=10):
# 封裝稱為智能等待方法
# loc:類型為元祖,格式(By.ID,value),(By.CLASS_NAME,value),(By.XPATH,value)
# timeout:搜尋逾時時間
return WebDriverWait(self.driver, timeout).until(lambda x: x.find_element(*loc))
def click_element(self,loc):
# 封裝點選操作
self.find_element(loc).click()
def input_text(self,loc,text):
# 封裝輸入操作
self.fm = self.find_element(loc)
self.fm.clear() # 需要先清空輸入框,防止有預設内容
self.fm.send_keys(text)
測試代碼: test.py
from appium import webdriver
from selenium.webdriver.common.by import By
# 導入已封裝的類 Base.py
from Base import Base
class Test_Base:
def __init__(self):
desired_caps = {}
# 手機 系統資訊
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 裝置号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 啟動名
desired_caps['appActivity'] = '.Settings'
# 允許輸入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手機驅動對象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
def test(self):
# 搜尋按鈕
search_button = (By.ID,"com.android.settings:id/search")
# 搜尋輸入框
search_text = (By.ID,"android:id/search_src_text")
# 搜尋框傳回按鈕
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
# 執行個體化二次封裝的Base類
base_object = Base(self.driver)
# 點選搜尋輸入框
base_object.click_element(search_button)
# 搜尋框内輸入123
base_object.input_text(search_text,123)
# 點選搜尋框傳回按鈕
base_object.click_element(search_return_button)
# 退出driver對象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
3.頁面元素操作封裝(一)
将頁面元素定位和元素操作封裝在一個類檔案中.
封裝步驟
确定好要封裝的頁面操作
業務場景:
1.進入設定
2.點選搜尋按鈕
3.輸入123
4.點選搜尋框傳回按鈕
需要用到哪些元素和定位方法
# 搜尋按鈕
search_button = (By.ID, "com.android.settings:id/search")
# 搜尋輸入框
search_text = (By.ID, "android:id/search_src_text")
# 搜尋框傳回按鈕
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
依賴的定位方法
使用二次封裝的方法,Base.py檔案
頁面封裝
編寫搜尋操作類:search_page.py
from selenium.webdriver.common.by import By
# 導入基礎定位封裝包
from Base import Base
class Search_Page:
def __init__(self,driver):
# 傳如driver對象
self.driver = driver
# 執行個體化二次封裝的類,用到封裝好的操作函數
self.base_object = Base(self.driver)
# 搜尋按鈕
self.search_button = (By.ID, "com.android.settings:id/search")
# 搜尋輸入框
self.search_text = (By.ID, "android:id/search_src_text")
# 搜尋框傳回按鈕
self.search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
def input_search_text(self,text):
# text: 需要輸入的内容
# 封裝搜尋按鈕的輸入操作
# 點選設定中搜尋按鈕
self.base_object.click_element(self.search_button)
# 在搜尋輸入框内輸入123
self.base_object.input_text(self.search_text,text)
# 點選搜尋框傳回按鈕
self.base_object.click_element(self.search_return_button)
編寫測試檔案: test.py
from appium import webdriver
# 導入封裝好的頁面類
from search_page import Search_Page
class Test_Base:
def __init__(self):
desired_caps = {}
# 手機 系統資訊
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 裝置号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 啟動名
desired_caps['appActivity'] = '.Settings'
# 允許輸入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手機驅動對象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
def test(self):
# 執行個體化頁面封裝類
sp = Search_Page(self.driver)
# 調用操作類
sp.input_search_text(123)
# 退出driver對象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
問題
1.若我要完成Base基礎類的導入,需要在每個頁面的封裝類内執行個體化Base類,
并需要執行個體化對象引用Base類内部方法。
即:
self.driver = driver
self.base_object = Base(self.driver)
2.若我們有多個測試函數檔案,需要在每個檔案中都聲明一次driver,
重複代碼太多,并且測試類内部備援代碼過多。
4.頁面元素操作封裝(二)
通過類繼承的方式完成頁面的封裝
頁面封裝
業務場景:
1.進入設定
2.點選搜尋按鈕
3.輸入123
4.點選搜尋框傳回按鈕
頁面封裝類:search_page.py
from selenium.webdriver.common.by import By
# 導入基礎定位封裝包
from Base import Base
class Search_Page(Base):
def __init__(self,driver):
Base.__init__(self,driver) # 父類初始化方法
# 搜尋按鈕
self.search_button = (By.ID, "com.android.settings:id/search")
# 搜尋輸入框
self.search_text = (By.ID, "android:id/search_src_text")
# 搜尋框傳回按鈕
self.search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
def input_search_text(self,text):
# text: 需要輸入的内容
# 封裝搜尋按鈕的輸入操作
# 點選設定中搜尋按鈕
self.click_element(self.search_button) # 子類繼承父類的所有方法
# 在搜尋輸入框内輸入
self.input_text(self.search_text,text)
# 點選搜尋框傳回按鈕
self.click_element(self.search_return_button)
獨立手機驅動對象方法
統一管理手機驅動對象的初始化
建立手機驅動對象檔案:Init_Driver.py
代碼:
from appium import webdriver
def init_driver():
# 服務端啟動參數
desired_caps = {}
# 手機 系統資訊
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 裝置号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 啟動名
desired_caps['appActivity'] = '.Settings'
# 允許輸入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手機驅動對象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver # 傳回driver對象
測試腳本編寫
代碼實作: test.py
# 導入封裝好的頁面類
from cup.search_page import Search_Page
# 導入獨立的手機驅動對象
from cup.Init_Driver import init_driver
class Test_Base:
def __init__(self):
self.driver = init_driver()
def test(self):
# 示例化頁面封裝類
sp = Search_Page(self.driver)
# 調用操作類
sp.input_search_text(123)
# 退出driver對象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
5.PO模式項目管理
目錄結構
清晰的目錄結構,友善代碼子產品化管理和他人解讀。
常用目錄結構:
App_Project # 項目名稱
- Basic # 存儲基礎設施類
- __init__.py # 空檔案
- Init_Driver.py # 手機驅動對象初始化
- Base.py # 方法的二次封裝
- read_data.py #資料解析讀取方法
- Page # 存儲封裝頁面檔案
- __init__.py # 存儲頁面元素
- search_page.py # 封裝頁面元素的操作方法
- Data # 存儲資料檔案
- search_data.yaml(也可以是其他檔案比如txt,excel,json,資料庫等)
- Test # 存儲測試腳本目錄
- test_search.py # 測試搜尋檔案
Basic目錄檔案代碼
1.Basic/Init_Driver.py
from appium import webdriver
def init_driver():
# 服務端啟動參數
desired_caps = {}
# 手機 系統資訊
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 裝置号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 啟動名
desired_caps['appActivity'] = '.Settings'
# 允許輸入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手機驅動對象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver
2.Basic/Base.py
from selenium.webdriver.support.wait import WebDriverWait
class Base(object):
def __init__(self,driver):
self.driver = driver
def find_element(self,loc,timeout=10):
'''
二次封裝find_element方法,增加了顯示等待和簡化參數傳遞
'''
# 原生方法:find_element(by=By.ID, value=None),需要傳遞兩個值
# *loc 将傳入(By.XX, "value")解包為兩個單獨的值,滿足find_element方法的參數傳遞
return WebDriverWait(self.driver, timeout).until(lambda x: x.find_element(*loc))
def click_element(self,loc):
'''
封裝點選操作函數
'''
self.find_element(loc).click()
def input_text(self,loc,text):
'''
封裝輸入操作函數
'''
self.fm = self.find_element(loc)
self.fm.clear() # 需要先清空輸入框,防止有預設内容
self.fm.send_keys(text)
Page目錄檔案代碼
1.Page/__init__.py
from selenium.webdriver.common.by import By # selenium原生定位政策集
'''
設定頁面
'''
# 搜尋按鈕
search_button = (By.ID, "com.android.settings:id/search")
# 搜尋輸入框
search_text = (By.ID, "android:id/search_src_text")
# 搜尋框傳回按鈕
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
2.Page/search_page.py
# 導入基礎定位封裝包
from Basic.Base import Base
import Page
class Search_Page(Base):
def __init__(self,driver):
Base.__init__(self,driver) # Base類的初始化方法
def input_search_text(self,text):
'''
封裝搜尋按鈕的輸入操作
'''
# 點選設定中搜尋按鈕
self.click_element(Page.search_button) # 傳入的__init__.py檔案聲明的search_button變量
# 在搜尋輸入框内輸入
self.input_text(Page.search_text,text) # 傳入的__init__.py檔案聲明的search_text變量
# 點選搜尋框傳回按鈕
self.click_element(Page.search_return_button) # 傳入的__init__.py檔案聲明的search_return_button變量
Test目錄檔案代碼
1.test_search.py
# 導入封裝好的頁面類
from Page.search_page import Search_Page
# 導入獨立的手機驅動對象
from Basic.Init_Driver import init_driver
import time
class Test_Base:
def __init__(self):
self.driver = init_driver()
def test(self):
# 示例化頁面封裝類
sp = Search_Page(self.driver)
# 調用操作類
sp.input_search_text(123)
# 退出driver對象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()