Python 搭建UI自动化测试框架教程

目录

前言

1、Python库选择

2、分层设计

3、基础类

浏览器

页面操作

4、公共类

获取绝对路径

读取excel用例

读取config配置

核心处理工厂

ddt驱动代码

执行并输出报告

打印Log

发送邮件


 

前言

一个迭代频繁的项目,少不了自动化测试,冒烟与回归全部使用自动化测试来实现,释放我们的人工来测试一些重要的,复杂的工作。节省成本是自动化测试最终目标

Python搭建自动化测试框架是高级测试的标志之一

核心处理工厂是一个骚操作

如果大家都懂了我的核心代码,实现了UI自动化框架后,做UI自动化测试时,时间成本比PO模式要低100倍,人力成本可以用初级测试工程师

PO :PageObject 

为啥要注释PO呢,有人私下给我留言,意思是我这框架没用PageObject,不行,没写好。

不好意思,PO时间和人力成本太高。个人追求高效高速低成本,觉得不配用,嘿嘿。

 

1、Python库选择

          这套框架主要的Python库有 Selenium、unittest、ddt、HTMLTestRunner、win32gui、win32con、openpyxl、configparser、logging、smtplib、os等等

          其中Selenium、unittest、ddt、HTMLTestRunner是框架核心模块,Selenium通过html属性得到元素,进而操作元素的方法属性,unittes单元测试模块与ddt数据驱动结合可以实现用例的收集与执行,HTMLTestRunner输出自动化测试报告。

        win32gui、win32con操作浏览器一些windows的弹出框,比如上传文件的弹出框

        openpyxl库读取excel文件,收集用例

        configparser库读取config配置文件,实现常规流程设置可配

        logging库输出自动化用例执行日志

       smtplib使用email发送测试报告

       os获取一些工程里的绝对路径

          

2、分层设计

目录:

         Python 搭建UI自动化测试框架教程_第1张图片

       分层设计如目录

       所谓分层设计:即是相同属性或类型的代码模块放到同一个目录下,使代码管理,扩展,维护待方便。

       basefactory :存放浏览器操作与网页操作的基础代码库与一些浏览器驱动

       common:存放执行工厂,收集用例,收集路径,读取配置文件的一代码库

       config:存放配置文件

       data:存放用例文件

       excutetest:存放数据驱动用例代码

       library:存放独立三方库,方便自行优化第三方库,引用方便,三方库跟着工程走,不会换环境又要重新下载

       result:有三个子目录,log目录,report目录,screenshot目录

3、基础类

浏览器

 浏览器操作,有开浏览器,关闭浏览器,切换网页,上传附件等等

个人喜好谷歌浏览器,所以IE与火狐暂没兼容,

1、配置浏览器驱动

     首先我们需要下载一个浏览器驱动,下驱动之前先确认下本地浏览器版本

从谷歌浏览器右上角-点击自定义与控制-帮助-关于Google Chrome(G)  找到版本信息,我的是83.0.4103.61

浏览器输入淘宝的谷歌驱动镜像

http://npm.taobao.org/mirrors/chromedriver

找到一个与版本相同或相近的版本驱动

    

选择与电脑系统对应的驱动下载,一般有linux、mac、win三种。

我的是window系统。所以就下载win32版本,放心64位的系统可以用

Python 搭建UI自动化测试框架教程_第2张图片

下载下来的驱动是个zip,解压出一个chromedriver.exe文件

驱动如何配置,可以参照

https://blog.csdn.net/yinshuilan/article/details/78742728

而我个人喜欢直接丢在当前工程的目录下,放在基础目录 ,方便直接用,随时换。

其实建议按上面链接里的来,换环境方便,我这只是简单粗暴。

2、调试驱动是否有用

  在browseroperator.py里写一段代码,

from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')

run,结果是自动打开谷歌进入百度了,安排

Python 搭建UI自动化测试框架教程_第3张图片

 

3、封装浏览器--初始化浏览器

谷歌驱动能用,万事俱备只欠东风,现我们来实现这个东风

代码引用里有from common.getconf import Config要先实现一个Config类,因为我们要的基础类里要用到一个配置项,这也是为了以后做大做强的需要一个东西,先说一下,具体见【读取config配置】

Python 搭建UI自动化测试框架教程_第4张图片

在config 目录下有一个配置文件,配置文件里配置了浏览器类型,只是为了以后能兼容三种浏览器,目前配置了chrome

首先我们新建名为BrowserOperator的类,初始化配置类的对象,获取到浏览器类型的配置项,得到驱动的目录。初始化用到了os

所以我们要import os

class BrowserOperator(object):

    def __init__(self):
        self.conf = Config()
        self.driver_path = os.path.join(BASEFACTORYDIR, 'chromedriver.exe')
        self.driver_type = str(self.conf.get('base', 'browser_type')).lower()

 

然后我们在基础类里实现一个方法def open_url(self, **kwargs): 打开浏览器,方法使用了不定长参数 **kwargs接收传参,所以只能传指定参数或整个字典,

def open_url(self, **kwargs):
    """
    打开网页
    :param url:
    :return: 返回 webdriver
    """
    try:
        url = kwargs['locator']
    except KeyError:
        return False, '没有URL参数'

这一段,kwargs里如果有locator,这是用例用例设计的一个参数,它负责传url与元素定位,后面讲excel读取时会说到的。

可以写成url = kwargs.get('locator'),就不需要try了,但要判断url是不是为None,是None还是要return False, ‘没有参数’,两个返回值是全框架设计的返回形式,用一个布尔值确认我的用例执行的成败,后面返回对象或执行日志。至于我写try,个人代码风格。

    try:
        if self.driver_type == 'chrome':
            #处理chrom弹出的info
            # chrome_options = webdriver.ChromeOptions()
            # #option.add_argument('disable-infobars')
            # chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
            # self.driver = webdriver.Chrome(options=chrome_options, executable_path=self.driver_path)
            self.driver = webdriver.Chrome(executable_path=self.driver_path)
            self.driver.maximize_window()
            self.driver.get(url)
        elif self.driver_type == 'ie':
            print('IE 浏览器')
        else:
            print('火狐浏览器')
    except Exception as e:
        return False, e
    return True, self.driver

上面一段内容,判断是哪个浏览器,然后指定驱动浏览器打开url。

核心代码

self.dirver = webdriver.Chrome(executable_path=self.driver_path)

此时代码会自动帮我们打开一个浏览器,将浏览的句柄赋值给self.driver。这个句柄是操作网页元素的一个把手,不到浏览器关闭,它不会释放。

然后我们就可以用self.driver.get(url)  打开网站了。

外围包了一个try,就是如果浏览器驱动失败了,我们将返回一个Flase和异常,如果成功了,返回一个True 和浏览器 对象driver,

 

这里有人会问,driver已经返回了,还把它搞成self.driver 干啥呢,因为这个类后期的关闭浏览器,上传附件等操作要用到这个对象。

哦对了,你们不要问这个把浏览器最大化的方法了,万能固定写法: self.driver.maximize_window()

写完这个,我们来调试一把,在browseroperator.py文件下面写下两代码,然后运行

wd = BrowserOperator()
wd.open_url(locator='https://www.qq.com')

对的,locator='https://ww.....'    这就是指定传参

run,结果是打开了qq网站,说明代码已经执行成功了

Python 搭建UI自动化测试框架教程_第5张图片

 

然后我们改一下代码,

wd = BrowserOperator()
isOK, deiver = wd.open_url(locator='https://www.qq.com')
time.sleep(5)
deiver.find_elements_by_xpath('//*[@id="sougouTxt"]')[0].send_keys('飞人')
deiver.find_elements_by_xpath('//*[@id="searchBtn"]')[0].click()

