Web UI自动化框架

Web UI自动化框架

介绍

Python + Selenium + BeautifulReport

代码结构

config
    -- settings.py
    
doc
    -- BeautifulReport.zip
    
lib
    -- Logs.py
    -- optCookies.py
    -- readDict.py
    -- webDriver.py

result
    -- img
    -- logs
    -- report
    
testCase
    -- 存放测试用例
    
testData
    -- 存放测试数据
    
testPage
    -- 页面操作方法
    -- map
   
tool
    -- merge_dict.py

requirements.txt
    pip freeze >requirements.txt
    pip install -r requirements.txt
    -- py 依赖

run_all_case.py
    -- 执行 test cese 下所有测试用例 并在 result/report 下生成 测试报告.html

安装教程

win
1.  下载对应本机 chrome 版本的 webdriver.exe
    http://chromedriver.storage.googleapis.com/index.html
2.  chrome运行路径 加入系统 path 变量
3.  下载好的 webdriver.exe 移至 python 的安装路径(py3\Scripts\)下
4.  把 doc 目录下 BeautifulReport.zip 解压放入 python 的安装路径(py3\Lib\site-packages\)下
mac
1.  需要指定以下信息:
    1)options = webdriver.ChromeOptions()
    2)options.binary_location = "/Applications/Google Chrome 2.app/Contents/MacOS/Google Chrome"
    3)chrome_driver_binary = "/usr/local/bin/chromedriver"
    4)driver = webdriver.Chrome(chrome_driver_binary, chrome_options=options)

快速上手

  1. map 组装页面元素
"格式"
# 百度
bai_du = {
     
    "点击新闻": {
     
        "mode": 'link',
        "element": '新闻_1'
    },
    "打开百度": "http://www.baidu.com"
}
"mode: selenium常见定位,目前支持 id,name,class_name,css,xpath,link"
"element: 如果是xpath就填入元素的xpath,xpath建议以最短的路径定位到元素"
# 还有一些特殊的操作 如 执行js代码,上传文件,鼠标操作,cookie操作,浏览器标签页操作等,参考webderiver.py的方法传参格式
  1. 编写page方法
from lib import webDriver
from testpage import login_page
# 1.导入用例所需的 map
from testpage.map import bai_du, login
from tool import merge_dict
from time import sleep


class BDPage(object):
		
    def __init__(self):
        """初始化"""
        # 初始化 ui_map
        ui_map = merge_dict.merge_dict(login, bai_du)
        # 初始化driver
        self.driver = webDriver.WebDriver(ui_map)
        # 登录
        login_page.LoginPage(self.driver)
	
	# 2.调用 driver 内的元素操作并传入定义好的map键
    def click_xinwen(self):
        self.driver.click("点击新闻")

    def open_bd(self):
        self.driver.get_url("打开百度")

    def switch_region(self):
        login_page.console_region(self.driver)
  1. 编写case
import os
import unittest
import warnings
from BeautifulReport import BeautifulReport
from lib import Logs
from config import settings
from time import sleep
# 1.导入用例所对应的page
from testpage import demo_page


