I’m using pytest-vcr to test HTTP requests and want to customize exception messages when a request doesn’t match a recorded cassette.
Right now, when a request fails to match, VCR.py raises:
vcr.errors.CannotOverwriteExistingCassetteException
I want to use the pytest_exception_interact hook to replace this with a more informative exception that includes fixture information, like the cassette requests and match properties.
I tried something like this in conftest.py:
@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node, call, report):
    excinfo = call.excinfo
    if report.when == "call" and isinstance(excinfo.value, CannotOverwriteExistingCassetteException):
        if hasattr(node, "__vcr_fixtures"):
            cassette = node.__vcr_fixtures.get("vcr_cassette")
            vcr = node.__vcr_fixtures.get("vcr")
            if cassette and vcr:
                raise MyCustomVCRException(
                    f"Cassette Requests: {cassette.requests}"
                ) from excinfo.value
Problem:
I’m unsure how to reliably access the vcr_cassette and vcr fixtures from the test node inside the hook, and how to modify the report so pytest shows my custom exception instead of the default one.
             
            
              
              
              
            
           
          
            
            
              In pytest_exception_interact, the test node has a funcargs dictionary with all resolved fixture values.
You can access vcr_cassette and vcr directly from there:
import pytest
from vcr.errors import CannotOverwriteExistingCassetteException
class MyCustomVCRException(Exception):
    pass
@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node, call, report):
    excinfo = call.excinfo
    if report.when == "call" and isinstance(excinfo.value, CannotOverwriteExistingCassetteException):
        vcr_cassette = node.funcargs.get("vcr_cassette")
        vcr_instance = node.funcargs.get("vcr")
        if vcr_cassette and vcr_instance:
            # raise custom exception while preserving original traceback
            raise MyCustomVCRException(
                f"Custom VCR Error: cassette {vcr_cassette} "
                f"with requests {vcr_cassette.requests}"
            ) from excinfo.value
    yield
Using funcargs is reliable because pytest resolves fixtures before the test runs.
             
            
              
              
              
            
           
          
            
            
              If you don’t want to replace the exception completely, you can wrap it with additional info and keep the original traceback:
@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node, call, report):
    excinfo = call.excinfo
    yield
    if report.when == "call" and isinstance(excinfo.value, CannotOverwriteExistingCassetteException):
        vcr_cassette = node.funcargs.get("vcr_cassette")
        extra_msg = f"\nCassette requests: {vcr_cassette.requests if vcr_cassette else 'N/A'}"
        report.longrepr = f"{excinfo.getrepr()} {extra_msg}"
This way, your pytest output includes the cassette details without changing the exception type.
             
            
              
              
              
            
           
          
            
            
              Sometimes accessing fixtures in the hook feels fragile, especially if the fixture hasn’t been requested by the test.
A simpler pattern is to wrap the test itself:
@pytest.fixture
def vcr_safe(vcr_cassette, vcr):
    try:
        yield
    except CannotOverwriteExistingCassetteException as e:
        raise MyCustomVCRException(
            f"Cassette {vcr_cassette} failed. Requests: {vcr_cassette.requests}"
        ) from e
Then in your test:
def test_my_api(vcr_safe, vcr_cassette, vcr):
    # your HTTP requests here
This avoids the need to deal with pytest_exception_interact and guarantees fixture access.