结果他开始搜索飞人了,说明第一步打开浏览器返回操作对象很成功。

Python 搭建UI自动化测试框架教程_第6张图片

 

4、关闭浏览器

在类下面实现一个方法def close_browser(self, **kwargs):

def close_browser(self, **kwargs):
    """
    关闭浏览器
    :return:
    """
    time.sleep(1)
    self.driver.quit()
    time.sleep(2)
    return True, '关闭浏览器成功'

里面的的睡眠时间很重要的,不然调试时肉眼看不到最后执行结果与否,浏览器就关掉了。

关键代码self.driver.quit(),这个方法退出浏览器,哈哈哈

wd = BrowserOperator()
isOK, deiver = wd.open_url(locator='https://www.qq.com')
time.sleep(5)
deiver.find_elements_by_xpath('//*[@id="sougouTxt"]')[0].send_keys('飞人')
deiver.find_elements_by_xpath('//*[@id="searchBtn"]')[0].click()
wd.close_browser()

在执行代码后面添加一行wd.close_browser()

run,看着他打开浏览器,搜索飞人,然后关闭浏览器,执行通过。

上传附件,后面写完用例模块时,才能涉及到。网上也有很多上传附件的实现方法。所以就不说了,里面涉及windows的消息机制,很高深,照搬代码即可。

browseroperator.py里所有的代码提供给各位看官,希望多给两个赞,谢谢

import os
import win32gui
import win32con
import time
from selenium import webdriver
from common.getconf import Config
from common.getfiledir import BASEFACTORYDIR
from pywinauto import application



class BrowserOperator(object):

    def __init__(self):
        self.conf = Config()
        self.driver_path = os.path.join(BASEFACTORYDIR, 'chromedriver.exe')
        self.deriver_type = str(self.conf.get('base', 'browser_type')).lower()

    def open_url(self, **kwargs):
        """
        打开网页
        :param url:
        :return: 返回 webdriver
        """
        try:
            url = kwargs['locator']
        except KeyError:
            return False, '没有URL参数'
        try:
            if self.deriver_type == 'chrome':
                #处理chrom弹出的info
                # chrome_options = webdriver.ChromeOptions()
                # #option.add_argument('disable-infobars')
                # chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
                # self.driver = webdriver.Chrome(options=chrome_options, executable_path=self.driver_path)
                self.driver = webdriver.Chrome(executable_path=self.driver_path)
                self.driver.maximize_window()
                self.driver.get(url)
            elif self.deriver_type == 'ie':
                print('IE 浏览器')
            else:
                print('火狐浏览器')
        except Exception as e:
            return False, e
        return True, self.driver




    def close_browser(self, **kwargs):
        """
        关闭浏览器
        :return:
        """
        time.sleep(1)
        self.driver.quit()
        time.sleep(2)
        return True, '关闭浏览器成功'


    def upload_file(self, **kwargs):
        """
        上传文件
        :param kwargs:
        :return:
        """
        try:
            dialog_class = kwargs['type']
            file_dir = kwargs['locator']
            button_name = kwargs['index']
        except KeyError:
            return True, '没传对话框的标记或没传文件路径,'

        if self.deriver_type == "chrome":
            title = "打开"
        elif self.deriver_type == "firefox":
            title = "文件上传"
        elif self.deriver_type == "ie":
            title = "选择要加载的文件"
        else:
            title = ""  # 这里根据其它不同浏览器类型来修改
            # 找元素
            # 一级窗口"#32770","打开"
        dialog = win32gui.FindWindow(dialog_class, title)
        if dialog == 0:
            return False, '传入对话框的class定位器有误'
        # 向下传递
        ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, "ComboBoxEx32", None)  # 二级
        comboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, "ComboBox", None)  # 三级
        # 编辑按钮
        edit = win32gui.FindWindowEx(comboBox, 0, 'Edit', None)  # 四级
        # 打开按钮

        button = win32gui.FindWindowEx(dialog, 0, 'Button', button_name)  # 二级
        if button == 0:
            return False, '按钮text属性传值有误'
        # 输入文件的绝对路径,点击“打开”按钮
        win32gui.SendMessage(edit, win32con.WM_SETTEXT, None, file_dir)  # 发送文件路径
        time.sleep(1)
        win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)  # 点击打开按钮
        return True, '上传文件成功'

页面操作

浏览器的代码先告一段落,接着得实现页面元素的操作类了

1、引用模块,导包

import os
import time
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver import Chrome
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from common.getfiledir import SCREENSHOTDIR

os 用来处理截图保存的路径

time处理等待、隐式、显示等待

NoSuchElementException, TimeoutException  前一个隐式等待的异常,后一个显示等的异常

Chrome用来声明driver是Chrome对象,方便driver联想出来方法

expected_conditions 判断元素的16种方法,显示等待用到

WebDriverWait 显示等待

By 可以By.ID,By.NAME等来用来决定元素定位,显示等待中用

SCREENSHOTDIR   截图路径,从getfiledir模块出来,具体见【获取工程绝对路径】

 

2、初始化类

创建一个类WebdriverOperator类并初始化,因为他需要浏览器driver对象,所以初始化这个对象为私有属性

class WebdriverOperator(object):

    def __init__(self, driver:Chrome):
        self.driver = driver

这个也不好调试,先过

2、实现截图

 先初始化文件路径与文件名称,文件名使用时间戳命名,保存为png

pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png' screent_path = os.path.join(SCREENSHOTDIR, pic_name)

这两行就是实现文件路径的代码

self.driver.get_screenshot_as_file(screent_path)

截屏代码,因为我们是截浏览器的屏,所以使用self.driver对象调用截屏方法,传入路径,它便会自动截屏保存在screent_path文件中,最后返回路径,具休代码如下

def get_screenshot_as_file(self):
    """
    截屏保存
    :return:返回路径
    """
    pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png'
    screent_path = os.path.join(SCREENSHOTDIR, pic_name)
    self.driver.get_screenshot_as_file(screent_path)
    return screent_path

我们来调试一下这个代码

在basefactory目录下面请建一个test.py文件

from basefactory.browseroperator import BrowserOperator
from basefactory.webdriveroperator import WebdriverOperator

bo = BrowserOperator()
isOK, deiver = bo.open_url(locator='https://www.qq.com')
wb = WebdriverOperator(deiver)
result = wb.get_screenshot_as_file()     #调用截图方法,返回路径打印
print(result)

输入这么一段代码。

run,它会打开浏览器,进入qq网站主页,然后打印截图文件目录

Python 搭建UI自动化测试框架教程_第7张图片

找到对应目录 与文件,截图方法调试成功

Python 搭建UI自动化测试框架教程_第8张图片

 

3、隐式等待

gotosleep(),这个方法不说了,死等。

隐式等待,等待页面元素加载成功便完成等待任务,只需要在用例初始化浏览器之处调用一次,全局可用,其实他只是一行代码,但为了符合框架统一,也封装一下,代码如下:

def web_implicitly_wait(self, **kwargs):
    """
    隐式等待
    :return:
    type  存时间
    """
    try:
        s = kwargs['time']
    except KeyError:
        s = 10
    try:
        self.driver.implicitly_wait(s)
    except NoSuchElementException:
        return False, '隐式等待设置失败'
    return True, '隐式等待设置成功'

其实只有一行代码 self.driver.implicitly_wait(s) 有用的,里面的s是用例传过来的,调试时,只需要传指定time传一个数字,例:time=5,每次页面刷新,程序将等待页面元素加载5秒,5秒后,不管加载成功与否都执行下一行代码,如果2秒有加载完,那么不必等5秒,直接执行下一行代码

切记,隐式等待只需要初始化浏览器调用一次,后面的代码都会隐式等待。