class TestBD(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        """修改当前工作路径和去除告警"""
        os.chdir(settings.BasePath)
        warnings.simplefilter("ignore", ResourceWarning)

    def save_img(self, img_name):
        """默认截图方法"""
        self.page.driver.save_img(img_name)

    def tearDown(self):
        """用例结束关闭浏览器"""
        self.page.driver.quit()

    def setUp(self):
        """准备页面"""
        self.log = Logs.Logs()
        # 2.实例化page
        self.page = demo_page.BDPage()
	
	# 3.编写测试类方法
	# 4.add_test_img()传入类方法的名称,如在执行过程中有任何错误,就截取当前页面图片,并以类方法名称命名
	# 5.图片默认保存 result/imge下,若是 BeautifulReport 批量执行则会在报告中展示
	# 6.报告显示图片bug,无法直接双击图片打开,解决办法:右键图片另外标签页打开,就可显示
	# 7.调用page定义好了的方法,组装测试步骤~~~~~
    @BeautifulReport.add_test_img("test_baidu_001")
    def test_baidu_001(self):
        u"""用例描述"""
        self.page.open_bd()
        sleep(3)
        self.page.click_xinwen()
        sleep(10)

# 8.单独调试用例方法
if __name__ == '__main__':
    unittest.main()
    # suit = unittest.TestSuite()
    # suit.addTest(TestJD('test_jd'))
    # runner = unittest.TextTestRunner()
    # runner.run(suit)

代码片段

setting.py

这里存放了一些 全局变量 和 基本路径 和 基本设置, 当前可设置 用例执行的 地域,账号,环境,访问url,测试报告名称等, 本地调试的时候,可以给地域,账号一个默认值

import os
import time

"""         基础设置              """

# 当前测试项目名称,会显示在测试报告里面的项目描述
Description = "TcaPlus"

# 隐式等待时长 (是否开启,等待时长秒)
ImplicitlyWait = (True, 10)

# 显式等待时长,如果指定元素在如下时间未出现则报错
WebDriverWait = 40

# 浏览器
Browser = "Chrome"

"""         全局变量              """
# 当前iframe框架名称
NowIframe = ""
# 当前应该执行的地域
ExecuteRegion = "xxx"
# 测试账号
Account = "xxx"
PassWord = "xxx"
# 当前环境 pro 现网, dev 测试环境
Environmental = "dev"
# 控制台预览链接
ConsolePath = "url"

"""         路径设置              """
# 测试报告名称
NowTime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
ReportName = "%s.html" % NowTime

# 工作路径(setting的爷爷)工作路径
BasePath = os.path.abspath(os.path.join(__file__, os.pardir, os.pardir))

# 测试用例路径
CasePath = os.path.join(BasePath, "testcase")

# 测试报告路径 集成 flask 的UI自动化框架需要注意测试报告目录为 flask 项目下的 templates\\test_report 目录
ReportPath = os.path.join(BasePath, "result", "report")
# ReportPath = "C:\\Project\\Flask\\templates\\test_report"

# 测试截图路径
ImagesPath = os.path.join(BasePath, "result", "img")

# 日志路径
LogPath = os.path.join(BasePath, "result", "logs")

# 测试数据存放
TestData = os.path.join(BasePath, "testdata")

# cookies路径
CookiesPath = os.path.join(BasePath, "testdata", "cookies")

webDriver.py

二次封装了selenium的方法,使它可以接收我们自定义的map字典来操作元素,以实现的操作(鼠标操作,浏览器操作,元素操作,js操作(js只能支持原生document和window方法)等,具体可以进入这个文件看)

import os
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from config import settings
from lib import readDict
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains


class WebDriver(object):
    def __init__(self, element):
        self.driver = self._browser()
        self.element = element
        if settings.ImplicitlyWait[0]:
            self.implicitly_wait(settings.ImplicitlyWait[1])
        self.readDict = readDict.ReadDict(self.element)

    def upd_ui_map(self, page_element, **kwargs):
        """
        给ui_map中的定位重新赋值
        示例map { "切换框架": {"iframe": "ptlogin_iframe"} }
        修改 切换框架 中的 iframe
        传入 page_element="切换框架",iframe="new name"
        需要修改多个,就多传
        :param page_element:元素定位名称 切换框架
        :param kwargs: url,mode,element,iframe,js,value,cookie_key,cookie_name,file_name,content
        """
        url = kwargs.get("url")
        if url:
            self.element[page_element] = url
        mode = kwargs.get("mode")
        if mode:
            self.element[page_element] = mode
        element = kwargs.get("element")
        if element:
            self.element[page_element] = element
        iframe = kwargs.get("iframe")
        if iframe:
            self.element[page_element] = iframe
        js = kwargs.get("js")
        if js:
            self.element[page_element] = js
        value = kwargs.get("value")
        if value:
            self.element[page_element] = value
        cookie_key = kwargs.get("cookie_key")
        if cookie_key:
            self.element[page_element] = cookie_key
        cookie_name = kwargs.get("cookie_name")
        if cookie_name:
            self.element[page_element] = cookie_name
        file_name = kwargs.get("file_name")
        if file_name:
            self.element[page_element] = file_name
        content = kwargs.get("content")
        if content:
            self.element[page_element] = content
        return kwargs

    def _browser(self):
        """初始化浏览器"""
        browser = settings.Browser
        if browser == "Chrome":
            return webdriver.Chrome()
        elif browser == "Firefox":
            return webdriver.Firefox()
        else:
            raise Exception("请选择正确的浏览器")

    def save_img(self, img_name):
        """截图"""
        self.driver.get_screenshot_as_file('{}/{}.png'.format(os.path.abspath(settings.ImagesPath), img_name))

    def find_element_by_id(self, element_id):
        """根据元素 id 获取元素对象"""
        return self.driver.find_element_by_id(element_id)

    def find_element_by_name(self, element_name):
        """根据元素 name 获取元素对象"""
        return self.driver.find_element_by_name(element_name)

    def find_element_by_xpath(self, element_xpath):
        """根据元素 xpath 获取元素对象"""
        return self.driver.find_element_by_xpath(element_xpath)

    def find_element_by_link_text(self, element_link_text):
        """根据元素 link 获取元素对象"""
        return self.driver.find_element_by_link_text(element_link_text)

    def find_element_by_class_name(self, element_class_name):
        """根据元素 class_name 获取元素对象"""
        return self.driver.find_element_by_class_name(element_class_name)

    def find_elements_by_css_selector(self, element_css_selector):
        """根据元素 css 获取元素对象"""
        return self.driver.find_elements_by_css_selector(element_css_selector)

    def _element_route(self, mode, element):
        """根据 mode 是什么返回 find_element_by_什么"""

        if mode == "id":
            return self.find_element_by_id(element)

        elif mode == "name":
            return self.driver.find_element_by_name(element)

        elif mode == "class_name":
            return self.driver.find_element_by_class_name(element)

        elif mode == "css":
            return self.driver.find_element_by_css_selector(element)

        elif mode == "xpath":
            return self.driver.find_element_by_xpath(element)

        elif mode == "link":
            return self.driver.find_element_by_link_text(element)

    def _element_route_by(self, mode):
        """by 根据mode式什么返回具体方法"""
        if mode == "id":
            return By.ID
        elif mode == "xpath":
            return By.XPATH
        elif mode == "link":
            return By.LINK_TEXT
        elif mode == "name":
            return By.NAME
        elif mode == "class_name":
            return By.CLASS_NAME
        elif mode == "css":
            return By.CSS_SELECTOR
        else:
            raise Exception("检查 mode 方法是否存在")

    # 窗口切换
    def get_current_window(self):
        """获取当前浏览器窗口"""
        return self.driver.current_window_handle

    def get_all_handles(self):
        """获取当前所有打开的窗口句柄"""
        return self.driver.window_handles

    def jump_window(self, handle):
        """
        跳转至指定窗口
        :param handle:
        """
        self.driver.switch_to.window(handle)

    def jump_new_window(self, old_all_handles, new_all_handles):
        """
        跳转至新打开的窗口
        :param old_all_handles: 旧的所有以开窗口句柄
        :param new_all_handles: 新的所有以开窗口句柄
        """
        for handles in new_all_handles:
            if handles not in old_all_handles:
                self.driver.switch_to.window(handles)

    # 操作浏览器方法
    def get_title(self):
        """获取当前页面的 title"""
        return self.driver.title

    def quit(self):
        """关闭所有页面"""
        self.driver.quit()

    def close(self):
        """关闭当前页面"""
        self.driver.close()

    def refresh(self):
        """刷新当前页面"""
        self.driver.refresh()

    def set_window_size(self, length, width):
        """设置浏览器大小"""
        self.driver.set_window_size(length, width)

    def maximize_window(self):
        """放大浏览器"""
        self.driver.maximize_window()

    def back(self):
        """浏览器后退"""
        self.driver.back()

    def forward(self):
        """浏览器前进"""
        self.driver.forward()

    def switch_to(self, page_element):
        """
        切换 iframe 框架
        :param page_element: {"iframe":"框架名称"}
        """
        iframe = self.readDict.get_value(page_element)["iframe"]
        settings.NowIframe = iframe
        self.driver.switch_to.frame(iframe)

    def switch_to_main(self):
        """
        直接返回主文档,如当前就是主文档则无效
        """
        self.driver.switch_to.default_content()

    def switch_to_back(self):
        """
        返回上一级iframe,如当前就是主文档则无效
        """
        self.driver.switch_to.parent_frame()

    def now_iframe(self):
        """
        返回当前iframe名称
        """
        return settings.NowIframe

    # 鼠标操作
    def mouse_right_click(self, page_element):
        """
        鼠标 右击
        :param page_element: "点击账号密码登录": {"mode": "id","element": 'switcher_plogin'}
        """
        location = self.readDict.get_value(page_element)
        element = self._element_route(location["mode"], location["element"])
        ActionChains(self.driver).context_click(element).perform()

    def mouse_double_click(self, page_element):
        """
        鼠标 双击
        :param page_element: "点击账号密码登录": {"mode": "id","element": 'switcher_plogin'}
        """
        location = self.readDict.get_value(page_element)
        element = self._element_route(location["mode"], location["element"])
        ActionChains(self.driver).double_click(element).perform()

    def mouse_drag_and_dropk(self, old_page_element, new_page_element):
        """
        鼠标 拖拽, 把元素 old 拖拽到 new
        :param old_page_element: "原图片位置": {"mode": "id","element": 'switcher_plogin'}
        :param new_page_element: "新图片位置": {"mode": "id","element": 'switcher_plogin'}
        """
        old_location = self.readDict.get_value(old_page_element)
        new_location = self.readDict.get_value(new_page_element)
        old_element = self._element_route(old_location["mode"], old_location["element"])
        new_element = self._element_route(new_location["mode"], new_location["element"])
        ActionChains(self.driver).drag_and_drop(old_element, new_element).perform()

    def mouse_move_to(self, page_element):
        """
        鼠标 悬停
        :param page_element: "原图片位置": {"mode": "id","element": 'switcher_plogin'}
        """
        location = self.readDict.get_value(page_element)
        element = self._element_route(location["mode"], location["element"])
        ActionChains(self.driver).move_to_element(element).perform()

    # 调用JavaScript代码
    def execute_script(self, page_element=None, js=None):
        """
        调用 JavaScript 代码
        :param js: "document.getElementsByClassName("xxx")"
        :param page_element:  {"js":"arguments[0].click();","element":"Default-VPC(默认)","mode":"link"}
        有两种方式执行js代码块
            1. 传入代码块 js="代码块"
            2. 传入page_element, mode 和 element 定位到元素(尽量唯一),js中 arguments[0] 代表定位出的第一个元素后执行js方法
        """
        if page_element is None and js is None:
            raise Exception("js直接执行 和 元素定位执行 两种方式必选其一")
        if js:
            return self.driver.execute_script(js)
        if page_element:
            location = self.readDict.get_value(page_element)
            mode = location["mode"]
            element = location["element"]
            page_js = location["js"]
            return self.driver.execute_script(page_js, self._element_route(mode, element))

    # 操作下拉框
    def select_by_value(self, page_element):
        """
        选择下拉框内容
        :param page_element: {"mode":"","element":"","value":""}
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        value = location["value"]
        sel = self._element_route(mode, element)
        Select(sel).select_by_value(value)

    # 操作cookie方法
    def get_all_cookies(self):
        """获取所有cookies"""
        return self.driver.get_cookies()

    def get_cookie(self, page_element):
        """
        指定获取cookie
        :param page_element: {"cookie_key":"key"}
        :return: cookie
        """
        cookie_key = self.readDict.get_value(page_element)["cookie_key"]
        return self.driver.get_cookie(cookie_key)

    def add_cookie(self, page_element):
        """
        添加 cookie
        :param page_element: {"cookie_key":[{cookie1:xx},{cookie2:xx}]}
        """
        cookie_key = self.readDict.get_value(page_element)["cookie_key"]
        for i in cookie_key:
            self.driver.add_cookie(i)

    def delete_cookie(self, page_element):
        """
        指定 cookie_name 删除
        :param page_element:   {"cookie_name":"name_value"}
        """
        cookie_name = self.readDict.get_value(page_element)["cookie_name"]
        self.driver.delete_cookie(cookie_name)

    def delete_all_cookie(self):
        """
        删除所有 cookie
        """
        self.driver.delete_all_cookies()

    def upload_file(self, page_element, file_name):
        """
        上传文件
        :param page_element: {"mode":"id","element":"元素"}
        :param file_name: "file_name1,file_name2,file_name3" testData下文件
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        all_file_name_list = file_name.strip(",").split(",")
        file_path = ""
        for item in all_file_name_list:
            # 默认获取的文件在 settings.TestData 目录下
            file_path1 = os.path.join(settings.TestData, item)
            if os.path.exists(file_path1) is False:
                raise Exception("<%s> 请检查文件是否存在!!!!" % file_path1)
            file_path += "%s\n" % file_path1
        self._element_route(mode, element).send_keys(file_path.strip("\n"))

    def get_url(self, page_element):
        """
        打开目标地址
        :param page_element: "http://www.baidu.com"
        """
        url = self.readDict.get_value(page_element)
        self.driver.get(url)

    def get_attribute(self, page_element):
        """
        获取元素属性值
        :param page_element: {"mode":"id","element":"元素","value":"属性名称"}
        :return: values
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        value = location["value"]
        self._element_route(mode, element).get_attribute(value)

    def implicitly_wait(self, sec):
        """
        设置隐式等待 ,等待时长 sec 可在 config\\settings.py 中配置
        """
        self.driver.implicitly_wait(sec)

    def element_wait(self, page_element):
        """
        等待元素出现,否则将在最大等待时间 config\\settings.py 抛出异常
        :param page_element: {"mode":"id","element":"元素"}
        """
        location = self.readDict.get_value(page_element)
        mode = self._element_route_by(location["mode"])
        element = location["element"]
        WebDriverWait(self.driver, settings.WebDriverWait, 0.5).until(
            EC.presence_of_element_located((mode, element)))

    def is_displayed(self, page_element):
        """
        首先确定元素是存在的 , 元素是否用户可见
        :param page_element: {"mode":"id","element":"元素"}
        :return:True or False
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        return self._element_route(mode, element).is_displayed()

    def is_enabled(self, page_element):
        """
        首先保证元素是存在的 , 元素是否可用
        用于判断若某一个按钮置灰不可点击等类似操作
        :param page_element: {"mode":"id","element":"元素"}
        :return: True or false
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        return self._element_route(mode, element).is_enabled()

    def is_selected(self, page_element):
        """
        首先保证元素是存在的 , 元素是否被选中
        用于判断单选框 复选框是否被勾选上
        :param page_element: {"mode":"id","element":"元素"}
        :return: True or false
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        return self._element_route(mode, element).is_selected()

    def is_element_exist(self, page_element):
        """
        判断元素是否存在
        如果在页面没有找到目标元素则返回 false
        :param page_element:  {"mode":"id","element":"元素"}
        :return: True or false
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        try:
            self._element_route(mode, element)
        except NoSuchElementException as e:
            return False
        else:
            return True

    def get_value(self, page_element):
        """
        获取元素的值
        :param page_element: {"mode":"id","element":"元素"}
        :return: 获取的值 str
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        return self._element_route(mode, element).text

    def clear(self, page_element):
        """
        清除元素文本内容
        :param page_element: {"mode":"id","element":"元素"}
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        self._element_route(mode, element).clear()

    def send_keys(self, page_element, content=None):
        """
        输入数据
        :param page_element: {"mode":"id","element":"元素","content":"输入内容"}
        :param content: 输入内容

        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        if "content" in location:
            self._element_route(mode, element).send_keys(location["content"])
        else:
            self._element_route(mode, element).send_keys(content)

    def click(self, page_element):
        """
        单击元素
        :param page_element: {"mode":"id","element":"元素"}
        """
        location = self.readDict.get_value(page_element)
        mode = location["mode"]
        element = location["element"]
        self._element_route(mode, element).click()

test_page/map.py

存放页面元素的定位信息,遵循 1.一个页面一个字典 2.键名清晰,易分辨,直观的表达定位的那个元素 3.同字典键名不重复,异字典键名不重复(page合并不同字典的时候会检查并抛出同名键名)

# 百度首页
bai_du = {
     
    "点击新闻": {
     
        "mode": 'link',
        "element": '新闻_1'
    },
    "打开百度": "http://www.baidu.com"
}

demo_page.py

定义页面元素的操作方法,比如 实现 点击新闻 这个动作;
init 内初始化步骤:

  1. 初始化 ui_map,需要先从 map 文件中导入map字典,把本条用例所需的 map页面元素定位字典合并上,需要多少合并多少
    2.初始化 driver 对象,把合并好的map传入后,它会返回一个driver对象,后续的页面操作就使用它
    3.封装好了的登录到被测系统的方法,需要传入 当前 driver 对象,编写页面操作方法可以直接从 主页 开始
    其它的类方法就自定义了,点击,打开页面,输入等
from lib import webDriver
from testpage import login_page
from testpage.map import bai_du, login
from tool import merge_dict
from time import sleep


class BDPage(object):

    def __init__(self):
        """初始化"""
        # 初始化 ui_map
        ui_map = merge_dict.merge_dict(login, bai_du)
        # 初始化driver
        self.driver = webDriver.WebDriver(ui_map)
        # 登录
        login_page.LoginPage(self.driver)

    def click_xinwen(self):
        self.driver.click("点击新闻")

    def open_bd(self):
        self.driver.get_url("打开百度")

test_demo_case.py

测试类继承 unittest 测试框架
1.setUpClass,修改当前工作路径(因为使用 BeautifulReport内的截图,是有默认的截图路径的,这里已经修改了原生BeautifulReport截图路径,修改后的存放 doc 下)
2.setUp,实例化 log 和 实例化 page
3.测试类方法,调用page内定义好了的页面元素操作方法

import os
import unittest
import warnings
from BeautifulReport import BeautifulReport
from lib import Logs
from config import settings
from time import sleep
from testpage import demo_page


class TestBD(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        """修改当前工作路径和去除告警"""
        os.chdir(settings.BasePath)
        warnings.simplefilter("ignore", ResourceWarning)

    def save_img(self, img_name):
        """默认截图方法"""
        self.page.driver.save_img(img_name)

    def tearDown(self):
        """用例结束关闭浏览器"""
        self.page.driver.quit()

    def setUp(self):
        """准备页面"""
        self.log = Logs.Logs()
        self.page = demo_page.BDPage()

    @BeautifulReport.add_test_img("test_baidu_001")
    def test_baidu_001(self):
        u"""用例描述"""
        self.page.open_bd()
        sleep(3)
        self.page.click_xinwen()
        sleep(10)


if __name__ == '__main__':
    unittest.main()
    # suit = unittest.TestSuite()
    # suit.addTest(TestJD('test_jd'))
    # runner = unittest.TextTestRunner()
    # runner.run(suit)

你可能感兴趣的:(UI自动化,selenium,python,软件测试,软件框架)