本人目前工作中未涉及到WebUI自動化測試,但為了提升自己的技術(shù),多學習一點還是沒有壞處的景东,廢話不多說了吞琐,目前主流的webUI測試框架應(yīng)該還是selenium捆探,考慮到可維護性、拓展性站粟、復用性等黍图,我們采用PO模式去寫我們的腳本,本文檔也主要整合了Selenium+PO模式+Pytest+Allure奴烙,下面我們進入正題助被。注:文章末尾附Github地址
技術(shù)前提:python剖张、selenium、pytest基礎(chǔ)知識
1. 項目結(jié)構(gòu)目錄:
image.png
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)的測試步驟
2019062515512657.png
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
6. 生成Allure測試報告
ALLURE.png