{"id":649,"library":"junitparser","title":"JUnitparser","description":"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.","status":"active","version":"4.0.2","language":"python","source_language":"en","source_url":"https://github.com/weiwei/junitparser","tags":["junit","xunit","xml","parser","testing","test-results"],"install":[{"cmd":"pip install junitparser","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Optional dependency for faster XML parsing performance.","package":"lxml","optional":true}],"imports":[{"symbol":"JUnitXml","correct":"from junitparser import JUnitXml"},{"symbol":"TestCase","correct":"from junitparser import TestCase"},{"symbol":"TestSuite","correct":"from junitparser import TestSuite"},{"symbol":"Skipped","correct":"from junitparser import Skipped"},{"symbol":"Error","correct":"from junitparser import Error"}],"quickstart":{"code":"import os\nfrom junitparser import TestCase, TestSuite, JUnitXml, Skipped, Error\n\n# Create some test cases\ncase1 = TestCase('test_feature_a', 'module.submodule', 1.23)\ncase1.result = Skipped('Not implemented yet')\n\ncase2 = TestCase('test_feature_b', 'module.submodule', 0.45)\n# By default, a test case is considered successful if no result is explicitly set or if it's an instance of Success\n\ncase3 = TestCase('test_feature_c', 'another_module', 2.00)\ncase3.result = Error('Connection refused', 'NetworkError')\n\n# Create a test suite and add cases\nsuite = TestSuite('MyTestSuite')\nsuite.add_testcase(case1)\nsuite.add_testcase(case2)\nsuite.add_testcase(case3)\n\n# Create a JUnitXml object and add the suite\nxml = JUnitXml()\nxml.add_testsuite(suite)\n\n# Define output path\noutput_filename = os.environ.get('JUNIT_OUTPUT_PATH', 'report.xml')\n\n# Write the XML to a file\nxml.write(output_filename, pretty=True)\n\nprint(f\"Generated JUnit XML report at: {output_filename}\")\n\n# Example of reading and modifying an existing XML (requires a dummy file)\n# with open('existing_report.xml', 'w') as f:\n#     f.write('<testsuites><testsuite name=\"Existing\" tests=\"1\" failures=\"0\"><testcase name=\"passing_test\" /></testsuite></testsuites>')\n# existing_xml = JUnitXml.fromfile('existing_report.xml')\n# for ts in existing_xml:\n#     ts.name = \"Modified Existing Suite\"\n# existing_xml.write('modified_report.xml', pretty=True)","lang":"python","description":"This quickstart demonstrates how to create a JUnit XML report from scratch, defining test cases with different results (skipped, successful, error), grouping them into a test suite, and then writing the complete report to an XML file. The `pretty=True` argument ensures human-readable formatting."},"warnings":[{"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)`.","message":"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`.","severity":"breaking","affected_versions":">=4.0.0"},{"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: ...`","message":"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.","severity":"breaking","affected_versions":">=4.0.0"},{"fix":"Ensure your project is running on Python 3.6 or later.","message":"In version 3.0.0, support for Python 2 was completely dropped.","severity":"breaking","affected_versions":">=3.0.0"},{"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: ...`.","message":"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.","severity":"breaking","affected_versions":">=2.0.0"},{"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.","message":"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.","severity":"gotcha","affected_versions":">=4.0.0"}],"env_vars":null,"last_verified":"2026-05-12T17:14:29.174Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"fix":"Run `pip install junitparser` in your terminal to install the library.","cause":"The 'junitparser' library is not installed in your Python environment.","error":"ModuleNotFoundError: No module named 'junitparser'"},{"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`.","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).","error":"TypeError: 'TestCase' object is not iterable"},{"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)`.","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.","error":"AttributeError: 'list' object has no attribute 'message' (or similar when accessing TestCase.result)"},{"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')`.","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.","error":"junitparser.JUnitXmlError: Missing file argument."}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":80,"quickstart_tag":"verified","pypi_latest":"5.0.0","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5.2,"disk_size":"17.9M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5.2,"disk_size":"17.9M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.5,"import_time_s":0.04,"mem_mb":5.2,"disk_size":"18M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":5.2,"disk_size":"18M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":5.4,"disk_size":"19.7M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.1,"mem_mb":5.4,"disk_size":"19.7M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.5,"import_time_s":0.08,"mem_mb":5.4,"disk_size":"20M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.08,"mem_mb":5.4,"disk_size":"20M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":5.2,"disk_size":"11.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":5.2,"disk_size":"11.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.4,"import_time_s":0.08,"mem_mb":5.2,"disk_size":"12M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.08,"mem_mb":5.2,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5.2,"disk_size":"11.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.08,"mem_mb":5.2,"disk_size":"11.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.4,"import_time_s":0.06,"mem_mb":5,"disk_size":"12M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5.1,"disk_size":"17.4M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":5.1,"disk_size":"17.4M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.05,"mem_mb":5.1,"disk_size":"18M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.05,"mem_mb":5.1,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":"verified","tag_description":"quickstart runs on critical runtimes, recently tested","results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}