Can I get the error message from `pytest_runtest_protocol`?

I’m trying to capture test case details using the pytest_runtest_protocol hook in conftest.py. I can get the test name and outcome like this:

def pytest_runtest_protocol(item, log=True, nextitem=None):
    reports = runtestprotocol(item, nextitem=nextitem)
    for report in reports:
        if report.when == 'call':
            print(f'{item.name} --- {report.outcome}')
    return True

I read in the documentation that pytest_runtest_protocol can also hold exception details. Is it possible to get the actual error message or exception that caused the test to fail from this hook?

You can get the actual exception from the pytest_runtest_protocol hook by inspecting the longrepr attribute of the call report:

from _pytest.runner import runtestprotocol

def pytest_runtest_protocol(item, nextitem=None):
    reports = runtestprotocol(item, nextitem=nextitem)
    for report in reports:
        if report.when == "call" and report.failed:
            print(f"{item.name} failed: {report.longrepr}")
    return True

longrepr contains the full traceback and message, so you can log or process it. I used this in a plugin once to send failed test messages to Slack.

If you only want the exception text, you can convert longrepr to a string or, in newer pytest versions, use reprcrash.message:

from _pytest.runner import runtestprotocol

def pytest_runtest_protocol(item, nextitem=None):
    reports = runtestprotocol(item, nextitem=nextitem)
    for report in reports:
        if report.when == "call" and report.failed:
            msg = getattr(report.longrepr, "reprcrash", None)
            if msg:
                print(f"{item.name} failed: {msg.message}")
            else:
                print(f"{item.name} failed: {report.longrepr}")
    return True

This gives just the exception text without the full traceback. I found it handy for summarizing failures in a custom report.

I usually like logging both the outcome and the actual exception in a readable way:

from _pytest.runner import runtestprotocol

def pytest_runtest_protocol(item, nextitem=None):
    reports = runtestprotocol(item, nextitem=nextitem)
    for report in reports:
        if report.when == "call":
            if report.failed:
                print(f"Test {item.name} failed with: {report.longreprtext}")
            else:
                print(f"Test {item.name} passed")
    return True

longreprtext is basically str(report.longrepr) and is safe to print directly. I’ve used this approach in CI pipelines to capture both passes and failures cleanly.