Skip to main content

junitparser – Pythonic JUnit/xUnit Result XML Parser

https://github.com/weiwei/junitparser/actions/workflows/ci.yml/badge.svg?branch=master https://codecov.io/gh/weiwei/junitparser/branch/master/graph/badge.svg?token=UotlfRXNnK

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

Note on version 5

Python 3.10 or above is now required. If you are still using Python 3.9 or below, please use version 4.0.2.

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.

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.

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:

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...

Merge XML files

You have two or more XML files, and you want to merge them into one.

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.

Junitparser also support extra schemas:

# Extended with extra properties/attributes from the xunit2 schema.
from junitparser.xunit2 import TestCase, 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.

Create XML with custom attributes

You want to use an attribute that is not supported by default.

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.

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:

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

$ 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
$ 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>.
$ 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

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

Contribute

PRs are welcome!

Metadata

Release files for junitparser 5.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for junitparser 5.0.3
File Size Uploaded Provenance Actions
junitparser-5.0.3.tar.gz 12.1 kB With provenance Details for junitparser-5.0.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for junitparser 5.0.3
File Interpreter ABI Platform Actions
junitparser-5.0.3-py3-none-any.whl Python 3 none any Details for junitparser-5.0.3-py3-none-any.whl

Total release size: 27.1 kB

Release files / junitparser-5.0.3.tar.gz

Download URL junitparser-5.0.3.tar.gz
Size 12.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f824ec1b7afbf2fd3b6813b87967b02fcda750e135d27be17cae497147419a04
BLAKE2b-256 checksum
How to use checksums
6cc9edeb58b0565b74ba0ffedf822d63aa40368bb4ff2da49751768eea696f5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / junitparser-5.0.3-py3-none-any.whl

Download URL junitparser-5.0.3-py3-none-any.whl
Size 15.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
553535b8cd1672a9c8bd2c7326ae1ae69279267fbc562106b5aa44d3c5f769af
BLAKE2b-256 checksum
How to use checksums
fc36dbb6f74621890e5daa831c5f474b81e8b773298adf5ea835c03215a9359c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Security for junitparser 5.0.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for junitparser-5.0.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

5.0.3 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page