如果想從頭學起selenium,可以去看看這個系列的文章哦!
https://www.cnblogs.com/miki-peng/category/1942527.html
PO模式
Page Object(簡稱PO)模式,是Selenium實戰中最為流行,並且是自動化測試中最為熟悉和推崇的一種設計模式。在設計自動化測試時,把頁面元素和元素的操作方法按照頁面抽象出來,分離成一定的物件,然後再進行組織。
做web自動化最頭疼的一個問題,莫過於頁面變化了,如果沒有使用PO設計模式,頁面一變化就意味著之前的元素定位甚至元素的操作方法不能用了,需要重新修改。你需要一個一個從測試指令碼中把需要修改的元素定位方式、元素的操作方法找出來,然後一一地修改。這樣的自動化指令碼不但繁瑣,維護成本也極高。
而page object模式就可以很好地解決這個問題,優點?:
- ? 減少程式碼冗餘
- ? 業務和實現分離
- ? 降低維護成本
那到底什麼是Page Object模式,見名知意,就是頁面物件,在實際自動化測試中,一般對指令碼分為三層:
- ? 物件層: 用於存放頁面元素定位
- ? 邏輯層: 用於存放一些封裝好的功能用例模組
- ? 業務層: 用於存放我們真正的測試用例的操作部分
除了以上三層,還有一個基礎層,基礎層主要是針對selenium的一些常用方法,根據實際業務需要進行二次封裝,如點選、輸入等操作加入一些等待、日誌輸入、截圖等操作,方便以後檢視指令碼的執行情況及問題排查。
基礎層
基礎層類名一般命名為BasePage
,後續的物件層操作元素時都繼承這個基礎類,下面以點選、輸入為例:
# basepage.py
import os
import time
import datetime
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.logging import log
from common.constant import IMG_DIR
class BasePage:
def __init__(self, driver: WebDriver):
self.driver = driver
def wait_ele_visible(self, loc, img_desc, timeout=20, frequency=0.5):
"""等待元素可見"""
try:
WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))
log.info("等待:{} - 元素{}可見成功。".format(img_desc, loc))
except:
log.exception("等待:{} - 元素{}可見失敗!".format(img_desc, loc))
self.save_img(img_desc)
raise
def get_element(self, loc, img_desc):
"""查詢元素"""
try:
ele = self.driver.find_element(*loc)
except:
log.exception("查詢:{} - 元素{}失敗!".format(img_desc, loc))
self.save_img(img_desc)
raise
else:
log.info("查詢:{} - 元素{}成功".format(img_desc, loc))
return ele
def click_element(self, loc, img_desc, timeout=20, frequency=0.5):
"""點選元素"""
self.wait_ele_visible(loc, img_desc, timeout, frequency)
ele = self.get_element(loc, img_desc)
try:
ele.click()
log.info("點選:{} - 元素{}成功".format(img_desc, loc))
except:
log.exception("點選:{} - 元素{}失敗!".format(img_desc, loc))
self.save_img(img_desc)
raise
def input_text(self, loc, value, img_desc, timeout=20, frequency=0.5):
"""在元素中輸入文字"""
self.wait_ele_visible(loc, img_desc, timeout, frequency)
ele = self.get_element(loc, img_desc)
try:
ele.send_keys(value)
log.info("輸入:在{} - 元素{}輸入文字值({})成功".format(img_desc, loc, value))
except:
log.exception("輸入:在{} - 元素{}輸入文字值({})失敗!".format(img_desc, loc, value))
self.save_img(img_desc)
raise
def save_img(self, img_description):
"""儲存異常截圖"""
now = time.strftime("%Y-%m-%d %H-%M-%S ", time.localtime())
img_path = os.path.join(IMG_DIR, now + img_description + '.png')
try:
self.driver.save_screenshot(img_path)
except:
log.exception("異常截圖失敗!")
else:
log.info("異常截圖成功,截圖存放在{}".format(img_path))
以點選click_element()
為例,這裡二次封裝時加入了等待操作、日誌輸入、異常截圖,後面點選元素時就直接呼叫click_element()
就可以一步到位,不需要再考慮等待、日誌、異常的情況,這裡都已經處理好了,雖然在初期寫基礎頁面會比較耗時,但只要基礎打好,在後續維護工作中會輕鬆很多。以上只是一個示例,可以根據自己的實際需要進行優化。
物件層及邏輯層
物件層存放頁面元素定位,邏輯層存放元素操作方法(頁面功能),元素定位可以根據實際需要,可以單獨放在一個模組來維護,也可以存放在excel中進行集中管理;下面演示的是元素定位和元素操作方法都存放到一個模組中,一個頁面一個模組,後續頁面元素髮生變化,只需要修改在這個模組中修改對應的定位表示式或者操作方法即可。
演示以百度首頁為例:
# baidu_page.py
from selenium.webdriver.common.by import By
from common.basepage import BasePage
class LoginPage(BasePage):
login_btn = (By.XPATH, '//div[@id="u1"]//a[@name="tj_login"]') # 登入按鈕
username_login_btn = (By.ID, 'TANGRAM__PSP_11__footerULoginBtn') # 使用者名稱登入按鈕
user_input = (By.ID, 'TANGRAM__PSP_11__userName') # 使用者資訊輸入框
pwd_input = (By.ID, 'TANGRAM__PSP_11__password') # 密碼輸入框
login_submit = (By.ID, 'TANGRAM__PSP_11__submit') # 登入提交按鈕
def login(self, user, pwd):
"""
百度使用者名稱登入
:param user: 手機/郵箱/使用者名稱
:param pwd: 密碼
:return:
"""
self.click_element(self.login_btn, '百度-登入')
self.click_element(self.username_login_btn, '百度登入-使用者名稱登入')
self.input_text(self.user_input, user, '使用者名稱登入-手機/郵箱/使用者名稱')
self.input_text(self.pwd_input, pwd, '使用者名稱登入-密碼')
self.click_element(self.login_submit, '使用者名稱登入-登入')
業務層
用於存放真正的測試用例操作,這裡不會出現元素定位、頁面功能,所有操作都是直接呼叫邏輯層的.
測試用例 = 測試物件的功能 + 測試資料,下面以百度登入為例(用於演示,簡略寫的):
import unittest
import pytest
import ddt
from selenium import webdriver
from PageObjects.baidu_login_page import LoginPage
from testdatas import common_datas as com_d
from testdatas import login_data as lo_d
from common.logging import log
@ddt.ddt
class TestLogin(unittest.TestCase):
def setUp(self):
log.info("-------用例前置工作:開啟瀏覽器--------")
self.driver = webdriver.Chrome()
self.driver.get(com_d.baidu_url)
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()
log.info("-------用例後置工作:關閉瀏覽器--------")
@pytest.mark.smoke
def test_login_success(self):
# 用例:登入頁的登入功能
# 步驟
LoginPage(self.driver).login(lo_d.success_data['user'], lo_d.success_data['pwd'])
# 斷言.....
執行結果:
Testing started at 11:50 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/learn/test/testcases/test_baidu_login.py
Launching unittests with arguments python -m unittest D:/learn/test/testcases/test_baidu_login.py in D:\learn\test\testcases
Process finished with exit code 0
2021-03-14 11:50:47,238-【test_baidu_login.py-->line:27】-INFO:-------用例前置工作:開啟瀏覽器--------
2021-03-14 11:50:51,327-【basepage.py-->line:38】-INFO:等待:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')可見成功,耗時0:00:00.056843秒
2021-03-14 11:50:51,339-【basepage.py-->line:77】-INFO:查詢:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')成功
2021-03-14 11:50:51,414-【basepage.py-->line:86】-INFO:點選:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')成功
2021-03-14 11:50:53,463-【basepage.py-->line:38】-INFO:等待:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')可見成功,耗時0:00:02.048293秒
2021-03-14 11:50:53,474-【basepage.py-->line:77】-INFO:查詢:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')成功
2021-03-14 11:50:53,535-【basepage.py-->line:86】-INFO:點選:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')成功
2021-03-14 11:50:53,576-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')可見成功,耗時0:00:00.040890秒
2021-03-14 11:50:53,584-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')成功
2021-03-14 11:50:53,714-【basepage.py-->line:98】-INFO:輸入:在使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')輸入文字值(15692004245)成功
2021-03-14 11:50:53,759-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')可見成功,耗時0:00:00.043882秒
2021-03-14 11:50:53,771-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')成功
2021-03-14 11:50:53,925-【basepage.py-->line:98】-INFO:輸入:在使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')輸入文字值(phang0209)成功
2021-03-14 11:50:53,958-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')可見成功,耗時0:00:00.031914秒
2021-03-14 11:50:53,969-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')成功
2021-03-14 11:50:54,051-【basepage.py-->line:86】-INFO:點選:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')成功
2021-03-14 11:50:56,426-【test_baidu_login.py-->line:35】-INFO:-------用例後置工作:關閉瀏覽器--------
Ran 1 test in 9.191s
OK
從輸出日誌來看,每一步操作都清晰可見,出現問題也能快速定位,這些都可以根據實際需要來優化。