因为我本地网速太好,打开了两个网络视频播放器去调试网页都没法使网页长时间加载,所以只贴上调试代码,结果自己看吧。

在test.py里更新调试代码

from basefactory.browseroperator import BrowserOperator
from basefactory.webdriveroperator import WebdriverOperator

bo = BrowserOperator()
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
wb = WebdriverOperator(deiver)
isOK, result = wb.web_implicitly_wait()    #设置隐式等待,打印隐式等待的结果
print(result)
deiver.find_elements_by_xpath('//*[@id="kw"]')[0].send_keys('飞人')
deiver.find_elements_by_xpath('//*[@id="su"]')[0].click()
deiver.find_elements_by_xpath('//*[@id="rs"]/div')

4、显示等待

隐式等待在页面切换后,加载成功但没展示出来,disable,hide等场景时,也能等待成功。所以不能满足需求。我们需要更强大的等待元素的方法,显示等待,代码如下:

def web_element_wait(self, **kwargs):
    """
    等待元素可见
    :return:
    """
    try:
        type = kwargs['type']
        locator = kwargs['locator']
    except KeyError:
        return False, '未传需要等待元素的定位参数'
    try:
        s = kwargs['time']
        if s is None:
            s = 30
    except KeyError:
        s = 30

    try:
        if type == 'id':
            WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))
        elif type == 'name':
            WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.NAME, locator)))
        elif type == 'class':
            WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CLASS_NAME, locator)))
        elif type == 'xpath':
            WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.XPATH, locator)))
        elif type == 'css':
            WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CSS_SELECTOR, locator)))
        else:
            return False, '不能识别元素类型[' + type + ']'
    except TimeoutException:
        screenshot_path = self.get_screenshot_as_file()
        return False, '元素[' + locator + ']等待出现失败,已截图[' + screenshot_path + '].'
    return True, '元素[' + locator + ']等待出现成功'

这两行

type = kwargs['type']

locator = kwargs['locator']

type是用例设计里的locator定位器的类型,有id,name,xpath等主要定位类型,locator定位参数

所以我们调式时,就传两个参数,一个type=''  一个locator=‘’  ,例type='xpath', locator='//*[@id="kw"]'

s = kwargs['time'] 哦,还有这个,传入时间,如果没传,默认等待30秒

核心代码

WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))

每0.5秒轮寻一次属性id为locator的元素是否可见,可见就跳出等待,返回等等元素出现成功;超过s秒便等待失败,返回元素等待出现失败,截图等信息。

调试一把,在test.py里修改代码如下

from basefactory.browseroperator import BrowserOperator
from basefactory.webdriveroperator import WebdriverOperator

bo = BrowserOperator()
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
wb = WebdriverOperator(deiver)
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
print(result)

Python 搭建UI自动化测试框架教程_第9张图片

设置0.001秒,也能成功,网速太快了,失败的例子等各位来重现了,不想去使用高延迟工具了。

 

5、输入操作

输入,元素的send_key() 方法,所以在WebdriverOperator类里实现一个element_input方法,代码如下:

def element_input(self, **kwargs):
    """

    :param kwargs:
    :return:
    """
    try:
        type = kwargs['type']
        locator = kwargs['locator']
        text = str(kwargs['input'])
    except KeyError:
        return False, '缺少传参'
    try:
        index = kwargs['index']
    except KeyError:
        index = 0       
    try:
        if type == 'id':
            elem = self.driver.find_elements_by_id(locator)[index]
        elif type == 'name':
            elem = self.driver.find_elements_by_name(locator)[index]
        elif type == 'class':
            elem = self.driver.find_elements_by_class_name(locator)[index]
        elif type == 'xpath':
            elem = self.driver.find_elements_by_xpath(locator)[index]
        elif type == 'css':
            elem = self.driver.find_elements_by_css_selector(locator)[index]
        else:
            return False, '不能识别元素类型:[' + type + ']'
    except Exception as e:
        screenshot_path = self.get_screenshot_as_file()
        return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
    try:
        elem.send_keys(text)
    except Exception:
        screenshot_path = self.get_screenshot_as_file()
        return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
    return True, '元素['+ locator +']输入['+ text +']成功'

 

代码

type = kwargs['type']
locator = kwargs['locator']
text = str(kwargs['input'])
index = kwargs['index']

type是用例设计里的locator定位器的类型,有id,name,xpath等主要类型,locator定位参数,input输入的内容,index是元素的在List里下标,一般页面相同的元素会有很多,我们find元素是得到一个list的结果,需要通过下标来取到自己想到的那个元素。不传默认为第0个。

所以我们调式时,就传四个参数,一个type=''  一个locator=‘’input=''   index=  ,例type='xpath', locator='//*[@id="kw"]', input='飞人',index=0

这一部分代码

try:
    if type == 'id':
        elem = self.driver.find_elements_by_id(locator)[index]
    elif type == 'name':
        elem = self.driver.find_elements_by_name(locator)[index]
    elif type == 'class':
        elem = self.driver.find_elements_by_class_name(locator)[index]
    elif type == 'xpath':
        elem = self.driver.find_elements_by_xpath(locator)[index]
    elif type == 'css':
        elem = self.driver.find_elements_by_css_selector(locator)[index]
    else:
        return False, '不能识别元素类型:[' + type + ']'
except Exception as e:
    screenshot_path = self.get_screenshot_as_file()
    return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'

是用来查找页面元素是否存在的,如果存在,将元素找到存在elem变量,继续执行,如果失败,截图,返回False与失败日志

通过type判断,我们使用哪种元素定位方法,这些是基础,不懂的朋友可百度一下寻找元素定位的方法。

输入input的代码:

try:
    elem.send_keys(text)
except Exception:
    screenshot_path = self.get_screenshot_as_file()
    return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
return True, '元素['+ locator +']输入['+ text +']成功'

输入成功,返回True与成功日志,失败,截图并返回False与失败日志

然后我们来调试一把,在test.py文件里更新代码如下:

bo = BrowserOperator()
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
wb = WebdriverOperator(deiver)
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
print(result)
isOK, result = wb.element_input(type='xpath', locator='//*[@id="kw"]', input='飞人', index=0)    #元素输入方法
print(result)

run,结果打开百度,在编搜索框输入 ‘飞人’,因为还没做点击,先不click百度一下了

Python 搭建UI自动化测试框架教程_第10张图片

再看打印的运行日志

Python 搭建UI自动化测试框架教程_第11张图片

运行成功。

 

6、优化输入方法

输入方法中的find页面元素是否存在的代码,很多元素操作都需要的。所以再把它分离出来写一个单独的方法find_element,代码如下:

def find_element(self, type, locator, index=None):
    """
    定位元素
    :param type:
    :param itor:
    :param index:
    :return:
    """
    time.sleep(1)
    #isinstance(self.driver, selenium.webdriver.Chrome.)
    if index is None:
        index = 0
    type = str.lower(type)
    try:
        if type == 'id':
            elem = self.driver.find_elements_by_id(locator)[index]
        elif type == 'name':
            elem = self.driver.find_elements_by_name(locator)[index]
        elif type == 'class':
            elem = self.driver.find_elements_by_class_name(locator)[index]
        elif type == 'xpath':
            elem = self.driver.find_elements_by_xpath(locator)[index]
        elif type == 'css':
            elem = self.driver.find_elements_by_css_selector(locator)[index]
        else:
            return False, '不能识别元素类型:[' + type + ']'
    except Exception as e:
        screenshot_path = self.get_screenshot_as_file()
        return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
    return True, elem

然后我们的输入方法代码修改为:

