PO:Page Object,po是一種設計模式,提供通用的方法,簡單來說就是分層設計。
PO在Selenium中的應用,官方文檔:https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/
場景:我們最初做UI自動化測試時,一個python文件可以實現整個流程的操作,那么會帶來的問題有:
- 代碼可讀性差,一旦有一個元素的位置發生改動,改起來特別費勁;
- 代碼維護性差,時間久了看不懂,別人也看不懂;
- 可擴展性弱,比如登錄,這個頁面需要登錄,到了另外一個頁面可能還需要登錄,需要再寫一遍。
要解決這種問題,我們需要把場景抽象化,通過PO思想進行封裝。把操作細節封裝成方法,對外只提供方法,不提供細節。當需要修改時只改操作細節,不改對外暴露的方法。但是,不是所有頁面都封裝,只封裝主要模塊,不封裝次要模塊。
截圖.png
PO原則
- 用一個方法代替頁面的服務
- 對外提供的方法不暴露細節
- 對外提供的方法中不包含斷言
- 如果頁面a導航到頁面b,page a應該return page b
- 不為頁面的所有元素封裝方法
- 當出現正確的和錯誤的結果時,我們封裝時要分開寫,不要揉到一起寫
舉個例子,以企業微信后臺為例,先構思一下用例:
image.png
用PO思想,我需要創建幾個文件:
- main_page.py:主頁的po,提供主頁的方法,不提供細節
- base_page.py:抽離通用方法的po
- add_member_page.py:添加成員頁po,繼承base_page,對應具體添加成員的操作細節
- contact_page.py:通訊錄頁的po,繼承base_page,對應具體通訊錄的操作細節
- test_add_member.py:添加成員的測試用例及斷言
main_page.py
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage
class MainPage(BasePage):
"""
首頁
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#index'
def goto_contact(self):
"""
跳轉通訊錄頁面
:return:
"""
return ContactPage()
def goto_add_member(self):
"""
跳轉添加成員頁面
:return:
"""
self.driver.find_element(By.XPATH, '//*[@id="_hmt_click"]/div[1]/div[4]/div[2]/a[1]/div/span[2]').click()
sleep(1)
return AddMemberPage(self.driver)
base_page.py
from time import sleep
import yaml
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.opera.options import Options
class BasePage:
"""
封裝所有頁面對象通用的操作,如driver的實例化
"""
_base_url = None
def __init__(self, base_driver=None):
"""
子類沒有構造函數,在實例化過程中會自動調用父類的構造函數,所以每個PO在實例化過程中都會執行構造函數的邏輯,這樣就會初始化多個driver
所以需要避免driver的重復實例化
"""
# 如果base_driver為真,就不需要重復driver實例化操作
if base_driver:
self.driver =base_driver
# 如果base_driver為None,就需要對driver實例化
else:
# 不加self是局部變量
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(5)
# url優化
if self._base_url != None:
self.driver.get(self._base_url)
cookie = yaml.safe_load(open('../data/login_cookie.yml'))
print(cookie)
# 種cookie
for c in cookie:
self.driver.add_cookie(c)
sleep(2)
self.driver.get(self._base_url)
def find(self, by, locaotor):
"""
封裝find_element操作
:param by:
:param locaotor:
:return:
"""
res = self.driver.find_element(by, locaotor)
print(f"找到的元素為{res}")
return res
add_member_page.py
from time import sleep
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage
class AddMemberPage(BasePage):
"""
添加成員頁
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
def goto_contact(self):
"""
跳轉通訊錄頁面
:return:
"""
return ContactPage()
def add_member(self, username, acctid, phonenum):
"""
添加成員操作
:return:
"""
self.find(By.ID, 'username').send_keys(username)
self.find(By.ID, 'memberAdd_acctid').send_keys(acctid)
self.find(By.ID, 'memberAdd_phone').send_keys(phonenum)
self.driver.find_element(By.CSS_SELECTOR, '.js_btn_save').click()
return ContactPage(self.driver)
def add_member_fail(self):
"""
添加成員操作
:return:
"""
self.find(By.ID, 'username').send_keys('')
self.find(By.ID, 'memberAdd_acctid').send_keys('111')
self.find(By.ID, 'memberAdd_phone').send_keys('13600001243')
error_list = self.driver.find_elements(By.CSS_SELECTOR, '.ww_inputWithTips_tips')
print(error_list)
# 尋找所有的錯誤信息,如果不為空,則返回
err_message = [ele.text for ele in error_list if ele.text != ""]
return err_message
return ContactPage(self.driver)
contact_page.py
from test_selenium.work_weixin.api.base_page import BasePage
class ContactPage(BasePage):
"""
通訊錄列表頁
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
def goto_add_member(self):
"""
添加成員
:return:
"""
# 如果出現A到B,B到A循環導入的場景,需要把其中一個的導包放在方法里
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
return AddMemberPage()
def member_list(self, member_name):
"""
成員列表list
:return:
"""
return ['member_name']
test_add_member.py
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.contact_page import ContactPage
from test_selenium.work_weixin.api.main_page import MainPage
class TestAddMember:
"""
添加成員的測試用例
"""
def setup_class(self):
"""
頁面實例化MainPage
:return:
"""
self.main = MainPage()
def test_add_member(self):
"""
1.在首頁,點擊添加成員按鈕,跳轉到添加成員頁面
2.在添加成員頁面,填寫成員信息,點擊保存
3.在通訊錄頁面查看信息是否添加成功
:return:
"""
# # 方法一
# # 頁面實例化,調用過于復雜
# main = MainPage()
# add_member_page = AddMemberPage()
# contact_page = ContactPage()
#
# # 1.在首頁,點擊添加成員按鈕,跳轉到添加成員頁面
# main.goto_add_member()
#
# # 2在添加成員頁面,填寫成員信息,點擊保存
# add_member_page.add_member()
#
# # 3.在通訊錄頁面查看成員是否添加成功
# contact_page.member_list()
# 方法二
username, acctid, phonenum = "吱吱3", '3', '18700001234'
# 通過前面return實例對象,可以直接用方法.方法的方式,實現鏈式調用
res = self.main.goto_add_member().add_member(username, acctid, phonenum).member_list('member_name')
# 斷言與手工測試一致,斷言添加的成員是否在結果中,如果在表示用例通過,否則失敗
assert 'member_name' in res
def test_add_member_fail(self):
"""
反向用例,成員名稱為空
:return:
"""
error_list = self.main.goto_add_member().add_member_fail()
assert "請填寫姓名" in error_list
def teardown_class(self):
self.main.driver.quit()
源碼彩蛋》》》
鏈接: https://pan.baidu.com/s/14p0Q38whTbjlRwlMZA-iVg
提取碼: 7wzh