In pytest, the pytest_report_teststatus hook executes after session-scoped teardown fixtures with yield statements.
I have a pytest_report_teststatus hook to log test results for setup, call, and teardown phases:
def pytest_report_teststatus(report):
sum_log = dnet_generic.get_logger()
if report.when == 'setup' and report.outcome == 'failed':
sum_log.info("---- Test Setup Stage FAILED!")
elif report.when == 'call':
sum_log.info(f'---- Test: {report.nodeid}, Result: {report.outcome.upper()}')
elif report.when == 'teardown' and report.outcome == 'failed':
sum_log.info("---- Test Teardown Stage FAILED!")
And a session-scoped fixture using yield:
@pytest.fixture(scope="session")
def logger(testinfo):
sum_log = dnet_generic.get_logger(initialize=True)
# log session start...
yield sum_log
# log session end...
The problem: the teardown portion of the hook gets executed after the session-scoped fixture writes its “Session Ended” messages.
Expected order:
---- Test Teardown Stage FAILED!
-----Pytest Session Ended
Actual order:
-----Pytest Session Ended
---- Test Teardown Stage FAILED!
How can I make pytest_report_teststatus execute before session-scoped fixture teardown?
I ran into this exact ordering problem and realized that session-scoped fixtures with yield always tear down after the reporting hooks.
To get pytest_report_teststatus to log before the fixture teardown, I wrapped my logging in a function- or module-scoped autouse fixture. For example:
@pytest.fixture(autouse=True)
def log_test_results():
yield
# This runs after each test but before session teardown
report = get_last_test_report()
if report.when == 'teardown' and report.outcome == 'failed':
sum_log = dnet_generic.get_logger()
sum_log.info("---- Test Teardown Stage FAILED!")
By moving the teardown logging to a smaller-scope fixture, it executes before the session-scoped logger’s yield cleanup.
From my experience, pytest_report_teststatus hooks run too late for session-scoped fixtures because they run after all teardown is finished. Switching to pytest_runtest_teardown(item, nextitem) gave me better control:
def pytest_runtest_teardown(item, nextitem):
report = item._report_sections[-1][2] # or use item._store to track
if report.failed:
sum_log = dnet_generic.get_logger()
sum_log.info("---- Test Teardown Stage FAILED!")
This hook executes immediately after the test teardown, so it naturally comes before any session-scoped fixture cleanup.
Another trick that worked for me: instead of using yield in the session fixture, I used request.addfinalizer to schedule teardown after the reporting is done:
@pytest.fixture(scope="session")
def logger(request):
sum_log = dnet_generic.get_logger(initialize=True)
# log session start...
def teardown():
sum_log.info("-----Pytest Session Ended")
request.addfinalizer(teardown)
return sum_log
Because pytest_report_teststatus hooks run before finalizers registered via addfinalizer, your “Teardown Stage FAILED” logs now appear before the session-end log.