UI自動化框架搭建(四):完整UI自動化框架實現


在第三節基礎上多了下面5個層級(具體層級可參考下圖)

components層:  組件層,放置UI自動化公共組件(比如selenium的操作類)以及頁面組件腳本(比如多個頁面腳本相同,可以用組件形式存儲,避免重復工作)

config層:            配置層,管理系統配置

log層:                日志層,放置UI自動化運行的日志信息

page層:             頁面層,放置UI自動化頁面操作腳本

screenshots層:  截圖層,放置UI自動化運行中捕獲的截圖

 

 

 

config、log、screenshots不難理解,主要解釋下page層和components層怎么來方便我們UI自動化腳本編輯?

1、代碼維護層級清晰:
page層: 維護UI腳本頁面操作
components層: 維護UI腳本組件
testcases層: 維護測試用例
2、減少重復工作:
page層維護登錄、目錄層級選擇這些公共頁面操作,只需要編寫一次
components層維護頁面操作的公共組件(比如多層目錄選擇),只需要編寫一次

 

接下來根據上節的test_aaa.py用例文件做下擴展

首先page層,針對 test_aaa.py進行頁面操作類封裝

aaa.py,具體代碼如下

#coding:utf-8
from component.common.webdriver_base import WebDriverBase
import time
from utils.log_util import LogUtil

logger = LogUtil("aaa").get_logger()
class TestAaa(WebDriverBase):

    def login1(self):
        # 訪問百度首頁
        self.open_url(r"http://www.baidu.com")
        # self.driver.get(r"http://www.baidu.com")
        # 百度輸入框輸入
        self.loc_method("kw", "send_keys", method='id', text="懶勺")
        # self.driver.find_element_by_id("kw").send_keys("懶勺")
        # 點百度一下
        self.loc_method("su", "click", method='id')
        # self.driver.find_element_by_id("su").click()
        #等待時間只是為了讓你可以看到目前效果,可以省略
        time.sleep(2)

    def login2(self):
        # 訪問qq首頁
        self.open_url(r"http://www.qq.com")
        # self.driver.get(r"http://www.qq.com")
        # 點新聞鏈接
        self.loc_method("//a[text()='新聞']", "click", method='xpath')
        # self.driver.find_element_by_xpath("//a[text()='新聞']").click()
        # 等待時間只是為了讓你可以看到目前效果,可以省略
        time.sleep(3)
        logger.info("測試login2方法")

 

test_aaa.py代碼變更如下(為什么要把頁面操作放到page層?分層方便代碼維護,以及2個test類共用了相同的頁面操作,可以直接調用,不需要重復維護):

# -*- coding:utf-8 -*-
import unittest
from page.aaa import TestAaa
import time

#QingQing類的名字任意命名,但命名()里的unittest.TestCase就是去繼承這個類,類的作用就是可以使runner.run識別
class QingQing(unittest.TestCase):
    #unittest.TestCase類定義的setUpClass和tearDownClass方法前一定要加@classmethod,
    #setUpClass在這個類里面是第一個執行的方法
    #tearDownClass在這個類里面是最后一個執行的方法
    #中間的執行順序是通過字符的大小進行順序執行,命名必須test_開頭

    #打開瀏覽器,獲取配置
    @classmethod
    def setUpClass(self):
        self.aaa = TestAaa()

    def test_01_search_baidu(self):
        # 訪問百度首頁
        # 百度輸入框輸入
        # 點百度一下
        self.aaa.login1()

    #執行商品收費功能
    def test_02_search_qq_news(self):
        # 訪問qq首頁
        # 點新聞鏈接
        self.aaa.login2()

    #退出瀏覽器
    @classmethod
    def tearDownClass(self):
        self.aaa.quit_browser()

if __name__ ==  "__main__":
    unittest.main()

 

最后components層,對selenium做如下封裝(為什么要封裝,比如你點擊和輸入文本操作,一般前提還得考慮元素是否存在才能去點擊或輸入,這部分重復性工作可以省去)

封裝類webdriver_base.py,具體代碼如下

# -*- coding:utf-8 -*-
from time import sleep

import os
from selenium.common.exceptions import *
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
from utils.datetime_util import DateTimeUtil

from utils.log_util import LogUtil
from utils.yaml_util import YamlUtil

logger = LogUtil('webdriver_base').get_logger()

