(一)POM模式介紹
1、什麼是POM介紹
POM是Page Object Model
頁面物件模型的簡稱。
POM是為Web UI
元素建立Object Repository
的設計模式 。
在這個模型下,對於應用程式中的每個網頁,應該有相應的頁面類。
此Page
類將會找到該Web頁面的WebElements
,並且還包含對這些WebElements
執行操作的頁面方法。
POM設計模式旨在為每個待測試的頁面建立一個頁面物件,將那些繁瑣的定位操作封裝到這個頁面物件中,只對外提供必要的操作介面,是一種封裝思想。
白話總結:
我們所做的自動化測試,就是模擬人在瀏覽器上的操作。而自動化測試中操作所有的元素的步驟,無非就是先定位到頁面的各種元素,然後在模擬各種對元素執行的操作。
而我們大量的工作都用在定位元素上,定位元素的方式有很多中,定位起來也非常的繁瑣。如果將這些程式碼全部放在程式碼中,不去好好的管理,程式碼會顯示非常的冗餘,而且不容易維護。所以將這些繁瑣的定位,封裝到一些頁面物件中,用例只需要去呼叫就可以了。
2、為什麼要使用POM模式
少數的自動化測試用例維護起來看起來是很容易的。但隨著時間的遷移,測試套件將持續的增長指令碼也將變得越來越臃腫龐大。如果變成我們需要維護10個頁面,100個頁面,甚至1000個呢?而且頁面元素很多是公用的,所以頁面元素的任何改變都會讓我們的指令碼維護變得繁瑣複雜,而且變得耗時易出錯。
也就是說頁面中有一個按鈕"元素A"。該元素A在十個測試用例中都被用到了,如果元素A被前端更新了,我就要去修改這十個自動化用例所用道元素A的地方。如果有100個、1000個用例用到了元素A,那我可就瘋了。
而POM設計模式,會把公共的元素抽取出來,該元素被前端修改,只需要更新該元素的定位方式即可,用例不需要改動。換句話說,不管我多少測試用例,用到了該元素,我只重新修改元素的定位方式,重新能夠獲得該元素即可。
3、POM的優勢
在自動化測試中,引入了Page Object Model
(POM):頁面物件模式,能讓我們的測試程式碼變得可讀性更好,高可維護性,高複用性。
還有如下優勢:
- 讓Ul自動化更早介入專案中,可專案開發完再進行元素定位的適配與除錯。
換句話說元素定位器分離出來寫,最後根據前端開發出來的頁面,再根據頁面編寫編寫元素定位器,前期可以做一些其他的工作。 - POM設計模式將頁面元素定位和業務操作流程分開,分離了測試物件和測試指令碼(物件庫與用例分離),使得我們更好的複用測試物件。
- 如果Ul頁面元素更改,測試指令碼不需要更改,只需要更改頁面物件中的某些程式碼就可以。
- POM設計模式能讓我們的測試程式碼變得更加優化,提高了可讀性,可維護性,可複用性。
- 可多人共同維護開發指令碼,利於團隊協作。
4、POM模式封裝思路
(1)POM模式將頁面分成三層
- 表現層:頁面中可見的元素,都屬於表現層。(元素定位器的編寫)
- 操作層:對頁面可見元素的操作。點選、輸入、拖拽等。
- 業務層:在頁面中對若干元素操作後所實現的功能。(就是測試用例)
(2)POM模式的核心要素(重點)
- 在POM模式中將公共方法統一封裝成到一個
BasePage
類中,換句話說該基類對Selenium的常用操作做二次封裝。 - 每個頁面對應一個
Page
類,Page
類都需要繼承BasePage
,通過driver
來管理本Page
類中的元素,並將Page
類中的操作封裝成一個個的方法。
換句話說,就是Page
類中封裝頁面表現層和操作層。 TestCase
繼承unittest.Testcase
類,並且依賴Page
類,從而實現相應的測試步驟。
(3)總結
就是按照系統或模組 —> 其中包含哪些被測頁面 —> 頁面中的哪些元素
換句話說,元素被頁面管理,頁面被模組管理。
- 根據頁面來進行管理例:
例如:測式xx頁面,需要用到的元素,把所有的元素定位器編寫出來。 - 頁面根據系統或者模組來管理例如:
例如:xx系統或模組,涉及到哪幾個頁面元素。
(4)非POM和POM對比圖
(5)POM設計模式核心架構圖
5、對POM小結:
- POM是
selenium webdriver
自動化測試實踐物件庫設計模式。 - POM使得測試指令碼更易於維護。
- POM通過物件庫方式進一步優化了元素、用例、資料的維護組織。
(二)將普通的Selenium程式碼封裝成POM模式
1、案例說明:
提示:這裡只是提供一種封裝的思路,小夥伴們可以根據自己的實際情況,按需封裝。
以下是簡單普通的登入測試用例
# 1. 匯入包
from selenium import webdriver
import time
# 2. 開啟谷歌瀏覽器(獲取瀏覽器操作物件)
driver = webdriver.Chrome()
# 3. 開啟快遞100網站
url = "https://sso.kuaidi100.com/sso/authorize.do"
driver.get(url)
time.sleep(3)
# 4. 登陸網站
driver.find_element_by_id("name").send_keys('xxxxxxxxxxx')
driver.find_element_by_id("password").send_keys('xxxxxx')
driver.find_element_by_id("submit").click()
time.sleep(3)
# 5. 關閉瀏覽器
driver.quit()
那我們如何進行一個改造升級呢?
2、加入unittest測試框架
# 1. 匯入包
from selenium import webdriver
import time
import unittest
# 定義測試類
class TestCaseLogin(unittest.TestCase):
def setUp(self) -> None:
"""
前置函式
用於開啟瀏覽器,連線資料庫,初始化資料等操作
"""
# 2. 開啟谷歌瀏覽器(獲取瀏覽器操作物件)
self.driver = webdriver.Chrome()
# 3. 開啟快遞100網站
url = "https://sso.kuaidi100.com/sso/authorize.do"
self.driver.get(url)
time.sleep(3)
def tearDown(self) -> None:
"""
後置函式
用於關閉瀏覽器,斷開資料庫連線,清理測試資料等操作
"""
# 5. 關閉瀏覽器
self.driver.quit()
def testLogin(self):
"""登陸測試用例"""
self.driver.find_element_by_id("name").send_keys('xxxxxxxxxxx')
self.driver.find_element_by_id("password").send_keys('xxxxxx')
self.driver.find_element_by_id("submit").click()
time.sleep(3)
if __name__ == '__main__':
unittest.main()
如果有不清楚unittest測試框架的小夥伴可以檢視我以前的unittest測試框架部落格有4篇,簡單易懂。
3、加入元素顯示等待
我們上邊的示例中,用的是固定的等待時間,我們需要有話一下程式碼的效率,加入元素的顯示等待。
關於元素顯示等待請看:元素等待的使用
Seleniun的EC模組:EC模組的使用
# 1. 匯入包
from selenium import webdriver
import time
import unittest
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 定義測試類
class TestCaseLogin(unittest.TestCase):
def setUp(self) -> None:
"""
前置函式
用於開啟瀏覽器,連線資料庫,初始化資料等操作
"""
# 2. 開啟谷歌瀏覽器(獲取瀏覽器操作物件)
self.driver = webdriver.Chrome()
# 3. 開啟快遞100網站
url = "https://sso.kuaidi100.com/sso/authorize.do"
self.driver.get(url)
time.sleep(2)
def tearDown(self) -> None:
"""
後置函式
用於關閉瀏覽器,斷開資料庫連線,清理測試資料等操作
"""
# 5. 關閉瀏覽器
time.sleep(2)
self.driver.quit()
def testLogin(self):
"""登陸測試用例"""
# 編寫定位器
name_input_locator = ("id", "name")
passwd_input_locator = ("id", "password")
submit_button_locator = ("id", "submit")
# 等待元素出現在操作元素
WebDriverWait(self.driver, 5).until(EC.visibility_of_element_located(name_input_locator))
WebDriverWait(self.driver, 5).until(EC.visibility_of_element_located(passwd_input_locator))
WebDriverWait(self.driver, 5).until(EC.visibility_of_element_located(submit_button_locator))
self.driver.find_element_by_id("name").send_keys('xxxxxxxxxxx')
self.driver.find_element_by_id("password").send_keys('xxxxxx')
self.driver.find_element_by_id("submit").click()
if __name__ == '__main__':
unittest.main()
4、引入POM模式
我們發現上面的程式碼越來越亂,程式碼冗餘,不利於維護,可讀性差,不可複用。
(1)改造案例思路:
- 第一, 我們要分離測試物件(元素物件)和測試指令碼(用例指令碼),那麼我們分別建立兩個指令碼檔案,分別為:
LoginPage.py
用於定義頁面元素物件,每一個元素都封裝成元件(可以看做存放頁面元素物件的倉庫)TestCaseLogin.py
測試用例指令碼。
- 第二,抽取出公共方法定義在
base.py
檔案中,每個Page
類都要繼承這個base.py
檔案,也就是每Page
類都能使用base
類中的方法,來操作頁面中的元素,同時也可以在每個Page
類中定義自己獨有的方法,解決工作中的實際需求。 - 第三,設計實現思想,一切元素和元素的操作元件化定義在
Page
頁面,用例指令碼頁面,通過呼叫Page
中的元件物件,進行拼湊成一個登入指令碼。
(2)封裝公共操作在base類
把一些公共的方法放到此類中,這個類將被PO物件繼承。
"""
封裝公共方法
"""
from selenium import webdriver
import time
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class Base:
def __init__(self, browser="chrome"):
"""
初始化driver
:param browser:瀏覽器名稱
"""
if browser == "chrome":
self.driver = webdriver.Chrome()
elif browser == "firefox":
self.driver = webdriver.Firefox()
elif browser == "ie":
self.driver = webdriver.Ie()
else:
self.driver = None
print("請輸入正確的瀏覽器,例如:chrome,firefox,ie")
def open_url(self, url):
"""
開啟地址
:param url: 被測地址
:return:
"""
self.driver.get(url)
time.sleep(2)
def find_element(self, locator, timeout=10):
"""
定位單個元素,如果定位成功返回元素本身,如果失敗,返回False
:param locator: 定位器,例如("id","id屬性值")
:return: 元素本身
"""
try:
element = WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))
return element
except:
print(f"{locator}元素沒找到")
return False
def click(self, locator):
"""
點選元素
:return:
"""
element = self.find_element(locator)
element.click()
def send_keys(self, locator, text):
"""
元素輸入
:param locator: 定位器
:param text: 輸入內容
:return:
"""
element = self.find_element(locator)
element.clear()
element.send_keys(text)
def close(self):
"""
關閉瀏覽器
:return:
"""
time.sleep(2)
self.driver.quit()
if __name__ == '__main__':
base = Base()
base.open_url("https://sso.kuaidi100.com/sso/authorize.do")
base.close()
(3)每個頁面對應一個Page類
定位元素的定位器和操作元素方法分離開,元素定位器全部放一起,然後每一個操作元素動作寫成一個方法。
"""
管理登陸頁面所有的元素,
以及操作這些元素所用的方法。
"""
from common.base import Base
class LoginPage(Base):
# 編寫定位器和頁面屬性
name_input_locator = ("id", "name")
passwd_input_locator = ("id", "password")
submit_button_locator = ("id", "submit")
username = 'xxxxxxxxxxx'
userpasswd = 'xxxxxx'
url = 'https://sso.kuaidi100.com/sso/authorize.do'
# """封裝元素操作"""
# 輸入使用者名稱
def name_imput(self):
self.send_keys(self.name_input_locator, self.username)
# 輸入密碼
def passwd_imput(self):
self.send_keys(self.passwd_input_locator, self.userpasswd)
# 點選登陸
def click_submit(self):
self.click(self.submit_button_locator)
if __name__ == '__main__':
base = Base('firefox')
base.open_url(url=LoginPage.url)
(4)原登陸案例封裝完成程式碼
測試方法及測試類的執行都在此檔案中。
# 1. 匯入包
import unittest
from pages.login_page import LoginPage
# 定義測試類
class TestCaseLogin(unittest.TestCase):
def setUp(self) -> None:
self.driver = LoginPage()
self.driver.open_url(LoginPage.url)
def tearDown(self) -> None:
# 5. 關閉瀏覽器
self.driver.close()
def testLogin(self):
"""登陸測試用例"""
self.driver.name_imput()
self.driver.passwd_imput()
self.driver.click_submit()
if __name__ == '__main__':
unittest.main()
提示:最後我們在使用測試套件來執行測試用例的時候,就定位這些
testcase
檔案就好。
5、總結
雖然該實現方法看上去複雜多了,但其中的設計好處是不同層關心不同的問題。
- 頁面物件只關心元素的定位。
- 測試用例只關心測試資料。
使用POM進行重新構造程式碼結構後,發現程式碼測試用例程式碼的可讀性提高很多。
定義好的PageObject
元件可以重複在其它的指令碼中進行使用,減少了程式碼的工作量,也方便對指令碼進行後期的維護管理,當元素屬性發生變化時,我們只需要對一個PageObaject
頁面中的物件元件定義進行更改即可。