How can I make the hook execute before the session-scoped fixture teardown?

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.