簡介:PO模式(Page Object), 是自動化測試中最為流行且最為熟悉和推崇的一種設計模式。PO模式把頁面元素和元素的操作方法按照頁面抽象出來收壕,分離成一定的對象,然后再進行組織遭居。
介紹
做web自動化最頭疼的問題是頁面變化了啼器,如果沒有使用PO設計模式,頁面一變化就意味著之前寫的元素定位不能用了俱萍,需要重新修改端壳,這樣就需要一個個從測試腳本中把需要修改的元素定位找出來,然后一一修改枪蘑。自動化腳本不但繁瑣损谦,而且成本極高。
PO模式就可以很好地解決這個問題岳颇,那如何操作呢照捡?
一般對腳本進行分層:
- 基礎層:點擊、輸入等操作加入一些等待话侧、日志輸入栗精、截圖等操作,方便以后查看腳本的運行情況及問題排查
- 對象邏輯層:用于存放頁面元素定位和存放一些封裝好對功能用例模塊
- 業(yè)務層:用于存放真正的測試用例的操作部分
優(yōu)點:
- 減少代碼冗余
- 業(yè)務和實現(xiàn)分離
- 降低維護成本
基礎層
后續(xù)的對象層操作元素時都繼承這個基礎類。
'''
Author: daju.huang
Date: 2021-07-22 15:40:38
LastEditors: daju.huang
LastEditTime: 2021-07-25 12:59:58
FilePath: /webUIAutoTest/public/page_obj/base.py
'''
import os,sys
from time import sleep
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from config import setting
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchFrameException,NoSuchWindowException,NoAlertPresentException,NoSuchElementException
import configparser
from public.models.log import Log
log = Log()
class Page(object):
'''
@description: 基礎類悲立,用于頁面對象類的繼承
@param {*} self
@param {*} selenium_driver
@param {*} base_url
@param {*} parent
@return {*}
'''
def __init__(self,selenium_driver,base_url,parent=None) -> None:
self.driver = selenium_driver
self.base_url = base_url
self.parent = parent
self.timeout = 10
'''
@description: url地址判斷是否是當前頁面
@param {*} self
@return {*}
'''
def on_page(self):
return self.driver.current_url == (self.base_url + self.url)
'''
@description: 打開瀏覽器URL訪問
@param {*} self
@param {*} url URL地址
@return {*}
'''
def _open(self,url):
url = self.base_url + url
self.driver.get(url)
assert self.on_page(),'Did not land on %s' % url
'''
@description: 內(nèi)部調(diào)用_open私有函數(shù)
@param {*} self
@return {*}
'''
def open(self):
self._open(self.url)
'''
@description: 單個元素定位
@param {*} self
@param {array} loc 傳入元素屬性
@return {*} 定位到的元素
'''
def find_element(self, *loc):
try:
WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(loc))
return self.driver.find_element(*loc)
except:
log.error("{0}頁面中未能找到{1}元素".format(self,loc))
'''
@description: 多個元素定位
@param {*} self
@param {array} loc 傳入元素屬性
@return {*} 定位到的元素
'''
def find_elements(self, *loc):
try:
WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(loc))
return self.driver.find_elements(*loc)
except:
log.error("{0}頁面中未能找到{1}元素".format(self,loc))
'''
@description: 單個元素定位
@param {*} self
@param {array} loc 傳入元素屬性
@return {*} 定位到的元素
'''
def find_element_xpath(self, loc_str):
try:
return self.driver.find_element_by_xpath(loc_str)
except:
log.error("{0}頁面中未能找到{1}元素".format(self,loc_str))
'''
@description: 提供調(diào)用JavaScript方法
@param {*} self
@param {*} src 腳本文件
@return {*} JavaScript腳本
'''
def script(self,src):
return self.driver.execute_script(src)
'''
@description:
@param {*} self
@param {*} loc
@param {*} value
@param {*} clear_first
@param {*} click_first
@return {*}
'''
def send_key(self, loc, value, clear_first=True, click_first=True):
try:
loc = getattr(self, "_%s" % loc) # getattr相當于實現(xiàn)self.loc
if click_first:
self.find_element(*loc).click()
if clear_first:
self.find_element(*loc).clear()
self.find_element(*loc).send_keys(value)
except AttributeError:
log.error("%s 頁面中未能找到 %s 元素" % (self, loc))
'''
@description: 多表單嵌套切換
@param {*} self
@param {*} loc 傳元素的屬性值
@return {*} 定位到的元素
'''
def switch_frame(self, loc):
try:
return self.driver.switch_to_frame(loc)
except NoSuchFrameException as msg:
log.error("查找iframe異常->{0}".format(msg))
'''
@description: 多窗口切換
@param {*} self
@param {*} loc
@return {*}
'''
def switch_windows(self,loc):
try:
return self.driver.switch_to_window(loc)
except NoSuchWindowException as msg:
log.error("查找窗口句柄handle異常->{0}".format(msg))
'''
@description: 警告框處理
@param {*} self
@return {*}
'''
def switch_alert(self):
try:
return self.driver.switch_to_alert()
except NoAlertPresentException as msg:
log.error("查找alert彈出框異常->{0}".format(msg))
對象邏輯層
存放頁面元素定位和元素操作方法鹿寨,實現(xiàn)一個頁面一個模塊,后續(xù)頁面元素發(fā)生變化薪夕,只需要修改這個模塊中對應的定位表達式或者操作方法即可脚草。
# baidu_page.py
from selenium.webdriver.common.by import By
from common.basepage import BasePage
class LoginPage(Base):
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') # 登錄提交按鈕
"""
百度用戶名登錄
:param user: 手機/郵箱/用戶名
:param pwd: 密碼
:return:
"""
def login(self, user, pwd):
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, '用戶名登錄-登錄')
業(yè)務層
用于存放真正的測試用例操作,這里不會出現(xiàn)元素定位原献、頁面功能馏慨,所有操作都是直接調(diào)用邏輯層的。
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'])
# 斷言.....