A pytest plugin for embedding log output into JUnit XML reports
Project description
pytest-junit-logging
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
stepattribute indicating whether it occurred during setup or test execution - 🔧 Configurable Log Levels: Control verbosity with
--junit-log-leveloption - 🚨 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:
- Create a git tag with version format
vX.Y.Z(e.g.,v1.0.0,v2.1.3) - Push the tag to trigger the release workflow:
git tag v1.0.0 git push origin v1.0.0
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_junit_logging-0.1.0.tar.gz.
File metadata
- Download URL: pytest_junit_logging-0.1.0.tar.gz
- Upload date:
- Size: 34.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03b22edd822d0c010df65def56e3a577314be3c2af8b8b93c11e9612f55acf0c
|
|
| MD5 |
3155af8357576cf4f52eaf53563dd496
|
|
| BLAKE2b-256 |
8b1d437ab81482d43a98151d5eae820cb896d440d162a56b9fb0187ad56391c7
|
Provenance
The following attestation bundles were made for pytest_junit_logging-0.1.0.tar.gz:
Publisher:
release.yml on Mewongu/pytest-junit-logging
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_junit_logging-0.1.0.tar.gz -
Subject digest:
03b22edd822d0c010df65def56e3a577314be3c2af8b8b93c11e9612f55acf0c - Sigstore transparency entry: 729665319
- Sigstore integration time:
-
Permalink:
Mewongu/pytest-junit-logging@640d0cf1c100569b8ba44345937167f949367dcd -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Mewongu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@640d0cf1c100569b8ba44345937167f949367dcd -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_junit_logging-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pytest_junit_logging-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ece2266ba1d2491da4c155c25410fcc9d5c8979426b9439e1ec7cc5ca0ab1dc
|
|
| MD5 |
0086efeceec0809b397b6bcbc4cad2a6
|
|
| BLAKE2b-256 |
c9332dae619380a9dd28f6504c6e1ee1969a5509e75fd80e994e9296961ba72f
|
Provenance
The following attestation bundles were made for pytest_junit_logging-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Mewongu/pytest-junit-logging
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_junit_logging-0.1.0-py3-none-any.whl -
Subject digest:
1ece2266ba1d2491da4c155c25410fcc9d5c8979426b9439e1ec7cc5ca0ab1dc - Sigstore transparency entry: 729665329
- Sigstore integration time:
-
Permalink:
Mewongu/pytest-junit-logging@640d0cf1c100569b8ba44345937167f949367dcd -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Mewongu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@640d0cf1c100569b8ba44345937167f949367dcd -
Trigger Event:
push
-
Statement type: