pytest-html的更多功能

前言

pytest的conftest.py文件觉得是一个值得探索的文件,它可以干什么呢

  • 可以修改pytest-html内容
  • 可以添加hook函数获取更多特性
  • 可以全局化driver

等等。

之前我们介绍过selenium+pytest的组合使用,也介绍了allure的使用,但是对于pytest-html没有怎么做优化。

今天趁着周末,来讲讲conftest.py的更多功能吧。

pytest版本说明

  • pytest版本——5.4.3
  • pytest-html版本——2.1.1

pytest-html标题更改

我们运行一下项目。查看结果。

pytest-html的更多功能

发现标题部分是report.html,没有什么可以使用东西。所以我们先来更改一下这部分。

修改项目根目录的conftest.py文件。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys

sys.path.append(‘.‘)
__author__ = ‘1084502012@qq.com‘

import os
import base64
import pytest
import allure
from py._xmlgen import html
from selenium import webdriver
from config.conf import SCREENSHOT_DIR
from tools.times import datetime_strftime
from common.inspect import inspect_element
driver = None


@pytest.fixture(scope=‘session‘, autouse=True)
def drivers(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()
    inspect_element()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin(‘html‘)
    outcome = yield
    report = outcome.get_result()
    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
        report.description = str(item.function.__doc__)
        report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")


@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.insert(1, html.th(‘用例名称‘))
    cells.insert(2, html.th(‘Test_nodeid‘))
    cells.pop(2)


@pytest.mark.optionalhook
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)


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


@pytest.mark.optionalhook
def pytest_html_report_title(report):
    report.title = "selenium+pytest示例项目测试报告"


def _capture_screenshot():
    ‘‘‘
    截图保存为base64
    ‘‘‘
    now_time = datetime_strftime("%Y%m%d%H%M%S")
    if not os.path.exists(SCREENSHOT_DIR):
        os.makedirs(SCREENSHOT_DIR)
    screen_path = os.path.join(SCREENSHOT_DIR, "{}.png".format(now_time))
    driver.save_screenshot(screen_path)
    allure.attach.file(screen_path, "测试失败截图...{}".format(
        now_time), allure.attachment_type.PNG)
    with open(screen_path, ‘rb‘) as f:
        imagebase64 = base64.b64encode(f.read())
    return imagebase64.decode()

通过pytest的hook功能,我们新添加了一个pytest_html_report_title函数。

然后我们来运行一下。在项目根目录cmd中输入pytest,运行完毕后查看报告文件。

pytest-html的更多功能

可以看到我们的报告标题已经被更改了,是不是更贴合测试的项目呢。

pytest-html Environment更改

在报告中有一个Environment环境变量选项,如图所示:

pytest-html的更多功能

这个对于我们的帮助不是很大,所以我们也更改一下他。

conftest.py文件中更新一下代码。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys

sys.path.append(‘.‘)
__author__ = ‘1084502012@qq.com‘

import os
import base64
import pytest
import allure
from py._xmlgen import html
from selenium import webdriver
from common.readconfig import ini
from config.conf import SCREENSHOT_DIR
from tools.times import datetime_strftime
from common.inspect import inspect_element

driver = None


@pytest.fixture(scope=‘session‘, autouse=True)
def drivers(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()
    inspect_element()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin(‘html‘)
    outcome = yield
    report = outcome.get_result()
    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
        report.description = str(item.function.__doc__)
        report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")


@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.insert(1, html.th(‘用例名称‘))
    cells.insert(2, html.th(‘Test_nodeid‘))
    cells.pop(2)


@pytest.mark.optionalhook
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)


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


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


@pytest.mark.optionalhook
def pytest_configure(config):
    config._metadata.clear()
    config._metadata[‘测试项目‘] = "测试百度官网搜索"
    config._metadata[‘测试地址‘] = ini.url



def _capture_screenshot():
    ‘‘‘
    截图保存为base64
    ‘‘‘
    now_time = datetime_strftime("%Y%m%d%H%M%S")
    if not os.path.exists(SCREENSHOT_DIR):
        os.makedirs(SCREENSHOT_DIR)
    screen_path = os.path.join(SCREENSHOT_DIR, "{}.png".format(now_time))
    driver.save_screenshot(screen_path)
    allure.attach.file(screen_path, "测试失败截图...{}".format(
        now_time), allure.attachment_type.PNG)
    with open(screen_path, ‘rb‘) as f:
        imagebase64 = base64.b64encode(f.read())
    return imagebase64.decode()

这次我们添加了pytest_configure函数。

  • 对原有的configure内容进行了清除。
  • 然后添加我们自己设定的两个值。

然后执行一下,查看结果:

pytest-html的更多功能

可以看到Environment中内容已经更改了,变成对我们有用的内容了。

pytest-html Summary更改

报告中还有一个Summary概要内容

pytest-html的更多功能

里面的都是对我们有用的,添加一些其他的信息。在contest.py中添加如下内容。

@pytest.mark.optionalhook
def pytest_html_results_summary(prefix, summary, postfix):
    prefix.extend([html.p("所属部门: XX公司测试部")])
    prefix.extend([html.p("测试执行人: 随风挥手")])

然后运行一下查看结果:

pytest-html的更多功能

可以看到添加的部门、执行人的信息已经在报告中出现了。

pytest执行失败发送邮件

之前我们讲过的示例项目中不管执行成功还是执行是失败都会发送邮件,但是这样有点不理智,收到邮件有点心慌慌,所以我们应该采取失败或者错误的时候在尝试发送邮件。

更新一下conftest.py文件。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys

sys.path.append(‘.‘)
__author__ = ‘1084502012@qq.com‘

import os
import base64
import pytest
import allure
from py._xmlgen import html
from selenium import webdriver
from common.readconfig import ini
from config.conf import SCREENSHOT_DIR
from common.inspect import inspect_element
from tools.send_mail import send_report
from tools.times import datetime_strftime, timestamp

driver = None


@pytest.fixture(scope=‘session‘, autouse=True)
def drivers(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()
    inspect_element()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin(‘html‘)
    outcome = yield
    report = outcome.get_result()
    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
        report.description = str(item.function.__doc__)


@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.insert(1, html.th(‘用例名称‘))
    cells.insert(2, html.th(‘Test_nodeid‘))
    cells.pop(2)


@pytest.mark.optionalhook
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)


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


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


@pytest.mark.optionalhook
def pytest_configure(config):
    config._metadata.clear()
    config._metadata[‘测试项目‘] = "测试百度官网搜索"
    config._metadata[‘测试地址‘] = ini.url


@pytest.mark.optionalhook
def pytest_html_results_summary(prefix, summary, postfix):
    # prefix.clear() # 清空summary中的内容
    prefix.extend([html.p("所属部门: XX公司测试部")])
    prefix.extend([html.p("测试执行人: 随风挥手")])


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 = datetime_strftime("%Y%m%d%H%M%S")
    if not os.path.exists(SCREENSHOT_DIR):
        os.makedirs(SCREENSHOT_DIR)
    screen_path = os.path.join(SCREENSHOT_DIR, "{}.png".format(now_time))
    driver.save_screenshot(screen_path)
    allure.attach.file(screen_path, "测试失败截图...{}".format(
        now_time), allure.attachment_type.PNG)
    with open(screen_path, ‘rb‘) as f:
        imagebase64 = base64.b64encode(f.read())
    return imagebase64.decode()

添加了一个hook函数pytest_terminal_summary该函数可以统计测试结果

passed、failed、error、skipped

因为要发送邮件所以只用failed和error作为发送邮件成立的条件。

代码加好了,我们来更新一下测试用例test_002,设置一个预期的错误:

        assert not all(["selenium" in i for i in search.imagine])

然后pyetest运行一下:

pytest-html的更多功能

可以看到有一个failed然后紧接着提示测试邮件发送成功。

我们打开邮箱查看一下,成功收到了错误邮件:

pytest-html的更多功能

pytest-html的相关内容就先到这里吧,下期再见。

参考文档

- 上海悠悠-pytest文档35-Hooks函数之统计测试结果(pytest_terminal_summary)

pytest-html的更多功能

上一篇:apache安装与启动


下一篇:接口开源框架--httprunner(三)