How to run a custom pytest_runtest_makereport hook after internal hooks like skipping.py to modify xfail/skipped report data?

I’m writing a hookwrapper for pytest_runtest_makereport and want my logic to execute after pytest’s internal skipping logic.

I tried both:

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item: Item, call: CallInfo):
    ...

and

@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_runtest_makereport(item: Item, call: CallInfo):
    ...

but my code doesn’t seem to run after the internal xfail/skipping processing.

I need guidance on how to ensure my wrapper executes last so I can safely adjust rep.outcome or rep.wasxfail.

The key is that pytest’s internal skipping and xfail logic runs in pytest_runtest_makereport hooks registered in pytest._internal, which are often invoked after user-defined hooks with trylast=True.

To ensure your hook sees the final report, do a hookwrapper and yield first, then modify the result:

import pytest

@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_runtest_makereport(item, call):
    # execute the standard hooks first
    outcome = yield
    rep = outcome.get_result()

    # now rep has final xfail/skipped info
    if getattr(rep, "wasxfail", False):
        # example: convert xfail to skip
        rep.outcome = "skipped"
        rep.wasxfail = None

trylast=True helps, but yielding first ensures internal hooks like skipping run before you access rep.

One nuance I learned is that the order of hookwrapper=True hooks matters.

Pytest executes internal hooks with lower priority first, so combining hookwrapper=True with trylast=True usually does the trick.

Always make sure you yield before inspecting rep, otherwise you’ll see the report before xfail/skipped processing.

If your goal is specifically to absorb xfail info or tweak outcomes, you can safely check rep.when and rep.outcome inside the hook:

if rep.when == "call" and rep.outcome == "failed" and getattr(rep, "wasxfail", False):
    rep.outcome = "skipped"
    rep.wasxfail = None

This way, setup/teardown phases aren’t affected, and you only modify the final call outcome.

It avoids interfering with pytest’s internal skipping logic elsewhere.