[pytest] HTML report modification and localization

foreword

The Pytest framework can use two test reports, one of which is the test report generated by using the pytest-html plug-in, but some information in the report is useless or not very good-looking, and some we want to display in the report But there is no information. Recently, someone asked me whether the report generated by pytest-html can be finished? The answer is yes, so today I will teach you how to optimize and Chineseize the pytest-html test report to solve the above problems

generate report

Let's write a simple test code first, generate an original test report, and see what needs to be modified

test code

test_pytest_html.py

"""
------------------------------------
@Time : 2019/8/28 19:45
@Auth : linux超
@File : test_pytest_html.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import pytest


def login(username, password):
    """模拟登录"""
    user = "linux静"
    pwd = "linux静静"
    if user == username and pwd == password:
        return {"code": 1001, "msg": "登录成功", "data": None}
    else:
        return {"code": 1000, "msg": "用户名或密码错误", "data": None}


test_data = [
    # 测试数据
    {
        "case": "用户名正确, 密码正确",
        "user": "linux静",
        "pwd": "linux静静",
        "expected": {"code": 1001, "msg": "登录成功", "data": None}
    },
    {
        "case": "用户名正确, 密码为空",
        "user": "linux静",
        "pwd": "",
        "expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}
    },
    {
        "case": "用户名为空, 密码正确",
        "user": "",
        "pwd": "linux静静",
        "expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}
    },
    {
        "case": "用户名错误, 密码错误",
        "user": "linux",
        "pwd": "linux",
        "expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}
    }
]


class TestLogin(object):

    @pytest.mark.parametrize("data", test_data)
    def test_login(self, data):
        result = login(data["user"], data["pwd"])
        assert result == data["expected"]


if __name__ == '__main__':
    pytest.main(['-sv', "D:\PythonTest\MyPytestHtml\pytest_html_optimization", "--html", "report.html"])

original report

Modify Environment

You can see that all the information in the Environment in the original test report has little to do with the environment of our system under test. The main information is actually some development environment. If I want to add or delete some information, such as deleting Java_Home, How to add the test interface address?

We need a new key contest.py file in the root directory of the project, and add the following code to the file

def pytest_configure(config):
    # 添加接口地址与项目名称
    config._metadata["项目名称"] = "Linux超博客园自动化测试项目v1.0"
    config._metadata['接口地址'] = 'https://www.cnblogs.com/linuxchao/'
    # 删除Java_Home
    config._metadata.pop("JAVA_HOME")

Modified effect

Modify Summary

The original Summary section only shows the number of test cases and the execution time of the test cases, so if we want to add some information such as testers in this position, how to achieve it? Also add the following code to the conftest.py file

@pytest.mark.optionalhook
def pytest_html_results_summary(prefix):
    prefix.extend([html.p("所属部门: xx测试中心")])
    prefix.extend([html.p("测试人员: Linux静")])

Modified effect

Modify Results

Results mainly displays the test result information, and there are also some information that does not meet our actual needs. For example, the content displayed in the Test column is very long. It is mainly composed of the path where the test case is located, the test class, the test case and the test data. Yes, the readability is very poor. From this description, it is impossible to see what our test case is testing, so we hope it can be displayed like this: test + case name [user: username-pwd: password], such as: test The user name is correct, the password is correct [user: Linux super-pwd: Linux super brother], let's achieve this effect

Optimize Test

First, let's analyze a wave of source code and see how the information in the Test list of the original test report is generated. Find the installation location of our pytest-html plugin, open the plugin.py file, and you will find such a piece of code

1    class TestResult:
2         def __init__(self, outcome, report, logfile, config):
3             self.test_id = report.nodeid
4             if getattr(report, "when", "call") != "call":
5                 self.test_id = "::".join([report.nodeid, report.when])
6             self.time = getattr(report, "duration", 0.0)
7             self.outcome = outcome

Lines 3 and 5 are the source of the Test information in the report. A piece of information like test_pytest_html.py::TestLogin::test_login[data0] is actually the nodeid of the use case, and [data0] is the parameterization of each test case. A parameter, the function of ids is mainly to mark the test case (if you don’t know, you can read that article), to increase the readability of the output information after the test case is executed, so we can use this parameter to change [data0], Let it display our test data, ok, add and modify the following code in our test code

# 改变输出结果
ids = [
    "测试:{}->[用户名:{}-密码:{}-预期:{}]".
        format(data["case"], data["user"], data["pwd"], data["expected"]) for data in test_data
]

class TestLogin(object):
    # 添加ids参数
    @pytest.mark.parametrize("data", test_data, ids=ids)
    def test_login(self, data):
        result = login(data["user"], data["pwd"])
        assert result == data["expected"]

Execute our test code again after modification to generate a test report

Unfortunately, the Chinese part shows garbled characters, and we have to continue to modify it

Solve Chinese garbled characters

Continue to add the following code in the conftest.py file

import pytest
from py._xmlgen import html


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    outcome = yield
    report = outcome.get_result()
    getattr(report, 'extra', [])
    report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")  # 解决乱码

Execute the test code again to view the report output

Chinese can be displayed normally, very happy, but there are still a bunch of directories and test classes in front, I still don’t want it, so let’s continue to modify

remove excess

To delete the redundant part in front, the next step is to modify the html of the report, because I really can’t find where to modify this display in the source code, let’s check the page element properties of the html report

Seeing this, do you think of any way to modify it? What I think of is to use js code to change the innerText of this element, let's experiment first, switch to [console] and execute the following codes in sequence

// 找到Test列中所有的表格元素
var test_name = document.getElementBysClassName("col-name");
// 修改第1个表格的innerText属性
test_name[0].innerText = test_name[0].innerText.split("\[")[1].split(["\]")[0];  // 以[符号分割一次,再以]分割一次,得到目标字符串

The above code can only modify one table, so to modify multiple tables, you can only use loops, and rewrite the js code again

var case_name_td = document.getElementsByClassName("col-name")
    for(var i = 0; i < case_name_td.length; i++)
        try{
            case_name_td[i].innerText = case_name_td[i].innerText.split("\[")[1].split("\]")[0];
        }
        catch(err){
            // 如果表格中没有[]会抛异常,如果抛异常我就显示null,如果你想显示别的东西自己改吧,因为通常只要我们使用参数化就有[]显示
            case_name_td[i].innerText = "null";
        }

We have already written the js code, but where should we put it is another problem? By looking at the html source code of the report, I found that some codes like this were introduced at the beginning of the html code

In the source code of the pytest-html plug-in, it is found that there is a main.js file in the resource directory of the plug-in. The code in it is the js script in the above html code, which is exactly the same

And there is indeed an init() method in main.js. Through the above analysis, it should not be difficult to guess that when the test report is generated, the main.js file will be automatically loaded into the html, and it starts from the init() method, so Let's try to put the js code we wrote inside the init() method to experiment

function init () {
    reset_sort_headers();

    add_collapse();

    show_filters();

    toggle_sort_states(find('.initial-sort'));

    find_all('.sortable').forEach(function(elem) {
        elem.addEventListener("click",
                              function(event) {
                                  sort_column(elem);
                              }, false)
    });
    // 修改用例报告显示的用例名称 add by linux静
    var case_name_td = document.getElementsByClassName("col-name")
        for(var i = 0; i < case_name_td.length; i++)
            try{
                case_name_td[i].innerText = case_name_td[i].innerText.split("\[")[1].split("\]")[0];
            }
            catch(err){
                // 如果表格中没有[]会抛异常,如果抛异常我就显示null,如果你想显示别的东西自己改吧,因为通常只要我们使用参数化就有[]显示
                case_name_td[i].innerText = "null";
            }

};

Remember to save after adding, execute the test code again, and view the test report

Modified effect

You read that right, it was indeed successful, and there is still a little sense of accomplishment, haha!

Delete Links

The links in the report have never seen any content, so I plan to delete this column and add the following code to the contest.py file

@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.pop(-1)  # 删除link列


@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
    cells.pop(-1)  # 删除link列

Modified effect

Add failure screenshots and use case descriptions

The screenshot of the failure to add a use case and the description of adding a list of use cases have already been mentioned in the previous article, so you can read my previous article through the following link. Error screenshots are only available in web automation tests.

Complete conftest.py code

for reference only

"""
------------------------------------
@Time : 2019/8/28 19:50
@Auth : linux超
@File : conftest.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import pytest
from selenium import webdriver
from py._xmlgen import html

driver = None


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """当测试失败的时候,自动截图,展示到html报告中"""
    outcome = yield
    pytest_html = item.config.pluginmanager.getplugin('html')

    report = outcome.get_result()
    extra = getattr(report, 'extra', [])
    # 如果你生成的是web ui自动化测试,请把下面的代码注释打开,否则无法生成错误截图
    # if report.when == 'call' or report.when == "setup":
    #     xfail = hasattr(report, 'wasxfail')
    #     if (report.skipped and xfail) or (report.failed and not xfail):  # 失败截图
    #         file_name = report.nodeid.replace("::", "_") + ".png"
    #         screen_img = capture_screenshot()
    #         if file_name:
    #             html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
    #                    'onclick="window.open(this.src)" align="right"/></div>' % screen_img
    #             extra.append(pytest_html.extras.html(html))
    #     report.extra = extra
    extra.append(pytest_html.extras.text('some string', name='Different title'))
    report.description = str(item.function.__doc__)
    report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")  # 解决乱码


def capture_screenshot():
    '''截图保存为base64'''
    return driver.get_screenshot_as_base64()


def pytest_configure(config):
    # 添加接口地址与项目名称
    config._metadata["项目名称"] = "Linux超博客园自动化测试项目v1.0"
    config._metadata['接口地址'] = 'https://www.cnblogs.com/linuxchao/'
    # 删除Java_Home
    config._metadata.pop("JAVA_HOME")


@pytest.mark.optionalhook
def pytest_html_results_summary(prefix):
    prefix.extend([html.p("所属部门: xx测试中心")])
    prefix.extend([html.p("测试人员: Linux静")])


@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.insert(1, html.th('Description'))
    cells.pop(-1)  # 删除link列


@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
    cells.insert(1, html.td(report.description))
    cells.pop(-1)  # 删除link列


@pytest.fixture(scope='session')
def driver():
    global driver
    print('------------open browser------------')
    driver = webdriver.Firefox()

    yield driver
    print('------------close browser------------')
    driver.quit()

Sinicization report

The last step is to Sinicize our test reports. Because they are all in English, some students may not like them or the leaders don’t like them. Next, we will start our Sinicization. Sinicization is relatively simple, just be careful.

Disclaimer: All files modified below exist in the source code directory of the pytest-html plugin

Modify plugin.py

The plugin.py file is mainly used to generate the html code of the test report, so Sinicization must be inseparable from modifying this file, and then follow my steps to modify it step by step (the comment part is the source code, followed by my modified code)

# if len(log) == 0:
#     log = html.div(class_='empty log')
#     log.append('No log output captured.')
if len(log) == 0:  # modify by linux超
       log = html.div(class_='empty log')
       log.append('未捕获到日志')
additional_html.append(log)
# head = html.head(
#     html.meta(charset='utf-8'),
#     html.title('Test Report'),
#     html_css)
head = html.head(  # modify by linux超
       html.meta(charset='utf-8'),
       html.title('测试报告'),
       html_css)
# outcomes = [Outcome('passed', self.passed),
#             Outcome('skipped', self.skipped),
#             Outcome('failed', self.failed),
#             Outcome('error', self.errors, label='errors'),
#             Outcome('xfailed', self.xfailed,
#                     label='expected failures'),
#             Outcome('xpassed', self.xpassed,
#                     label='unexpected passes')]
outcomes = [Outcome('passed', self.passed, label="通过"),
            Outcome('skipped', self.skipped, label="跳过"),
            Outcome('failed', self.failed, label="失败"),
            Outcome('error', self.errors, label='错误'),
            Outcome('xfailed', self.xfailed,
                    label='预期失败'),
            Outcome('xpassed', self.xpassed,
                    label='预期通过')]
# if self.rerun is not None:
#     outcomes.append(Outcome('rerun', self.rerun))
if self.rerun is not None:
    outcomes.append(Outcome('重跑', self.rerun))

# summary = [html.p(
#     '{0} tests ran in {1:.2f} seconds. '.format(
#         numtests, suite_time_delta)),
#     html.p('sfsf',
#            class_='filter',
#            hidden='true')]
summary = [html.p(  # modify by linux超
    '执行了{0}个测试用例, 历时:{1:.2f}秒 . '.format(
        numtests, suite_time_delta)),
    html.p('(取消)勾选复选框, 以便筛选测试结果',
           class_='filter',
           hidden='true')]
# cells = [
#     html.th('Result',
#             class_='sortable result initial-sort',
#             col='result'),
#     html.th('Test', class_='sortable', col='name'),
#     html.th('Duration', class_='sortable numeric', col='duration'),
#     html.th('Links')]# modify by linux超
cells = [
    html.th('通过/失败',
            class_='sortable result initial-sort',
            col='result'),
    html.th('用例', class_='sortable', col='name'),
    html.th('耗时', class_='sortable numeric', col='duration'),
    html.th('链接')]
# results = [html.h2('Results'), html.table([html.thead(
#     html.tr(cells),
#     html.tr([
#         html.th('No results found. Try to check the filters',
#                 colspan=len(cells))],
#             id='not-found-message', hidden='true'),
#     id='results-table-head'),
#     self.test_logs], id='results-table')]
results = [html.h2('测试结果'), html.table([html.thead(  # modify by linux超
    html.tr(cells),
    html.tr([
        html.th('无测试结果, 试着选择其他测试结果条件',
                colspan=len(cells))],
            id='not-found-message', hidden='true'),
    id='results-table-head'),
    self.test_logs], id='results-table')]
#html.p('Report generated on {0} at {1} by '.format( 
html.p('生成报告时间{0} {1} Pytest-Html版本:'.format(  # modify by linux超
# body.extend([html.h2('Summary')] + summary_prefix
#             + summary + summary_postfix)
body.extend([html.h2('用例统计')] + summary_prefix  # modify by linux超
            + summary + summary_postfix)
# environment = [html.h2('Environment')]
environment = [html.h2('测试环境')]  # modify by linux超

The plugin.py file has been modified so far, you can generate a report to view the following effects, and then modify main.js

Modify main.js

/*
function add_collapse() {
    // Add links for show/hide all
    var resulttable = find('table#results-table');
    var showhideall = document.createElement("p");
    showhideall.innerHTML = '<a href="javascript:show_all_extras()">Show all details</a> / ' +
                            '<a href="javascript:hide_all_extras()">Hide all details</a>';
    resulttable.parentElement.insertBefore(showhideall, resulttable);
*/
function add_collapse() {  // modify by linux超
    // Add links for show/hide all
    var resulttable = find('table#results-table');
    var showhideall = document.createElement("p");
    showhideall.innerHTML = '<a href="javascript:show_all_extras()">显示详情</a> / ' +
                            '<a href="javascript:hide_all_extras()">隐藏详情</a>';
    resulttable.parentElement.insertBefore(showhideall, resulttable);
修改style.css

Finally modify style.css, this file is mainly used to beautify the test report, if you are familiar with css, you can customize the report style

.expander::after {
    content: " (展开详情)";
    color: #BBB;
    font-style: italic;
    cursor: pointer;
}
.collapser::after {
    content: " (隐藏详情)";
    color: #BBB;
    font-style: italic;
    cursor: pointer;
}

So far, all the localization has been completed. Next, let's execute the test code and check the effect of the report.

Final Report Effect

Install the Chinese version of the plug-in

For the convenience of everyone, skip the process of modifying the source code. I have uploaded the source code of the Chinese version of the pytest-html plug-in to my GitHub. Let me talk about how to use it

method 1

1. If you have already installed pytest-html, please uninstall pip uninstall pytest-html first

2. Download the plug-in source code to the local, git clone https://github.com/13691579846/pytest-html or download according to the method shown in the figure below

3. Open CMD and switch to the directory where the setup.py file in the plug-in is located, and execute the following command

python setup.py install

Method 2

1. If you have not installed the pytest-html plugin, please execute the following command to install it first

pip install pytest-html

2. Download the finished plug-in according to method 1, and overwrite some parts of the downloaded plug-in to some files under pytest-html under the python third-party library directory

Method 3

Directly download the source code after Sinicization, and throw the pytest_html in the source code directly to the python third-party storage directory.

Method 4

The last method is to follow the steps of my article and modify your source code step by step

Method 5

After downloading the pytest source code, use pip install ./pytest-html to install

Guess you like

Origin blog.csdn.net/xiao1542/article/details/130691046