Skip to main content

pytest-junit-logging

Test Code Quality PyPI version Python versions

A pytest plugin that captures log output during test execution and embeds it into JUnit XML reports.

Features

  • 📋 Log Integration: Automatically captures all log output during test execution and embeds it into JUnit XML reports
  • 🎯 Smart Scope Distribution: Session-scoped logs appear in all tests, module-scoped logs appear in module tests, function-scoped logs appear only in specific tests
  • ⚡ Execution Phase Tags: Each log entry includes a step attribute indicating whether it occurred during setup or test execution
  • 🔧 Configurable Log Levels: Control verbosity with --junit-log-level option
  • 🚨 Assertion Capture: Automatically captures assertion failure messages as ASSERT-level logs
  • 📁 Relative Paths: File paths in logs are relative to project root for portability
  • 🔒 Safe HTML: Automatically escapes HTML entities in log messages

Installation

pip install pytest-junit-logging

Usage

The plugin activates automatically when you use pytest's --junit-xml option:

# Basic usage - captures all logs (DEBUG level and above)
pytest --junit-xml=results.xml

# Control log verbosity
pytest --junit-xml=results.xml --junit-log-level=WARNING

# Only capture errors and critical logs
pytest --junit-xml=results.xml --junit-log-level=ERROR

Command Line Options

Option Choices Default Description
--junit-log-level DEBUG, INFO, WARNING, ERROR, CRITICAL DEBUG Minimum log level to include in JUnit XML

Example

Test Code

# test_example.py
import logging
import pytest

@pytest.fixture(scope="session")
def database():
    logging.info("Setting up database connection")
    yield "db_connection"
    logging.info("Closing database connection")

@pytest.fixture
def user_data():
    logging.debug("Creating test user data")
    return {"id": 123, "name": "Test User"}

def test_user_creation(database, user_data):
    logging.info("Starting user creation test")
    logging.debug(f"Using database: {database}")
    logging.debug(f"User data: {user_data}")

    # Simulate test logic
    assert user_data["id"] == 123

    logging.info("User creation test completed successfully")

def test_user_validation(database, user_data):
    logging.warning("This is a validation test")
    assert user_data["name"] == "Test User"

Generated JUnit XML

pytest test_example.py --junit-xml=results.xml --junit-log-level=INFO

The resulting results.xml will include embedded logs:

<?xml version='1.0' encoding='utf-8'?>
<testsuites name="pytest tests">
    <testsuite name="pytest" errors="0" failures="0" skipped="0" tests="2" time="0.01">
        <testcase classname="test_example" name="test_user_creation" time="0.001">
            <logs>
                <log step="setup" ts="2025-11-27T10:00:00.000000+00:00" level="INFO" src="test_example.py:8">Setting up database connection</log>
                <log step="test" ts="2025-11-27T10:00:00.100000+00:00" level="INFO" src="test_example.py:15">Starting user creation test</log>
                <log step="test" ts="2025-11-27T10:00:00.200000+00:00" level="INFO" src="test_example.py:22">User creation test completed successfully</log>
            </logs>
        </testcase>
        <testcase classname="test_example" name="test_user_validation" time="0.001">
            <logs>
                <log step="setup" ts="2025-11-27T10:00:01.000000+00:00" level="INFO" src="test_example.py:8">Setting up database connection</log>
                <log step="test" ts="2025-11-27T10:00:01.100000+00:00" level="WARNING" src="test_example.py:25">This is a validation test</log>
                <log step="test" ts="2025-11-27T10:00:01.200000+00:00" level="INFO" src="test_example.py:10">Closing database connection</log>
            </logs>
        </testcase>
    </testsuite>
</testsuites>

Log Scope Distribution

The plugin intelligently distributes logs based on fixture scopes:

  • Session-scoped fixture logs: Appear in all test cases
  • Module-scoped fixture logs: Appear in all tests within the same module
  • Function-scoped fixture logs: Appear only in the specific test that uses the fixture
  • Test execution logs: Appear only in their respective test case

Execution Phase Tags

Each log entry includes a step attribute that indicates during which execution phase the log was generated:

  • step="setup": Logs from fixture setup (session, module, function fixture initialization)
  • step="test": Logs from test execution and fixture teardown

This makes it easy to distinguish between infrastructure logs (fixture setup) and actual test logic:

<logs>
    <log step="setup" ts="..." level="INFO" src="conftest.py:10">Database connection established</log>
    <log step="setup" ts="..." level="INFO" src="conftest.py:25">User fixtures initialized</log>
    <log step="test" ts="..." level="DEBUG" src="test_user.py:15">Starting user validation test</log>
    <log step="test" ts="..." level="INFO" src="test_user.py:20">User validation completed</log>
    <log step="test" ts="..." level="INFO" src="conftest.py:30">Cleaning up user fixtures</log>
</logs>

Post-processing: To flatten the structure and remove step attributes for legacy compatibility, simply strip the step="..." attributes from log elements.

Log Level Filtering

Control the verbosity of logs in your JUnit XML:

# Capture everything (default)
pytest --junit-xml=results.xml --junit-log-level=DEBUG

# Only warnings and above
pytest --junit-xml=results.xml --junit-log-level=WARNING

# Only errors and critical
pytest --junit-xml=results.xml --junit-log-level=ERROR

Note: ASSERT level logs (from assertion failures) are always included regardless of the log level filter.

Integration with CI/CD

The plugin works seamlessly with CI/CD systems that consume JUnit XML reports:

  • GitHub Actions: Use with dorny/test-reporter
  • Jenkins: Native JUnit plugin support
  • GitLab CI: Built-in JUnit report processing
  • Azure DevOps: Test result publishing

Requirements

  • Python 3.10+
  • pytest 6.0+

Development

Setup Development Environment

# Clone the repository
git clone <repository-url>
cd pytest-junit-logging

# Install in development mode with dev dependencies
pip install -e ".[dev]"

Running Tests

Local Testing

# Run tests for current Python version
pytest

# Run tests across all supported Python/pytest combinations (requires all Python versions)
tox

# Run specific tox environment
tox -e py312-pytest8

Docker-based Testing (Recommended)

# Run all tests across all Python versions in Docker
./scripts/test-all.sh

# Test specific Python version
./scripts/test-single.sh 3.11 8    # Python 3.11 with pytest 8.x

# Test individual components with docker-compose
docker-compose -f docker-compose.test.yml run --rm test-py310    # Python 3.10 tests
docker-compose -f docker-compose.test.yml run --rm test-quality  # Linting & coverage
docker-compose -f docker-compose.test.yml run --rm test-mindeps  # Minimum dependencies

Tox Test Matrix

The project tests against these combinations:

Python pytest 6.x pytest 7.x pytest 8.x
3.10 ✅ ✅ ✅
3.11 - ✅ ✅
3.12 - - ✅
3.13 - - ✅

Code Quality

Manual Code Quality Checks

# Format code
black src/ tests/

# Lint code
ruff check src/ tests/

# Type checking
mypy src/

Pre-commit Hooks (Recommended)

Install pre-commit to automatically run quality checks before commits:

# Install pre-commit
pip install pre-commit

# Install the git hook scripts
pre-commit install

# (Optional) Run against all files
pre-commit run --all-files

Once installed, pre-commit will automatically run:

  • Black (code formatting)
  • Ruff (linting with auto-fixes)
  • MyPy (type checking)
  • Bandit (security scanning)
  • Basic file checks (trailing whitespace, YAML validation, etc.)

This ensures all committed code meets quality standards and matches the CI pipeline checks.

CI/CD and Releases

Automated Testing

The project uses GitHub Actions for comprehensive CI/CD:

  • Test Matrix: Runs all tests across Python 3.10-3.13 and pytest 6.x-8.x combinations
  • Code Quality: Automated linting (ruff), formatting (black), and type checking (mypy)
  • Integration Tests: End-to-end testing with real pytest runs and XML validation
  • Security Scanning: Automated security scans with bandit
  • Coverage Reporting: Code coverage tracking and reporting

All checks must pass before code can be merged to main.

Releases

Releases are fully automated:

  1. Create a git tag with version format vX.Y.Z (e.g., v1.0.0, v2.1.3)
  2. Push the tag to trigger the release workflow:
    git tag v1.0.0
    git push origin v1.0.0
    
  3. Automated pipeline runs:
    • Full test suite across all supported versions
    • Code quality checks
    • Package building and validation
    • PyPI publishing (using trusted publishing)
    • GitHub release creation with changelog

Version Management

The project uses setuptools-scm for automatic version management:

  • Version is automatically derived from git tags
  • No manual version bumping required
  • Supports semantic versioning and pre-releases (e.g., v1.0.0-beta1)

License

MIT License - see LICENSE file for details.

Metadata

Release files for pytest-junit-logging 0.1.0

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

Source distribution (sdist)

Source distribution for pytest-junit-logging 0.1.0
File Size Uploaded
pytest_junit_logging-0.1.0.tar.gz 34.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-junit-logging 0.1.0
File Interpreter ABI Platform
pytest_junit_logging-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.0 kB

Release files / pytest_junit_logging-0.1.0.tar.gz

Download URL pytest_junit_logging-0.1.0.tar.gz
Size 34.1 kB
Tags Source
SHA-256 checksum
How to use checksums
03b22edd822d0c010df65def56e3a577314be3c2af8b8b93c11e9612f55acf0c
BLAKE2b-256 checksum
How to use checksums
8b1d437ab81482d43a98151d5eae820cb896d440d162a56b9fb0187ad56391c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Nov 27, 2025.

Transparency log

Release files / pytest_junit_logging-0.1.0-py3-none-any.whl

Download URL pytest_junit_logging-0.1.0-py3-none-any.whl
Size 11.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ece2266ba1d2491da4c155c25410fcc9d5c8979426b9439e1ec7cc5ca0ab1dc
BLAKE2b-256 checksum
How to use checksums
c9332dae619380a9dd28f6504c6e1ee1969a5509e75fd80e994e9296961ba72f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Nov 27, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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