junitparser handles JUnit/xUnit Result XML files. Use it to parse and manipulate existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
- Parse or modify existing JUnit/xUnit XML files.
- Parse or modify non-standard or customized JUnit/xUnit XML files, by monkey patching existing element definitions.
- Create JUnit/xUnit test results from scratch.
- Merge test result XML files.
- Specify XML parser. For example you can use lxml to speed things up.
- Invoke from command line, or python -m junitparser
Python 3.10 or above is now required. If you are still using Python 3.9 or below, please use version 4.0.2.
Version 2 improved support for pytest result XML files by fixing a few issues,
notably that there could be multiple <Failure> or <Error> entries. There is a
breaking change that TestCase.result is now a list instead of a single item.
If you are using this attribute, please update your code accordingly.
pip install junitparser
You should be relatively familiar with the Junit XML format. If not, run
pydoc on the exposed classes and functions to see how it's structured.
You have some test result data, and you want to convert them into junit.xml format.
fromjunitparserimportTestCase, TestSuite, JUnitXml, Skipped, Error# Create casescase1=TestCase('case1', 'class.name', 0.5) # params are optionalcase1.classname="modified.class.name"# specify or change case attrscase1.result= [Skipped()] # You can have a list of resultscase2=TestCase('case2')
case2.result= [Error('Example error message', 'the_error_type')]
# Create suite and add casessuite=TestSuite('suite1')
suite.add_property('build', '55')
suite.add_testcase(case1)
suite.add_testcase(case2)
suite.remove_testcase(case2)
#Bulk add cases to suitecase3=TestCase('case3')
case4=TestCase('case4')
suite.add_testcases([case3, case4])
# Add suite to JunitXmlxml=JUnitXml()
xml.add_testsuite(suite)
xml.write('junit.xml')You have some existing junit.xml files, and you want to modify the content.
fromjunitparserimportJUnitXmlxml=JUnitXml.fromfile('/path/to/junit.xml')
forsuiteinxml:
# handle suitesforcaseinsuite:
# handle casesxml.write() # Writes back to fileIt is also possible to use a custom parser. For example lxml provides a plethora of parsing options. We can use them this way:
fromlxml.etreeimportXMLParser, parsefromjunitparserimportJUnitXmldefparse_func(file_path):
xml_parser=XMLParser(huge_tree=True)
returnparse(file_path, xml_parser)
xml=JUnitXml.fromfile('/path/to/junit.xml', parse_func)
# process xml...You have two or more XML files, and you want to merge them into one.
fromjunitparserimportJUnitXmlxml1=JUnitXml.fromfile('/path/to/junit1.xml')
xml2=JUnitXml.fromfile('/path/to/junit2.xml')
newxml=xml1+xml2# Alternatively, merge in placexml1+=xml2Note that it won't check for duplicate entries. You need to deal with them on your own.
By default junitparser supports the schema of windyroad, which is a relatively simple schema.
Junitparser also support extra schemas:
# Extended with extra properties/attributes from the xunit2 schema.fromjunitparser.xunit2importTestCase, TestSuite, RerunFailure# TestSuite supports system_err.suite=TestSuite("mySuite")
suite.system_err="System err"# TestCase supports interim results.case=TestCase("myCase")
rerun_failure=RerunFailure("Not found", "404")
rerun_failure.stack_trace="Stack"rerun_failure.system_err="E404"rerun_failure.system_out="NOT FOUND"case.add_interim_result(rerun_failure)
# TestCase supports properties.case.add_property("cmake_labels", "cuda;tier2")Currently supported schemas including:
- xunit2, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.
PRs are welcome to support more schemas.
You want to use an attribute that is not supported by default.
fromjunitparserimportTestCase, Attr, IntAttr, FloatAttr# Add the custom attributeTestCase.id=IntAttr('id')
TestCase.rate=FloatAttr('rate')
TestCase.custom=Attr('custom')
case=TestCase()
case.id=123case.rate=0.95case.custom='foobar'There may be once in 1000 years you want to it this way, but anyways. Suppose you want to add element CustomElement to TestCase.
fromjunitparserimportElement, Attr, TestSuite# Create the new element by subclassing Element,# and add custom attributes to it.classCustomElement(Element):
_tag='custom'foo=Attr()
bar=Attr()
testcase=TestCase()
custom=CustomElement()
testcase.append(custom)
# To find a single sub-element:testcase.child(CustomElement)
# To iterate over custom elements:forcustomintestcase.iterchildren(CustomElement):
... # Do things with custom elementSay you have some data stored in the XML as custom attributes and you want to read them out:
fromjunitparserimportTestCase, Attr, JUnitXml# Create the new element by subclassing Element or one of its child class,# and add custom attributes to it.classMyTestCase(TestCase):
foo=Attr()
xml=JUnitXml.fromfile('/path/to/junit.xml')
forsuiteinxml:
# handle suitesforcaseinsuite:
my_case=MyTestCase.fromelem(case)
print(my_case.foo)$ junitparser --helpusage: junitparser [-h] [-v] {merge} ...Junitparser CLI helper.positional arguments:{merge} command merge Merge Junit XML format reports with junitparser. verify Return a non-zero exit code if one of the testcases failed or errored.optional arguments:-h, --help show this help message and exit-v, --version show program's version number and exit$ junitparser merge --helpusage: junitparser merge [-h] [--glob] paths [paths ...] outputpositional arguments: paths Original XML path(s). output Merged XML Path, setting to "-" will output consoleoptional arguments: -h, --help show this help message and exit --glob Treat original XML path(s) as glob(s). --suite-name SUITE_NAME Name added to <testsuites>.$ junitparser verify --helpusage: junitparser verify [-h] [--glob] paths [paths ...]positional arguments: paths XML path(s) of reports to verify.optional arguments: -h, --help show this help message and exit --glob Treat original XML path(s) as glob(s).The tests are written with python unittest, to run them, use
pytest:
pytest
If you get a failure like unsupported locale setting you may need to add
extra locales that the tests use. Refer to the steps used in the
CI build workflow:
sudo locale-gen en_US.UTF-8 sudo locale-gen de_DE.UTF-8 sudo update-locale
PRs are welcome!