driver = None
class WebDriverBase(object):
# 頁面操作基礎類

    def __init__(self):
        global driver
        # 如果driver不為空,直接使用原來的driver
        if driver !=  None:
            self.driver = driver
            return

        # 獲取驅動
        chromeDriverPath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'driver',
                                             'chromedriver.exe')
        option = webdriver.ChromeOptions()
        option.add_argument("disable-infobars")
        # 獲取配置文件
        sysConfig = YamlUtil('sysconfig.yaml').read_yaml()
        # 找瀏覽器的名字
        browserName = sysConfig['browser']['browserName']
        if str(browserName).lower() == 'chrome':
            # 獲取谷歌的驅動
            driver = webdriver.Chrome(executable_path=chromeDriverPath, chrome_options=option)
            self.driver = driver
        else:
            logger.error("暫不支持谷歌以外的驅動")
            raise Exception("暫不支持谷歌以外的驅動")

        if self.driver == None:
            logger.error("打開瀏覽器驅動失敗")
            raise Exception("打開瀏覽器驅動失敗")

        self.maximize_window()

    def open_url(self, url):
        # 訪問瀏覽器地址
        self.driver.get(url)

    def get_driver(self):
        return self.driver

    def loc_method(self, eleLoc, action, method='CSS', text=None):
        """
        通用元素定位方法主入口
        :param eleLoc: 定位的元素路徑
        :param action: 頁面動作(輸入文本,點擊等等)
        :param method: 定位方式(css, path)提示:id、name、class屬性都可以用css定位到,默認為CSS
        :param text: 如果是需要文本信息輸入校驗,才需要用到
        :return:
        """

        #loc放到selenium的driver.find_element方法就會自動識別元素
        if str(method).upper() == 'CSS':
            loc = (By.CSS_SELECTOR, eleLoc)
        elif str(method).upper() == 'XPATH':
            loc = (By.XPATH, eleLoc)
        elif str(method).upper() == 'ID':
            loc = (By.ID, eleLoc)
        elif str(method).upper() == 'NAME':
            loc = (By.NAME, eleLoc)
        elif str(method).upper() == 'CLASS':
            loc = (By.CLASS_NAME, eleLoc)
        else:
            loc = None

        try:
            if loc != None:
                if action == 'click':
                    self.click(loc)
                elif action == 'send_keys':
                    self.send_keys(text, loc)
                elif action == 'select_by_text':
                    self.select_by_text(text, loc)
                elif action == 'select_by_index':
                    self.select_by_index(text, loc)
                elif action == 'select_by_value':
                    self.select_by_value(text, loc)
                elif action == 'get_element_text':
                    return self.get_element_text(loc)
                elif action == 'get_element_attribute':
                    return self.get_element_attribute(text, loc)
                elif action == 'text_in_element':
                    return self.text_in_element(text, loc)
                elif action == 'value_in_element':
                    return self.value_in_element(text, loc)
                else:
                    logger.error("action錯誤:請確認action值:%s" % action)
            else:
                logger.error("method錯誤:請確認method值:%s" % method)
        except Exception as e:
            logger.error(e)

    def send_keys(self, text, loc):
        # 輸入框輸入文本信息,先清除文本框內容后輸入
        self.clear_input_box(loc)
        try:
            self.find_element(*loc).send_keys(text)
            sleep(1)
        except Exception as e:
            logger.error(e)
            self.get_screen_img()
            raise

    def clear_input_box(self, loc):
        # 清除輸入框內容
        self.find_element(*loc).clear()
        sleep(1)

    def click(self, loc):
        # 點擊
        try:
            self.find_element(*loc).click()
            sleep(2)
        except Exception as e:
            logger.error(e)
            self.get_screen_img()
            raise

    def move_to_element(self, *loc):
        # 鼠標懸停
        above = self.find_element(*loc)
        ActionChains(self.driver).move_to_element(above).perform()

    def close_single_window(self):
        # 關閉當前窗口(單個的)
        self.driver.close()

    def quit_browser(self):
        # 退出瀏覽器,關閉所有窗口
        self.driver.quit()

    def maximize_window(self):
        # 瀏覽器窗口最大化
        self.driver.maximize_window()

    def browser_forward(self):
        # 瀏覽器前進
        self.driver.forward()

    def browser_back(self):
        # 瀏覽器后退
        self.driver.back()

    def browser_refresh(self):
        # 瀏覽器刷新
        self.driver.refresh()

    def get_element_text(self, loc):
        # 獲取元素的文本
        return self.find_element(*loc).text

    def get_element_attribute(self, attributeItem, loc):
        # 獲取元素的屬性,可以是id,name,type或其他任意屬性
        return self.find_element(*loc).get_attribute(attributeItem)

    def implicitly_wait(self, seconds):
        # 隱式等待時間,最長等待seconds秒,超過拋出超時異常,常用於頁面加載等待
        self.driver.implicitly_wait(seconds)

    def select_by_index(self, index, *loc):
        # 通過index 下標取select
        ele = self.find_element(*loc)
        Select(ele).select_by_index(index)
        sleep(1)

    def select_by_value(self, value, *loc):
        # 通過value值取select
        ele = self.find_element(*loc)
        Select(ele).select_by_value(value)
        sleep(1)

    def select_by_text(self, text, loc):
        # 通過文本text值取select
        ele = self.find_element(*loc)
        Select(ele).select_by_visible_text(text)
        sleep(1)

    def text_in_element(self, text, *loc, timeout=10):
        # 判斷某個元素的text是否包含了預期的值
        # 沒定位到元素返回False,定位到元素返回判斷結果布爾值true
        try:
            ele = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element(*loc, text))
        except TimeoutException:
            logger.error("查找超時,%s不在元素的文本里面" % text)
            return False
        return ele

    def value_in_element(self, value, *loc, timeout=10):
        # 判斷某個元素的value是否包含了預期的值
        # 沒定位到元素返回False,定位到元素返回判斷結果布爾值true
        try:
            ele = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element_value(*loc, value))
        except TimeoutException:
            logger.info("查找超時,%s不在元素的value里面" % value)
            return False
        return ele

    def find_element(self, *loc):
        """
        定位元素
        :param loc: 元組 示例:(By.CSS,'id')
        :return:
        """
        try:
            WebDriverWait(self.driver, 10).until(lambda driver: driver.find_element(*loc).is_displayed())
            return self.driver.find_element(*loc)
        except NoSuchElementException:
            logger.error("找不到定位的元素:%s" % loc[1])
            raise
        except TimeoutException:
            logger.error("元素查找超時:%s" % loc[1])
            raise

    def get_screen_img(self):
        #截圖保存ui運行結果
        imgPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'screenshots')
        screenName = DateTimeUtil().get_current_time() + '.png'
        screenFile = os.path.join(imgPath, screenName)
        try:
            self.driver.get_screenshot_as_file(screenFile)
        except Exception as e:
            logger.error("沒有成功截到圖,原因是: %s" % e)

    def switch_to_next_window(self, currentHandle):
        # 當打開的窗口不是當前窗口,就切換
        allHandles = self.driver.window_handles
        for handle in allHandles:
            if handle != currentHandle:
                self.driver.switch_to.window(handle)
                break

    def switch_to_next_frame(self, iframe):
        # 表單切換到iframe,其中iframe是id
        self.driver.switch_to.frame(iframe)

    def execute_script(self, js):
        #執行js命令
        self.driver.execute_script(js)
