UI自動化測試:Selenium+PO模式+Pytest+Allure整合

本人目前工作中未涉及到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

Github地址:https://github.com/Zimo6/Selenium_Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市噩咪,隨后出現(xiàn)的幾起案子顾彰,更是在濱河造成了極大的恐慌,老刑警劉巖剧腻,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拘央,死亡現(xiàn)場離奇詭異,居然都是意外死亡书在,警方通過查閱死者的電腦和手機灰伟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儒旬,“玉大人栏账,你說我怎么就攤上這事≌辉矗” “怎么了挡爵?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甚垦。 經(jīng)常有香客問我茶鹃,道長,這世上最難降的妖魔是什么艰亮? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任闭翩,我火速辦了婚禮,結(jié)果婚禮上迄埃,老公的妹妹穿的比我還像新娘疗韵。我一直安慰自己,他們只是感情好侄非,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布蕉汪。 她就那樣靜靜地躺著流译,像睡著了一般。 火紅的嫁衣襯著肌膚如雪者疤。 梳的紋絲不亂的頭發(fā)上福澡,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音驹马,去河邊找鬼竞漾。 笑死,一個胖子當著我的面吹牛窥翩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鳞仙,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寇蚊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棍好?” 一聲冷哼從身側(cè)響起仗岸,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎借笙,沒想到半個月后扒怖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡业稼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年盗痒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片低散。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡俯邓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熔号,到底是詐尸還是另有隱情稽鞭,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布引镊,位于F島的核電站朦蕴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弟头。R本人自食惡果不足惜吩抓,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亮瓷。 院中可真熱鬧琴拧,春花似錦、人聲如沸嘱支。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沛膳,卻和暖如春扔枫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锹安。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工短荐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叹哭。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓忍宋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親风罩。 傳聞我的和親對象是個殘疾皇子糠排,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容