python - 通过Robot Framwork结果xml 生产自定义报告

本文介绍如何通过Python的pyH库和Robot Framework的结果XML文件生成自定义的HTML报告。首先,需要安装pyH和lxml库。然后,根据XML文件以指定编码(如GBK或UTF-8)生成HTML报告。在Jenkins中,由于邮件和 Jenkins 查看报告需要不同的编码,所以需要创建两份报告以避免乱码问题。邮件设置中会链接到UTF-8编码的报告,而GBK编码的报告则用于邮件内容。
部署运行你感兴趣的模型镜像

python - 通过Robot Framwork结果xml 生产自定义报告

Max.Bai

2017-08-03


1. 准备

1.1 安装pyH

1.2 安装lxml


2. 生成html 文件

根据robot framwork 结果xml文件 按照指定编码格式生成 html格式报告文件

# -*- coding:utf-8 -*-
# @file: generateRFreport.py
# @purpose: a robotframework htm report generator
# @author: Max Bai <wangzi611@163.com>

__doc__ = """The generateRFreport.py is generate html report from robotframework xml report.
"""
__author__ = "Max Bai <wangzi611@163.com>"
__version__ = '$Revision: 1 $'
__date__ = '$Date$'

from xml.etree import ElementTree
from pyh import *
import sys     # 1


"""
Generate html format test report from xml result of robot framework.

Args:
input xml file path: the xml result path
output report file path: the output html report file path
encode: html file encodeing e.g: GBK, UTF-8

Sample:
generateRFreport      c:\temp\output.xml c:\temp\report.html utf-8    #generate utf-8 encode format html report
generateRFreport      c:\temp\output.xml c:\temp\report.html GBK    #generate GBK encode format html report
"""

def main(argv):
    reload(sys)                         # 2
    sys.setdefaultencoding('utf-8')     # 3
    print argv[0]
    print argv[1]
    print argv[2]
    print argv[3]
    gr = GenerateReport("Automation Test Report")
    suiteinfo = gr.get_suite_info(argv[1])
    gr.inthtml(suiteinfo[0], argv[3])
    total = gr.get_total_data(argv[1])
    gr.addSummary(total[0], total[1], suiteinfo[1], suiteinfo[2])
    cases = gr.get_cases_data(argv[1])
    gr.addDetail(cases)
    gr.writeTofile(argv[2], argv[3])