View Code

 

截圖中提到的工具類和配置代碼如下

log_util.py

# -*- coding:utf-8 -*-
import logging
from datetime import datetime
import os


class LogUtil():
    def __init__(self, logname=None):
        # 日志名稱
        self.logger = logging.getLogger(logname)
        # 日志級別
        self.logger.setLevel(logging.DEBUG)
        # 日志輸出到控制台
        self.console = logging.StreamHandler()
        self.console.setLevel(logging.DEBUG)
        # 輸出到文件
        self.date = datetime.now().strftime("%Y-%m-%d") + '.log'
        self.filename = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs', self.date)
        self.file = logging.FileHandler(self.filename, encoding='utf-8')
        self.file.setLevel(logging.DEBUG)
        # 日志顯示內容
        self.formatstr = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s'
        self.format = logging.Formatter(self.formatstr)
        self.console.setFormatter(self.format)
        self.file.setFormatter(self.format)
        # 加入到hander
        self.logger.addHandler(self.console)
        self.logger.addHandler(self.file)

    def get_logger(self):
        return self.logger
View Code

datebase_util.py

# -*- coding:utf-8 -*-
from utils.log_util import LogUtil
from utils.yaml_util import YamlUtil
import pymysql
import cx_Oracle

logger = LogUtil('database_util').getLogger()

class DataBase(object):
    def __init__(self):
        pass

    def queryDataBase(self, querySql):
        # 獲取游標
        try:
            cursor = self.con.cursor()
            cursor.execute(querySql)
            return cursor.fetchone()[0]
        except Exception as e:
            logger.error(e)
        finally:
            self.con.close()

    def updateData(self, querySql):
        # 修改數據庫數據
        try:
            cursor = self.con.cursor()
            cursor.execute(querySql)
            self.con.commit()
        except Exception as e:
            self.con.rollback()
            logger.error(e)
        finally:
            self.con.close()


class OracleDataBase(DataBase):
    def __init__(self):
        sysConfig = YamlUtil('sysconfig.yaml').readYaml()
        host = sysConfig['oralceConfig']['host']
        port = sysConfig['oralceConfig']['port']
        user = sysConfig['oralceConfig']['username']
        pwd = sysConfig['oralceConfig']['password']
        database = sysConfig['oralceConfig']['database']
        self.con = cx_Oracle.connect("{}/{}@{}:{}/{}".format(user, pwd, host, port, \
                                                             database).format(), encoding="UTF-8", nencoding="UTF-8")

class MysqlDataBase(DataBase):
    def __init__(self):
        sysConfig = YamlUtil('sysconfig.yaml').readYaml()
        host = sysConfig['mysqlConfig']['host']
        port = sysConfig['mysqlConfig']['port']
        user = sysConfig['mysqlConfig']['username']
        pwd = sysConfig['mysqlConfig']['password']
        database = sysConfig['mysqlConfig']['database']
        self.con = pymysql.Connect(
            host=host,
            port=port,
            user=user,
            passwd=pwd,
            db=database,
            charset='utf8'
        )

if __name__ == "__main__":
    pass
View Code

yaml_util.py

# -*- coding:utf-8 -*-
import os

from ruamel import yaml

from utils.log_util import LogUtil

logger = LogUtil('yaml_util').get_logger()


class YamlUtil(object):
    def __init__(self, file=None):
        try:
            if file != None:
                self.configPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                               'config', file)
            if self.configPath:
                with open(self.configPath, 'r', encoding='utf-8') as f:
                    self.Yamlobject = yaml.safe_load(f)
        except Exception as e:
            logger.error(e)

    def read_yaml(self):
        return self.Yamlobject

    def write_yaml(self, name, value):
        self.Yamlobject[name] = value
        with open(self.file, 'w+', encoding='utf-8') as  fout:
            yaml.dump(self.Yamlobject, fout, default_flow_style=False, allow_unicode=True)


if __name__ == '__main__':
    configPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                              'config', 'sysconfig.yaml')
    r = YamlUtil(configPath).read_yaml()
    print(r['browser']['browserName'])
View Code

datetime_util.py

# -*- coding:utf-8 -*-
from datetime import datetime


class DateTimeUtil(object):
    def __init__(self):
        pass

    def get_current_time(self):
        return datetime.now().strftime("%Y%m%d%H%M%S")

    def get_current_date(self):
        return datetime.now().strftime("%Y-%m-%d")

if __name__=="__main__":
    dateTime = DateTimeUtil()
    print(dateTime.get_current_time())
View Code

sysconfig.yaml

browser:
    browserName:  chrome

login:
    account: renlk24211
    passwd: '12345678'
    url: https://blade.com.cn

mysqlConfig:
    host: 192.168.160.141
    port: 3306
    username: root
    password: 123456
    database: auto

oracleConfig:
    host: 192.168.160.141
    port: 3306
    username: root
    password: 123456
    database: auto

db2Config:
    host: 192.168.160.141
    port: 3306
    username: root
    password: 123456
    database: auto
View Code

主入口run_all_case.py封裝

# -*- coding:utf-8 -*-
import unittest
import os
from utils.HTMLTestRunnerForPy3 import HTMLTestRunner
from datetime import datetime


class RunAllCase(object):

    def __init__(self):
        pass

    def add_cases(self):
        # 挑選用例,pattern='test_*.py'表示添加test_開頭的py文件
        casePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'testcases')
        discover = unittest.defaultTestLoader.discover(
            start_dir=casePath,
            pattern='test_*.py'
        )

        return discover

    def get_report_file_path(self):
        # 指定生成報告地址
        report_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'reports')
        report_name = datetime.now().strftime("%Y%m%d%H%M%S") + '.html'
        report_file = os.path.join(report_path, report_name)

        return report_file

    def run_cases(self, report_file, discover, title, description):
        # 運行用例
        runner = HTMLTestRunner(
            stream=open(report_file, 'wb'),
            # 生成的html報告標題
            title=title,
            # 1是粗略的報告,2是詳細的報告
            verbosity=2,
            # 生成的html描述
            description=description
        )
        runner.run(discover)

if __name__ == "__main__":
    r = RunAllCase()
    discover = r.add_cases()
    report_file = r.get_report_file_path()

    title = '銀行UI自動化測試報告'
    description = '銀行UI自動化測試報告'
    r.run_cases(report_file, discover, title, description)
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM