前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ui自动化Python版本

ui自动化Python版本

原创
作者头像
Meccer
修改2022-02-11 14:21:41
1.5K0
修改2022-02-11 14:21:41
举报
文章被收录于专栏:测试学习之路测试学习之路

框架设计

pytest

selenium

POM页面对象模型(Page Object Model)


目录结构

代码语言:javascript
复制
common                 ——公共类
Page                   ——基类
PageElements           ——页面元素类
PageObject             ——页面对象类
TestCase               ——测试用例
conftest.py            ——pytest前后置配置文件
pytest.ini             ——pytest配置文件

添加配置文件

配置文件总是项目中必不可少的部分!

将固定不变的信息集中在固定的文件中

conf.py

项目中都应该有一个文件对整体的目录进行管理,我也在这个python项目中设置了此文件。

在项目config目录创建conf.py文件,所有的目录配置信息写在这个文件里面。

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
from selenium.webdriver.common.by import By
from utils.times import dt_strftime


class ConfigManager(object):
    # 项目目录
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    # 页面元素目录
    ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')

    # 报告文件
    REPORT_FILE = os.path.join(BASE_DIR, 'report.html')

    #元素文件
    DATA_PATH = os.path.join (BASE_DIR, 'page_element', 'data.xlsx')

    #配置目录
    CONFIG_PATH = os.path.join (BASE_DIR, "config", "config.ini")

    #谷歌浏览器驱动
    CHROMEDRIVER_PATH = os.path.join (BASE_DIR, "resource","chromedriver.exe")

    # 邮件信息
    EMAIL_INFO = {
        'username': '553187951@163.com',  # 切换成你自己的地址
        'password': 'HGCXVUSRONKBRLTS',
        'smtp_host': 'smtp.163.com',
        'smtp_port': 465
    }

    # 收件人
    ADDRESSEE = [
        '553187951@qq.com',
    ]

    @property
    def screen_path(self):
        """截图目录"""
        screenshot_dir = os.path.join(self.BASE_DIR, 'screen_capture')
        if not os.path.exists(screenshot_dir):
            os.makedirs(screenshot_dir)
        now_time = dt_strftime("%Y%m%d%H%M%S")
        screen_file = os.path.join(screenshot_dir, "{}.png".format(now_time))
        return now_time, screen_file

    @property
    def log_file(self):
        """日志目录"""
        log_dir = os.path.join(self.BASE_DIR, 'logs')
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        return os.path.join(log_dir, '{}.log'.format(dt_strftime()))

    @property
    def ini_file(self):
        """配置文件"""
        ini_file = self.CONFIG_PATH
        if not os.path.exists(ini_file):
            raise FileNotFoundError("配置文件%s不存在!" % ini_file)
        return ini_file


cm = ConfigManager()
if __name__ == '__main__':
    print(cm.BASE_DIR)

在这个文件中我们可以设置自己的各个目录,也可以查看自己当前的目录。

遵循了约定:不变的常量名全部大写,函数名小写。看起来整体美观。

config.ini

在项目config目录新建一个config.ini文件,里面暂时先放入我们的需要测试的host和url

代码语言:javascript
复制
[HOST]
host = https://account.369zhan.com


url=/auth/loginPage?platformName=%E6%99%BA%E5%A5%A5%E4%B8%AD%E6%99%BA%E5%85%B4%E4%B8%BB%E5%9C%BA%E6%9C%8D%E5%8A%A1%E5%B9%B3%E5%8F%B0&tokenUrl=https%3A%2F%2Fzhan.zzxes.com.cn%2F%23%2F

读取配置文件

配置文件创建好了,接下来我们需要读取这个配置文件以使用里面的信息。

我们在common目录中新建一个readconfig.py文件

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import configparser
from config.conf import cm




class ReadConfig(object):
    """配置文件"""

    def __init__(self):
        self.config = configparser.RawConfigParser()  # 当有%的符号时请使用Raw读取
        self.config.read(cm.ini_file, encoding='utf-8')

    def _get(self, section, option):
        """获取"""
        return self.config.get(section, option)

    def _set(self, section, option, value):
        """更新"""
        self.config.set(section, option, value)
        with open(cm.ini_file, 'w') as f:
            self.config.write(f)

    def url(self,HOST, key):
        return self._get(HOST, key)


ini = ReadConfig()

if __name__ == '__main__':
    print(ini.url("HOST","host")+ini.url("HOST","url"))

用python内置的configparser模块对config.ini文件进行了读取。

对于url值的提取,使用了@property属性值,写法更简单。

管理时间

因为很多的模块会用到时间戳,或者日期等等字符串,所以我们先单独把时间封装成一个模块。

然后让其他模块来调用即可。在utils目录新建times.py模块

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import time
import datetime
from functools import wraps


def timestamp():
    """时间戳"""
    return time.time()


def dt_strftime(fmt="%Y%m"):
    """
    datetime格式化时间
    :param fmt "%Y%m%d %H%M%S
    """
    return datetime.datetime.now().strftime(fmt)


def sleep(seconds=1.0):
    """
    睡眠时间
    """
    time.sleep(seconds)


def running_time(func):
    """函数运行时间"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = timestamp()
        res = func(*args, **kwargs)
        print("校验元素done!用时%.3f秒!" % (timestamp() - start))
        return res

    return wrapper


if __name__ == '__main__':
    print(dt_strftime("%Y%m%d%H%M%S"))


记录操作日志

日志,大家应该都很熟悉这个名词,就是记录代码中的动作。

utils目录中新建logger.py文件。

这个文件就是我们用来在自动化测试过程中记录一些操作步骤的。

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import logging
from config.conf import cm


class Log:
    def __init__(self):
        self.logger = logging.getLogger()
        if not self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            # 创建一个handle写入文件
            fh = logging.FileHandler(cm.log_file, encoding='utf-8')
            fh.setLevel(logging.INFO)

            # 创建一个handle输出到控制台
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)

            # 定义输出的格式
            formatter = logging.Formatter(self.fmt)
            fh.setFormatter(formatter)
            ch.setFormatter(formatter)

            # 添加到handle
            self.logger.addHandler(fh)
            self.logger.addHandler(ch)

    @property
    def fmt(self):
        return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'


log = Log().logger

if __name__ == '__main__':
    log.info('hello world')

在终端中运行该文件,就看到命令行打印出了:

代码语言:javascript
复制
INFO	2020-12-01 16:00:05,467	[logger.py:38]	hello world

然后在项目logs目录下生成了当月的日志文件。

pytest 输出日志需要在配置文件中添加参数

代码语言:javascript
复制
log_cli = 1
log_cli_level = INFO
log_cli_format = %(asctime) s [%(levelname) 8s] %(message) s (%(filename) s:%(lineno) s)
log_cli_date_format=%Y-%m-%d %H:%M:%S

简单理解POM模型

由于下面要讲元素相关的,所以首先理解一下POM模型

Page Object模式具有以下几个优点。

  • 抽象出对象可以最大程度地降低开发人员修改页面代码对测试的影响, 所以, 你仅需要对页 面对象进行调整, 而对测试没有影响;
  • 可以在多个测试用例中复用一部分测试代码;
  • 测试代码变得更易读、 灵活、 可维护

Page Object模式图

img
img
  • basepage ——selenium的基类,对selenium的方法进行封装
  • pageelements——页面元素,把页面元素单独提取出来,放入一个文件中
  • searchpage ——页面对象类,把selenium方法和页面元素进行整合
  • testcase ——使用pytest对整合的searchpage进行测试用例编写

通过上图我们可以看出,通过POM模型思想,我们把:

  • selenium方法
  • 页面元素
  • 页面对象
  • 测试用例

以上四种代码主体进行了拆分,虽然在用例很少的情况下做会增加代码,但是当用例多的时候意义很大,代码量会在用例增加的时候显著减少。我们维护代码变得更加直观明显,代码可读性也变得比工厂模式强很多,代码复用率也极大的得到了提高。


元素定位

xpath
语法规则

[菜鸟教程](https://www.runoob.com/xpath/xpath-intro.html)中对于 xpath 的介绍是一门在 XML 文档中查找信息的语言。

表达式

介绍

备注

/

根节点

绝对路径

//

当前节点的所有子节点

相对路径

*

所有节点元素的

@

属性名的前缀

@class @id

*[1]

[] 下标运算符

[]

[ ]谓词表达式

//input[@id='kw']

Following-sibling

当前节点之后的同级

preceding-sibling

当前节点之前的同级

parent

当前节点的父级节点

定位工具
  • chropath

管理页面元素

项目框架设计中有一个目录page_element就是专门来存放定位元素的文件的。

通过对各种配置文件的对比,我在这里选择的是excel文件。其易读,交互性好。

page_element中新建一个data.xlxs文件。

common目录中创建ParseExcel.py文件。

代码语言:javascript
复制
import logging

from openpyxl import load_workbook

from config.conf import cm


class ParseExcel(object):

    def __init__(self):
        self.wk = load_workbook(cm.DATA_PATH)
        self.excelFile = cm.DATA_PATH

    def get_sheet_first(self):
        """获取sheet对象"""
        sheet = self.wk[0]
        return sheet

    def get_sheet_by_name(self, sheet_name):
        """获取sheet对象"""
        sheet = self.wk[sheet_name]
        return sheet

    def get_row_num(self, sheet):
        """获取有效数据的最大行号"""
        return sheet.max_row

    def get_cols_num(self, sheet):
        """获取有效数据的最大列号"""
        return sheet.max_column

    def get_row_values(self, sheet, row_num):
        """获取某一行的数据"""
        max_cols_num = self.get_cols_num(sheet)
        row_values = []
        for colsNum in range(1, max_cols_num + 1):
            value = sheet.cell(row_num, colsNum).value
            if value is None:
                value = ''
            row_values.append(value)
        return tuple(row_values)

    def get_column_values(self, sheet, column_num):
        """获取某一列的数据"""
        max_row_num = self.get_row_num(sheet)
        column_values = []
        for rowNum in range(2, max_row_num + 1):
            value = sheet.cell(rowNum, column_num).value
            if value is None:
                value = ''
            column_values.append(value)
        return tuple(column_values)

    def get_value_of_cell(self, sheet, row_num, column_num):
        """获取某一个单元格的数据"""
        value = sheet.cell(row_num, column_num).value
        if value is None:
            value = ''
        return value

    def get_all_values_of_sheet(self, sheet):
        """获取某一个sheet页的所有测试数据,返回一个元祖组成的列表"""
        max_row_num = self.get_row_num(sheet)
        column_num = self.get_cols_num(sheet)
        all_values = []
        for row in range(2, max_row_num + 1):
            row_values = []
            for column in range(1, column_num + 1):
                value = sheet.cell(row, column).value
                if value is None:
                    value = ''
                row_values.append(value)
            all_values.append(tuple(row_values))
        return all_values




if __name__ == '__main__':
    key="lp_loginUsername"
    exe=ParseExcel()
    sheet=exe.get_sheet_by_name('element')
    row = exe.get_row_num (sheet)
    try:
        for i in range(row):
            value = exe.get_value_of_cell (sheet,i+1,2)
            if value==key:
                LocatorValue=exe.get_value_of_cell (sheet,i+1,4)
                print (LocatorValue)

    except Exception:
        pass

然后就可以读取对应的元素。


封装Selenium基类

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
selenium基类
本文件存放了selenium基类的封装方法
"""
import logging
import time

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

