Selenium HTMLTestRunner_Chart 包含历史结果的测试报告

点点 · 2018年09月17日 · 最后由 LRF520 回复于 2019年12月24日 · 3799 次阅读

HTMLTestRunner_Chart 基于 unittest 的测试报告,使用详情见 demo

GitHub: https://github.com/githublitao/HTMLTestRunner_Chart 欢迎 star
参考链接:
http://tungwaiyip.info/software/HTMLTestRunner.html
https://github.com/GoverSky/HTMLTestRunner_cn

优化报告内容

  1. 测试报告中文显示,优化一些断言失败正文乱码问题
  2. 新增错误和失败截图,展示到 html 报告里
  3. 增加饼图统计
  4. 失败后重试功能
  5. 保存近 10 次测试结果,并通过柱状图展示
  6. 切换测试日期,展示历史测试结果
  7. 兼容 python2.x 和 3.x

注意:

  1. 在是 python3.x 中,如果在这里 setUp 里初始化 driver ,因为 3.x 版本 unittest 运行机制不同,会导致用力失败时截图失败,目前只有采用捕获异常来截图,或者在 setUpClass 里初始化 driver
  2. driver 初始化变量名必须命名为 driver

报告首页:

用例截图:

失败饼图:

历史走势:

失败重试:

  1. 生成报告的参数里面加了一个参数 retry=1,这个表示用例失败后,会重新跑一次。

    if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(case_01)
    runner = HTMLTestRunner(
        title="带截图,饼图,折线图,历史结果查看的测试报告",
        description="",
        stream=open("./demo.html", "wb"),
        verbosity=2,
        retry=0,
        save_last_try=True)
    runner.run(suite)
    

    保存测试结果到 json 文件:

    def mkdir_json(self):
        is_exists = os.path.exists(self.path)
        # 判断结果
        if not is_exists:
            try:
                # 如果不存在则创建目录
                # 创建目录操作函数
                with open(self.path, "w+") as f:
                    f.write("var data = []")
                return True
            except Exception as e:
                print(e)
                return False
        else:
            return True
    
    def Write(self, title, heading, desc, data):
        try:
            with open(self.path, "r+") as f:
                all_data = f.read().split(" = ", 1)
                data_json = all_data[1]
                data_json = eval(data_json)
                if len(data_json) >= 10:
                    del data_json[0]
                description = dict()
                description["startTime"] = heading[0][1]
                description["duration"] = heading[1][1]
                if PY3K:
                    description["title"] = title
                    description["status"] = heading[2][1]
                    description["desc"] = desc
                    description["data"] = data
                    status = heading[2][1].split(" ")
                    for j in range(0, len(status)):
                        if status[j] == "通过":
                            description["success"] = str(status[j + 1])
                        if status[j] == "失败":
                            description["fail"] = str(status[j + 1])
                        if status[j] == "错误":
                            description["error"] = str(status[j + 1])
                else:
                    description["title"] = title.encode("gbk")
                    description["status"] = heading[2][1].encode("gbk")
                    description["desc"] = desc.encode("gbk")
                    description["data"] = data.encode("gbk")
                    status = heading[2][1].split(" ")
                    for j in range(0, len(status)):
                        if status[j] == u"通过":
                            description["success"] = str(status[j + 1])
                        if status[j] == u"失败":
                            description["fail"] = str(status[j + 1])
                        if status[j] == u"错误":
                            description["error"] = str(status[j + 1])
                data_json.append(description)
                data_json = str(data_json)
                f.seek(0)
                f.truncate()
                f.write(str("var data = " + data_json))
        except IndexError:
            sys.stderr.write("JSON初始化内容有误! 初始化内容’var data = []‘")
    

    错误/失败截图,修改 addError 和 addFail 函数:

    def addFailure(self, test, err):
        self.failure_count += 1
        self.status = 1
        TestResult.addFailure(self, test, err)
        _, _exc_str = self.failures[-1]
        output = self.complete_output()
        self.result.append((1, test, output, _exc_str))
        if not getattr(test, "driver",""):
            pass
        else:
            try:
                driver = getattr(test, "driver")
                test.imgs.append(driver.get_screenshot_as_base64())
            except Exception as e:
                pass
        if self.verbosity > 1:
            sys.stderr.write('F  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('F')
    

    错误重试,修改 stopTest 函数:

    def stopTest(self, test):
       # Usually one of addSuccess, addError or addFailure would have been called.
       # But there are some path in unittest that would bypass this.
       # We must disconnect stdout in stopTest(), which is guaranteed to be called.
       if self.retry:
           if self.status == 1:
               self.trys += 1
               if self.trys <= self.retry:
                   if self.save_last_try:
                       t = self.result.pop(-1)
                       if t[0]==1:
                           self.failure_count-=1
                       else:
                           self.error_count -= 1
                   test=copy.copy(test)
                   sys.stderr.write("Retesting... ")
                   sys.stderr.write(str(test))
                   sys.stderr.write('..%d \n' % self.trys)
                   doc = test._testMethodDoc or ''
                   if doc.find('_retry')!=-1:
                       doc = doc[:doc.find('_retry')]
                   desc ="%s_retry:%d" %(doc, self.trys)
                   if not PY3K:
                       if isinstance(desc, str):
                           desc = desc.decode("utf-8")
                   test._testMethodDoc = desc
                   test(self)
               else:
                   self.status = 0
                   self.trys = 0
       self.complete_output()
    

    HTML 模板导入 JSON 历史结果,如果 JSON 出现错误,则历史结果和走势图错误:

    <head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <script type="text/javascript" src="%(jsonpath)s" charset="gbk"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
    <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->
    
    %(stylesheet)s
    

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 38 条回复 时间 点赞

第 5、6 点挺好的。比如 open('./report/result.html', 'wb') 这样第 5、6 点展示不出来,而 open('./result.html', 'wb') 是可以的。

666😆 果然引用现成的 UI 库比自己造轮子好看~~

回复

谢谢指出,已修复这个 bug

5楼 已删除
再见理想 回复

这个报告很早之前看过了,但就想自己改改

你好,问一下我这边为何不能显示最近十次的运行结果,如下图所示:

运行环境

  • Python 3.5
  • Appium 1.7.2
  • Win10 64bit

相关代码

run.py

import unittest
# from  BSTestRunner import BSTestRunner
from HTMLTestRunner_Chart import HTMLTestRunner
import time,logging


test_dir='../test_case'
report_dir='../reports'

discover=unittest.defaultTestLoader.discover(test_dir,pattern='test_login.py')

now=time.strftime('%Y-%m-%d %H_%M_%S')
report_name=report_dir+'/'+now+' test_report.html'

with open(report_name,'wb') as f:
    runner=HTMLTestRunner(stream=f,title='Kyb Test Report',
                          description='kyb Android app test report',
                          retry=1,verbosity=2,save_last_try=True)

    logging.info('start run test case...')
    runner.run(discover)

test_login.py

from common.myunit import StartEnd
from businessView.loginView import LoginView
import unittest
import logging

class TestLogin(StartEnd):
    csv_file='../data/account.csv'

    # @unittest.skip('test_login_zxw2018')
    def test_login_zxw2018(self):
        logging.info('======test_login_zxw2018=====')
        l=LoginView(self.driver)
        data=l.get_csv_data(self.csv_file,2)

        l.login_action(data[0],data[1])
        self.assertTrue(l.check_loginStatus())

    # @unittest.skip('skip test_login_zxw2017')
    def test_login_zxw2017(self):
        logging.info('======test_login_zxw2017=====')
        l=LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 1)

        l.login_action(data[0], data[1])
        self.assertTrue(l.check_loginStatus())

    # @unittest.skip('test_login_error')
    def test_login_error(self):
        logging.info('======test_login_error=====')
        l = LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 3)

        l.login_action(data[0], data[1])
        self.assertFalse(l.check_loginStatus(),msg='login fail!')

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

myunit.py

Tips:由于自己单独封装了截图的方法,所以这里依旧把 driver 初始化放在 setUp 方法。

import unittest
from common.desired_caps import appium_desired
import logging
from time import sleep

class StartEnd(unittest.TestCase):
    def setUp(self):
        logging.info('=====setUp====')
        self.driver=appium_desired()

    def tearDown(self):
        logging.info('====tearDown====')
        sleep(5)
        self.driver.close_app()
Sutune 回复

F12, 贴 console,还有生成的 json 文件开头部分,以及 html 报告,head 部分

点点 #11 · 2018年09月19日 Author
Sutune 回复

又是这个,看来很多人玩平台嘛,不支持 url 方式查看报告,你直接本地打开报告,应该就可以看到历史数据了,另外,既然你有平台,为何不把历史数据存放在数据库中,这样存储量和查询速度,都不说现在这个 json 存储方式可比的

点点 #12 · 2018年09月19日 Author
Sutune 回复

目前并没有想到兼容两种本地和远程 URL 访问报告的方法

点点 回复

我没有部署到平台啊,直接在本地用浏览器打开生成的 html 测试报告,打开之后就是没有历史数据。你下载我给你提供的那个生成在本地的 html 测试报告看看:https://pan.baidu.com/s/1Eoj_UT03-TUemmsq6F5q_w 也是一样的。

点点 #14 · 2018年09月19日 Author


你看你的报错,很明显是通过一个 url 去访问的这个 json 文件,这种不兼容,你直接通过 file://打开报告就可以了
我下载你的报告,打开是没有问题的

点点 回复

好的,在本地文件夹中使用浏览器单独打开可以了。我在 pycharm 里面打开预览加载本地服务,导致使用 url 预览无法查看。

点点 回复

不过还有一个问题,最近 10 次运行的结果只显示当前打开报告运行的结果,不会显示之前运行的结果。这个是怎么回事?

点点 #17 · 2018年09月19日 Author
Sutune 回复

有个时间选择框,选择对于的测试时间就可以切换报告了

点点 回复

你说的是那个下拉菜单吧,我这边运行多次测试后,也生成了多个测试报告,打开最后一个生成的测试报告,但是下拉菜单只有最后一次运行的结果,没有之前运行的结果记录。

点点 #19 · 2018年09月19日 Author
Sutune 回复

你是把报告的名称写成动态参数了,既然生成了多个 html 报告,又何必需要这个功能呢,你可以把报告名写死试试

点点 回复

哦,懂了,原来如此。


大佬们,请问我 HtmlTestRunner 加这个为啥报这个?

自己封装截图的也是一样

点点 #22 · 2019年11月06日 Author
薇大帅 回复

报错很明显啊,没有这个属性,代码不全无法具体定位

23楼 已删除
41楼 已删除
25楼 已删除

选择一个日期后,页面的中文有变成乱码的,大家有碰到过吗?

这个如果想用英文版在哪里设置呀,我没有查出来,谁能告诉一下,多谢

loveshanshan 回复

找到原因了,html 引入 json 格式改成 utf-8 就不会是乱码了

29楼 已删除
30楼 已删除
31楼 已删除
点点 #32 · 2019年12月24日 Author

可以加

点点 回复

加不了啊,是不是 runner 里面注释了什么代码啊?"""注释""" 、"注释" 、'注释' 、u"""注释""" ,这些我全部试过了,都没有显示出来啊😖 😖 😖

试过这个版本的简单修改版,这个http://tungwaiyip.info/software/HTMLTestRunner.html🎄就可以显示出来,好希望现在这个带折线图的也可以正常显示出来注释啊,是不是需要导入什么特殊的第三方包呢,或者是需要安装其他特殊插件呢? 🎄 🎄

点点 #35 · 2019年12月24日 Author
LRF520 回复

不用装插件,加功能就行,我有空的时候看看

点点 回复

感谢指导,等你的回复,圣诞节快乐!❤

点点 #37 · 2019年12月24日 Author
LRF520 回复

刚才看了一下,不是有这个功能吗

点点 回复

你是怎么备注的,我就是显示不出来啊,反而是原生态的那个 runner 修改部分代码后可以显示出来。

39楼 已删除
点点 #40 · 2019年12月24日 Author

就我 github 上的 demo 就可以,你这个需要调试一下才知道问题

41楼 已删除
42楼 已删除
点点 #43 · 2019年12月24日 Author

看一下这个参数 verbosity,默认是 1,不会显示注释的,需要设置大于 1

点点 回复

感谢你!!!有两个 verbosity=1,都改成了 verbosity=2,这个会影响其他功能吗?🎄 🎄 🎄 🎄 🎄 🎄 🎄 🎄

点点 #45 · 2019年12月24日 Author
LRF520 回复

不会影响,只是默认的日志级别比较高而已,你可以不用改源码,在入口赋值 verbosity=2 就行

点点 回复

日志级别啊,这里分了几级啊?

点点 #47 · 2019年12月24日 Author
LRF520 回复

这块不是我做的,看源码貌似只有 1 和大于 1

点点 回复

好的,谢谢!❤

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册