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.