本人目前工作中未涉及到WebUI自動化測試,但為了提升自己的技術(shù),多學習一點還是沒有壞處的景东,廢話不多說了吞琐,目前主流的webUI測試框架應(yīng)該還是selenium捆探,考慮到可維護性、拓展性站粟、復用性等黍图,我們采用PO模式去寫我們的腳本,本文檔也主要整合了Selenium+PO模式+Pytest+Allure奴烙,下面我們進入正題助被。注:文章末尾附Github地址
技術(shù)前提:python剖张、selenium、pytest基礎(chǔ)知識
1. 項目結(jié)構(gòu)目錄:
2. PO模式介紹
PO模式特點:
- 易于維護
- 復用性高
- 腳本易于閱讀理解
PO模式要素:
1. 在PO模式中抽象封裝成一個BasePage類揩环,該基類應(yīng)該擁有一個只實現(xiàn) webdriver 實例的屬性
2. 每個一個 pag 都繼承BasePage搔弄,通過driver來管理本page中元素將page中的操作封裝成一個個的方法
3. TestCase依賴 page 類,從而實現(xiàn)相應(yīng)的測試步驟
3. BasePage 頁面封裝
import logging
import os
import time
from datetime import datetime
from time import sleep
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from Utils.myLog import MyLog
"""
此類封裝所有操作丰滑,所有頁面繼承該類
"""
class BasePage(object):
def __init__(self, driver):
self.logger = MyLog().getLog()
self.driver = driver
# 等待元素可見
def wait_eleVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
"""
:param loc:元素定位表達;元組類型,表達方式(元素定位類型,元素定位方法)
:param timeout:等待的上限
:param poll_frequency:輪詢頻率
:param model:等待失敗時,截圖操作,圖片文件中需要表達的功能標注
:return:None
"""
self.logger.info(f'等待"{model}"元素,定位方式:{loc}')
try:
start = datetime.now()
WebDriverWait(self.driver, timeout, poll_frequency).until(EC.visibility_of_element_located(loc))
end = datetime.now()
self.logger.info(f'等待"{model}"時長:{end - start}')
except TimeoutException:
self.logger.exception(f'等待"{model}"元素失敗,定位方式:{loc}')
# 截圖
self.save_webImgs(f"等待元素[{model}]出現(xiàn)異常")
raise
# 等待元素不可見
def wait_eleNoVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
"""
:param loc:元素定位表達;元組類型,表達方式(元素定位類型,元素定位方法)
:param timeout:等待的上限
:param poll_frequency:輪詢頻率
:param model:等待失敗時,截圖操作,圖片文件中需要表達的功能標注
:return:None
"""
logging.info(f'等待"{model}"消失,元素定位:{loc}')
try:
start = datetime.now()
WebDriverWait(self.driver, timeout, poll_frequency).until_not(EC.visibility_of_element_located(loc))
end = datetime.now()
self.logger.info(f'等待"{model}"時長:{end - start}')
except TimeoutException:
self.logger.exception(f'等待"{model}"元素失敗,定位方式:{loc}')
# 截圖
self.save_webImgs(f"等待元素[{model}]消失異常")
raise
# 查找一個元素element
def find_element(self, loc, model=None):
self.logger.info(f'查找"{model}"元素顾犹,元素定位:{loc}')
try:
return self.driver.find_element(*loc)
except NoSuchElementException:
self.logger.exception(f'查找"{model}"元素失敗,定位方式:{loc}')
# 截圖
self.save_webImgs(f"查找元素[{model}]異常")
raise
# 查找元素elements
def find_elements(self, loc, model=None):
self.logger.info(f'查找"{model}"元素集,元素定位:{loc}')
try:
return self.driver.find_elements(*loc)
except NoSuchElementException:
self.logger.exception(f'查找"{model}"元素集失敗,定位方式:{loc}')
# 截圖
self.save_webImgs(f"查找元素集[{model}]異常")
raise
# 輸入操作
def input_text(self, loc, text, model=None):
# 查找元素
ele = self.find_element(loc, model)
# 輸入操作
self.logger.info(f'在"{model}"輸入"{text}",元素定位:{loc}')
try:
ele.send_keys(text)
except:
self.logger.exception(f'"{model}"輸入操作失敗!')
# 截圖
self.save_webImgs(f"[{model}]輸入異常")
raise
# 清除操作
def clean_inputText(self, loc, model=None):
ele = self.find_element(loc, model)
# 清除操作
self.logger.info(f'清除"{model}",元素定位:{loc}')
try:
ele.clear()
except:
self.logger.exception(f'"{model}"清除操作失敗')
# 截圖
self.save_webImgs(f"[{model}]清除異常")
raise
# 點擊操作
def click_element(self, loc, model=None):
# 先查找元素在點擊
ele = self.find_element(loc, model)
# 點擊操作
self.logger.info(f'點擊"{model}",元素定位:{loc}')
try:
ele.click()
except:
self.logger.exception(f'"{model}"點擊失敗')
# 截圖
self.save_webImgs(f"[{model}]點擊異常")
raise
# 獲取文本內(nèi)容
def get_text(self, loc, model=None):
# 先查找元素在獲取文本內(nèi)容
ele = self.find_element(loc, model)
# 獲取文本
self.logger.info(f'獲取"{model}"元素文本內(nèi)容褒墨,元素定位:{loc}')
try:
text = ele.text
self.logger.info(f'獲取"{model}"元素文本內(nèi)容為"{text}",元素定位:{loc}')
return text
except:
self.logger.exception(f'獲取"{model}"元素文本內(nèi)容失敗,元素定位:{loc}')
# 截圖
self.save_webImgs(f"獲取[{model}]文本內(nèi)容異常")
raise
# 獲取屬性值
def get_element_attribute(self, loc, name, model=None):
# 先查找元素在去獲取屬性值
ele = self.find_element(loc, model)
# 獲取元素屬性值
self.logger.info(f'獲取"{model}"元素屬性炫刷,元素定位:{loc}')
try:
ele_attribute = ele.get_attribute(name)
self.logger.info(f'獲取"{model}"元素"{name}"屬性集為"{ele_attribute}",元素定位:{loc}')
return ele_attribute
except:
self.logger.exception(f'獲取"{model}"元素"{name}"屬性失敗,元素定位:{loc}')
# 截圖
self.save_webImgs(f"獲取[{model}]屬性異常")
raise
# iframe 切換
def switch_iframe(self, frame_refer, timeout=30, poll_frequency=0.5, model=None):
# 等待 iframe 存在
self.logger.info('iframe 切換操作:')
try:
# 切換 == index\name\id\WebElement
WebDriverWait(self.driver, timeout, poll_frequency).until(
EC.frame_to_be_available_and_switch_to_it(frame_refer))
sleep(0.5)
self.logger.info('切換成功')
except:
self.logger.exception('iframe 切換失敗!!!')
# 截圖
self.save_webImgs(f"iframe切換異常")
raise
# 窗口切換 = 如果是切換到新窗口,new. 如果是回到默認的窗口,default
def switch_window(self, name, cur_handles=None, timeout=20, poll_frequency=0.5, model=None):
"""
調(diào)用之前要獲取window_handles
:param name: new 代表最新打開的一個窗口. default 代表第一個窗口. 其他的值表示為窗口的 handles
:param cur_handles:
:param timeout:等待的上限
:param poll_frequency:輪詢頻率
:param model:等待失敗時,截圖操作,圖片文件中需要表達的功能標注
:return:
"""
try:
if name == 'new':
if cur_handles is not None:
self.logger.info('切換到最新打開的窗口')
WebDriverWait(self.driver, timeout, poll_frequency).until(EC.new_window_is_opened(cur_handles))
window_handles = self.driver.window_handles
self.driver.swich_to.window(window_handles[-1])
else:
self.logger.exception('切換失敗,沒有要切換窗口的信息!!!')
self.save_webImgs("切換失敗_沒有要切換窗口的信息")
raise
elif name == 'default':
self.logger.info('切換到默認頁面')
self.driver.switch_to.default()
else:
self.logger.info('切換到為 handles 的窗口')
self.driver.swich_to.window(name)
except:
self.logger.exception('切換窗口失敗!!!')
# 截圖
self.save_webImgs("切換失敗_沒有要切換窗口的信息")
raise
# 截圖
def save_webImgs(self, model=None):
# filepath = 指圖片保存目錄/model(頁面功能名稱)_當前時間到秒.png
# 截圖保存目錄
# 拼接日志文件夾郁妈,如果不存在則自動創(chuàng)建
cur_path = os.path.dirname(os.path.realpath(__file__))
now_date = time.strftime('%Y-%m-%d', time.localtime(time.time()))
screenshot_path = os.path.join(os.path.dirname(cur_path), f'Screenshots\\{now_date}')
if not os.path.exists(screenshot_path):
os.mkdir(screenshot_path)
# 當前時間
dateNow = time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
# 路徑
filePath = '{}\\{}_{}.png'.format(screenshot_path, model, dateNow)
try:
self.driver.save_screenshot(filePath)
self.logger.info(f"截屏成功,圖片路徑為{filePath}")
except:
self.logger.exception('截屏失敗!')
# 退出
def get_driver(self):
return self.driver
4. 頁面繼承BasPage
from Common.basePage import BasePage
from selenium.webdriver.common.by import By
from time import sleep
class BaiduIndex(BasePage):
"""
頁面元素
"""
# 百度首頁鏈接
baidu_index_url = "https://www.baidu.com"
# 搜索框
search_input = (By.ID, "kw")
# "百度一下"按鈕框
search_button = (By.ID, "su")
# 查詢操作
def search_key(self, search_key):
self.logger.info("【===搜索操作===】")
# 等待用戶名文本框元素出現(xiàn)
self.wait_eleVisible(self.search_input, model='搜索框')
# 輸入內(nèi)容
self.input_text(self.search_input, "阿崔", model="搜索框")
# 清除文本框內(nèi)容
self.clean_inputText(self.search_input, model='搜索框')
# 輸入用戶名
self.input_text(self.search_input, text=search_key, model='搜索框')
# 等待搜索按鈕出現(xiàn)
self.wait_eleVisible(self.search_button, model='"百度一下"搜索按鈕')
# 點擊搜索按鈕
self.click_element(self.search_button, model='"百度一下"搜索按鈕')
# 搜索后等待界面加載完成
self.driver.implicitly_wait(10)
sleep(3)
5. pytest+allure編寫測試用例
注:Pytest整合Allure教程請參考:https://www.cnblogs.com/huny/p/13752406.html
import os
import time
import pytest
import allure
from time import sleep
from selenium import webdriver
from PageObject.baiduIndex import BaiduIndex
driver = webdriver.Chrome()
baidu_index = BaiduIndex(driver)
@pytest.fixture(scope="class")
def init():
# 打開瀏覽器,訪問登錄頁面
baidu_index.logger.info("\nWebDriver 正在初始化...")
driver.get(baidu_index.baidu_index_url)
baidu_index.logger.info(f"打開鏈接: {baidu_index.baidu_index_url}...")
# 窗口最大化
driver.maximize_window()
# 隱式等待
driver.implicitly_wait(10)
baidu_index.logger.info("WebDriver 初始化完成浑玛!")
yield
driver.quit()
baidu_index.logger.info("WebDriver 成功退出...")
@allure.feature("百度搜索")
class TestBaiduSearch:
@allure.story("搜索指定關(guān)鍵字")
@pytest.mark.baidu_search
@pytest.mark.parametrize("key_word", [
"哈哈",
"呵呵",
], )
def test_search(self, init, key_word):
# @pytest.mark.parametrize 參數(shù)化
baidu_index.search_key(key_word)
web_title = driver.title
assert "哈哈_百度搜索" == web_title