整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

自动化测试之HTML测试报告

自动化测试之HTML测试报告

载地址HTMLTestRunner.py文件:

http://tungwaiyip.info/software/HTMLTestRunner.html

下载的适合python2,如果python3要修改一些内容

首先吧HTMLTestRunner文件添加到环境变量里,可以直接放到python的Lib目录下

HTMLTestRunner是python标准库unittest单元测试框架的一个扩展,用于生成HTML测试报告


生成HTML测试报告

#coding:utf-8
import unittest, HTMLTestRunner

class Testcase(unittest.TestCase): # 测试用例类
# 具体的测试用例,一定要以test开头
def test1(self):
self.assertEqual(1, 1)

def test2(self):
self.assertEqual(2, 2)

if __name__=="__main__":
# 构造测试集
suite=unittest.TestSuite()
suite.addTest(unittest.makeSuite(Testcase)) # 执行该测试类所有用例

# 定义报告的存放路径,以二进制写的形式打开文件
f=open('test.html', 'wb')

# 定义测试报告,stream:报告存放路径,title:报告标题,description:描述
runner=HTMLTestRunner.HTMLTestRunner(stream=f, title=
u'测试用例标题', description=u'描述')
runner.run(suite) # 运行测试用例
f.close() # 关闭文件


返回结果(测试报告详情):

--stream :存放报告写入文件的存入区域

--title :测试报告的主题

--description :测试报告的描述


报告用例类和用例方法加备注

为了生成带有中文描述的测试用例类和测试用例

在用例类和用例方法下,通过’’’ ‘’’或””” “””来添加备注

#coding:utf-8
import unittest, HTMLTestRunner

class Testcase(unittest.TestCase): # 测试用例类
u'''类名后加备注'''

def test1(self):
u'''用例后面加备注1'''
self.assertEqual(1, 1)

def test2(self):
u'''用例后面加备注2'''
self.assertEqual(2, 2)

if __name__=="__main__":
# 构造测试集
suite=unittest.TestSuite()
suite.addTest(unittest.makeSuite(Testcase))

# 定义报告的存放路径,以二进制写的形式打开文件
f=open('test.html', 'wb')

# 定义测试报告,stream:报告存放路径,title:报告标题,description:描述
runner=HTMLTestRunner.HTMLTestRunner(stream=f, title=
u'测试用例标题', description=u'描述')
runner.run(suite) # 运行测试用例
f.close() # 关闭文件


返回结果:

测试报告文件名

#coding:utf-8
import unittest, HTMLTestRunner, time
from unittest.loader import makeSuite

class Testcase(unittest.TestCase): # 测试用例类
# 具体的测试用例,一定要以test开头
def test1(self):
self.assertEqual(1, 1)

def test2(self):
self.assertEqual(2, 2)

if __name__=="__main__":
# 构造测试集
suite=unittest.TestSuite()
suite.addTest(makeSuite(Testcase)) # 执行该测试类所有用例

# 定义报告的存放路径,以二进制写的形式打开文件
now=time.strftime("%y-%m-%d %H_%M_%S")
f=open('./' + now + 'test.html', 'wb')

# 定义测试报告,stream:报告存放路径,title:报告标题,description:描述
runner=HTMLTestRunner.HTMLTestRunner(stream=f, title=
u'测试用例标题', description=u'描述')
runner.run(suite) # 运行测试用例
f.close() # 关闭文件


返回结果:


测试报告乱码问题


将红框里的内容注释掉改成uo=o.decode('utf-8')

什么是HTML

HTML 是用来描述网页的一种语言。HTML 是一种在 Web 上使用的通用标记语言。HTML 允许你格式化文本,添加图片,创建链接、输入表单、框架和表格等等,并可将之存为文本文件,浏览器即可读取和显示。

  • HTML 指的是超文本标记语言: HyperText Markup Language
  • HTML 不是一种编程语言,而是一种标记语言
  • 标记语言是一套标记标签 (markup tag)
  • HTML 使用标记标签来描述网页
  • HTML 文档包含了HTML 标签文本内容
  • HTML文档也叫做 web 页面

2 入门实例

新建一个test.html文件,内容如下

<!DOCTYPE html><html><head><meta charset="utf-8"><title>ZONGXP</title></head><body> <h1>我的第一个标题</h1><p>我的第一个段落。</p> </body></html>

其中:

  • <!DOCTYPE html> 声明为 HTML5 文档
  • <html> 元素是 HTML 页面的根元素
  • <head> 元素包含了文档的元(meta)数据,如 <meta charset="utf-8"> 定义网页编码格式为 utf-8(由于在大部分浏览器中直接输出中文会出现乱码,所以要在头部将字符声明为UTF-8
  • <title> 元素描述了文档的标题
  • <body> 元素包含了可见的页面内容
  • <h1> 元素定义一个大标题
  • <p> 元素定义一个段落

保存后运行,即可在浏览器中打开如下界面

3 各部分详解

3.1 标题

HTML 标题(Heading)是通过<h1> - <h6> 标签来定义的.

<!DOCTYPE html><html><head><meta charset="utf-8"><title>ZONGXP</title></head><body> <h1>这是标题 1</h1><h2>这是标题 2</h2><h3>这是标题 3</h3><h4>这是标题 4</h4><h5>这是标题 5</h5><h6>这是标题 6</h6> </body></html>

3.2 段落

HTML 段落是通过标签 <p> 来定义的

<!DOCTYPE html><html><head><meta charset="utf-8"><title>ZONGXP</title></head><body> <p>这是一个段落。</p><p>这是一个段落。</p><p>这是一个段落。</p> </body></html>

3.3 链接

HTML 链接是通过标签 <a> 来定义的

<!DOCTYPE html><html><head><meta charset="utf-8"><title>ZONGXP</title></head><body> <a href="https://blog.csdn.net/zong596568821xp">这是一个链接使用了 href 属性</a> </body></html>

3.4 图像

HTML 图像是通过标签 <img> 来定义的。注意: 图像的名称和尺寸是以属性的形式提供的。

<!DOCTYPE html><html><head><meta charset="utf-8"><title>ZONGXP</title></head><body> <img src="zongxp.jpg" width="640" height="640" /> </body></html>

3.5 表格

表格由 <table> 标签来定义。每个表格均有若干行(由 <tr> 标签定义),每行被分割为若干单元格(由 <td> 标签定义)。字母 td 指表格数据(table data),即数据单元格的内容。数据单元格可以包含文本、图片、列表、段落、表单、水平线、表格等等。表格的表头使用 <th> 标签进行定义。如果不定义边框属性,表格将不显示边框。有时这很有用,但是大多数时候,我们希望显示边框。使用边框属性来显示一个带有边框的表格:

<table border="1">    <tr>        <th>Header 1</th>        <th>Header 2</th>    </tr>    <tr>        <td>row 1, cell 1</td>        <td>row 1, cell 2</td>    </tr>    <tr>        <td>row 2, cell 1</td>        <td>row 2, cell 2</td>    </tr></table>

4 速查列表

4.1 基本文档

<!DOCTYPE html><html><head><title>文档标题</title></head><body>可见文本...</body></html>

4.2 基本标签

<h1>最大的标题</h1><h2> . . . </h2><h3> . . . </h3><h4> . . . </h4><h5> . . . </h5><h6>最小的标题</h6> <p>这是一个段落。</p><br> (换行)<hr> (水平线)<!-- 这是注释 -->

4.3 文本格式化

<b>粗体文本</b><code>计算机代码</code><em>强调文本</em><i>斜体文本</i><kbd>键盘输入</kbd> <pre>预格式化文本</pre><small>更小的文本</small><strong>重要的文本</strong> <abbr> (缩写)<address> (联系信息)<bdo> (文字方向)<blockquote> (从另一个源引用的部分)<cite> (工作的名称)<del> (删除的文本)<ins> (插入的文本)<sub> (下标文本)<sup> (上标文本)

4.4 链接

普通的链接:<a href="http://www.example.com/">链接文本</a>图像链接: <a href="http://www.example.com/"><img src="URL" alt="替换文本"></a>邮件链接: <a href="mailto:webmaster@example.com">发送e-mail</a>书签:<a id="tips">提示部分</a><a href="#tips">跳到提示部分</a>

4.5 图片

<img src="URL" alt="替换文本" height="42" width="42">

4.6 样式/区块

<style type="text/css">h1 {color:red;}p {color:blue;}</style><div>文档中的块级元素</div><span>文档中的内联元素</span>

4.7 无序列表

<ul>    <li>项目</li>    <li>项目</li></ul>

4.8 有序列表

本次介绍的UI Test Framework 分为以下几层,UI自动化测试框架主要思想Page Object 的思想,即:将一个页面的元素封装成一个类,本文以禅道项目为例:详情如下:

分层介绍如下:

actions:动作层,封装每个页面的业务。

common:底层,封装一些工具包,以及对Selenium 3 里面的元素定位,鼠标,键盘等动作进行二次封装。

config:配置文件层,存放配置文件,一些主机地址信息,邮件地址,日志地址,驱动地址,日志级别设置,截图地址,一些账号信息等。

element_info:元素信息层,将每个页面的元素封装成类,类似的属性是元素,方法是元素的动作。

element_info_datas:元素信息表层,每个页面的元素信息都单独存放在对应的excel表格中

logs :日志层

reports:报告层

runner:运行层

sample:测试层,用于代码测试

screen_shot:存放截图图片层

test_data:测试数据层

testcases:测试用例集层

webdriver:存放浏览器驱动层

详细说明如下:

common层:

base_page.py对selenium 3框架基本引用进行二次放在都放在这个文件里面

# desc:页面基础类
import os, time
from common.log_utils import logs_obj
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from common.config_utils import config_obj
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from common import HTMLTestReportCN


class BasePage(object):
    def __init__(self, driver):
        # self.driver=webdriver.Chrome()
        self.driver=driver

    def open_url(self, url):
        self.driver.get(url)
        logs_obj.info('打开url地址:{}'.format(url))

    def set_browser_max(self):
        self.driver.maximize_window()
        logs_obj.info('窗口最大化')

    def set_browser_min(self):
        self.driver.minimize_window()
        logs_obj.info('窗口最小化')

    def refresh(self):
        self.driver.refresh()
        logs_obj.info('刷新')

    def close_tab(self):
        self.wait(2)
        self.driver.close()
        logs_obj.info('------- 关闭当前tab页面 --------')

    def quit_driver(self):
        self.wait(1)
        self.driver.quit()
        logs_obj.info('------- 退出浏览器 --------')

    def get_url(self):
        value=self.driver.current_url
        logs_obj.info('------- 获取网页url:{} --------'.format(value))
        return value

    def get_title(self):
        value=self.driver.title
        logs_obj.info('获取网页title:{}'.format(value))
        return value

    # 清空文本框的内容
    def clear(self, element_info):
        self.find_element(element_info).clear()

    # self.username_inputbox={'element_name': '用户名输入框',
    #                           'locator_type': 'ID',
    #                           'locator_value': 'account',
    #                           'timeout': '5'}
    def find_element(self, element_info):
        '''
        通过 element_info (元素识别的数据,返回一个元素)
        :param element_info:元素识别信息字典
        :return: element
        id,name,class,tag,linktext,plinktext,xpath,css
        '''
        try:
            locator_type_name=element_info['locator_type']
            locator_value_info=element_info['locator_value']
            locator_timeout=element_info['timeout']
            if locator_type_name=='id':
                locator_type=By.ID
            elif locator_type_name=='xpath':
                locator_type=By.XPATH
            elif locator_type_name=='name':
                locator_type=By.NAME
            elif locator_type_name=='class':
                locator_type=By.CLASS_NAME
            elif locator_type_name=='css':
                locator_type=By.CSS_SELECTOR
            elif locator_type_name=='tag':
                locator_type=By.TAG_NAME
            elif locator_type_name=='linktext':
                locator_type=By.LINK_TEXT
            elif locator_type_name=='plinktext':
                locator_type=By.PARTIAL_LINK_TEXT

            # self.driver.find_element(By.XPATH,'//li[@data-id="product"]')
            # 显示等待识别元素
            element=WebDriverWait(self.driver, locator_timeout).until(
                lambda x: x.find_element(locator_type, locator_value_info))  # 最核心的代码
            logs_obj.info('[{}] 识别成功'.format(element_info['element_name']))
        except Exception as e:
            logs_obj.info('[{}] 识别不成功,原因是:{}'.format(element_info['element_name'], e.__str__()))
            self.screenshot_as_file()
        return element

    # 封装元素操作方法
    def click(self, element_info):
        element=self.find_element(element_info)
        element.click()
        logs_obj.info('点击:{}'.format(element_info['element_name']))

    # 元素输入操作
    def send_keys(self, element_info, content):
        element=self.find_element(element_info)
        element.send_keys(content)
        logs_obj.info('[{}]中输入{}'.format(element_info['element_name'], content))

    # 获取元素的某个属性值
    def get_attribute(self, element_info):
        title=self.find_element(element_info).get_attribute('title')
        logs_obj.info('title是:{}'.format(title))
        return title

    # 获取元素的某个属性值
    def get_text_value(self, element_info):
        text=self.find_element(element_info).text
        logs_obj.info('text是:{}'.format(text))
        return text

    # 切框架:思路1  通过元素是被数据字典,获取元素再切
    def switch_to_frame_by_element(self, element_info):
        element=self.driver.find_element(element_info)
        self.driver.switch_to.frame(element)
        logs_obj.info('通过元素切换框架,该元素为:{}'.format(element_info['locator_value']))

    # 切框架:思路2  使用id或name切
    def switch_to_frame_id_or_name(self, id_or_name):
        self.driver.switch_to.frame(id_or_name)
        logs_obj.info('通过id或name切换框架,id或name为:{}'.format(id_or_name))

    # 切框架:思路3  把前面两种思路整合,封装成一个统一的方法
    def switch_to_frame(self, **element_dict):
        if 'id' in element_dict.keys():
            self.driver.switch_to.frame(element_dict['id'])
            logs_obj.info('通过id切换框架,id为:{}'.format(element_dict['id']))
        elif 'name' in element_dict.keys():
            self.driver.switch_to.frame(element_dict['name'])
            logs_obj.info('通过name切换框架,name为:{}'.format(element_dict['name']))
        elif 'element_info' in element_dict.keys():
            element=self.find_element(element_dict['element_info'])
            logs_obj.info('通过element_info切换框架,element_info为:{}'.format(element_dict['element_info']))
            self.driver.switch_to.frame(element)
        return element_dict

    def switch_to_default_content(self):
        self.driver.switch_to.default_content()

    # 弹出框封装
    # def switch_to_alert(self,action='accept',time_out=config_obj.time_out):
    #     self.wait(time_out)
    #     alert=self.driver.switch_to.alert
    #     alert_text=alert.text
    #     if action=='accept':
    #         alert.accept()  #确认
    #     else:
    #         alert.dismiss
    #     return alert_text

    def switch_to_alert_2(self, action='accept', time_out=config_obj.time_out):
        WebDriverWait(self.driver, time_out).until(EC.alert_is_present())
        alert=self.driver.switch_to.alert
        logs_obj.info('----------- 切换到弹框 -----------')
        alert_text=alert.text
        logs_obj.info('-------- 弹框的文本内容为:{} -------'.format(alert_text))
        self.wait(3)
        if action=='accept':
            alert.accept()
            logs_obj.info('-------- 点击弹框的确定按钮! ---------')
        else:
            alert.dismiss()
            logs_obj.info('---------- 点击弹框的取消按钮 -----------')
        return alert_text

    # 切句柄
    # 获取当前页面的句柄
    def get_window_handle(self):
        logs_obj.info('---- 获取窗口的句柄:{}  ---------'.format(self.driver.current_window_handle))
        return self.driver.current_window_handle

    # 切句柄
    def switch_to_window_by_handle(self, window_handle):
        self.driver.switch_to.window(window_handle)
        logs_obj.info('--------  进行切换句柄,句柄为:{} ----------'.format(window_handle))

    # 通过title切
    # def switch_to_window_by_title(self,title):
    #     window_handles=self.driver.window_handles
    #     for window_handle in window_handles:
    #         self.driver.switch_to.window(window_handle)
    #         if self.get_title()==title:
    #             break;

    # 通过url切
    # def switch_to_window_by_url(self,url):
    #     window_handles=self.driver.window_handles
    #     for window_handle in window_handles:
    #         self.driver.switch_to.window(window_handle)
    #         if self.get_url()==url:
    #             break;

    # 切句柄继续封装  加等待时间,只检查标题标题,url的部分内容
    def switch_to_window_by_title(self, title):
        window_handles=self.driver.window_handles
        for window_handle in window_handles:
            self.driver.switch_to.window(window_handle)
            if WebDriverWait(self.driver, config_obj.time_out).until(EC.title_contains(title)):
                logs_obj.info('--------- 通过title({})切换窗口:-----------'.format(title))
                break;

    def switch_to_window_by_url(self, url):
        window_handles=self.driver.window_handles
        for window_handle in window_handles:
            self.driver.switch_to.window(window_handle)
            if WebDriverWait(self.driver, config_obj.time_out).until(EC.title_contains(url)):
                logs_obj.info('--------- 通过url({})切换窗口:-----------'.format(url))
                break;

    # 修改某个元素的属性
    def set_element_attribute(self, element_info, attribute_name, attribute_value):
        js='arguments[0].setAttribute("%s","%s");' % (attribute_name, attribute_value)
        el=self.find_element(element_info)
        self.__execute_script(js, el)
        logs_obj.info('[{}],正在执行修改[{}]属性操作,修改成:{}'.format(element_info['element_name'], attribute_value))

    # 移除某个属性
    def remove_element_attribute(self, element_info, attribute_name):
        js='arguments[0].removeAttribute("%s");' % attribute_name
        el=self.find_element(element_info)
        self.__execute_script(js, el)
        logs_obj.info('[{}],正在执行移除[{}]属性操作'.format(element_info['element_name'], attribute_name))

    # 滚动条
    def scroll(self, height):
        self.wait(2)
        js='window.scrollBy(0,{});'.format(height)  # height:正数向下滚,负数向上滚
        self.__execute_script(js)
        logs_obj.info('正在执行页面滚动操作,滚动的高度为:{}'.format(height))

    # 继续封装 selenium 执行 js 脚本   深入封装
    def __execute_script(self, js, element=None):
        if element:
            self.driver.execute_script(js, element)
            logs_obj.info('[{}]:正在进行js操作,js代码为'.format(element['element_name'], js))
        else:
            self.driver.execute_script(js)
            logs_obj.info('正在执行js,js是{}'.format(js))

    # 固定等待
    def wait(self, s=config_obj.time_out):
        time.sleep(s)
        logs_obj.info('固定等待:%s' % s)

    # 隐式等待
    def implicitly_wait(self, seconds=config_obj.time_out):
        self.driver.implicitly_wait(seconds)
        logs_obj.info('------- 浏览器设置隐式等待:{} --------'.format(seconds))

    # 显示等待

    # 鼠标悬停
    def move_to_element_by_mouse(self, element_info):
        element=self.find_element(element_info)
        ActionChains(self.driver).move_to_element(element)
        logs_obj.info('[{}]:元素进行悬停'.format(element_info['element_name']))

    # 长按不停
    def long_press_element(self, element_info):
        element=self.find_element(element_info)
        ActionChains(self.driver).click_and_hold(element).perform()
        logs_obj.info('[{}]:元素进行长按'.format(element_info['element_name']))

    # 鼠标右击
    def right_click_element(self, element_info):
        element=self.find_element(element_info)
        ActionChains(self.driver).context_click(element).perform()
        logs_obj.info('[{}]:元素进行右击'.format(element_info['element_name']))

    # 截图
    # def screenshot_as_file(self, *screenshot_path):
    #     # 如果没有喘截图路径,则将截图放入默认路径,
    #     if len(screenshot_path)==0:
    #         screenshot_filepath=config_obj.screen_shot_path
    #     else:
    #         screenshot_filepath=screenshot_path[0]
    #     now=time.strftime('%Y_%m_%d_%H_%M_%S')
    #     current_dir=os.path.dirname(__file__)
    #     screenshot_filepath=os.path.join(current_dir, '..', screenshot_filepath,
    #                                        'UTest_%s.png' % now)
    #     self.driver.save_screenshot(screenshot_filepath)
    #     logs_obj.info(' 截图保存成功,图片位置:{}'.format(screenshot_filepath))

    # 第二次讲截图
    def screenshot_as_file(self):
        '''
        把错误截图放到HTML中
        :return:
        '''
        report_path=os.path.join(os.path.abspath(__file__),'..',config_obj.report_path)
        report_dir=HTMLTestReportCN.ReportDirectory('report_path')
        report_dir.get_screenshot(self.driver)


    # 下拉定位,一下分别通过Select类的索引
    def select_by_element_info(self, element_info):
        element=self.find_element(element_info)
        self.click(element)

    def select_by(self, element_info, **by_index_or_value_or_visible_text):
        element=self.find_element(element_info)
        select=Select(element)
        if 'index' in by_index_or_value_or_visible_text.keys():
            select.select_by_index(by_index_or_value_or_visible_text['index'])
            logs_obj.info('通过select的index定位到该元素,index值为:{}'.format(by_index_or_value_or_visible_text['text']))
        elif 'value' in by_index_or_value_or_visible_text.keys():
            select.select_by_value(by_index_or_value_or_visible_text['value'])
            logs_obj.info('通过select的value定位到该元素,value值为:{}'.format(by_index_or_value_or_visible_text['value']))
        elif 'text' in by_index_or_value_or_visible_text.keys():
            select.select_by_visible_text(by_index_or_value_or_visible_text['text'])
            logs_obj.info('通过select的text定位到该元素,text值为:{}'.format(by_index_or_value_or_visible_text['text']))


if __name__=='__main__':
    from common.browser import Browser

    driver=Browser().get_driver()
    base_page=BasePage(driver)
    base_page.open_url('https://www.baidu.com')
    base_page.scroll(1000)

browser.py主要对Google、Firefox、msedge三大浏览器驱动进行封装:

浏览器驱动需要下载本地计算机浏览器对应的版本:

浏览器驱动

# @desc:浏览器封装
import os
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from common.config_utils import config_obj
from selenium.webdriver.chrome.options import Options
from common.log_utils import logs_obj

dri_path=os.path.join(os.path.dirname(__file__), '..', config_obj.get_driver_path)


class Browser():
    def __init__(self, driver_path=dri_path):
        self.__driver_path=driver_path

    def __get_chrome_driver(self):
        chrome_options=Options()
        chrome_options.add_argument('--disable-gpu')  # 谷歌文档提到需要加上这个属性来规避bug
        chrome_options.add_argument('lang=zh_CN.UTF-8')  # 设置默认编码为utf-8
        chrome_options.add_experimental_option('useAutomationExtension', False)  # 取消chrome受自动控制提示
        chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])  # 取消chrome受自动控制提示
        driver_path=os.path.join(self.__driver_path, 'chromedriver.exe')
        driver_server=Service(driver_path)
        chrome_driver=webdriver.Chrome(service=driver_server, options=chrome_options)
        logs_obj.info('----- 初始化google浏览器,并启动 -----')
        return chrome_driver

    def __get_firefox_driver(self):
        firefox_driver_path=os.path.join(self.__driver_path, 'geckodriver.exe')
        driver_server=Service(firefox_driver_path)
        firefox_driver=webdriver.Firefox(service=driver_server)
        logs_obj.info('----- 初始化firefox浏览器,并启动 -----')
        return firefox_driver

    def __get_edge_driver(self):
        edge_driver_path=os.path.join(self.__driver_path, 'msedgedriver.exe')
        driver_server=Service(edge_driver_path)
        edge_driver=webdriver.Edge(service=driver_server)
        logs_obj.info('----- 初始化edge浏览器,并启动 -----')
        return edge_driver

    def get_driver(self):
        ##设置默认驱动 chrome,firefox,msedge
        if config_obj.get_driver_name=='chrome':
            driver=self.__get_chrome_driver()
            logs_obj.info('------- 配置文件配置的浏览器驱动是:{} --------'.format(config_obj.get_driver_name))
        elif config_obj.get_driver_name=='firefox':
            driver=self.__get_firefox_driver()
            logs_obj.info('------- 配置文件配置的浏览器驱动是:{} --------'.format(config_obj.get_driver_name))
        elif config_obj.get_driver_name=='msedge':
            driver=self.__get_edge_driver()
            logs_obj.info('------- 配置文件配置的浏览器驱动是:{} --------'.format(config_obj.get_driver_name))
        return driver

config_utils.py读取配置文件类:

config.ini文件内容为:

配置文件所在的目录

[name]
name1=test
name2=dev01
name3=dev02
name4=test01
name5=test02

[password]
password1=xxxx
password2=xxxx
password3=xxxx
password4=xxxx
password5=xxxx

[ZenTao_url]
url=http://xx.xxx.xxxx.xx/zentao/www/index.php?m=user&f=login

[default]
driver_path=webdriver

#日志路径
log_path=logs
#日志级别 0 NOTSET    10 DEBUG    20 INFO     30 WARNING    40 ERROR    50 CRITICAL
log_level=20

#设置默认驱动 chrome,firefox,msedge
driver_name=chrome

#元素识别信息路径
element_info_path=element_info_datas

#默认等待时间
time_out=30

#默认存放截图目录
screen_shot_path=screen_shot

#默认登录的账户名和密码
username=test01
password=xxxxxxx


#测试数据路径
test_data_path=test_data

#测试用例路径
case_path=testcases

#测试报告路径
report_path=reports/

[email]
#发送服务器
smtp_server=smtp.qq.com
#发送账户
smtp_sender=xxxxx@qq.com
#QQ第三方登录密码
smtp_password=xxxxx
#测接收人
smtp_receiver=xxxxx@qq.com
#抄送人
smtp_cc=2656343994@qq.com
#邮件标题
smtp_subject=禅道UI自动化测试报告

config_utils.py类:

# 读取配置文件
import configparser, os

config_path=os.path.join(os.path.dirname(__file__), '../config/config.ini')


class ConfigUtils:
    def __init__(self, cof_path):
        self.config_path=cof_path
        self.conf=configparser.ConfigParser()
        self.conf.read(self.config_path, encoding='utf-8')

    @property
    def get_zentao_url(self):
        return self.conf.get('ZenTao_url', 'url')

    @property
    def get_name_01(self):
        return self.conf.get('name', 'name1')

    @property
    def get_name_02(self):
        return self.conf.get('name', 'name2')

    @property
    def get_password_01(self):
        return self.conf.get('password', 'password1')

    @property
    def get_password_02(self):
        return self.conf.get('password', 'password2')

    @property
    def get_driver_path(self):
        return self.conf.get('default', 'driver_path')

    @property
    def get_log_path(self):
        return self.conf.get('default', 'log_path')

    @property
    def get_log_level(self):
        return int(self.conf.get('default', 'log_level'))

    # 获取默认驱动名称
    @property
    def get_driver_name(self):
        return self.conf.get('default', 'driver_name')

    # 获取默认超时时间
    @property
    def time_out(self):
        return float(self.conf.get('default', 'time_out'))

    # 获取截图默认路径
    @property
    def screen_shot_path(self):
        return self.conf.get('default', 'screen_shot_path')

    @property
    def user_name(self):
        return self.conf.get('default', 'username')

    @property
    def pass_word(self):
        return self.conf.get('default', 'password')

    @property
    def test_data_path(self):
        return self.conf.get('default', 'test_data_path')

    # 测试用例路径
    @property
    def case_path(self):
        return self.conf.get('default', 'case_path')

    # 测试报告路径
    @property
    def report_path(self):
        return self.conf.get('default', 'report_path')

    # 获取email的smtp_server
    @property
    def smtp_server(self):
        return self.conf.get('email', 'smtp_server')

    # 获取email的smtp_sender
    @property
    def smtp_sender(self):
        return self.conf.get('email', 'smtp_sender')

    # 获取email的smtp_password
    @property
    def smtp_password(self):
        return self.conf.get('email', 'smtp_password')

    # 获取email的smtp_receiver
    @property
    def smtp_receiver(self):
        return self.conf.get('email', 'smtp_receiver')

    # 获取email的smtp_cc
    @property
    def smtp_cc(self):
        return self.conf.get('email', 'smtp_cc')

    # 获取email的smtp_subject
    @property
    def smtp_subject(self):
        return self.conf.get('email', 'smtp_subject')


    #element_info_path
    @property
    def element_info_path(self):
        return self.conf.get('default', 'element_info_path')


config_obj=ConfigUtils(config_path)

element_data_utils.py读取excel元素文件类,excel元素文件目录结构如下,文件内容如图:

一个页面一个excel文件

登录页面元素信息如下图,其它页面元素类似:

element_data_utils.py类

# encoding: utf-8
# @author: newdream_daliu
# @file: element_data_utils.py
# @time: 2022-07-10 17:12
# @desc: 读取页面元素类

import os
import xlrd
from common.config_utils import config_obj

current_path=os.path.dirname(__file__)
excel_path=os.path.join(current_path, '..',config_obj.element_info_path)
#element_info_datas/login/login_page.xlsx
#固定元素的总路径/模块路径/页面名称.xlsx

class ElementDataUtils():
    def __init__(self,module_name,page_name, element_path=excel_path):  # 将页面名称定义
        self.element_path=element_path
        self.element_path=os.path.join(self.element_path,module_name,page_name+'.xlsx')
        self.workbook=xlrd.open_workbook(self.element_path)
        self.sheet=self.workbook.sheet_by_index(0)  # 每次取第一个sheet
        self.row_count=self.sheet.nrows

    def get_element_info(self):
        element_infos={}
        for i in range(1, self.row_count):
            # if self.sheet.cell_value(i, 2)==page_name:
            element_info={}
            element_info['element_name']=self.sheet.cell_value(i, 1)
            element_info['locator_type']=self.sheet.cell_value(i,2)
            element_info['locator_value']=self.sheet.cell_value(i, 3)
            timeout_value=self.sheet.cell_value(i,4)
            #方法1:
            # if timeout_value=='':
            #     timeout_value=config_obj.time_out  #如果没有设置,就取config.ini设置的默认值
            # else:
            #     timeout_value=float(timeout_value)  #如果元素表设置了默认值则就转成浮点型
            # element_info['timeout']=timeout_value
            # 方法2:
            timeout_value=float(timeout_value) if isinstance(timeout_value,float) else config_obj.time_out
            element_info['timeout']=timeout_value

            element_infos[self.sheet.cell_value(i, 0)]=element_info
        return element_infos

if __name__=='__main__':
    elements=ElementDataUtils('login','login_page').get_element_info()
    print(elements)
    # elements=ElementDataUtils('main').get_element_info('main_page')
    # print(elements)
    # elements=ElementDataUtils('login').get_element_info('login_page')
    # print(elements)

email_util.py 发送邮件类

import smtplib, os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from common.log_utils import logs_obj
from email.mime.base import MIMEBase
from email import encoders
from common.config_utils import config_obj
from common import zip_utils


class EmailUtils:
    def __init__(self, smtp_body, smtp_file_path=None):
        self.smtp_server=config_obj.smtp_server
        self.smtp_sender=config_obj.smtp_sender
        self.smtp_password=config_obj.smtp_password
        self.smtp_receiver=config_obj.smtp_receiver
        self.smtp_cc=config_obj.smtp_cc
        self.smtp_subject=config_obj.smtp_subject
        self.smtp_body=smtp_body
        self.smtp_file=smtp_file_path

    def mail_content(self):
        if self.smtp_file !=None:
            if self.smtp_file.split('.')[-1].__eq__('zip'):
                return self.__mail_zip_content()
            # elif扩展
            else:
                return self.__mail_text_content()

    def mail_content_by_zip(self):
        report_zip_path=self.smtp_file + '/../禅道UI自动化测试报告.zip'
        zip_utils.zip_dir(self.smtp_file, report_zip_path)
        self.smtp_file=report_zip_path
        message=self.mail_content()
        return message

    def __mail_text_content(self):
        message=MIMEText(self.smtp_body, 'html', 'utf-8')
        message['From']=self.smtp_sender
        message['To']=self.smtp_receiver
        message['Cc']=self.smtp_cc
        message['Subject']=self.smtp_subject
        return message

    def __mail_zip_content(self):
        message=MIMEMultipart()
        with open(self.smtp_file, 'rb') as f:
            mime=MIMEBase('zip', 'zip', filename=self.smtp_file.split('/')[-1])
            mime.add_header('Content-Disposition', 'attachment',
                            filename=('gb2312', '', self.smtp_file.split('/')[-1]))
            mime.add_header('Content-ID', '<0>')
            mime.add_header('X-Attachment-Id', '0')
            mime.set_payload(f.read())
            encoders.encode_base64(mime)
            message.attach(mime)
        message.attach(MIMEText(self.smtp_body, 'html', 'utf-8'))
        message['From']=self.smtp_sender
        message['To']=self.smtp_receiver
        message['Cc']=self.smtp_cc
        message['Subject']=self.smtp_subject
        return message

    def zip_send_mail(self):
        try:
            smtp=smtplib.SMTP()
            smtp.connect(self.smtp_server, 25)  # 25 为 SMTP 端口号
            smtp.login(self.smtp_sender, self.smtp_password)
        except:
            smtp=smtplib.SMTP_SSL()
            smtp.login(self.smtp_sender, self.smtp_password)
        mail_content=self.mail_content_by_zip()
        try:
            smtp.sendmail(self.smtp_sender, self.smtp_receiver.split(',') + self.smtp_cc.split(','),
                          mail_content.as_string())
            logs_obj.info('邮件发送人是:{},邮件接收人是;{},邮件抄送人是:{}'.format(self.smtp_sender,
                                                                           self.smtp_receiver.split(',')
                                                                          ,self.smtp_cc.split(',')))
            print("邮件发送成功")
        except smtplib.SMTPException:
            logs_obj.info('邮件发送失败,失败原因:{}'.format(smtplib.SMTPException))
        smtp.close()
        logs_obj.info('发送邮件结束')

excel_util.py读取excel工具包:

# encoding:utf-8
# @author:yangshhiyu
# @file:excel_utils.py
# @time:2022/7/2415:29
# @desc:底层读取excel底层封装
import xlrd, os
from common.config_utils import config_obj

# current_path=os.path.dirname(__file__)
# el_path=os.path.join(current_path, '../element_info_datas/element_info_datas.xlsx')
# test_data_path=os.path.join(current_path,'..',config_obj.test_data_path)

class ExcelUtils():
    def __init__(self, excel_path, sheet_name=None):
        self.excel_path=excel_path
        self.sheet_name=sheet_name
        self.sheet_data=self.__get_sheet_data()

    def __get_sheet_data(self):
        '''
        通过sheet_name 获取一个sheet,如果没有就返回第一个sheet
        :return: sheet  data
        '''
        workbook=xlrd.open_workbook(self.excel_path)
        if self.sheet_name:
            sheet=workbook.sheet_by_name(self.sheet_name)
        else:
            sheet=workbook.sheet_by_index(0)
        return sheet

    @property
    def __get_row_count(self):
        '''获取总的行数'''
        row_count=self.__get_sheet_data().nrows
        return row_count

    @property
    def __get_col_count(self):
        '''获取总的列数 '''
        rol_count=self.__get_sheet_data().ncols
        return rol_count

    def get_sheet_data_by_list(self):
        '''
        通过读取excel中数据
        :return: [[],[],[]....]
        '''
        all_excel_data=[]  # 总的数据
        for rownum in range(self.__get_row_count):
            row_excel_data=[]  # 一行的数据
            for colnum in range(self.__get_col_count):
                cell_value=self.__get_sheet_data().cell_value(rownum, colnum)
                row_excel_data.append(cell_value)
            all_excel_data.append(row_excel_data)
        return all_excel_data


if __name__=='__main__':
    current_path=os.path.dirname(__file__)
    el_path=os.path.join(current_path, '../element_info_datas/element_info_datas.xlsx')
    test_data_path=os.path.join(current_path, '..', config_obj.test_data_path)
    datas=ExcelUtils(test_data_path,'login_suit').get_sheet_data_by_list()
    for data in datas:
        print(data)

HTMLTestReportCN.py生产测试报告类,可以自行网上下载:

log_util.py日志文件类:

日志文件目录:

import logging, os,time
from common.config_utils import config_obj

logs_path=os.path.join(os.path.dirname(__file__), '..',config_obj.get_log_path)


class LogUtils(object):
    def __init__(self, logger=None):
        self.log_name=os.path.join(logs_path,'UITest_%s.log'%time.strftime('%Y_%m_%d'))
        self.logger=logging.getLogger(logger)
        self.logger.setLevel(config_obj.get_log_level)  #日志级别,info 级别

        self.fh=logging.FileHandler(self.log_name, encoding='utf-8')
        self.fh.setLevel(config_obj.get_log_level)
        self.ch=logging.StreamHandler()   #写控制带
        self.ch.setLevel(config_obj.get_log_level)

        formatter=logging.Formatter("[%(asctime)s]  - %(filename)s - [line:%(lineno)d] - %(levelname)s: %(message)s")
        self.fh.setFormatter(formatter)
        self.ch.setFormatter(formatter)
        self.logger.addHandler(self.fh)
        self.logger.addHandler(self.ch)
        self.fh.close()
        self.ch.close()

    def info(self, message):
        return self.logger.info(message)

    def error(self, message):
        return self.logger.error(message)

    def debug(self, message):
        return self.logger.debug(message)

    def critical(self, message):
        return self.logger.critical(message)

    def warning(self, message):
        return self.logger.warning(message)


logs_obj=LogUtils()

selenium_base_case.py 测试用例初始化,清理工作封装:

# @desc:unittest 的二次封装
import unittest
from common.config_utils import config_obj
from common.browser import Browser
from common.base_page import BasePage
from common.log_utils import logs_obj

class SeleniumBaseCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.url=config_obj.get_zentao_url

    def setUp(self):
        self.driver=Browser().get_driver()
        self.base_page=BasePage(self.driver)
        self.base_page.set_browser_max()
        self.base_page.implicitly_wait()
        self.base_page.open_url(self.url)

    def tearDown(self):
        errors=self._outcome.errors
        for test,exc_info in errors:
            if exc_info:
                self.base_page.wait(3)
                self.base_page.screenshot_as_file()
        logs_obj.info('-----------  测试用例执行完毕  --------')
        self.base_page.quit_driver()

test_data_utils.py读取测试用例测试数据封装:

# encoding:utf-8
# @author:yangshhiyu
# @file:test_data_utils.py
# @time:2022/7/2720:54
# @desc:读取测试用例数据
import os
from common.excel_utils import ExcelUtils
from common.config_utils import config_obj


class TestDataUtils:
    def __init__(self,test_suit_name,test_file_name,test_class_name):
        current_path=os.path.dirname(__file__)
        test_data_path=os.path.join(current_path, '..', config_obj.test_data_path)
        test_data_path=os.path.join(test_data_path,test_suit_name,test_file_name+'.xlsx')
        self.test_class_name=test_class_name
        self.excel_data=ExcelUtils(test_data_path,test_class_name).get_sheet_data_by_list()
        self.excel_row=len(self.excel_data)

    def convert_exceldata_to_testdata(self):
        '''把excel表中的数据转换成用例的字典数据'''
        test_data_infos={}
        for i in range(1,self.excel_row):
            test_data_info={}
            # if self.excel_data[i][2].__eq__(self.test_class_name):
            test_data_info['test_name']=self.excel_data[i][1]
            test_data_info['isnot']=self.excel_data[i][2]
            test_data_info['isnot']=False if self.excel_data[i][2].__eq__('是') else True
            test_data_info['expected_result']=self.excel_data[i][3]
            test_parameter={}
            for j in range(4,len(self.excel_data[i])):
                if self.excel_data[i][j].__contains__('=') and len(self.excel_data[i][j])>2:
                    parameter_info=self.excel_data[i][j].split('=')
                    test_parameter[parameter_info[0]]=parameter_info[1]
            test_data_info['test_parameter']=test_parameter
            test_data_infos[self.excel_data[i][0]]=test_data_info
        return test_data_infos

if __name__=='__main__':
    infos=TestDataUtils('qa_suit','qa_case', 'CreateBugSuit').convert_exceldata_to_testdata()
    for info in infos.values():
        print(info)

zip_utils.py 将测试报告压缩成zip文件,进行封装:

# encoding:utf-8
# @author:yangshhiyu
# @file:test_data_utils.py
# @time:2022/7/2720:54
# @desc:读取测试用例数据
import os
from common.excel_utils import ExcelUtils
from common.config_utils import config_obj


class TestDataUtils:
    def __init__(self,test_suit_name,test_file_name,test_class_name):
        current_path=os.path.dirname(__file__)
        test_data_path=os.path.join(current_path, '..', config_obj.test_data_path)
        test_data_path=os.path.join(test_data_path,test_suit_name,test_file_name+'.xlsx')
        self.test_class_name=test_class_name
        self.excel_data=ExcelUtils(test_data_path,test_class_name).get_sheet_data_by_list()
        self.excel_row=len(self.excel_data)

    def convert_exceldata_to_testdata(self):
        '''把excel表中的数据转换成用例的字典数据'''
        test_data_infos={}
        for i in range(1,self.excel_row):
            test_data_info={}
            # if self.excel_data[i][2].__eq__(self.test_class_name):
            test_data_info['test_name']=self.excel_data[i][1]
            test_data_info['isnot']=self.excel_data[i][2]
            test_data_info['isnot']=False if self.excel_data[i][2].__eq__('是') else True
            test_data_info['expected_result']=self.excel_data[i][3]
            test_parameter={}
            for j in range(4,len(self.excel_data[i])):
                if self.excel_data[i][j].__contains__('=') and len(self.excel_data[i][j])>2:
                    parameter_info=self.excel_data[i][j].split('=')
                    test_parameter[parameter_info[0]]=parameter_info[1]
            test_data_info['test_parameter']=test_parameter
            test_data_infos[self.excel_data[i][0]]=test_data_info
        return test_data_infos

if __name__=='__main__':
    infos=TestDataUtils('qa_suit','qa_case', 'CreateBugSuit').convert_exceldata_to_testdata()
    for info in infos.values():
        print(info)

元素信息层:

登录页面元素信息类,login_page.py:

# desc:登录页面
from common.base_page import BasePage
from common.browser import Browser
from common.element_data_utils import ElementDataUtils
from common.log_utils import logs_obj

elements=ElementDataUtils('login','login_page').get_element_info()


class LoginPage(BasePage):
    def __init__(self,driver):
        # 元素识别分离
        super().__init__(driver)  # 初始化父类构建函授
        logs_obj.info('------- 启动浏览器  -------')

        self.username_input_box=elements['username_input_box']
        self.password_input_box=elements['password_input_box']
        self.login_button=elements['login_button']
        self.keepLogin_checkbox=elements['keepLogin_checkbox']

    def input_username(self, name):
        self.send_keys(self.username_input_box, name)

    def input_password(self, password):
        self.send_keys(self.password_input_box, password)

    def click_login(self):
        self.click(self.login_button)

    def click_keep_login(self):
        self.click(self.keepLogin_checkbox)

    #封装一个登录失败,弹出的提示框中点确认,并返回提示框中的内容
    def get_login_fail_alert_content(self):
        return self.switch_to_alert_2()


if __name__=='__main__':
    # 驱动处理
    login_page=LoginPage(Browser().get_driver())
    login_page.open_url('http://47.107.178.45/zentao/www/index.php?m=user&f=loginz')
    login_page.set_browser_max()
    login_page.input_username('test01')
    login_page.input_password('newdream13')
    login_page.click_login()
    login_page.get_login_fail_alert_content()
    login_page.quit_driver()

主页面元素信息类,main_page.py

# desc主页面类
from common.browser import Browser
from element_info.login.login_page import LoginPage
from common.element_data_utils import ElementDataUtils
from common.base_page import BasePage
from common.log_utils import logs_obj


class MainPage(BasePage):
    def __init__(self, driver):
        # 元素识别分离
        super().__init__(driver)  # 初始化父类构建函授
        logs_obj.info('------- 启动浏览器  -------')

        # 难点,页面衔接
        elements=ElementDataUtils('main', 'main_page').get_element_info()
        self.company_name_show_box=elements['company_name_show_box']
        self.my_zone_menu=elements['my_zone_menu']
        self.my_product_menu=elements['my_product_menu']
        self.my_project_menu=elements['my_project_menu']
        self.my_qa_menu=elements['my_qa_menu']
        self.username_show_box=elements['username_show_box']
        self.quit_button=elements['quit_button']

    def get_company_name(self):
        self.get_attribute(self.company_name_show_box)

    def goto_myzone(self):
        self.click(self.my_zone_menu)

    def goto_product(self):
        self.click(self.my_product_menu)

    def goto_project(self):
        self.click(self.my_project_menu)

    def goto_qa(self):
        self.click(self.my_qa_menu)

    def get_username(self):
        text=self.get_text_value(self.username_show_box)
        return text

    def click_username_button(self):
        self.click(self.username_show_box)

    def click_quit_button(self):
        self.click(self.quit_button)


if __name__=='__main__':
    driver=Browser().get_driver()
    LoginPage_obj=LoginPage(driver)
    main_page_obj=MainPage(driver)
    main_page_obj.open_url('http://47.107.178.45/zentao/www/index.php?m=user&f=loginz')
    main_page_obj.set_browser_max()
    # 登录
    LoginPage_obj.input_username('test01')
    LoginPage_obj.input_password('newdream123')
    LoginPage_obj.click_login()
    # 点击我的,项目,产品,
    main_page_obj.get_company_name()
    main_page_obj.get_username()
    main_page_obj.goto_myzone()
    main_page_obj.goto_project()
    main_page_obj.goto_product()
    main_page_obj.goto_qa()
    main_page_obj.click_username_button()
    main_page_obj.click_quit_button()
    LoginPage_obj.quit_driver()

产品信息元素类,product_page.py暂未补充代码。

qa页面元素类,create_bug_page.py

# @desc 提bug元素和动作页面
from common.base_page import BasePage
from common.browser import Browser
from common.log_utils import logs_obj
from common.element_data_utils import ElementDataUtils
from element_info.login.login_page import LoginPage
from element_info.main.main_page import MainPage


class CreateBUGPage(BasePage):
    def __init__(self,driver):
        # 元素识别分离
        super().__init__(driver)  # 初始化父类构建函授
        logs_obj.info('------- 启动浏览器  -------')
        # 难点,页面衔接
        elements=ElementDataUtils('qa', 'create_bug_page').get_element_info()
        self.bug_module_link=elements['bug_module_link']
        self.commit_bug_link=elements['commit_bug_link']
        self.product_selects=elements['product_selects']
        self.Ecommerce_projects_select=elements['Ecommerce_projects_select']
        self.module_box_selects=elements['module_box_selects']
        self.customer_center_select=elements['customer_center_select']
        self.bug_errors_selects=elements['bug_errors_selects']
        self.page_error_select=elements['page_error_select']
        self.bug_os_selects=elements['bug_os_selects']
        self.windows_select=elements['windows_select']
        self.bug_browser_selects=elements['bug_browser_selects']
        self.chrome_select=elements['chrome_select']
        self.edition_selects=elements['edition_selects']
        self.v1_0_select=elements['v1_0_select']
        self.loadAllUsers_button=elements['loadAllUsers_button']
        self.assignedTo_selects=elements['assignedTo_selects']
        self.LanShu_select=elements['LanShu_select']
        self.deadline_input=elements['deadline_input']
        self.bug_title_input=elements['bug_title_input']
        self.severity_selects=elements['severity_selects']
        self.two_severity_select=elements['two_severity_select']
        self.pri_selects=elements['pri_selects']
        self.two_pri_select=elements['two_pri_select']
        self.report_steps_iframe=elements['report_steps_iframe']
        self.report_steps_body=elements['report_steps_body']
        self.storyIdBox_span=elements['storyIdBox_span']
        self.upload_file_button=elements['upload_file_button']
        self.submit_button=elements['submit_button']

    # 点击主页面测试 --->
    def click_bug_module_link(self):
        self.click(self.bug_module_link)

    # 点击提bug按钮
    def click_commit_bug_link(self):
        self.click(self.commit_bug_link)

    def get_commit_bug_text(self):
        bug_text=self.get_text_value(self.commit_bug_link)
        return bug_text

    # 激活系统存在的产品
    def click_product_selects(self):
        self.click(self.product_selects)

    # 选择电商项目产品项
    def click_Ecommerce_projects_select(self):
        self.click(self.Ecommerce_projects_select)

    # 激活产品模块
    def click_module_selects(self):
        self.click(self.module_box_selects)

    # 选择/后台-客户中心模块
    def click_customer_center_select(self):
        self.click(self.customer_center_select)

    # 激活bug错误类型下拉框
    def click_bug_selects(self):
        self.click(self.bug_errors_selects)

    # 点击界面错误项
    def click_page_error_select(self):
        self.click(self.page_error_select)

    # 激活bug系统错误类型
    def click_bug_os_selects(self):
        self.click(self.bug_os_selects)

    # 选择bug错误类型:windows
    def click_bug_windows_select(self):
        self.click(self.windows_select)

    # 激活bug属于什么浏览器错误
    def click_bug_browser_selects(self):
        self.click(self.bug_browser_selects)

    # 选择属于chrome 浏览器
    def click_chrome_select(self):
        self.click(self.chrome_select)

    # 点击版本下拉框
    def click_edition_selects(self):
        self.click(self.edition_selects)

    # 选择v1.0版本
    def click_v1_0_select(self):
        self.click(self.v1_0_select)

    # 点击加载所有用户按钮
    def click_loadAllUsers(self):
        self.click(self.loadAllUsers_button)

    # 激活指派人下拉列表
    def click_assignedTo_selects(self):
        self.click(self.assignedTo_selects)

    # 选择指派人兰输
    def click_LanShu_select(self):
        self.click(self.LanShu_select)

    # 输入截至日期:'2022-08-08'
    def input_deadline(self,deadline):
        self.send_keys(self.deadline_input,deadline )

    # 输入bug标题:'人才中心-新增接口404'
    def input_bug_title(self,bug_title):
        self.send_keys(self.bug_title_input, bug_title)

    # 激活严重等级下拉框
    def click_severity_selects(self):
        self.click(self.severity_selects)

    # 选择严重程度2选项
    def click_two_severity_select(self):
        self.click(self.two_severity_select)

    # 激活优先级下拉框
    def click_pri_selects(self):
        self.click(self.pri_selects)

    # 选择严重程度2选项
    def click_two_pri_select(self):
        self.click(self.two_pri_select)

    # 切入重现步骤框架中
    def enter_report_steps_iframe(self):
        self.switch_to_frame(element_info=self.report_steps_iframe)

    # 清空重现步骤内容
    def clear_report_steps_body(self):
        self.clear(self.report_steps_body)

    # 输入重现步骤内容:'[步骤]:1.登录成功后。2.点击人才中心。3.点击新增按钮。4.输入内容,点击保存按钮。.\n\n'
    #                        '[结果]:点击保存提示:"system error".\n\n'
    #                        '[期望]:点击保存提示:"保存成功"'
    def input_report_steps_body(self,report_steps_content):
        self.send_keys(self.report_steps_body,report_steps_content)

    # 激活相关需求
    def click_storyBox_span(self):
        self.click(self.storyIdBox_span)

    # 点击上传文件按钮
    def click_upload_file_button(self):
        self.click(self.upload_file_button)

    # 点击提交按钮
    def click_submit_button(self):
        self.click(self.submit_button)


if __name__=='__main__':
    driver=Browser().get_driver()
    LoginPage_obj=LoginPage(driver)
    main_page_obj=MainPage(driver)
    create_bug_page_obj=CreateBUGPage(driver)
    main_page_obj.open_url('http://47.107.178.45/zentao/www/index.php?m=user&f=loginz')
    main_page_obj.set_browser_max()
    # 登录
    LoginPage_obj.input_username('test01')
    LoginPage_obj.input_password('newdream123')
    LoginPage_obj.click_login()
    main_page_obj.goto_qa()
    create_bug_page_obj.click_bug_module_link()
    # create_bug_page_obj.click_commit_bug_link()
    print(create_bug_page_obj.get_commit_bug_text())

动作层,目录结构如下图:

login_action.py

# -- coding: utf-8 --
# @Time : 2022/7/22 17:13
# @Author : siyu.yang
# @File : login_action.py
# @Software: PyCharm
# @desc: 功能层
from element_info.login.login_page import LoginPage
from element_info.main.main_page import MainPage
from common.config_utils import config_obj


class LoginAction():
    def __init__(self, driver):
        self.login_page=LoginPage(driver)

    # 登录操作
    def login_action(self, username, password):
        self.login_page.input_username(username)
        self.login_page.input_password(password)
        self.login_page.click_login()

    def login_success(self, username, password):
        self.login_action(username, password)
        return MainPage(self.login_page.driver)

    def login_fail(self, username, password):
        self.login_action(username, password)
        return self.login_page.get_login_fail_alert_content()

    # 默认登录
    def default_login(self):
        self.login_action(config_obj.user_name, config_obj.pass_word)
        return MainPage(self.login_page.driver)

    # 扩展:
    def login_by_cookie(self):
        pass

main_action.py

# @desc: 主页面业务类
from element_info.login.login_page import LoginPage
from element_info.main.main_page import MainPage


class MainPageAction():
    def __init__(self, driver):
        self.main_page=MainPage(driver)

    # 登录-进入我的地盘-退出
    def goto_myzone_quit(self):
        self.main_page.goto_myzone()
        self.main_page.click_username_button()
        self.main_page.click_quit_button()
        return LoginPage(self.main_page.driver)  # 退出操作返回主页面

    # 登录-点击我的项目-退出
    def goto_project_quit(self):
        self.main_page.goto_project()
        self.main_page.click_username_button()
        self.main_page.click_quit_button()
        return LoginPage(self.main_page.driver)  # 退出操作返回主页面

    # 登录-点击我的产品-退出
    def goto_product_quit(self):
        self.main_page.goto_product()
        self.main_page.click_username_button()
        self.main_page.click_quit_button()
        return LoginPage(self.main_page.driver)  # 退出操作返回主页面

    #登录-获取当前登录的用户-退出
    def get_username_quit(self):
        self.main_page.get_username()
        self.main_page.click_username_button()
        self.main_page.click_quit_button()
        return LoginPage(self.main_page.driver)  # 退出操作返回主页面

quit_action.py

from element_info.main.main_page import MainPage
from element_info.login.login_page import LoginPage


class QuitAction():
    def __init__(self, driver):
        self.main_page=MainPage(driver)

    def quit_action(self):
        self.main_page.click_username_button()
        self.main_page.click_quit_button()
        return LoginPage(self.main_page.driver)

create_bug_action.py

# -- coding: utf-8 --
# @Time : 2022/7/22 17:54
# @Author : siyu.yang
# @File : create_bug_action.py
# 可以把操作封装成功能函数也行
from element_info.qa.create_bug_page import CreateBUGPage
from actions.main_action import MainPage
import os


class CreateBugAction():
    def __init__(self, driver):
        self.bug_page=CreateBUGPage(driver)

    def commit_bug_action(self, deadline, bug_title, report_steps_content):
        '''
        提bug
        :param deadline: 截至日期
        :param bug_title: bug的标题
        :param report_steps_content: 重现步骤
        :return:
        '''
        self.bug_page.click_bug_module_link()
        self.bug_page.click_commit_bug_link()
        self.bug_page.click_product_selects()
        self.bug_page.click_Ecommerce_projects_select()
        self.bug_page.wait(1)
        self.bug_page.click_module_selects()
        self.bug_page.click_customer_center_select()
        self.bug_page.wait(1)
        self.bug_page.click_bug_selects()
        self.bug_page.click_page_error_select()
        self.bug_page.click_bug_os_selects()
        self.bug_page.click_bug_windows_select()
        self.bug_page.click_bug_browser_selects()
        self.bug_page.click_chrome_select()
        self.bug_page.click_edition_selects()
        self.bug_page.click_v1_0_select()
        self.bug_page.wait(1)
        self.bug_page.click_loadAllUsers()
        self.bug_page.wait(1)
        self.bug_page.click_assignedTo_selects()
        self.bug_page.wait(1)
        self.bug_page.click_LanShu_select()
        self.bug_page.wait(1)
        self.bug_page.input_deadline(deadline)
        self.bug_page.input_bug_title(bug_title)
        self.bug_page.click_severity_selects()
        self.bug_page.click_two_severity_select()
        self.bug_page.click_pri_selects()
        self.bug_page.click_two_pri_select()
        self.bug_page.enter_report_steps_iframe()
        self.bug_page.clear_report_steps_body()
        self.bug_page.input_report_steps_body(report_steps_content)
        self.bug_page.switch_to_frame()
        self.bug_page.switch_to_default_content()
        self.bug_page.scroll(150)
        self.bug_page.click_storyBox_span()
        self.bug_page.click_upload_file_button()
        os.system('E:/auto3_script/up.exe')
        self.bug_page.scroll(100)
        self.bug_page.click_submit_button()
        return MainPage(self.bug_page.driver)

testcase层,目录结果如下图:

login_case.py

# -- coding: utf-8 --
# @Time : 2022/7/22 17:57
# @Author : siyu.yang
# @File : login_case.py
# @Software: PyCharm
import unittest
from actions.login_action import LoginAction
from common.selenium_base_case import SeleniumBaseCase
from common.test_data_utils import TestDataUtils


class LoginCase(SeleniumBaseCase):
    test_class_data=TestDataUtils('login_suit', 'login_case', 'LoginCase').convert_exceldata_to_testdata()

    def setUp(self):
        super().setUp()
        print('LoginCase 测试类初始化父类')
        # self.test_class_data=TestDataUtils('login_suit', 'LoginCase').convert_exceldata_to_testdata()
        #

    @unittest.skipIf(test_class_data['test_login_success'].get('isnot'), reason='如果条件为真,就执行改用例')
    def test_login_success(self):
        test_function_data=self.test_class_data['test_login_success']
        login_action=LoginAction(self.base_page.driver)
        login_action=login_action.login_success(test_function_data['test_parameter'].get('username'),
                                                  test_function_data['test_parameter'].get('password'))
        actual=login_action.get_username()
        self.assertEqual(actual, test_function_data['expected_result'], 'test01,登录失败')

    @unittest.skipIf(test_class_data['test_login_fail_case'].get('isnot'), reason='如果条件为真,就执行改用例')
    def test_login_fail(self):
        test_function_data=self.test_class_data['test_login_fail_case']
        login_action=LoginAction(self.base_page.driver)
        actual=login_action.login_fail(test_function_data['test_parameter'].get('username'),
                                         test_function_data['test_parameter'].get('password'))
        self.assertEqual(actual, test_function_data['expected_result'])


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

quit_case.py

import unittest
from actions.login_action import LoginAction
from actions.quit_action import QuitAction
from common.selenium_base_case import SeleniumBaseCase
from common.test_data_utils import TestDataUtils


class QuitCase(SeleniumBaseCase):
    test_class_data=TestDataUtils('main_suit', 'quit_case', 'QuitCase').convert_exceldata_to_testdata()
    def setUp(self):
        super().setUp()
        print('LoginCase 测试类初始化父类')

    @unittest.skipIf(test_class_data['test_quit_success'].get('isnot'), reason='如果条件为真,就执行改用例')
    def test_quit_success(self):
        test_function_data=self.test_class_data['test_quit_success']
        """正常退出操作"""
        login_action=LoginAction(self.driver)
        main_page=login_action.default_login()
        quit_action=QuitAction(main_page.driver)
        login_page=quit_action.quit_action()  # 退出后返回主页面
        actual=login_page.get_title()
        self.assertEqual(actual.__contains__('用户登录'), True)


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

commit_bug_case.py

import unittest
from actions.login_action import LoginAction
from actions.create_bug_action import CreateBugAction
from common.selenium_base_case import SeleniumBaseCase
from common.test_data_utils import TestDataUtils


class CreateBugSuit(SeleniumBaseCase):
    test_bug_class_data=TestDataUtils('qa_suit', 'qa_case', 'CreateBugSuit').convert_exceldata_to_testdata()

    def setUp(self):
        super().setUp()
        print('LoginCase 测试类初始化父类')
        # self.test_bug_class_data=TestDataUtils('bug_suit', 'CreateBugSuit').convert_exceldata_to_testdata()

    @unittest.skipIf(test_bug_class_data['test_commit_bug_success'].get('isnot'), reason='如果条件为真,就执行改用例')
    def test_commit_bug_success(self):
        """正常提交bug操作"""
        test_function_data=self.test_bug_class_data['test_commit_bug_success']
        login_action=LoginAction(self.base_page.driver)
        main_page=login_action.default_login()
        main_page.goto_qa()

        bug_page=CreateBugAction(main_page.driver)
        bug_page.commit_bug_action(test_function_data['test_parameter'].get('deadline'),
                                   test_function_data['test_parameter'].get('bug_title'),
                                   test_function_data['test_parameter'].get('report_steps_content'))
        actual_result=main_page.get_title()
        self.assertEqual(actual_result.__contains__(test_function_data['expected_result']), True)


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

screen_shot 截图图片层

test_data测试数据层,目录结构如下图;


其中login_case.xlsx内容为,其它页面内容相似:

runner 运行层,run_all_case.py

import os, unittest
import shutil
import sys
sys.path.append('C:\\Users\\kcadmin\\Desktop\\python code\\PO_UI_Test_Framework')
from common import HTMLTestReportCN
from common.config_utils import config_obj
from common.email_utils import EmailUtils

current_path=os.path.dirname(__file__)
case_path=os.path.join(current_path, '..', config_obj.case_path)
report_path=os.path.join(current_path, '..', config_obj.report_path)


class RunAllCase:
    def __init__(self):
        self.test_case=case_path
        self.report_path=report_path
        self.title="禅道UI自动化测试报告"
        self.description="禅道UI自动化测试报告"

    def run(self):
        discover=unittest.defaultTestLoader.discover(start_dir=self.test_case,
                                                       pattern='*_case.py',
                                                       top_level_dir=self.test_case)
        all_suit=unittest.TestSuite()
        all_suit.addTest(discover)
        report_dir=HTMLTestReportCN.ReportDirectory(self.report_path)
        report_dir.create_dir(self.title)
        dir_path=HTMLTestReportCN.GlobalMsg.get_value('dir_path')
        report_path=HTMLTestReportCN.GlobalMsg.get_value('report_path')
        fp=open(report_path, 'wb')
        runner=HTMLTestReportCN.HTMLTestRunner(stream=fp,
                                                 title=self.title,
                                                 description=self.description,
                                                 tester='YangShiYu')
        runner.run(all_suit)
        fp.close()
        return dir_path


if __name__=='__main__':
    dir_path=RunAllCase().run()  #运行所有用例
    #将生成的测试报告打包成zip文件,并发送邮件
    # shutil.copytree(dir_path,sys.argv[1])
    smtp_body='禅道UI自动化测试报告,请注意查收!'
    send_email_obj=EmailUtils(smtp_body, dir_path).zip_send_mail()

最后运行run_all_case.py,会自动生产测试报告和产生日志文件,且对应的邮件也会收到运行的测试报告