from common.ParseExcelFile import ParseExcel
from config.conf import cm
from utils.logger import log
from utils.times import sleep


class basepage(object):
    """selenium基类"""

    def __init__(self, driver):
        # self.driver = webdriver.Chrome()
        self.driver = driver
        self.timeout = 20
        self.wait = WebDriverWait(self.driver, self.timeout)

    def get_url(self, url):
        """打开网址并验证"""
        self.driver.maximize_window()
        self.driver.set_page_load_timeout(60)
        try:
            self.driver.get(url)
            self.driver.implicitly_wait(10)
            log.info("打开网页:%s" % url)
        except TimeoutException:
            raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)

#通过excel读取方式获取元素定位方式,name,id,xpath等
    def getlocatorBy(self,key):
        global locatorBy
        excel = ParseExcel ()
        sheet = excel.get_sheet_by_name ('element')
        row = excel.get_row_num(sheet)
        try:
            for i in range (row):
                value = excel.get_value_of_cell (sheet, i + 1, 2)
                if value == key:
                    locatorBy = excel.get_value_of_cell (sheet, i + 1, 3)
                    break
        except Exception:
            pass
        return locatorBy

# 通过读取excel中的数据获取元素定位值
    def getlocatorValue(self,key):
        global locatorValue
        excel = ParseExcel ()
        sheet = excel.get_sheet_by_name ('element')
        row = excel.get_row_num(sheet)
        try:
            for i in range (row):
                value = excel.get_value_of_cell (sheet, i + 1, 2)
                if value == key:
                    locatorValue = excel.get_value_of_cell (sheet, i + 1, 4)
                    break
        except Exception:
            pass
        return locatorValue

 # 通过读取excel中的key,以元祖的形式输入定位方式和值。
    def getByLocal(self, key):
        locatorBy= self.getlocatorBy (key)
        locatorValue=self.getlocatorValue(key)
        if  locatorBy == 'name':
            return By.NAME, locatorValue
        elif locatorBy == 'id':
            return By.ID, locatorValue
        elif locatorBy == 'xpath':
            return By.XPATH, locatorValue
        elif locatorBy == 'link_text':
            return By.LINK_TEXT, locatorValue
        elif locatorBy == 'class_name':
            return By.CLASS_NAME, locatorValue
        else:
            print("暂未定义此定位元素方式")

    #js的定位封装
    def  getElement_js(self,key):
        locatorBy = self.getlocatorBy (key)
        locatorValue = self.getlocatorValue (key)
        log.info ("你的定位信息的方式为" + locatorBy);
        log.info ("你的定位信息的值为" + locatorValue);
        if locatorBy=="id":
            return self.driver.execute_script("return document.getElementById('%s')"%locatorValue)
        #如果是name,tag、classname的话返回多个元素对象的话,默认操作第一个
        elif locatorBy=="name":
            return self.driver.execute_script("return document.getElementsByName('%s')"%locatorValue)[0]
        elif locatorBy=="tag":
            return self.driver.execute_script("return document.getElementsByTagName('%s')"%locatorValue)[0]
        elif locatorBy=="css":
            pass
        else:
            log.info("定位方式不支持!")

    #显示等待定位单个元素方法方法
    def getElement(self,key):
        return WebDriverWait (self.driver, 10).until (lambda driver: driver.find_element (*(self.getByLocal(key))))# 判断元素是否存在

    #显示等待定位多个元素方法方法
    def getElements(self,key):
        return WebDriverWait (self.driver, 10).until (lambda driver: driver.find_elements(*(self.getByLocal(key))))# 判断元素集合是否存在
        # return self.driver.find_elements(*(self.getByLocal(key)))

    #通过文本直接定位对应元素
    def getElement_containstext(self,key):
        return self.driver.findElement(By.xpath("//*[contains(text(),'"+self.getlocatorValue(key)+"')]"));

    # 打开网页
    def open_url(self, url):
        log.info("打开地址"+url)
        self.driver.get(url)

    # 文本框输入数据
    def send(self, element, key):
        self.getElement(element).send_keys(key)

    def click(self, element):
        self.getElement(element).click()

    def clear(self, element):
        self.getElement(element).clear()

    def sleep(self,sec):
        time.sleep(sec)

    def get_txt(self,key):
        """
        方法用于获取元素文本值
        """
        _text = self.getElement(key).text
        return _text

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

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


