Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Work with pytest-recording (VCR.py) for recording and replaying HTTP interactions in tests. Use when writing VCR tests, managing cassettes, configuring VCR options, filtering sensitive data, or debugging recorded HTTP responses.
.claude/skills/aiskillstore-pytest-recording/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 52% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -41% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 168% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -68% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 57% | 0% |
pytest-recording wraps VCR.py to record HTTP interactions as YAML cassettes, enabling deterministic tests without live API calls.
bash# Run all tests (uses existing cassettes) uv run pytest tests/ # Run a single test uv run pytest tests/test_module.py::test_function # Rewrite all cassettes with fresh responses uv run pytest tests/ --vcr-record=rewrite # Record only missing cassettes uv run pytest tests/ --vcr-record=new_episodes # Disable VCR (make live requests) uv run pytest tests/ --disable-recording
| Mode | Flag | Behavior | |------|------|----------| | none | --vcr-record=none | Only replay, fail if no cassette | | once | (default) | Record if no cassette exists | | new_episodes | --vcr-record=new_episodes | Record new requests, keep existing | | all | --vcr-record=all | Always record, overwrite existing | | rewrite | --vcr-record=rewrite | Delete and re-record all cassettes |
Basic test with VCR:
pythonimport pytest @pytest.mark.vcr() def test_api_call(): response = my_api_function() assert response.status_code == 200
Custom cassette name:
python@pytest.mark.vcr("custom_cassette_name.yaml") def test_with_custom_cassette(): pass
Multiple cassettes:
python@pytest.mark.vcr("cassette1.yaml", "cassette2.yaml") def test_with_multiple_cassettes(): pass
The vcr_config fixture controls VCR behavior:
python@pytest.fixture(scope="module") def vcr_config(): return { # Filter sensitive headers from recordings "filter_headers": ["authorization", "api-key", "x-api-key"], # Filter query parameters "filter_query_parameters": ["key", "api_key", "token"], # Match requests by these criteria "match_on": ["method", "scheme", "host", "port", "path", "query"], # Ignore certain hosts (don't record) "ignore_hosts": ["localhost", "127.0.0.1"], # Record mode "record_mode": "once", }
For LLM providers, filter authentication:
python@pytest.fixture(scope="module") def vcr_config(): return { "filter_headers": [ "authorization", # OpenAI, Anthropic "api-key", # Azure OpenAI "x-api-key", # Anthropic "x-goog-api-key", # Google AI ], "filter_query_parameters": ["key"], }
Use pytest_recording_configure for advanced processing:
pythondef pytest_recording_configure(config, vcr): vcr.serializer = "yaml" vcr.decode_compressed_response = True # Sanitize response headers def sanitize_response(response): response['headers']['Set-Cookie'] = 'REDACTED' return response vcr.before_record_response = sanitize_response
Cassettes are stored in tests/cassettes/ by default, organized by test module:
tests/
├── cassettes/
│ └── test_module/
│ └── test_function.yaml
└── test_module.pyIf tests fail with "Can't find cassette":
--vcr-record=once to create missing cassettesIf VCR can't match requests:
match_on criteria in vcr_config--vcr-record=new_episodes to add missing interactionsWhen API responses change:
--vcr-record=rewrite to refresh all cassettesbash# View a cassette file cat tests/cassettes/test_module/test_function.yaml # Search for specific content in cassettes grep -r "error" tests/cassettes/
When adding a new provider:
filter_headers in vcr_configfilter_query_parameters--vcr-record=once to create cassettesCommon provider authentication:
| Provider | Headers to Filter | |----------|-------------------| | OpenAI | authorization | | Anthropic | x-api-key, authorization | | Azure OpenAI | api-key | | Google AI | x-goog-api-key | | Cohere | authorization |
scope="module" for shared fixtures| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 12,429 | 8,431 | -32% | 1 | 1 | 0% | 1,189 | 1,813 | +52% | 0 | 0 | — |
case-02 | fail→fail | 18,024 | 12,322 | -32% | 1 | 1 | 0% | 3,417 | 3,651 | +7% | 0 | 0 | — |
case-03 | fail→pass | 23,952 | 9,865 | -59% | 1 | 1 | 0% | 3,719 | 2,201 | -41% | 0 | 0 | — |
case-04 | fail→pass | 9,665 | 8,244 | -15% | 1 | 1 | 0% | 714 | 1,914 | +168% | 0 | 0 | — |
case-05 | fail→pass | 37,160 | 3,059 | -92% | 1 | 1 | 0% | 5,731 | 1,831 | -68% | 0 | 0 | — |
case-06 | fail→pass | 7,692 | 3,020 | -61% | 1 | 1 | 0% | 1,194 | 1,875 | +57% | 0 | 0 | — |
case-07 | fail→pass | 9,906 | 8,132 | -18% | 1 | 1 | 0% | 1,670 | 2,745 | +64% | 0 | 0 | — |
case-08 | pass→pass | 12,490 | 8,620 | -31% | 1 | 1 | 0% | 1,400 | 2,041 | +46% | 0 | 0 | — |
case-09 | pass→pass | 7,626 | 9,974 | +31% | 1 | 1 | 0% | 1,286 | 2,252 | +75% | 0 | 0 | — |
case-10 | fail→pass | 11,164 | 9,371 | -16% | 1 | 1 | 0% | 1,141 | 2,208 | +94% | 0 | 0 | — |
case-11 | pass→pass | 13,068 | 7,989 | -39% | 1 | 1 | 0% | 2,139 | 2,820 | +32% | 0 | 0 | — |
case-12 | fail→pass | 14,105 | 2,957 | -79% | 1 | 1 | 0% | 1,530 | 1,917 | +25% | 0 | 0 | — |
case-13 | pass→pass | 13,463 | 7,695 | -43% | 1 | 1 | 0% | 1,502 | 1,812 | +21% | 0 | 0 | — |
case-14 | fail→fail | 11,899 | 16,733 | +41% | 1 | 1 | 0% | 2,120 | 2,682 | +27% | 0 | 0 | — |
case-15 | pass→pass | 10,602 | 2,848 | -73% | 1 | 1 | 0% | 887 | 1,853 | +109% | 0 | 0 | — |
case-16 | pass→pass | 16,732 | 4,454 | -73% | 1 | 1 | 0% | 1,902 | 2,168 | +14% | 0 | 0 | — |
case-17 | pass→pass | 7,134 | 2,107 | -70% | 1 | 1 | 0% | 966 | 1,688 | +75% | 0 | 0 | — |
case-18 | pass→pass | 15,133 | 7,839 | -48% | 1 | 1 | 0% | 1,821 | 2,834 | +56% | 0 | 0 | — |
case-19 | pass→pass | 9,908 | 2,093 | -79% | 1 | 1 | 0% | 819 | 1,753 | +114% | 0 | 0 | — |
case-20 | pass→pass | 8,392 | 4,893 | -42% | 1 | 1 | 0% | 1,430 | 2,168 | +52% | 0 | 0 | — |
case-21 | pass→pass | 10,298 | 1,902 | -82% | 1 | 1 | 0% | 909 | 1,714 | +89% | 0 | 0 | — |
case-22 | pass→pass | 9,516 | 8,086 | -15% | 1 | 1 | 0% | 1,856 | 2,998 | +62% | 0 | 0 | — |
case-23 | pass→pass | 7,337 | 10,378 | +41% | 1 | 1 | 0% | 1,433 | 2,462 | +72% | 0 | 0 | — |
case-24 | pass→pass | 22,664 | 13,682 | -40% | 1 | 1 | 0% | 2,252 | 3,036 | +35% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +33 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.