def element_input(self, **kwargs):
    """

    :param kwargs:
    :return:
    """
    try:
        type = kwargs['type']
        locator = kwargs['locator']
        text = str(kwargs['input'])
    except KeyError:
        return False, '缺少传参'
    try:
        index = kwargs['index']
    except KeyError:
        index = 0
    isOK, result = self.find_element(type, locator, index)
    if not isOK:  # 元素没找到,返回失败结果
        return isOK, result
    elem = result
    try:
        elem.send_keys(text)
    except Exception:
        screenshot_path = self.get_screenshot_as_file()
        return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
    return True, '元素['+ locator +']输入['+ text +']成功'

继续使用用test.py中的代码调试,运行结果与第5点输入操作的运行结果一样,就优化成功了。

 

7、点击操作

点击操作,元素的click()方法,没有什么好说的,因为他的代码与第5点输入操作一样,只是少了个input参数。所以直接贴代码

def element_click(self, **kwargs):
    """

    :param kwargs:
    :return:
    """
    try:
        type = kwargs['type']
        locator = kwargs['locator']

    except KeyError:
        return False, '缺少传参'
    try:
        index = kwargs['index']
    except KeyError:
        index = 0
    isOK, result = self.find_element(type, locator, index)
    if not isOK:      #元素没找到,返回失败结果
        return isOK, result
    elem = result
    try:
        elem.click()
    except Exception:
        screenshot_path = self.get_screenshot_as_file()
        return False, '元素['+ locator +']点击失败,已截图[' + screenshot_path + '].'
    return True, '元素['+ locator +']点击成功'

 

调试,在test.py里更新代码

bo = BrowserOperator()
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
wb = WebdriverOperator(deiver)
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
print(result)
isOK, result = wb.element_input(type='xpath', locator='//*[@id="kw"]', input='飞人', index=0)
print(result)
isOK, result = wb.element_click(type='xpath', locator='//*[@id="su"]', index=0)    #元素点击方法
print(result)

run,运行结果,一切正常

Python 搭建UI自动化测试框架教程_第12张图片

Python 搭建UI自动化测试框架教程_第13张图片

 

好,基本元素操作的代码解说完了,全部代码如下,不想自己写的可以copy。

还缺少执行JS与一些偏门控件的方法,这些给各位去自个儿写。

import os
import time

from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver import Chrome
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from common.getfiledir import SCREENSHOTDIR




class WebdriverOperator(object):

    def __init__(self, driver:Chrome):
        self.driver = driver

    def get_screenshot_as_file(self):
        """
        截屏保存
        :return:返回路径
        """
        pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png'
        screent_path = os.path.join(SCREENSHOTDIR, pic_name)
        self.driver.get_screenshot_as_file(screent_path)
        return screent_path

    def gotosleep(self, **kwargs):
        time.sleep(3)
        return True, '等待成功'


    def web_implicitly_wait(self, **kwargs):
        """
        隐式等待
        :return:
        type  存时间
        """
        try:
            s = kwargs['time']
        except KeyError:
            s = 10
        try:
            self.driver.implicitly_wait(s)
        except NoSuchElementException:
            return False, '隐式等待 页面元素未加载完成'
        return True, '隐式等待 元素加载完成'


    def web_element_wait(self, **kwargs):
        """
        等待元素可见
        :return:
        """
        try:
            type = kwargs['type']
            locator = kwargs['locator']
        except KeyError:
            return False, '未传需要等待元素的定位参数'
        try:
            s = kwargs['time']
            if s is None:
                s = 30
        except KeyError:
            s = 30

        try:
            if type == 'id':
                WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))
            elif type == 'name':
                WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.NAME, locator)))
            elif type == 'class':
                WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CLASS_NAME, locator)))
            elif type == 'xpath':
                WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.XPATH, locator)))
            elif type == 'css':
                WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CSS_SELECTOR, locator)))
            else:
                return False, '不能识别元素类型[' + type + ']'
        except TimeoutException:
            screenshot_path = self.get_screenshot_as_file()
            return False, '元素[' + locator + ']等待出现失败,已截图[' + screenshot_path + '].'
        return True, '元素[' + locator + ']等待出现成功'


    def find_element(self, type, locator, index=None):
        """
        定位元素
        :param type:
        :param itor:
        :param index:
        :return:
        """
        time.sleep(1)
        #isinstance(self.driver, selenium.webdriver.Chrome.)
        if index is None:
            index = 0
        type = str.lower(type)
        try:
            if type == 'id':
                elem = self.driver.find_elements_by_id(locator)[index]
            elif type == 'name':
                elem = self.driver.find_elements_by_name(locator)[index]
            elif type == 'class':
                elem = self.driver.find_elements_by_class_name(locator)[index]
            elif type == 'xpath':
                elem = self.driver.find_elements_by_xpath(locator)[index]
            elif type == 'css':
                elem = self.driver.find_elements_by_css_selector(locator)[index]
            else:
                return False, '不能识别元素类型:[' + type + ']'
        except Exception as e:
            screenshot_path = self.get_screenshot_as_file()
            return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
        return True, elem


    def element_click(self, **kwargs):
        """

        :param kwargs:
        :return:
        """
        try:
            type = kwargs['type']
            locator = kwargs['locator']

        except KeyError:
            return False, '缺少传参'
        try:
            index = kwargs['index']
        except KeyError:
            index = 0
        isOK, result = self.find_element(type, locator, index)
        if not isOK:      #元素没找到,返回失败结果
            return isOK, result
        elem = result
        try:
            elem.click()
        except Exception:
            screenshot_path = self.get_screenshot_as_file()
            return False, '元素['+ locator +']点击失败,已截图[' + screenshot_path + '].'
        return True, '元素['+ locator +']点击成功'


    def element_input(self, **kwargs):
        """

        :param kwargs:
        :return:
        """
        try:
            type = kwargs['type']
            locator = kwargs['locator']
            text = str(kwargs['input'])
        except KeyError:
            return False, '缺少传参'
        try:
            index = kwargs['index']
        except KeyError:
            index = 0
        isOK, result = self.find_element(type, locator, index)
        if not isOK:  # 元素没找到,返回失败结果
            return isOK, result
        elem = result
        try:
            elem.send_keys(text)
        except Exception:
            screenshot_path = self.get_screenshot_as_file()
            return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
        return True, '元素['+ locator +']输入['+ text +']成功'





 

4、公共类

获取框架项目目录的绝对路径

先在common目录下新增一个,getfiledir.py的文件

Python 搭建UI自动化测试框架教程_第14张图片

这一段没什么多说的,就是获取本框架各目录的绝对路径

import os

dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

DATADIR = os.path.join(dir, 'data')

CONFDIR = os.path.join(dir, 'config')

BASEFACTORYDIR = os.path.join(dir, 'basefactory')

RESULTDIR = os.path.join(dir, 'result')

LOGDIR = os.path.join(RESULTDIR, 'log')

REPORTDIR = os.path.join(RESULTDIR, 'report')

SCREENSHOTDIR = os.path.join(RESULTDIR, 'screenshot')

CASEDIR = os.path.join(dir, 'excutetest')

#print(SCREENSHOTDIR)


 

我们可以在每行代码下面写一个print()语名来打印目录,调试一把,结果如下,所以目录已成功得到

Python 搭建UI自动化测试框架教程_第15张图片

 

读取excel用例

先在common目录下新增一个getcase.py文件

Python 搭建UI自动化测试框架教程_第16张图片

1、用例设计与用例格式

首先,我们设计测试用例,格式如下:

Python 搭建UI自动化测试框架教程_第17张图片

保存为一个xlsx格式的excel文档,放在data目录下,这个我们的数据目录,也是存放测试用例的目录,数据驱动的数据,也就是这个目录。

读取用例在框架里存放的格式如下,