class GenerateReport():

    def __init__(self, title):
        self.page = PyH(title)

    def print_node(self, node):
        print "=============================================="
        # print "node.attrib:%s" % node.attrib

        print "node.tag:%s" % node.tag
        # print "node.text:%s" % node.text
        if ("name" in node.attrib) > 0:
            print "node.attrib['name']:%s" % node.attrib['name']
        if ("status" in node.attrib) > 0:
            print "node.attrib['status']:%s" % node.attrib['status']

    def get_suite_info(self, file):
        """
        read xml and return the table
        :return:
        """
        #root = ElementTree.parse(r"c:/temp/output.xml")
        root = ElementTree.parse(file)
        #root = ElementTree.fromstring(text)

        suite = root.find("./suite")
        suteinfo = [0, 0, 0]   #name, start, end
        suteinfo[0] = suite.attrib['name']
        status = suite.find("status")
        suteinfo[1] = status.attrib['starttime']
        suteinfo[2] = status.attrib['endtime']
        return suteinfo

    def get_cases_data(self, file):
        """
        read xml and return the table
        :return:
        """
        #root = ElementTree.parse(r"c:/temp/output.xml")
        root = ElementTree.parse(file)
        #root = ElementTree.fromstring(text)

        node_findall = root.findall(".//test")
        cases = []
        for node in node_findall:
            case = [0, 0, 0, 0]
            # case name
            case[0] = node.attrib['name']
            # case result
            status = node.find("status")
            case[1] = status.attrib['status']
            # case failed text
            if status.text is None:
                case[2] = ''
            else:
                case[2] = status.text
            # case tags
            case[3] = ''
            tags = node.find("tags")
            if tags is None:
                case[3] = ''
            else:
                case[3] = ','.join([tag.text for tag in tags.findall("tag")])
            cases.append(case)
        return cases

    def get_total_data(self, file):
        """

        :param file:
        :return:
        """
        # 加载XML文件(2种方法,一是加载指定字符串,二是加载指定文件)
        #root = ElementTree.parse(r"c:/temp/output.xml")
        root = ElementTree.parse(file)
        #root = ElementTree.fromstring(text)

        node_findall = root.findall(".//total/stat")
        total = [0, 0]
        for node in node_findall:
            total[0] = node.attrib['pass']
            total[1] = node.attrib['fail']
            # total.append(node.attrib['pass'])
            # total.append(node.attrib['fail'])
        return total

    def inthtml(self, h1title, ec='utf-8'):
        """
        inital html head and css
        :return:
        """
        css="<style type='text/css'>h1{\ntext-align:center;font-family: verdana,arial,sans-serif;font-size:30px;color:#333333;border-width: 1px;border-color: #666666;	border-collapse: collapse;}\n\
            h2{font-family: verdana,arial,sans-serif;font-size:20px;color:#333333;border-width: 1px;border-color: #666666;border-collapse: collapse;}\n\
            table.summary {	font-family: verdana,arial,sans-serif;font-size:11px;color:#333333;border-width: 1px;border-color: #666666;border-collapse: collapse;}\n\
            table.gridtable {font-family: verdana,arial,sans-serif;font-size:11px;color:#333333;border-width: 1px;border-color: #666666;border-collapse: collapse;}\n\
            table.gridtable th {border-width: 1px;padding: 8px;border-style: solid;border-color: #666666;background-color: #dedede;}\n\
            table.gridtable td {border-width: 1px;padding: 8px;border-style: solid;border-color: #666666;background-color: #ffffff;}\n\
            table .FAIL {color: Red;}\n\
            table .PASS {color: Green;}\n</style>"
        self.page.head+=css
        utf8 = "<meta http-equiv=\"Content-Type\" content=\"text/html;charset="+ ec +"\" />"
        self.page.head+=utf8
        self.page << h1(h1title)

    def addSummary(self, passed, failed, start, end):
        """
        add summary html to page
        :return:
        """
        self.page << h2('Summary Information')
        sumtable = self.page << table(cl='summary')
        sumtable << tr(th('Total:',align='right') + td(int(passed) + int(failed)))
        sumtable << tr(th('Passed:',align='right') + td(passed, cl='PASS'))
        sumtable << tr(th('Fail:',align='right') + td(failed, cl='FAIL'))
        sumtable << tr(th('Start Time:',align='right') + td(start))
        sumtable << tr(th('End Time:',align='right') + td(end))

    def addDetail(self, cases):
        """
        add detail to page
        :return:
        """
        self.page << h2('Detail')
        dtable = self.page << table(cl='gridtable')
        thead = dtable << tr()
        thead << th('#')
        thead << th('Case Name', width="400px")
        thead << th('Tags')
        thead << th('Status')
        thead << th('Message')
        index = 1
        for case in cases:
            dtable << tr(td(index) + td(case[0]) + td(case[3]) + td(case[1], cl=case[1]) + td(case[2]))
            index = index + 1

    def writeTofile(self, report, ec='utf-8'):
        """
        wirte page to html file
        :return:
        """
        self.page.printOut(report, ec)

if __name__ == '__main__':
    main(sys.argv)
    #gr = GenerateReport("Test Report")
    # gr.get_title("c:/temp/ttt.xml")


3. pyh库文件修改

参见 文章http://blog.youkuaiyun.com/max229max/article/details/50695980

修改好的文件如下:

# coding=utf-8
# @file: pyh.py
# @purpose: a HTML tag generator
# @author: Emmanuel Turlay <turlay@cern.ch>

__doc__ = """The pyh.py module is the core of the PyH package. PyH lets you
generate HTML tags from within your python code.
See http://code.google.com/p/pyh/ for documentation.
"""
__author__ = "Emmanuel Turlay <turlay@cern.ch>"
__version__ = '$Revision: 43 $'
__date__ = '$Date$'

from sys import _getframe, stdout, modules, version
nOpen={}

import sys
reload(sys) 
sys.setdefaultencoding('utf-8')

nl = '\n'
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
charset = '<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />\n'

tags = ['html', 'body', 'head', 'link', 'meta', 'div', 'p', 'form', 'legend', 
        'input', 'select', 'span', 'b', 'i', 'option', 'img', 'script',
        'table', 'tr', 'td', 'th', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
        'fieldset', 'a', 'title', 'body', 'head', 'title', 'script', 'br', 'table']

selfClose = ['input', 'img', 'link', 'br']

