Pytest Marker

The main tool offered by this plugin to delete cassettes on failure is the pytest.mark.vcr_delete_on_fail() decorator.

Automatic naming

By default it will target the cassette found at
./cassettes/{module-name}/{test-class-if-any.}{test_name}.yaml
which is the same policy used by pytest-recording:
import pytest
import requests


# Configure pytest_recording
@pytest.fixture(scope="module")
def vcr_config():
    return {"record_mode": ["once"]}


# this will trigger pytest-recording and record a vcr
@pytest.mark.vcr
# this will delete that cassette, if the test fails
@pytest.mark.vcr_delete_on_fail
# @pytest.mark.vcr_delete_on_fail()  # alternative syntax
def test_this():
    requests.get("https://github.com")
    assert False

Note

When using the marker decorator, the cassette will be deleted after the test teardown phase. This is different from how the context managers work.

Warning

This marker will delete any cassette found at the given path, even if the test didn’t reach the instruction that would have recorded a new cassette in the run that resulted in a failure. This is intended to ensure a fresh environment after every failure.

The marker is actually quite flexible and accepts several arguments (the full signature can be found in the API Reference: pytest.mark.vcr_delete_on_fail()).

Multiple cassettes

More than one cassette can be specified by passing in a list:

my_vcr = vcr.VCR(record_mode="once")


def get_cassette_name(letter: str) -> str:
    return f"{letter}.yaml"


@pytest.mark.vcr_delete_on_fail(["a.yaml", get_cassette_name("b")])
def test_this():
    with my_vcr.use_cassette("a.yaml"):
        requests.get("https://github.com")
    with my_vcr.use_cassette(get_cassette_name("b")):
        requests.get("https://gitlab.com")
    assert False

Note

The marker argument list element get_cassette_name("b") will execute immediately at test collection time and will result in a simple string passed as list element.

Delete cassettes programmatically

The cassette can also be determined by functions that will run after the test teardown:

from _pytest.python import Function

# setup vcr
my_vcr = vcr.VCR(record_mode="once")


def get_cassette(node: Function) -> str:
    # the Function argument is a pytest node that has several information about the test
    # and can be used to programmatically build the cassette path
    test_name = node.name
    return f"{test_name}.yaml"


# a function can be passed as target as a list element. It will run after the test teardown
@pytest.mark.vcr_delete_on_fail([get_cassette])
# @pytest.mark.vcr_delete_on_fail(target=get_cassette)  # alternative syntax
# @pytest.mark.vcr_delete_on_fail.with_args(get_cassette)  # alternative syntax
def test_this(request):
    # This node is the same Function instance that 'get_cassette' will get as argument later
    node: Function = request.node
    test_name = node.name
    with my_vcr.use_cassette(f"{test_name}.yaml"):
        requests.get("https://github.com")
    assert False

Warning

A function can’t be passed directly as the only unnamed marker argument. This is why the syntax @pytest.mark.vcr_delete_on_fail(get_cassette) will not work (and will probably means that the test will not even be detected).

Functions and lists can be nested arbitrarily: all str will be extracted and treated as paths of cassettes to be deleted.

The correct type for a valid target can be found in the Api Reference: pytest_vcr_delete_on_fail.ValidTarget.

Skip cassette deletion

It’s possible to selectively skip a cassette deletion, which could come in handy to inspect its content:

# pytest markers propagate to all test in a class
@pytest.mark.vcr
@pytest.mark.vcr_delete_on_fail
class TestCollection:

    # this test cassette will be delete, since the test failed
    def test_this(self):
        requests.get("https://github.com")
        assert False

    # this test cassette will remain on disk despite the test failure
    @pytest.mark.vcr_delete_on_fail(skip=True)
    # @pytest.mark.vcr_delete_on_fail(None)  # equivalent to the above
    def test_this_as_well(self):
        requests.get("https://github.com")
        assert False

About pytest fixtures

Other than in the test itself, this marker detects failures in every setup / teardown pytest function scoped fixtures.

import pytest
import requests


@pytest.fixture
def setup_fixture():
    raise Exception
    yield


@pytest.fixture
def teardown_fixture():
    yield
    raise Exception


@pytest.mark.vcr
@pytest.mark.vcr_delete_on_fail
def test_one(setup_fixture):
    assert requests.get("https://github.com").status_code == 200


@pytest.mark.vcr
@pytest.mark.vcr_delete_on_fail
def test_two(teardown_fixture):
    assert requests.get("https://github.com").status_code == 200

Both these tests would result in no cassette saved on disk.