Package: junitparser
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.FeaturesParse 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, orpython -m junitparserPython 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
2, so there is no plan to drop Python 2 support)Note on version 2Version 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 thatTestCase.resultis now a list instead of a single item.
If you are using this attribute, please update your code accordingly.Installationpip install junitparserUsageYou should be relatively familiar with the Junit XML format. If not, runpydocon the exposed classes and functions to see how it’s structured.Create Junit XML format reports from scratchYou 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')Read and manipulate existing JUnit/xUnit XML filesYou 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 parsingoptions. 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...Merge XML filesYou 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.Schema SupportBy default junitparser supports the schema ofwindyroad, which is a relatively
simple schema.Junitparser also support extra schemas:fromjunitparser.xunit2importJUnitParser,TestCase,TestSuite,\RerunFailure# These classes are redefined to support extra properties and attributes# of the xunit2 schema.suite=TestSuite("mySuite")suite.system_err="System err"# xunit2 specific propertycase=TestCase("myCase")rerun_failure=RerunFailure("Not found","404")# case propertyrerun_failure.stack_trace="Stack"rerun_failure.system_err="E404"rerun_failure.system_out="NOT FOUND"case.add_rerun_result(rerun_failure)Currently supported schemas including:xunit2, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.PRs are welcome to support more schemas.Create XML with custom attributesYou 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'Handling XML with custom elementThere 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 elementHandling custom XML attributesSay 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)Command Line$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$junitparsermerge--helpusage: junitparser merge [-h] [--glob] paths [paths ...] output

positional arguments:
  paths       Original XML path(s).
  output      Merged XML Path, setting to "-" will output console

optional 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>.$junitparserverify--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).TestThe tests are written with pythonunittest, to run them, use pytest:pytest test.pyContributePRs are welcome!