class Tag(list):
    tagname = ''
    
    def __init__(self, *arg, **kw):
        self.attributes = kw
        if self.tagname : 
            name = self.tagname
            self.isSeq = False
        else: 
            name = 'sequence'
            self.isSeq = True
        self.id = kw.get('id', name)
        #self.extend(arg)
        for a in arg: self.addObj(a)

    def __iadd__(self, obj):
        if isinstance(obj, Tag) and obj.isSeq:
            for o in obj: self.addObj(o)
        else: self.addObj(obj)
        return self
    
    def addObj(self, obj):
        if not isinstance(obj, Tag): obj = str(obj)
        id=self.setID(obj)
        setattr(self, id, obj)
        self.append(obj)

    def setID(self, obj):
        if isinstance(obj, Tag):
            id = obj.id
            n = len([t for t in self if isinstance(t, Tag) and t.id.startswith(id)])
        else:
            id = 'content'
            n = len([t for t in self if not isinstance(t, Tag)])
        if n: id = '%s_%03i' % (id, n)
        if isinstance(obj, Tag): obj.id = id
        return id

    def __add__(self, obj):
        if self.tagname: return Tag(self, obj)
        self.addObj(obj)
        return self

    def __lshift__(self, obj):
        self += obj
        return obj

    def render(self):
        result = ''
        if self.tagname:
            result = '<%s%s%s>' % (self.tagname, self.renderAtt(), self.selfClose()*' /')
        if not self.selfClose():
            for c in self:
                if isinstance(c, Tag):
                    result += c.render()
                else: result += c
            if self.tagname: 
                result += '</%s>' % self.tagname
        result += '\n'
        return result

    def renderAtt(self):
        result = ''
        for n, v in self.attributes.iteritems():
            if n != 'txt' and n != 'open':
                if n == 'cl': n = 'class'
                result += ' %s="%s"' % (n, v)
        return result

    def selfClose(self):
        return self.tagname in selfClose        
    
def TagFactory(name):
    class f(Tag):
        tagname = name
    f.__name__ = name
    return f

thisModule = modules[__name__]

for t in tags: setattr(thisModule, t, TagFactory(t)) 

def ValidW3C():
    out = a(img(src='http://www.w3.org/Icons/valid-xhtml10', alt='Valid XHTML 1.0 Strict'), href='http://validator.w3.org/check?uri=referer')
    return out

class PyH(Tag):
    tagname = 'html'
    
    def __init__(self, name='MyPyHPage'):
        self += head()
        self += body()
        self.attributes = dict(xmlns='http://www.w3.org/1999/xhtml', lang='en')
        self.head += title(name)

    def __iadd__(self, obj):
        if isinstance(obj, head) or isinstance(obj, body): self.addObj(obj)
        elif isinstance(obj, meta) or isinstance(obj, link): self.head += obj
        else:
            self.body += obj
            id=self.setID(obj)
            setattr(self, id, obj)
        return self

    def addJS(self, *arg):
        for f in arg: self.head += script(type='text/javascript', src=f)

    def addCSS(self, *arg):
        for f in arg: self.head += link(rel='stylesheet', type='text/css', href=f)

    def printOut(self,file='', ec='UTF-8'):
        if file: f = open(file,'wb')
        else: f = stdout
        f.write(doctype.encode(ec))
        f.write(self.render().encode(ec))
        f.flush()
        if file: f.close()
    
class TagCounter:
    _count = {}
    _lastOpen = []
    for t in tags: _count[t] = 0
    def __init__(self, name):
        self._name = name
    def open(self, tag):
        if isLegal(tag): 
            self._count[tag] += 1
            self._lastOpen += [tag]
    def close(self, tag):
        if isLegal(tag) and self._lastOpen[-1] == tag: 
            self._count[tag] -= 1
            self._lastOpen.pop()
        else:
            print 'Cross tagging is wrong'
    def isAllowed(self, tag, open):
        if not open and self.isClosed(tag):
            print 'TRYING TO CLOSE NON-OPEN TAG: %s' % tag
            return False
        return True
    def isOpen(self, tag):
        if isLegal(tag): return self._count[tag]
    def isClosed(self, tag):
        if isLegal(tag): return not self._count[tag]

    
def isLegal(tag):
    if tag in tags: return True
    else:
        print 'ILLEGAL TAG: %s' % tag
        return False


4. jenkins 中使用

执行用例在jenkins中的时候 邮件发送报告需要 gbk编码的,不然邮件内容会出现乱码,但是jenkins 里面的格式需要utf-8格式的,不然在jenkins上查看就是乱码,所以需要2份报告,一份gbk的,一份utf-8 的。

使用上面的代码生成

python Utils\generateRFreport.py report\output.xml report\emailreport.html gbk
python Utils\generateRFreport.py report\output.xml report\utfreport.html utf-8


copy report\emailreport.html  emailreport.html


email 设置:

测试报告地址:<a href="${BUILD_URL}robot/report/utfreport.html">测试报告</a><br/><hr/>
测试日志地址:<a href="${BUILD_URL}robot/report/log.html">${BUILD_URL}robot/report/log.html</a><br/><hr/>

测试报告:<br/><hr/>
${FILE,path="emailreport.html"}


邮件效果图:








您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值