这个是基础,看得懂就行,写好了代码一万年不要动,似懂非懂利害了,需要去学习巩固一下Python dict,其他语言的json串

[   
    {   整个xlsx文件的用例
        "a": [    整个sheet页的用例
            {用例
                "A": 2     
            },
            {
                "A": 3
            }
        ]
    },
    {
        "b": [
            {
                "A": 2
            },
            {
                "A": 3
            }
        ]
    }
]

 

2、引用导包,初始化case.xlsx目录

import openpyxl
import os
from common.getfiledir import DATADIR   #导入data目录
file = os.path.join(DATADIR, 'case.xlsx')     #得到case文件的路径 

导入了openpyxl,它只能操作xlsx,支持读写,本人只喜欢读,不喜欢写。

os,初始化用例目录有用到。写死用例文件,如果想灵活多变,可以把用例名字变成传参或变成配置

用例目录

3、初始化ReadCase

class ReadCase(object):
    def __init__(self):
        self.sw = openpyxl.load_workbook(file)
        print(self.sw)

初始化类,顺便读取一下用例文件

调试

在文件里输入,

xlsx = ReadCase()

Python 搭建UI自动化测试框架教程_第18张图片

run,打印出这么一串人类不认识,那么就成功了,他打印了一个内存对象,保存了整个case文件

 

4、读取单个sheet页用例

实现一个方法readcase, 代码如下

def readcase(self, sh):
    """
    组合sheet页的数据
    :param sh:
    :return: list,返回组合数据
    """
    if sh is None:
        return False, '用例页参数未传'
    datas = list(sh.rows)
    if datas == []:
        return False, '用例[' + sh.title + ']里面是空的'
    title = [i.value for i in datas[0]]
    rows = []
    sh_dict = {}
    for i in datas[1:]:
        data = [v.value for v in i]
        row = dict(zip(title, data))
        try:
            if str(row['id'])[0] is not '#':
                row['sheet'] = sh.title
                rows.append(row)
        except KeyError:
            raise e
            rows.append(row)
        sh_dict[sh.title] = rows
    return True, sh_dict

参数传入一个sheet页对象,

然后判空,

if sh is None:
    return False, '用例页参数未传'

通过列表保存sheet的每一行,判空

datas = list(sh.rows)
if datas == []:
    return False, '用例[' + sh.title + ']里面是空的'

得到第一行为title,为啥呢,看excel文件的格式

title = [i.value for i in datas[0]]

用一个循环得到每一行的用例与title结合成一个字典json串,返回

for i in datas[1:]:
    data = [v.value for v in i]
    row = dict(zip(title, data))
    try:
        if str(row['id'])[0] is not '#':
            row['sheet'] = sh.title
            rows.append(row)
    except KeyError:
        raise e
        rows.append(row)
    sh_dict[sh.title] = rows
return True, sh_dict

好了,来调试一发,在下面输入代码:

xlsx = ReadCase()
for sh in xlsx.sw:
    isOK, result = xlsx.readcase(sh)
    print(result)

先看我们用例文件

Python 搭建UI自动化测试框架教程_第19张图片

run,运行结果如下

格式化后的结果,完全是我们需要的样子,成功读取用例。

{
    'baidu': [{
        'id': 1,
        'result': None,
        'keyword': '打开网页',
        'type': 'url',
        'locator': 'https://www.baidu.com',
        'index': None,
        'input': None,
        'check': None,
        'time': None,
        'sheet': 'baidu'
    }, {
        'id': 4,
        'result': None,
        'keyword': '等待元素可见',
        'type': 'xpath',
        'locator': '//*[@id="kjw"]',
        'index': None,
        'input': None,
        'check': None,
        'time': 3,
        'sheet': 'baidu'
    }, {
        'id': 2,
        'result': None,
        'keyword': '输入',
        'type': 'xpath',
        'locator': '//*[@id="kw"]',
        'index': None,
        'input': '飞人',
        'check': None,
        'time': None,
        'sheet': 'baidu'
    }]
}

 

5、读取所有用例

因为我们设计的readcase方法,只需要传入一个sheet页,所以实现一个readallcase的方法,遍历所有sheet页,传给readcase读取所有用例,代码如下:

def readallcase(self):
    """
    取所有sheet页
    :return:list,返回sheet页里的数据
    """
    sheet_list = []
    for sh in self.sw:     #遍历sheet,
        if 'common' != sh.title.split('_')[0] and 'common' != sh.title.split('-')[0] and sh.title[0] is not '#' :   #判断是否可用的用例
            isOK, result = self.readcase(sh)     #传给readcase取用例
            if isOK:
                sheet_list.append(result)  #得到结果放到列表,又给用例套了一层sheet页的框
    if sheet_list is None:
        return False, '用例集是空的,请检查用例'
    return True, sheet_list

这里面有一个判断条件,是公共用例,common开头的用例不读取,注释用例以#号开头的用例,不读取

调试代码

xlsx = ReadCase()
isOK, result = xlsx.readallcase()
print(result)

运行结果,请看比第4点运行的结果多了一个中括号,表示他已经可以读取所有的用例了

 

6、读取公共用例

实现一个get_common_case方法,方法很简单,查询是否有传参sheetname的sheet页存在,有的话就读取sheet页的用例,返回,代码如下。

def get_common_case(self, case_name):
    """
    得到公共用例
    :param case_name:
    :return:
    """
    try:
        sh = self.sw.get_sheet_by_name(case_name)
    except KeyError:
        return False, '未找到公共用例[' + case_name + '],请检查用例'
    except DeprecationWarning:
        pass
    return self.readcase(sh)

要结合核心模块的调用

调试代码如下

xlsx = ReadCase()
isOK, result = xlsx.get_common_case('baidu')
print(result)

结果如第5点结果一样

 

读取用例的全部代码如下:

import openpyxl
import os
from common.getfiledir import DATADIR
file = os.path.join(DATADIR, 'case.xlsx')

class ReadCase(object):
    def __init__(self):
        self.sw = openpyxl.load_workbook(file)
        print(self.sw)


    def openxlsx(self, file):
        """
        打开文件
        :param dir:
        :return:
        """
        # self.sw = openpyxl.load_workbook(file)

    #[{"a": [{"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}]},{"a": [5, 3, 2]}, {"a": [10, 4, 6]}]
    def readallcase(self):
        """
        取所有sheet页
        :return:list,返回sheet页里的数据
        """
        sheet_list = []
        for sh in self.sw:
            if 'common' != sh.title.split('_')[0] and 'common' != sh.title.split('-')[0] and sh.title[0] is not '#' :
                isOK, result = self.readcase(sh)
                if isOK:
                    sheet_list.append(result)
        if sheet_list is None:
            return False, '用例集是空的,请检查用例'
        return True, sheet_list


    def readcase(self, sh):
        """
        组合sheet页的数据
        :param sh:
        :return: list,返回组合数据
        """
        if sh is None:
            print('sheet页为空')
        datas = list(sh.rows)
        if datas == []:
            return False, '用例[' + sh.title + ']里面是空的'
        title = [i.value for i in datas[0]]
        rows = []
        sh_dict = {}
        for i in datas[1:]:
            data = [v.value for v in i]
            row = dict(zip(title, data))
            try:
                if str(row['id'])[0] is not '#':
                    row['sheet'] = sh.title
                    rows.append(row)
            except KeyError:
                raise e
                rows.append(row)
            sh_dict[sh.title] = rows
        return True, sh_dict


    def get_common_case(self, case_name):
        """
        得到公共用例
        :param case_name:
        :return:
        """
        try:
            sh = self.sw.get_sheet_by_name(case_name)
        except KeyError:
            return False, '未找到公共用例[' + case_name + '],请检查用例'
        except DeprecationWarning:
            pass
        return self.readcase(sh)



读取config配置

目录:

Python 搭建UI自动化测试框架教程_第20张图片

Config类没有什么好解析的,暂时是为了封装而封装

具体代码如下:

import os
from configparser import ConfigParser
from common.getfiledir import CONFDIR


class Config(ConfigParser):

    def __init__(self):
        self.conf_name = os.path.join(CONFDIR, 'base.ini')
        super().__init__()
        super().read(self.conf_name, encoding='utf-8')


    def save_data(self, section, option, value):
        super().set(section=section, option=option, value=value)
        super().write(fp=open(self.conf_name, 'w'))

配置文件分析一下

目录

内容

[base]
browser_type = chrome
# browser_type = IE
# browser_type = firefox

[LOG]
level = INFO

[email]
host = smtp.qq.com
port = 465
user = [email protected]      
pwd = *********          
from_addr = [email protected] 
to_addr = [email protected] 
[Function]               
打开网页 = open_url
关闭浏览器 = close_browser
对话框上传文件 = upload_file
点击 = element_click
输入 = element_input
截图 = get_screenshot_as_file
寻找元素 = find_element
隐式等待 = web_implicitly_wait
等待元素可见 = web_element_wait
等待 = gotosleep
input上传文件 = upload_input_file
模拟按键上传文件 = upload_endkey_file
切换页面 = toggle_page
执行JS = execute_js

1、base配置,框架中的基础配置

目前只有浏览器类型

2、LOG配置

配置LOG的输出等级,为INFO,日志模块会讲到

3、email配置

email的一些参数配置,smtp服务器与端口,smtp账密,收发地址等

4、Function配置

两个基础类操作行方法 与 用例参数里的keyword的映射关系,核心处理工厂会用到

 

核心处理工厂

核心处理工厂为啥核心呢?

各位自动化测试大佬,应该知道PO模型吧,一些常用公共页面,封装到一个类里。之后哪里要用到,哪里就初始化这个类来调用各页面的代码执行。

但每个项目都有这么多页面,每更新一个项目就要写海量代码,这也是UI自动化不如接口自动化的地方,难维护,成本高,

但我这个核心代码,完全不需要用PO模式了,只需要在excel里面写公共用例,即可替代PO模式。

可以说,如果大家都懂了我这段核心代码,实现了UI自动化框架后,做UI自动化时,时间成本就比PO模式要低100倍,人力成本可以用初级测试工程师

上面只是说明核心是重点,但真正核心还是因为他是全框架的总枢纽,后面会细述说来

 

 

目录

Python 搭建UI自动化测试框架教程_第21张图片

1、引用模块,导包

from common.getconf import Config
from common.getcase import ReadCase
from basefactory.browseroperator import BrowserOperator
from basefactory.webdriveroperator import WebdriverOperator

导入了Config,配置文件读取

导入了ReadCase,来初始化用例

导入了BrowserOperator,WebdriverOperator,初始化这个两个类,执行用例

 

2、初始化工厂类

class Factory(object):

    def __init__(self):
        self.con = Config()
        self.con_fun = dict(self.con.items('Function'))
        """
        浏览器操作对象
        """
        self.browser_opr = BrowserOperator()
        """
        网页操作对象
        """
        self.webdriver_opr = None

 

将配置项Function初始化进来

self.con = Config()
self.con_fun = dict(self.con.items('Function'))

再初始化一下两个基础类对象,因为WebdriverOperator类要在打开URL后才能初始化,先初始化为None

self.browser_opr = BrowserOperator()

self.webdriver_opr = None

然后我们将初始化webdriver_opr的代码放在一个函数里面,方便后面执行方法里面调用,里面强制转换一下,不然很难联想其对应的方法

def init_webdriver_opr(self, driver):
    self.webdriver_opr = WebdriverOperator(driver)

下面我们先说如何实现代替PO模型的用例初始化

3、初始化执行用例

初始化执行用例 def init_execute_case(self): 里面的内容:

def init_execute_case(self):
    print("----------初始化用例----------")
    xlsx = ReadCase()
    isOK, result = xlsx.readallcase()
    if not isOK:
        print(result)
        print("----------结束执行----------")
        exit()
    all_cases = result
    excu_cases = []
    for cases_dict in all_cases:
        for key, cases in cases_dict.items():
            isOK, result = self.init_common_case(cases)
            if isOK:
                cases_dict[key] = result
            else:
                cases_dict[key] = cases
            excu_cases.append(cases_dict)
            print("----------初始化用例完成----------")
    return excu_cases

一、读取了excel里所有可执行用例

二、调用用初始化公共用例

4、初始化公共用例

#初始化公共用例 def init_common_case(self, cases): 里面的内容:

def init_common_case(self, cases):
    """
    :param kwargs:
    :return:
    """
    cases_len = len(cases)
    index = 0
    for case in cases:
        if case['keyword'] == '调用用例':
            xlsx = ReadCase()
            try:
                case_name = case['locator']
            except KeyError:
                return False, '调用用例没提供用例名,请检查用例'
            isOK, result = xlsx.get_common_case(case_name)
            if isOK and type([]) == type(result):
                isOK, result_1 = self.init_common_case(result)     #递归检查公共用例里是否存在调用用例
            elif not isOK:
                return isOK, result
            list_rows = result[case_name]
            cases[index: index+1] = list_rows    #将公共用例插入到执行用例中去
        index += 1
    if cases_len == index:
        return False, ''
    return True, cases

一、判断是否有‘调用用例’命令,有则取公共用例合并成可执行用例

二、递归取公共用例里是否有‘调用用例’命令,有则继续取公共合并成可执行用例

注意:公共用例不能调用自已,递归死循环

调试这两个方法一起

首先人们得在用例里面写上一个公共用例

Python 搭建UI自动化测试框架教程_第22张图片

再在baidu用例里写一行,调用用例

Python 搭建UI自动化测试框架教程_第23张图片

然后你们看看,这是不是就是PO模型,只需要在excel里面写写就行,完全不需要代码了

我们来调试一下,大工厂类里写下调试代码

fac = Factory()
isOK, result = fac.init_execute_case()
print(result)

运行结果如下,我们初始化用例后,会把调用用例里被调用例的用例插入到执行用例中来,全部去执行。

 

下面来讲执行部分的

 

5、获取执行方法

获取两个基础WebdriverOperator、BrowserOperator的方法,具体代码如下:

def get_base_function(self, function_name):
    try:
        function = getattr(self.browser_opr, function_name)
    except Exception:
        try:
            function = getattr(self.webdriver_opr, function_name)
        except Exception:
            return False, '未找到注册方法[' + function_name + ']所对应的执行函数,请检查配置文件'
    return True, function

传入方法名称function_name,通过getattr得到基础类的方法,成功得到方法,返回True,function,没有得到,返回False,日志

 

6、方法执行

实现一个方法,execute_keyword,统一入口调用两个基础类的操作

先放代码,再讲原理,代码如下:

def execute_keyword(self, **kwargs):
    """
    工厂函数,用例执行方法的入口
    :param kwargs:
    :return:
    """
    try:
        keyword = kwargs['keyword']
        if keyword is None:
            return False, '没有keyword,请检查用例'
    except KeyError:
        return False, '没有keyword,请检查用例'

    _isbrowser = False

    try:
        function_name = self.con_fun[keyword]
    except KeyError:
        return False, '方法Key['+ keyword +']未注册,请检查用例'

    #获取基础类方法
    isOK, result = self.get_base_function(function_name)
    if isOK:
        function = result
    else:
        return isOK, result

    #执行基础方法,如打网点页、点击、定位、隐式等待 等
    isOK, result = function(**kwargs)

    #如果是打开网页,是浏览器初始化,需要将返回值传递给另一个基础类
    if '打开网页' == keyword and isOK:
        url = kwargs['locator']
        self.init_webdriver_opr(result)
        return isOK, '网页[' + url + ']打开成功'
    return isOK, result

原理:先得到用例里的keyword,然后获取到两个基础类里方法,再传入**kwargs调用,执行操作

取到keyword关键字,回顾上面用例excel里的有一个keyword的字段,传入进来,先取这个字段

try:
    keyword = kwargs['keyword']
    if keyword is None:
        return False, '没有keyword,请检查用例'
except KeyError:
    return False, '没有keyword,请检查用例'

在self.con_fun键值对里取到keyword对应的方法名,具体方法可见配置文件那一节

try:
    function_name = self.con_fun[keyword]
except KeyError:
    return False, '方法Key['+ keyword +']未注册,请检查用例'

通过get_base_function得到基础类的方法

isOK, result = self.get_base_function(function_name)
if isOK:
    function = result
else:
    return isOK, result

 

执行方法,返回结果

#执行基础方法,如打网点页、点击、定位、隐式等待 等
isOK, result = function(**kwargs)

 

当然,里面有一个特别的,如果方法是打开浏览器,这时就要初始化一下self.webdriver

if '打开网页' == keyword and isOK:
    url = kwargs['locator']
    self.init_webdriver_opr(result)
    return isOK, '网页[' + url + ']打开成功'


最后我们返回执行结果
return isOK, result

 

调试,我们在common里面新增一个test.py文件,在里面输入调试代码

导入Factory,初始化一个工厂对象fac,用fac,调用execute_keyword()方法来执行所有网页操作

from common.factory import Factory


fac = Factory()

isOK, result = fac.execute_keyword(keyword='打开网页', locator='http://www.baidu.com')
print(result)
isOK, result = fac.execute_keyword(keyword='隐式等待', time='30')
print(result)
isOK, result = fac.execute_keyword(keyword='输入', type='xpath', locator='//*[@id="kw"]',input='飞人乔丹')
print(result)
idOK, result = fac.execute_keyword(keyword='点击', type='xpath', locator='//*[@id="su"]')
print(result)

run,运行结果:

Python 搭建UI自动化测试框架教程_第24张图片

Python 搭建UI自动化测试框架教程_第25张图片

再联合读取excel用例来一起调试

因为

fac.execute_keyword(keyword='点击', type='xpath', locator='//*[@id="su"]')

这样传参其实就等于传了一个字典,所以我们可以将用例里面字典直接传与给execute_keyword()

首先我们整理下excel里的用例

公共用例 common-bai里面是在一个id=kw的输入框中输入‘路飞’

Python 搭建UI自动化测试框架教程_第26张图片

可执行用例baidu,是打开百度网站,输入飞人,然后调用公共用例,再点击搜索的一个模拟搜索的用例

Python 搭建UI自动化测试框架教程_第27张图片

我们来写一下调试代码 ,在test.py里面修改代码

先初始化用例init_exceute_case()

然后解析用例,

for acases in result:    #遍历外层list

        for key, cases in acases.items():    #遍历中间的dict

                  for case in cases:      #遍历将case取出

用例的层级关系在下面,用上面三行代码解析出case

[   
    {   整个xlsx文件的用例
        "a": [    整个sheet页的用例
            {用例
                "A": 2     
            },
            {
                "A": 3
            }
        ]
    },
    {
        "b": [
            {
                "A": 2
            },
            {
                "A": 3
            }
        ]
    }
]

解析完了

调用execute_keyword(**case)

from common.factory import Factory


fac = Factory()

isOK, result = fac.init_execute_case()

for acases in result:
    for key, cases in acases.items():
        for case in cases:
            isOK, result = fac.execute_keyword(**case)
            print(result)

run,运行结果如下

Python 搭建UI自动化测试框架教程_第28张图片

Python 搭建UI自动化测试框架教程_第29张图片

看,在执行用例里面的输入【飞人】成功,然后调用公共用例里面的,执行输入【路飞】成功

最后又回到执行用例里的点击su百度一下成功

核心代码的讲述解束

 

核心部分全部代码如下:

from common.getconf import Config
from common.getcase import ReadCase
from basefactory.browseroperator import BrowserOperator
from basefactory.webdriveroperator import WebdriverOperator


class Factory(object):

    def __init__(self):
        self.con = Config()
        self.con_fun = dict(self.con.items('Function'))


        """
        浏览器操作对象
        """
        self.browser_opr = BrowserOperator()
        """
        网页操作对象
        """
        self.webdriver_opr = None


    def init_webdriver_opr(self, driver):
        self.webdriver_opr = WebdriverOperator(driver)


    def get_base_function(self, function_name):
        try:
            function = getattr(self.browser_opr, function_name)
        except Exception:
            try:
                function = getattr(self.webdriver_opr, function_name)
            except Exception:
                return False, '未找到注册方法[' + function_name + ']所对应的执行函数,请检查配置文件'
        return True, function

    def execute_keyword(self, **kwargs):
        """
        工厂函数,用例执行方法的入口
        :param kwargs:
        :return:
        """
        try:
            keyword = kwargs['keyword']
            if keyword is None:
                return False, '没有keyword,请检查用例'
        except KeyError:
            return False, '没有keyword,请检查用例'

        _isbrowser = False

        try:
            function_name = self.con_fun[keyword]
        except KeyError:
            return False, '方法Key['+ keyword +']未注册,请检查用例'

        #获取基础类方法
        isOK, result = self.get_base_function(function_name)
        if isOK:
            function = result
        else:
            return isOK, result

        #执行基础方法,如打网点页、点击、定位、隐式等待 等
        isOK, result = function(**kwargs)

        #如果是打开网页,是浏览器初始化,需要将返回值传递给另一个基础类
        if '打开网页' == keyword and isOK:
            url = kwargs['locator']
            self.init_webdriver_opr(result)
            return isOK, '网页[' + url + ']打开成功'
        return isOK, result


    def init_common_case(self, cases):
        """
        :param kwargs:
        :return:
        """
        cases_len = len(cases)
        index = 0
        for case in cases:
            if case['keyword'] == '调用用例':
                xlsx = ReadCase()
                try:
                    case_name = case['locator']
                except KeyError:
                    return False, '调用用例没提供用例名,请检查用例'
                isOK, result = xlsx.get_common_case(case_name)
                if isOK and type([]) == type(result):
                    isOK, result_1 = self.init_common_case(result)     #递归检查公共用例里是否存在调用用例
                elif not isOK:
                    return isOK, result
                list_rows = result[case_name]
                cases[index: index+1] = list_rows    #将公共用例插入到执行用例中去
            index += 1
        if cases_len == index:
            return False, ''
        return True, cases

    # [{"a": [{"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}]},{"a": [5, 3, 2]}, {"a": [10, 4, 6]}]


    def init_execute_case(self):
        print("----------初始化用例----------")
        xlsx = ReadCase()
        isOK, result = xlsx.readallcase()
        if not isOK:
            print(result)
            print("----------结束执行----------")
            exit()
        all_cases = result
        excu_cases = []
        for cases_dict in all_cases:
            for key, cases in cases_dict.items():
                isOK, result = self.init_common_case(cases)
                if isOK:
                    cases_dict[key] = result
                else:
                    cases_dict[key] = cases
                excu_cases.append(cases_dict)
                print("----------初始化用例完成----------")
        return excu_cases



 

ddt驱动代码

 

1、文件目录

Python 搭建UI自动化测试框架教程_第30张图片

执行用例代码目录 excutetest下新增一个test_caserun.py  ,先说,这里最好是以test开头定义执行文件

然后library里面放到ddt,数据驱动装饰器

2、导入库

在test_caserun.py里写代码

import unittest
from common.factory import Factory
from common.log import mylog
from library.ddt import ddt, data

unittest UT测试库,作自动化测试的都知道他是干啥的

Factory,这个框架的核心类

mylog,下一章实现的日志打印

ddt, data,数据驱动装饰器

2、代码分析

 

@ddt
class Test_caserun(unittest.TestCase):
    fac = Factory()
    isOK, excu_cases = fac.init_execute_case()

    @data(*excu_cases)
    def test_run(self, acases):
        for key, cases in acases.items():
            mylog.info('\n----------用例【%s】开始----------' % cases[0].get('sheet'))
            print('\n')
            for case in cases:
                isOK, result = self.fac.execute_keyword(**case)
                if isOK:
                    print(result)
                    mylog.info(result)
                else:
                    mylog.error(result)
                    raise Exception(result)
            mylog.info('\n----------用例【%s】结束----------\n' % cases[0].get('sheet')).

定义一个类Test_caserun,用@ddt来装饰一下这个类,不要问这啥原理,一下子说不清楚,按要求来就行

初始化工厂fac = Factory()

得到用例isOK, excu_cases = fac.init_execute_case()

实现一个执行用例的方法,test_run

用@data() 来装饰这个方法,这个装饰器来帮忙遍历excu_cases用例,然后返回给test_run方法的acases,

@data() 的参数可以是元组、列表、字典等格式

data只遍历了第一层,后面的我们代码里面写

for key, cases in acases.items():
    mylog.info('\n----------用例【%s】开始----------' % cases[0].get('sheet'))
    print('\n')
    for case in cases:
        isOK, result = self.fac.execute_keyword(**case)
        if isOK:
            print(result)
            mylog.info(result)
        else:
            mylog.error(result)
            raise Exception(result)
    mylog.info('\n----------用例【%s】结束----------\n' % cases[0].get('sheet'))

这一段就是执行用例,决断执行结果如

理念就是简练代码,一个用例执行代码,调用一个执行入口,执行任意网页操作,返回成功或失败

isOK is True 执行成功打info日志

isOK is False 执行失败打error日志,抛出异常日志

调试代码看下章

 

执行并输出报告

1、目录

在工程主目录下添加一个文件test_run.py

Python 搭建UI自动化测试框架教程_第31张图片

https://download.csdn.net/download/weixin_40331132/12521680

下载上面链接里的一个文件放到library目录下,因为我的

网上有很多,但因为是我写的用例格式,所以HTMLTestRunner里的代码我改了一些。暂时不会修改这个文件的朋友,可以先下载我的来学习用

2、导包

在test_run里导入

import os
import unittest
from library.HTMLTestRunnerNew import HTMLTestRunner
from common.getfiledir import CASEDIR, REPORTDIR

  os来处理路径 

unittest来Loadcase的代码文件

HTMLTestRunner来执行用例,输出报告

具体代码如下,这一段网上通用,我也懒懒不细说了直接上

class Test_run(object):

    def __init__(self):
        self.suit = unittest.TestSuite()
        self.load = unittest.TestLoader()
        self.suit.addTest(self.load.discover(CASEDIR))
        self.runner = HTMLTestRunner(
            stream=open(os.path.join(REPORTDIR, 'report.html'), 'wb'),
            title='魂尾自动化工厂',
            description='唯a饭木领',
            tester='HUNWEI'
        )

    def excute(self):
        self.runner.run(self.suit)





if __name__=="__main__":
    test_run = Test_run()
    test_run.excute()

init初始化用例,与报告对象

excute执行

添加工程主执行入口main

直接运行调试,还是在之前的excel用例基础上运行

运行结果

Python 搭建UI自动化测试框架教程_第32张图片

输出日志:

Python 搭建UI自动化测试框架教程_第33张图片

报告结果:

找到这个目录Python 搭建UI自动化测试框架教程_第34张图片

打开这个html

Python 搭建UI自动化测试框架教程_第35张图片

好,整个框架完成了,只剩扩展一些边边角角的了

比如:下面打印日志,与发送邮件的,便不细说了,可以直接看代码学习学习,很简单的。

打印Log

相关代码

import logging
import os
from logging.handlers import TimedRotatingFileHandler
from common.getconf import Config
from common.getfiledir import LOGDIR

class Handlogging():

    @staticmethod
    def emplorlog():
        conf = Config()
        # set format
        formatter = logging.Formatter("%(asctime)s - %(name)s-%(levelname)s %(message)s")

        # create log set getlog level
        mylog = logging.getLogger('HunWei')
        mylog.setLevel(conf.get('LOG', 'level'))

        # create outputsteam set level
        sh = logging.StreamHandler()
        sh.setLevel(conf.get('LOG', 'level'))
        sh.setFormatter(formatter)
        mylog.addHandler(sh)

        #create file set level
        #fh = logging.FileHandler(os.path.join(LOGDIR,'test.log'), encoding='utf-8')

        log_path = os.path.join(LOGDIR, 'test')
        # interval 滚动周期,
        # when="MIDNIGHT", interval=1 表示每天0点为更新点,每天生成一个文件
        # backupCount  表示日志保存个数
        fh = TimedRotatingFileHandler(
            filename=log_path, when="D", backupCount=15, encoding='utf-8'
        )
        fh.suffix = "%Y-%m-%d.log"
        fh.setLevel(conf.get('LOG', 'level'))
        fh.setFormatter(formatter)
        mylog.addHandler(fh)
        return mylog


mylog = Handlogging.emplorlog()

 

发送邮件

相关代码

import smtplib
import os
from common.getconf import Config
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from common.getfiledir import REPORTDIR



class Opr_email(object):

    def __init__(self):
        """
        初始化文件路径与相关配置
        """
        self.conf = Config()
        all_path = []
        for maindir, subdir, file_list in os.walk(REPORTDIR):
            pass
        for filename in file_list:
            all_path.append(os.path.join(REPORTDIR, filename))
        self.filename = all_path[0]
        self.host = self.conf.get('email', 'host')
        self.port = self.conf.get('email', 'port')
        self.user = self.conf.get('email', 'user')
        self.pwd = self.conf.get('email', 'pwd')
        self.from_addr = self.conf.get('email', 'from_addr')
        self.to_addr = self.conf.get('email', 'to_addr')



    def get_email_host_smtp(self):
        """
        连接stmp服务器
        :return:
        """
        self.smtp = smtplib.SMTP_SSL(host=self.host, port=self.port)
        self.smtp.login(user=self.user, password=self.pwd)


    def made_msg(self):
        """
        构建一封邮件
        :return:
        """
        self.msg = MIMEMultipart()

        with open(self.filename, 'rb') as f:
            content = f.read()
        # 创建文本内容
        text_msg = MIMEText(content, _subtype='html', _charset='utf8')
        # 添加到多组件的邮件中
        self.msg.attach(text_msg)
        # 创建邮件的附件
        report_file = MIMEApplication(content)
        report_file.add_header('Content-Disposition', 'attachment', filename=str.split(self.filename, '\\').pop())

        self.msg.attach(report_file)
        # 主题
        self.msg['subject'] = '自动化测试报告'
        # 发件人
        self.msg['From'] = self.from_addr
        # 收件人
        self.msg['To'] = self.to_addr


    def send_email(self):
        """
        发送邮件
        :return:
        """
        self.get_email_host_smtp()
        self.made_msg()
        self.smtp.send_message(self.msg, from_addr=self.from_addr, to_addrs=self.to_addr)

 

 

 

 

 

 

你可能感兴趣的:(自动化框架封装,selenium,python,软件测试,测试工程师,ab测试)