if __name__ == "__main__":
    pass

创建页面对象

page_object目录下创建一个LoginPage.py文件。

代码语言:javascript
复制
from page.basepage import basepage


class LoginPage(basepage):
    #
    def __init__ (self, driver):
        self.driver = driver

    def get_loginUsername(self):
        return self.getElement("lp_loginUsername")

    def get_loginPassword(self):
        return self.getElement("lp_loginPassword")

    def get_loginButton(self):
        return self.getElement("lp_loginButton")


    def send_username(self,key):
        element=self.get_loginUsername()
        self.clear(element)
        self.send(element,key)


    def send_password(self,key):
        element = self.get_loginPassword ()
        self.clear(element)
        self.send (element, key)


    def click_loginButton(self):
        element=self.get_loginButton()
        self.click(element)



    def Login(self,username,password):
        self.send_username(username)
        self.send_password(password)
        self.click_loginButton()

下面开始编写测试用例。熟悉一下pytest测试框架。


简单了解Pytest

打开pytest框架的官网。http://www.pytest.org/en/latest/

代码语言:javascript
复制
# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

推荐看一下[上海悠悠的pytest教程](https://www.cnblogs.com/yoyoketang/tag/pytest/)。

pytest.ini

pytest项目中的配置文件,可以对pytest执行过程中操作做全局控制。

在项目根目录新建pytest.ini文件。

代码语言:javascript
复制
[pytest]
addopts = --html=report.html --self-contained-html
  • addopts 指定执行时的其他参数说明:
    • --html=report/report.html --self-contained-html 生成pytest-html带样式的报告
    • -s 输出用例中的调式信息
    • -q 安静的进行测试
    • -v可以输出用例更加详细的执行信息,比如用例所在的文件及用例名称等

编写测试用例

使用pytest编写测试用例。

TestCase目录中创建test_login.py文件。

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
import pytest
import allure

from page_object.LoginPage import LoginPage
from common.readconfig import ini
from utils.logger import log


@allure.feature("测试登录模块")
class TestLogin:
    @pytest.fixture(scope='function', autouse=True)
    def open_browser(self, drivers):
        l = LoginPage (drivers)
        l.get_url(ini.url("HOST","host")+ini.url("HOST","url"))

    @allure.story("测试登录用例")
    @pytest.mark.smoke
    @pytest.mark.parametrize ('username, password',[ ('13129562261', 'czh123')])
    def test_login(self, username, password,drivers):
        l = LoginPage (drivers)
        l.Login(username,password)


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

我们测试用了就编写好了。

  • pytest.fixture 这个实现了和unittest的setup,teardown一样的前置启动,后置清理的装饰器。

main方法中为执行启动的语句。

这时候我们应该进入执行了,但是还有一个问题,我们还没有把driver传递。

conftest.py

我们在项目根目录下新建一个conftest.py文件。

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import base64
import logging
import os
import pytest
import allure
from py.xml import html
from selenium import webdriver

from config.conf import cm
from common.readconfig import ini
from script.addpath import BASE_DIR
from utils.times import timestamp
from utils.send_mail import send_report

driver = None
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


@pytest.fixture(scope='session', autouse=True)
def drivers(request):
    global driver
    if driver is None:
        option = webdriver.ChromeOptions ()
        option.headless = False
        driver = webdriver.Chrome(executable_path =cm.CHROMEDRIVER_PATH,options = option)
        driver.maximize_window()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    report.description = str(item.function.__doc__)
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            screen_img = _capture_screenshot()
            if screen_img:
                html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
                       'onclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra


def pytest_html_results_table_header(cells):
    cells.insert(1, html.th('用例名称'))
    cells.insert(2, html.th('Test_nodeid'))
    cells.pop(2)


def pytest_html_results_table_row(report, cells):
    cells.insert(1, html.td(report.description))
    cells.insert(2, html.td(report.nodeid))
    cells.pop(2)


def pytest_html_results_table_html(report, data):
    if report.passed:
        del data[:]
        data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))


def pytest_html_report_title(report):
    report.title = "pytest示例项目测试报告"


def pytest_configure(config):
    config._metadata.clear()
    config._metadata['测试项目'] = "测试登录"
    config._metadata['测试地址'] = ini.url


def pytest_html_results_summary(prefix, summary, postfix):
    # prefix.clear() # 清空summary中的内容
    prefix.extend([html.p("所属公司: 九象展览科技")])
    prefix.extend([html.p("测试执行人: czh")])


def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """收集测试结果"""
    result = {
        "total": terminalreporter._numcollected,
        'passed': len(terminalreporter.stats.get('passed', [])),
        'failed': len(terminalreporter.stats.get('failed', [])),
        'error': len(terminalreporter.stats.get('error', [])),
        'skipped': len(terminalreporter.stats.get('skipped', [])),
        # terminalreporter._sessionstarttime 会话开始时间
        'total times': timestamp() - terminalreporter._sessionstarttime
    }
    print(result)
    if result['failed'] or result['error']:
        send_report()


def _capture_screenshot():
    """截图保存为base64"""
    now_time, screen_file = cm.screen_path
    driver.save_screenshot(screen_file)
    allure.attach.file(screen_file,
                       "失败截图{}".format(now_time),
                       allure.attachment_type.PNG)
    with open(screen_file, 'rb') as f:
        imagebase64 = base64.b64encode(f.read())
    return imagebase64.decode()

conftest.py测试框架pytest的胶水文件,里面用到了fixture的方法,封装并传递出了driver。


执行用例

以上我们已经编写完成了整个框架和测试用例。

我们进入到当前项目的主目录执行命令:

代码语言:javascript
复制
pytest

命令行输出:

代码语言:javascript
复制
============================= test session starts =============================
platform win32 -- Python 3.8.5, pytest-6.2.4, py-1.9.0, pluggy-0.13.1
rootdir: E:\workspace\ne_p_uitest, configfile: pytest.ini
plugins: allure-pytest-2.8.40, html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1
collected 1 item

TestCase/test_login.py::TestLogin::test_login[13129562261-czh123] 
------------------------------- live log setup --------------------------------
2022-02-08 18:33:09 [    INFO] 打开网页:https://account.369zhan.com/auth/loginPage?platformName=%E6%99%BA%E5%A5%A5%E4%B8%AD%E6%99%BA%E5%85%B4%E4%B8%BB%E5%9C%BA%E6%9C%8D%E5%8A%A1%E5%B9%B3%E5%8F%B0&tokenUrl=https%3A%2F%2Fzhan.zzxes.com.cn%2F%23%2F (basepage.py:37)
PASSED

------ generated html file: file://E:\workspace\ne_p_uitest\report.html -------
{'total': 1, 'passed': 1, 'failed': 0, 'error': 0, 'skipped': 0, 'total times': 9.162506341934204}
============================== 1 passed in 9.16s ==============================
Report successfully generated to allure-report

项目的report目录中生成了一个report.html文件。

这就是生成的测试报告文件。


发送邮件

当项目执行完成之后,需要发送到自己或者其他人邮箱里查看结果。

我们编写发送邮件的模块。

utils目录中新建send_mail.py文件

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import zmail
from config.conf import cm


def send_report():
    """发送报告"""
    with open(cm.REPORT_FILE, encoding='utf-8') as f:
        content_html = f.read()
    try:
        mail = {
            'from': '553187951@163.com',
            'subject': '最新的测试报告邮件',
            'content_html': content_html,
            'attachments': [cm.REPORT_FILE, ]
        }
        server = zmail.server(*cm.EMAIL_INFO.values())
        server.send_mail(cm.ADDRESSEE, mail)
        print("测试邮件发送成功!")
    except Exception as e:
        print("Error: 无法发送邮件,{}!", format())


if __name__ == "__main__":
    '''请先在config/conf.py文件设置QQ邮箱的账号和密码'''
    send_report()

执行该文件:

代码语言:javascript
复制
测试邮件发送成功!

运行

安装依赖

代码语言:javascript
复制
pip install -r requirements.txt

执行主文件

  • 在项目根目录执行run_case.py文件即可运行项目

allure参数说明

  • pytest --alluredir result-path
    • --clean-alluredir 清除历史生成记录
  • allure generate result-path
    • -c 生成报告前删除上一次生成的报告
    • -o 指定生成的报告目录
  • allure open report-path

持续集成:

在jekins上新建一个自由风格项目,然后进入项目配置页面:

1.设置工作做目录

2.配置构建命令

然后 插件管理处添加allure插件,

Global Tool Configuration中去加入allure相关路径配置

回到项目的配置页面,添加相关配置,主要是resultpath和reportpath

完成,此时项目构建完成后,会有对应的报告展示区域,点击后跳转至allure报告页

最后附上代码地址:

纳尼/MyPythonUItest (gitee.com)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 框架设计
    • 目录结构
      • 添加配置文件
        • conf.py
        • config.ini
        • 读取配置文件
      • 管理时间
        • 记录操作日志
          • 简单理解POM模型
            • 元素定位
              • 管理页面元素
                • 封装Selenium基类
                  • 创建页面对象
                    • 简单了解Pytest
                      • pytest.ini
                    • 编写测试用例
                      • conftest.py
                        • 执行用例
                          • 发送邮件
                            • 运行
                              • 安装依赖
                              • 执行主文件
                          • allure参数说明
                          • 持续集成:
                          相关产品与服务
                          持续集成
                          CODING 持续集成(CODING Continuous Integration,CODING-CI)全面兼容 Jenkins 的持续集成服务,支持 Java、Python、NodeJS 等所有主流语言,并且支持 Docker 镜像的构建。图形化编排,高配集群多 Job 并行构建全面提速您的构建任务。支持主流的 Git 代码仓库,包括 CODING 代码托管、GitHub、GitLab 等。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档