1. 程式人生 > 實用技巧 >UI自動化框架搭建(四):完整UI自動化框架實現

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

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

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

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

log層: 日誌層,放置UI自動化執行的日誌資訊

page層: 頁面層,放置UI自動化頁面操作指令碼

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

config、log、screenshots不難理解,主要解釋下page層和components層怎麼來方便我們UI自動化指令碼編輯

根據上節的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(object):
    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 = cx_Oracle.connect("{}/{}@{}:{}/{}".format(user, pwd, host, port, \
                                                             database).format(), encoding="UTF-8", nencoding="UTF-8")

class MysqlDataBase(object):
    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

接著針對 test_aaa.py,頁面操作類封裝