junitparser -- Pythonic JUnit/xUnit Result XML Parser
======================================================
.. image:: https://github.com/weiwei/junitparser/workflows/build/badge.svg?branch=master
:target: https://github.com/weiwei/junitparser/actions
.. image:: https://codecov.io/gh/weiwei/junitparser/branch/master/graph/badge.svg?token=UotlfRXNnK
:target: https://codecov.io/gh/weiwei/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.
Features
--------
* 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 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 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.
Installation
-------------
::
pip install junitparser
Usage
-----
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.
Create Junit XML format reports from scratch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You have some test result data, and you want to convert them into junit.xml
format.
.. code-block:: python
from junitparser import TestCase, TestSuite, JUnitXml, Skipped, Error
# Create cases
case1 = TestCase('case1', 'class.name', 0.5) # params are optional
case1.classname = "modified.class.name" # specify or change case attrs
case1.result = [Skipped()] # You can have a list of results
case2 = TestCase('case2')
case2.result = [Error('Example error message', 'the_error_type')]
# Create suite and add cases
suite = TestSuite('suite1')
suite.add_property('build', '55')
suite.add_testcase(case1)
suite.add_testcase(case2)
suite.remove_testcase(case2)
#Bulk add cases to suite
case3 = TestCase('case3')
case4 = TestCase('case4')
suite.add_testcases([case3, case4])
# Add suite to JunitXml
xml = JUnitXml()
xml.add_testsuite(suite)
xml.write('junit.xml')
Read and manipulate existing JUnit/xUnit XML files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You have some existing junit.xml files, and you want to modify the content.
.. code-block:: python
from junitparser import JUnitXml
xml = JUnitXml.fromfile('/path/to/junit.xml')
for suite in xml:
# handle suites
for case in suite:
# handle cases
xml.write() # Writes back to file
It is also possible to use a custom parser. For example lxml provides a plethora
of parsing options_. We can use them this way:
.. code-block:: python
from lxml.etree import XMLParser, parse
from junitparser import JUnitXml
def parse_func(file_path):
xml_parser = XMLParser(huge_tree=True)
return parse(file_path, xml_parser)
xml = JUnitXml.fromfile('/path/to/junit.xml', parse_func)
# process xml...
.. _options: https://lxml.de/api/lxml.etree.XMLParser-class.html
Merge XML files
~~~~~~~~~~~~~~~
You have two or more XML files, and you want to merge them into one.
.. code-block:: python
from junitparser import JUnitXml
xml1 = JUnitXml.fromfile('/path/to/junit1.xml')
xml2 = JUnitXml.fromfile('/path/to/junit2.xml')
newxml = xml1 + xml2
# Alternatively, merge in place
xml1 += xml2
Note that it won't check for duplicate entries. You need to deal with them on
your own.
Schema Support
~~~~~~~~~~~~~~~
By default junitparser supports the schema of windyroad_, which is a relatively
simple schema.
.. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
Junitparser also support extra schemas:
.. code-block:: python
from junitparser.xunit2 import JUnitParser, 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 property
case = TestCase("myCase")
rerun_failure = RerunFailure("Not found", "404") # case property
rerun_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.
.. _xunit2: https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
PRs are welcome to support more schemas.
Create XML with custom attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You want to use an attribute that is not supported by default.
.. code-block:: python
from junitparser import TestCase, Attr, IntAttr, FloatAttr
# Add the custom attribute
TestCase.id = IntAttr('id')
TestCase.rate = FloatAttr('rate')
TestCase.custom = Attr('custom')
case = TestCase()
case.id = 123
case.rate = 0.95
case.custom = 'foobar'
Handling XML with custom element
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There may be once in 1000 years you want to it this way, but anyways.
Suppose you want to add element CustomElement to TestCase.
.. code-block:: python
from junitparser import Element, Attr, TestSuite
# Create the new element by subclassing Element,
# and add custom attributes to it.
class CustomElement(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:
for custom in testcase.iterchildren(CustomElement):
... # Do things with custom element
Handling custom XML attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Say you have some data stored in the XML as custom attributes and you want to
read them out:
.. code-block:: python
from junitparser import TestCase, Attr, JUnitXml
# Create the new element by subclassing Element or one of its child class,
# and add custom attributes to it.
class MyTestCase(TestCase):
foo = Attr()
xml = JUnitXml.fromfile('/path/to/junit.xml')
for suite in xml:
# handle suites
for case in suite:
my_case = MyTestCase.fromelem(case)
print(my_case.foo)
Command Line
------------
.. code-block:: console
$ junitparser --help
usage: 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
.. code-block:: console
$ junitparser merge --help
usage: 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>.
.. code-block:: console
$ junitparser verify --help
usage: 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).
Test
----
The tests are written with python ``unittest``, to run them, use pytest::
pytest test.py
Contribute
----------
PRs are welcome!
Raw data
{
"_id": null,
"home_page": "https://github.com/weiwei/junitparser",
"name": "junitparser",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "junit xunit xml parser",
"author": "Weiwei Wang",
"author_email": "gastlygem@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/57/88/6a268028a297751ed73be8e291f12aa727caf22adbc218e8dfbafcc974af/junitparser-3.2.0.tar.gz",
"platform": null,
"description": "junitparser -- Pythonic JUnit/xUnit Result XML Parser\n======================================================\n\n.. image:: https://github.com/weiwei/junitparser/workflows/build/badge.svg?branch=master\n :target: https://github.com/weiwei/junitparser/actions\n.. image:: https://codecov.io/gh/weiwei/junitparser/branch/master/graph/badge.svg?token=UotlfRXNnK\n :target: https://codecov.io/gh/weiwei/junitparser\n\njunitparser handles JUnit/xUnit Result XML files. Use it to parse and manipulate\nexisting Result XML files, or create new JUnit/xUnit result XMLs from scratch.\n\nFeatures\n--------\n\n* Parse or modify existing JUnit/xUnit XML files.\n* Parse or modify non-standard or customized JUnit/xUnit XML files, by monkey\n patching existing element definitions.\n* Create JUnit/xUnit test results from scratch.\n* Merge test result XML files.\n* Specify XML parser. For example you can use lxml to speed things up.\n* Invoke from command line, or `python -m junitparser`\n* Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python\n 2, so there is no plan to drop Python 2 support)\n\nNote on version 2\n-----------------\n\nVersion 2 improved support for pytest result XML files by fixing a few issues,\nnotably that there could be multiple <Failure> or <Error> entries. There is a\nbreaking change that ``TestCase.result`` is now a list instead of a single item.\nIf you are using this attribute, please update your code accordingly.\n\nInstallation\n-------------\n\n::\n\n pip install junitparser\n\nUsage\n-----\n\nYou should be relatively familiar with the Junit XML format. If not, run\n``pydoc`` on the exposed classes and functions to see how it's structured.\n\nCreate Junit XML format reports from scratch\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou have some test result data, and you want to convert them into junit.xml\nformat.\n\n.. code-block:: python\n\n from junitparser import TestCase, TestSuite, JUnitXml, Skipped, Error\n\n # Create cases\n case1 = TestCase('case1', 'class.name', 0.5) # params are optional\n case1.classname = \"modified.class.name\" # specify or change case attrs\n case1.result = [Skipped()] # You can have a list of results\n case2 = TestCase('case2')\n case2.result = [Error('Example error message', 'the_error_type')]\n\n # Create suite and add cases\n suite = TestSuite('suite1')\n suite.add_property('build', '55')\n suite.add_testcase(case1)\n suite.add_testcase(case2)\n suite.remove_testcase(case2)\n\n #Bulk add cases to suite\n case3 = TestCase('case3')\n case4 = TestCase('case4')\n suite.add_testcases([case3, case4])\n\n # Add suite to JunitXml\n xml = JUnitXml()\n xml.add_testsuite(suite)\n xml.write('junit.xml')\n\nRead and manipulate existing JUnit/xUnit XML files\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou have some existing junit.xml files, and you want to modify the content.\n\n.. code-block:: python\n\n from junitparser import JUnitXml\n\n xml = JUnitXml.fromfile('/path/to/junit.xml')\n for suite in xml:\n # handle suites\n for case in suite:\n # handle cases\n xml.write() # Writes back to file\n\nIt is also possible to use a custom parser. For example lxml provides a plethora\nof parsing options_. We can use them this way:\n\n.. code-block:: python\n\n from lxml.etree import XMLParser, parse\n from junitparser import JUnitXml\n\n def parse_func(file_path):\n xml_parser = XMLParser(huge_tree=True)\n return parse(file_path, xml_parser)\n\n xml = JUnitXml.fromfile('/path/to/junit.xml', parse_func)\n # process xml...\n\n.. _options: https://lxml.de/api/lxml.etree.XMLParser-class.html\n\nMerge XML files\n~~~~~~~~~~~~~~~\n\nYou have two or more XML files, and you want to merge them into one.\n\n.. code-block:: python\n\n from junitparser import JUnitXml\n\n xml1 = JUnitXml.fromfile('/path/to/junit1.xml')\n xml2 = JUnitXml.fromfile('/path/to/junit2.xml')\n\n newxml = xml1 + xml2\n # Alternatively, merge in place\n xml1 += xml2\n\nNote that it won't check for duplicate entries. You need to deal with them on\nyour own.\n\nSchema Support\n~~~~~~~~~~~~~~~\n\nBy default junitparser supports the schema of windyroad_, which is a relatively\nsimple schema.\n\n.. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd\n\nJunitparser also support extra schemas:\n\n.. code-block:: python\n\n from junitparser.xunit2 import JUnitParser, TestCase, TestSuite, \\\n RerunFailure\n # These classes are redefined to support extra properties and attributes\n # of the xunit2 schema.\n suite = TestSuite(\"mySuite\")\n suite.system_err = \"System err\" # xunit2 specific property\n case = TestCase(\"myCase\")\n rerun_failure = RerunFailure(\"Not found\", \"404\") # case property\n rerun_failure.stack_trace = \"Stack\"\n rerun_failure.system_err = \"E404\"\n rerun_failure.system_out = \"NOT FOUND\"\n case.add_rerun_result(rerun_failure)\n\nCurrently supported schemas including:\n\n- xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.\n\n.. _xunit2: https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd\n\nPRs are welcome to support more schemas.\n\nCreate XML with custom attributes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou want to use an attribute that is not supported by default.\n\n.. code-block:: python\n\n from junitparser import TestCase, Attr, IntAttr, FloatAttr\n\n # Add the custom attribute\n TestCase.id = IntAttr('id')\n TestCase.rate = FloatAttr('rate')\n TestCase.custom = Attr('custom')\n case = TestCase()\n case.id = 123\n case.rate = 0.95\n case.custom = 'foobar'\n\n\nHandling XML with custom element\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThere may be once in 1000 years you want to it this way, but anyways.\nSuppose you want to add element CustomElement to TestCase.\n\n.. code-block:: python\n\n from junitparser import Element, Attr, TestSuite\n\n # Create the new element by subclassing Element,\n # and add custom attributes to it.\n class CustomElement(Element):\n _tag = 'custom'\n foo = Attr()\n bar = Attr()\n\n testcase = TestCase()\n custom = CustomElement()\n testcase.append(custom)\n # To find a single sub-element:\n testcase.child(CustomElement)\n # To iterate over custom elements:\n for custom in testcase.iterchildren(CustomElement):\n ... # Do things with custom element\n\nHandling custom XML attributes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSay you have some data stored in the XML as custom attributes and you want to\nread them out:\n\n.. code-block:: python\n\n from junitparser import TestCase, Attr, JUnitXml\n\n # Create the new element by subclassing Element or one of its child class,\n # and add custom attributes to it.\n class MyTestCase(TestCase):\n foo = Attr()\n\n xml = JUnitXml.fromfile('/path/to/junit.xml')\n for suite in xml:\n # handle suites\n for case in suite:\n my_case = MyTestCase.fromelem(case)\n print(my_case.foo)\n\nCommand Line\n------------\n\n.. code-block:: console\n\n $ junitparser --help\n usage: junitparser [-h] [-v] {merge} ...\n\n Junitparser CLI helper.\n\n positional arguments:\n {merge} command\n merge Merge Junit XML format reports with junitparser.\n verify Return a non-zero exit code if one of the testcases failed or errored.\n\n optional arguments:\n -h, --help show this help message and exit\n -v, --version show program's version number and exit\n\n\n.. code-block:: console\n\n $ junitparser merge --help\n usage: junitparser merge [-h] [--glob] paths [paths ...] output\n\n positional arguments:\n paths Original XML path(s).\n output Merged XML Path, setting to \"-\" will output console\n\n optional arguments:\n -h, --help show this help message and exit\n --glob Treat original XML path(s) as glob(s).\n --suite-name SUITE_NAME\n Name added to <testsuites>.\n\n.. code-block:: console\n\n $ junitparser verify --help\n usage: junitparser verify [-h] [--glob] paths [paths ...]\n\n positional arguments:\n paths XML path(s) of reports to verify.\n\n optional arguments:\n -h, --help show this help message and exit\n --glob Treat original XML path(s) as glob(s).\n\nTest\n----\n\nThe tests are written with python ``unittest``, to run them, use pytest::\n\n pytest test.py\n\nContribute\n----------\n\nPRs are welcome!\n",
"bugtrack_url": null,
"license": "Apache 2.0",
"summary": "Manipulates JUnit/xUnit Result XML files",
"version": "3.2.0",
"project_urls": {
"Homepage": "https://github.com/weiwei/junitparser"
},
"split_keywords": [
"junit",
"xunit",
"xml",
"parser"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "5af9321d566c9f2af81fdb4bb3d5900214116b47be9e26b82219da8b818d9da9",
"md5": "a7ff006becdfcfb7734445d55b5ab053",
"sha256": "e14fdc0a999edfc15889b637390e8ef6ca09a49532416d3bd562857d42d4b96d"
},
"downloads": -1,
"filename": "junitparser-3.2.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "a7ff006becdfcfb7734445d55b5ab053",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 13394,
"upload_time": "2024-09-01T04:07:40",
"upload_time_iso_8601": "2024-09-01T04:07:40.541797Z",
"url": "https://files.pythonhosted.org/packages/5a/f9/321d566c9f2af81fdb4bb3d5900214116b47be9e26b82219da8b818d9da9/junitparser-3.2.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "57886a268028a297751ed73be8e291f12aa727caf22adbc218e8dfbafcc974af",
"md5": "dd14d3692188087c74d5c9ebf70a861d",
"sha256": "b05e89c27e7b74b3c563a078d6e055d95cf397444f8f689b0ca616ebda0b3c65"
},
"downloads": -1,
"filename": "junitparser-3.2.0.tar.gz",
"has_sig": false,
"md5_digest": "dd14d3692188087c74d5c9ebf70a861d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 20073,
"upload_time": "2024-09-01T04:07:42",
"upload_time_iso_8601": "2024-09-01T04:07:42.291536Z",
"url": "https://files.pythonhosted.org/packages/57/88/6a268028a297751ed73be8e291f12aa727caf22adbc218e8dfbafcc974af/junitparser-3.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-01 04:07:42",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "weiwei",
"github_project": "junitparser",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [],
"lcname": "junitparser"
}