Pytest impl hooks to alter terminal output

27-05-2025

API:

Hooks:

Terminal reporter:

Refs:

import warnings
from collections import defaultdict
 
import pytest
from _pytest.runner import CallInfo
from _pytest.terminal import TerminalReporter
 
 
@pytest.hookimpl()
def pytest_sessionstart(session):
    print("Hello from `pytest_sessionstart`")
 
 
@pytest.hookimpl()
def pytest_sessionfinish(session, exitstatus):
    print("Hello from `pytest_sessionfinish` hook {exitstatus=}")
 
 
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call: CallInfo):
    # Let's ensure we are dealing with a test report
    if call.when == "call":
        outcome = call.excinfo
 
        try:
            # Access the test outcome (passed, failed, etc.)
            test_outcome = "failed" if outcome else "passed"
            # Access the test duration
            test_duration = call.duration
            # Access the test ID (nodeid)
            test_id = item.nodeid
 
            # Print Test Outcome and Duration
            print(f"Test: {test_id}=, {test_outcome=}, {test_duration=:.5f} seconds")
        except Exception as e:
            print("ERROR:", e)
 
 
@pytest.hookimpl()
def pytest_warning_recorded(warning_message: warnings.WarningMessage, nodeid: str):
    """
    This hook is called when a warning is recorded during test execution.
    It allows us to capture and process warnings.
    """
    # print(nodeid)
    return None
 
 
@pytest.hookimpl(trylast=True)
def pytest_terminal_summary(terminalreporter: TerminalReporter, exitstatus, config):
    terminal_warnings = terminalreporter.stats.get("warnings", [])
    if len(terminal_warnings) == 0:
        return None
 
    warning_location_to_nodeid = defaultdict(set)
 
    warning_count_by_nodeid = defaultdict(int)
    for report in terminal_warnings:
        nodeid = report.nodeid
        warning_count_by_nodeid[nodeid] += 1
 
        warning_location = report.fslocation
        warning_location_to_nodeid[warning_location].add(nodeid)
 
    # sort the warning_count_by_nodeid by count
    sorted_warnings = sorted(warning_count_by_nodeid.items(), key=lambda x: x[1], reverse=True)
    terminalreporter.write_line("Warnings Summary by nodeid:", bold=True)
    for nodeid, count in sorted_warnings:
        terminalreporter.write_line(f"{nodeid}: {count} warnings", yellow=True)
 
    sorted_location_warnings = sorted(
        warning_location_to_nodeid.items(), key=lambda x: len(x[1]), reverse=True
    )
    terminalreporter.write_line("Warnings Summary by type:", bold=True)
    for warning_location, nodeids in sorted_location_warnings:
        warning_location_str = "::".join([str(w) for w in warning_location])
        terminalreporter.write_line(f"{warning_location_str}: {len(nodeids)} warnings", yellow=True)
        for nodeid in nodeids:
            terminalreporter.write_line(f"  - {nodeid}")