Skip to main content

pytest-human

PyPI version Python versions License

logo

A pytest plugin for generating beautiful, human-readable HTML reports for individual tests with collapsible nested logging spans and syntax highlighting. Inspired by Robot Framework and Playwright reports.

Unlike other pytest HTML report plugins, pytest-human creates a separate HTML log file for each test, aimed at helping you dive into specific parts of the test that are relevant for debugging, while hiding unrelated logs.

Works with standard python logging, no need to rewrite existing tests to get going!

Features

  • Logs
    • Beautiful test logs
    • Collapsible spans
    • Syntax highlighting
    • Colored log levels
    • Support for existing native python logging
    • Streaming log writing
  • Tracing
    • Automatic fixture logging
    • Automatic method call logging
    • Third-party method tracing
  • Debugging
    • Deep error highlighting
    • Regex search in collapsed spans
    • Artifacts collection

Demo

https://github.com/user-attachments/assets/5c9932b1-396c-4705-ba7d-95fbff7f07be

Example Report and test source file.

Installation

Install from PyPI:

pip install pytest-human

Quick Start

Basic Usage

  1. Enable the plugin when running pytest:

    pytest --enable-html-log
    
  2. Use the human object and the @traced decorator in your tests:

    from pytest_human.tracing import traced
    from pytest_human.log import get_logger
    
    log = get_logger(__name__)
    
    # Each call to this function will be logged
    @traced
    def insert_db(data):
        query = "INSERT INTO flowers (petals) VALUES ('{{1,2,3,4,5}}');"
        log.info(f"executing {query=}")
        return len(data)
    
    def test_example(human):
        """This test demonstrates pytest-human logging."""
        human.log.info("Established test agent connection")
    
        with human.span.info("Generating sample data"):
            data = [1, 2, 3, 4, 5]
            human.log.info(f"Loaded sample data {data=} {len(data)=}", highlight=True)
            insert_db(data)
    
            with human.span.debug("Validating sample"):
                result = sum(data)
                human.log.debug(f"Sum {result=}", highlight=True)
    
        assert result == 15
    
  3. Open the HTML test log

    At the end of individual tests you will see a similar line:

    🌎 Test test_example HTML log at file:///tmp/pytest-of-john.doe/pytest-2/session_logs/test_example.html
    

    You can Ctrl/⌘-Click the link in most terminals to open the file.

  4. Debug!

    Screenshot

Command Line Options

Enable HTML Logging

# To enable HTML logging support and use this plugin, pass this flag

pytest --enable-html-log

Log Location

Control where HTML logs are saved:

# Save all test logs in a custom directory specified by the user.
pytest --html-output-dir /path/to/logs

# By default logs are saved in the session temp directory with the test name
# e.g. /tmp/pytest-of-user.name/pytest-446/session_logs/test_method_tracing.html
pytest --enable-html-log

# Save in individual test temporary directories as test.html
# e.g. /tmp/pytest-of-user.name/pytest-446/session_logs/test_examplecurrent/test.html
pytest --enable-html-log --html-use-test-tmp

Log Level

Control the minimum log level:

# Use pytest's global log level.
# Opt to use this setting in order to capture non-human python logs.
pytest --enable-html-log --log-level DEBUG

# Set log level for HTML logs specifically.
pytest --enable-html-log --html-log-level INFO

Quiet mode

--html-quiet reduces the amount of console messages generated by pytest, especially test files locations. Using pytest -q also achieves the same behavior.

Logging to root logger

Human tracing and logs are logged by default only to the HTML log. If you would like to use the tracing features for the regular python log you can use --html-log-to-all, which will log everything to the root logger.

Logger API

Fixtures

  • human - Supplies a human object to the test. Includes a logger and attachment collector.
  • test_log - Supplies a logger to the test, equivalent to human.log.
  • human_test_log_path - Path of the html log file for the current test

Logging Methods

def test_logging_methods(human):
    # Basic logging at different levels
    human.log.trace("Trace level message")
    human.log.debug("Debug level message")
    human.log.info("Info level message")
    human.log.warning("Warning level message")
    human.log.error("Error level message")
    human.log.critical("Critical level message")

    # Syntax highlighting for code
    code = """
    import numpy as np

    def bark(volume: float) -> bool:
        return volume > 0.5:
    """
    human.log.info(code, highlight=True)

Screenshot

Direct logger access

Get the test logger programmatically, this allows to tweak the source name and is useful if you don't want to pass the human object around.

from pytest_human.log import get_logger

def test_programmatic_logger():
    logger = get_logger(__name__)
    logger.info("Custom logger")

Collapsible Spans

Create nested, collapsible sections in your HTML logs.

This allows partitioning the log into sections and diving only into the parts of the logs that are relevant to your debug session.

def test_spans(human):
    human.log.info("Starting complex operation")

    with human.span.info("Phase 1: Initialization"):
        human.log.debug("Initializing resources...")

        with human.span.debug("Loading configuration"):
            human.log.trace("Reading config file")
            config = load_config()
            human.log.debug(f"Config loaded: {config}")

        human.log.info("Initialization complete")

    with human.span.info("Phase 2: Processing"):
        human.log.debug("Processing data...")
        process_data()

    human.log.info("Operation completed")

Screenshot

Method Tracing

A lot of debug logging is centered around logging a function when called, returned a value or threw an error. Human supplies the @traced decorator to allow for automatic method tracing in log.

Each method call shows the function call parameters, opens a new span that includes all of the logging that happened in its scope, as well as its return value. This allows for easy segmentation of nested loggings and reduces the amount of noise encountered when debugging.

Screenshot

import logging
import time
from pytest_human.log import get_logger
from pytest_human.tracing import traced

# Add the @traced decorator for automatic logging of method call/return
@traced
def save_login(login):
    log = get_logger(__name__)
    log.info("a log inside save_login")
    return update_db(login)

@traced(log_level=logging.TRACE)
def update_db(login):
    log = get_logger(__name__)
    delay_time = 2
    log.info("delaying db update by 2 seconds")
    time.sleep(delay_time)
    return delay_time

def test_method_tracing(human):
    delay = save_login("hello")
    assert delay == 2

@traced supports the following parameters:

  • suppress_return - do not log the return value, useful when it is overly long
  • suppress_params - do not log the method parameters, useful when they are overly long
  • suppress_self - do not show the self argument logging function parameters, default True
  • suppress_none - do not show parameters with the value None, can clean up traces of highly parameterized functions, default False.
  • truncate_values - truncates overly long values in traces, default True.
  • log_level - set a log level for the method trace

Note: tracing will have some performance implications on method calls, also you should limit using @traced on frequently called functions as to reduce log noise.

Third-party method tracing

The @traced decorator is very useful for debugging but it is unfortunately restricted to code you own.

In order to trace third-party methods, you can use the trace_calls and trace_public_api methods, which wrap third party code with human tracing.

trace_calls adds tracing to a list of functions, while trace_public_api adds tracing to all public methods of modules/classes.

import pytest
from playwright.sync_api import Locator, LocatorAssertions, Page
from pytest_human.tracing import trace_calls, trace_public_api

@pytest.fixture(autouse=True)
def log_3rdparty_methods():
    with (
        trace_calls(
            pytest.Pytester.runpytest,
            pytest.Pytester.makepyfile,
        ),

        # set a custom configuration for a traced function
        trace_calls(Page.screenshot, suppress_return=True, suppress_self=False, suppress_none=True),

        # this skips Page.screenshot as it is already defined above
        trace_public_api(
            Page, Locator, LocatorAssertions, suppress_self=False, suppress_none=True
        ),

        # alternatively use a full string path. This also works when regular objects fail.
        trace_calls("requests.get")
    ):
        yield

Fixture tracing

Each fixture setup and teardown calls are traced with parameters and return value, similar to the @traced decorator. This happens without any user action and are located in the Test Setup and Test Teardown spans.

Artifacts

Sometimes you might have extra logs that are generated by subprocesses or used external resources such as Kubernetes, Docker or others. Human automatically collects stdout/stderr and allows you to collect custom logs to be attached to the log.

Screenshot

def test_artifacts(human):
    human.log.info("Attaching artifacts to the test log")

    print("logging something to stdout")

    log_content = """
    [10:00:01] First line of the log.
    [10:00:03] Line 2 of the log.
    [10:00:05] Line 3 of the log.
    """
    human.artifacts.add_log_text(log_content, "sample.log", description="Sample log file")

Logging

Using Standard Python Logging

pytest-human integrates with Python's standard logging system. All logs from any logger will be captured:

import logging

def test_standard_logging(human):
    # pytest-human logger
    human.log.info("Using human fixture")

    # Standard Python logger - also captured in HTML
    logger = logging.getLogger(__name__)
    logger.info("Using standard logger")

[!NOTE] The Python logger is captured through the root logger, and will only be captured according to its log level. You can change the root log level from pytest using the --log-level switch.

TRACE Logging

pytest-human adds a custom TRACE log level below DEBUG for more verbose logging:

def test_trace_logging(human):
    human.log.trace("Very detailed trace information")

Run with trace level:

pytest --enable-html-log --log-level trace

Keyboard navigation

You can use the keyboard to navigate around the log.

  • Press Tab and Shift+Tab to jump between the expand buttons (+).
  • Press Enter to expand and collapse a span when hovering over a button.
  • Press / to jump to the search box and Esc to unfocus.
  • When searching use Enter to jump to the next result and Shift+Enter to jump to the previous result.

Development

Running Tests

# Install development dependencies
pip install --group dev -e .

# Run tests
pytest

# Run with coverage
pytest --cov=pytest_human

Alternatively use tox

tox

License

Distributed under the Apache Software License 2.0. See LICENSE for more information.

Links

Metadata

Release files for pytest-human 1.2.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-human 1.2.0
File Size Uploaded
pytest_human-1.2.0.tar.gz 50.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-human 1.2.0
File Interpreter ABI Platform
pytest_human-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 95.5 kB

Release files / pytest_human-1.2.0.tar.gz

Download URL pytest_human-1.2.0.tar.gz
Size 50.8 kB
Tags Source
SHA-256 checksum
How to use checksums
19ee022af6bafbb97c70f673b4315f730a9c5c02aca47ed1020c51afe2953856
BLAKE2b-256 checksum
How to use checksums
788827e93bfd872ddc7824e15e8a3da5f223ed58f35113df1ecdcd05fb5b2d4c
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 Jan 25, 2026.

Transparency log

Release files / pytest_human-1.2.0-py3-none-any.whl

Download URL pytest_human-1.2.0-py3-none-any.whl
Size 44.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d3426bf6ac3ec096e1bb2e74669fc4408756934bd88e10166f0d59c6620d23bf
BLAKE2b-256 checksum
How to use checksums
b07f610e754a5886af07dd93c232915e979b26d9d1e4b67ebe5bfe0380f852a7
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 Jan 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

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