JUnitparser
raw JSON → 4.0.2 verified Tue May 12 auth: no python install: verified quickstart: verified
JUnitparser is a Python library designed to parse and manipulate JUnit/xUnit Result XML files. It allows users to read and modify existing XML reports or create new ones from scratch. The library is actively maintained, with its current version being 4.0.2, and has a consistent release cadence addressing new features and bug fixes.
pip install junitparser Common errors
error ModuleNotFoundError: No module named 'junitparser' ↓
cause The 'junitparser' library is not installed in your Python environment.
fix
Run
pip install junitparser in your terminal to install the library. error TypeError: 'TestCase' object is not iterable ↓
cause This error often occurs when attempting to iterate directly over a `TestCase` object, or if the JUnit XML file structure does not match expectations (e.g., missing a top-level `<testsuites>` tag when iterating an XML object expecting multiple suites).
fix
Ensure you are iterating over a
JUnitXml object to get TestSuite objects, and then iterating over TestSuite objects to get TestCase objects. If your XML has only one suite and no <testsuites> root, JUnitXml.fromfile() will parse it correctly, but direct iteration assumes a root of TestSuite or JUnitXml containing suites. For example: xml = JUnitXml.fromfile('report.xml'); for suite in xml: for case in suite: pass. error AttributeError: 'list' object has no attribute 'message' (or similar when accessing TestCase.result) ↓
cause In `junitparser` version 2.0.0 and later, the `TestCase.result` attribute was changed from a single `Result` object to a list of `Result` objects to accommodate multiple failures/errors per test case. Code written for older versions will fail when trying to access attributes directly on this list.
fix
Update your code to iterate over the
TestCase.result list to access individual result objects and their attributes. For example, change case.result.message to for r in case.result: print(r.message). error junitparser.JUnitXmlError: Missing file argument. ↓
cause This error occurs when attempting to write a JUnit XML object to a file without providing a target file path or a file-like object.
fix
Provide a valid file path (string or
Path object) or a file-like object when calling write_xml() or JUnitXml.write(). For example: xml.write('output.xml') or write_xml(xml, 'output.xml'). Warnings
breaking In version 4.0.0, several arguments for writing methods were renamed or changed from positional to keyword. `filepath` in `write_xml`, `TestSuite.write`, and `JUnitXml.write` is now `file_or_filename`. `pretty` became a keyword-only argument. `to_console` was removed; use `sys.stdout` for console output. `JUnitXml.fromfile`'s `filepath` argument is now `file`. ↓
fix Update method calls to use new argument names (`file_or_filename`, `file`) and pass `pretty` as a keyword argument. Replace `to_console=True` with `obj.write(sys.stdout)`.
breaking As of version 4.0.0, `JUnitXml.fromfile`, `JUnitXml.fromstring`, and `JUnitXml.fromroot` methods consistently return a `JUnitXml` instance. Previously, if the root element was `<testsuite>`, it might return a `TestSuite` instance directly. ↓
fix Always expect a `JUnitXml` object as the return type and iterate over its `testsuites` attribute if you need to access individual suites: `for suite in xml: ...`
breaking In version 3.0.0, support for Python 2 was completely dropped. ↓
fix Ensure your project is running on Python 3.6 or later.
breaking In version 2.0.0, the `TestCase.result` attribute was changed from a single object to a list of result objects (e.g., `Failure`, `Skipped`, `Error`, `Success`). This allows for multiple outcomes, particularly relevant for pytest reports. ↓
fix Update code that accesses `TestCase.result` to expect and iterate over a list. For example, `case.result[0]` to get the first result or `for res in case.result: ...`.
gotcha The `TestCase.result` setter in version 4.0.0 and above now throws a `ValueError` if an invalid type is assigned, whereas earlier versions might have silently ignored it. ↓
fix Ensure that any value assigned to `TestCase.result` is an instance of a valid result class (e.g., `Success`, `Failure`, `Error`, `Skipped`) or a list of such instances.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.07s 17.9M
3.10 alpine (musl) - - 0.07s 17.9M
3.10 slim (glibc) wheel 1.5s 0.04s 18M
3.10 slim (glibc) - - 0.04s 18M
3.11 alpine (musl) wheel - 0.09s 19.7M
3.11 alpine (musl) - - 0.10s 19.7M
3.11 slim (glibc) wheel 1.5s 0.08s 20M
3.11 slim (glibc) - - 0.08s 20M
3.12 alpine (musl) wheel - 0.09s 11.6M
3.12 alpine (musl) - - 0.09s 11.6M
3.12 slim (glibc) wheel 1.4s 0.08s 12M
3.12 slim (glibc) - - 0.08s 12M
3.13 alpine (musl) wheel - 0.07s 11.3M
3.13 alpine (musl) - - 0.08s 11.2M
3.13 slim (glibc) wheel 1.4s 0.06s 12M
3.13 slim (glibc) - - 0.07s 12M
3.9 alpine (musl) wheel - 0.07s 17.4M
3.9 alpine (musl) - - 0.07s 17.4M
3.9 slim (glibc) wheel 1.7s 0.05s 18M
3.9 slim (glibc) - - 0.05s 18M
Imports
- JUnitXml
from junitparser import JUnitXml - TestCase
from junitparser import TestCase - TestSuite
from junitparser import TestSuite - Skipped
from junitparser import Skipped - Error
from junitparser import Error
Quickstart verified last tested: 2026-04-24
import os
from junitparser import TestCase, TestSuite, JUnitXml, Skipped, Error
# Create some test cases
case1 = TestCase('test_feature_a', 'module.submodule', 1.23)
case1.result = Skipped('Not implemented yet')
case2 = TestCase('test_feature_b', 'module.submodule', 0.45)
# By default, a test case is considered successful if no result is explicitly set or if it's an instance of Success
case3 = TestCase('test_feature_c', 'another_module', 2.00)
case3.result = Error('Connection refused', 'NetworkError')
# Create a test suite and add cases
suite = TestSuite('MyTestSuite')
suite.add_testcase(case1)
suite.add_testcase(case2)
suite.add_testcase(case3)
# Create a JUnitXml object and add the suite
xml = JUnitXml()
xml.add_testsuite(suite)
# Define output path
output_filename = os.environ.get('JUNIT_OUTPUT_PATH', 'report.xml')
# Write the XML to a file
xml.write(output_filename, pretty=True)
print(f"Generated JUnit XML report at: {output_filename}")
# Example of reading and modifying an existing XML (requires a dummy file)
# with open('existing_report.xml', 'w') as f:
# f.write('<testsuites><testsuite name="Existing" tests="1" failures="0"><testcase name="passing_test" /></testsuite></testsuites>')
# existing_xml = JUnitXml.fromfile('existing_report.xml')
# for ts in existing_xml:
# ts.name = "Modified Existing Suite"
# existing_xml.write('modified_report.xml', pretty=True)