Welcome to Chapter 3!
In the previous chapter, vcr_config, we set up the "Rules of Engagement" for our network recorder. We told it to record new requests and replay old ones.
However, we left one question unanswered: Where exactly do we put all these recordings?
If we just threw every recording into one giant folder, it would be a disaster. You would have hundreds of files like test_1.yaml, test_2.yaml, and good luck finding the one you need!
The Use Case: Imagine your project is like a large library. You have tests for different parts of your code:
tests/agents/test_worker.py)tests/tools/google/test_search.py)tests/memory/test_storage.py)
The Problem:
If test_worker.py and test_search.py both try to save a recording named cassette.yaml in the same folder, they will overwrite each other.
The Solution:
We need a dynamic "Filing System." If a test lives in tests/tools/google/, its recordings should automatically go into tests/cassettes/tools/google/.
This is exactly what vcr_cassette_dir does. It mirrors your test folder structure into a cassette folder structure.
The vcr_cassette_dir abstraction is a module-scoped fixture. This means it runs once for every test file.
Think of it as a Librarian that looks at the book you are holding (the test file) and decides exactly which shelf (directory) the backup copy should go to.
Here is how the fixture transforms inputs (your test file location) into outputs (storage locations):
| Test File Location | Resulting Cassette Directory |
|---|---|
crewai/tests/test_agent.py |
crewai/tests/cassettes |
crewai/tests/tools/test_search.py |
crewai/tests/cassettes/tools |
crewai/tests/llms/openai/test_gpt.py |
crewai/tests/cassettes/llms/openai |
As you can see, it keeps everything perfectly organized!
Let's look at conftest.py to see how this path calculation works.
First, the fixture needs to know who is asking for the directory.
@pytest.fixture(scope="module")
def vcr_cassette_dir(request: Any) -> str:
"""Generate cassette directory path based on test module location."""
# Get the path of the test file currently running
test_file = Path(request.fspath)
# ... logic continues ...
request: A special pytest object that contains information about the currently running test.request.fspath: The absolute path to the test file (e.g., /Users/you/project/tests/test_agent.py).
The code is designed for a "monorepo" (a repository with multiple packages, like crewai and crewai-tools). It needs to find where the package starts.
# Iterate up the folder tree to find the package root
for parent in test_file.parents:
if (
parent.name in ("crewai", "crewai-tools", "crewai-files")
and parent.parent.name == "lib"
):
package_root = parent
break
else:
# Fallback if standard structure isn't found
package_root = test_file.parent
parent, parent.parent, etc.).crewai. This ensures we anchor our path correctly, even if the project is nested deep in your computer.Now that we know where the package starts and where the test is, we calculate the middle part.
tests_root = package_root / "tests"
test_dir = test_file.parent
if test_dir != tests_root:
# Example: if test is in tests/tools/google
# relative_path becomes "tools/google"
relative_path = test_dir.relative_to(tests_root)
cassette_dir = tests_root / "cassettes" / relative_path
else:
cassette_dir = tests_root / "cassettes"
relative_to: This subtracts the root path from the full path, leaving us with just the subfolders (e.g., tools/google).cassette_dir: We build the new path by injecting /cassettes/ into the structure.Finally, we ensure the folder actually exists before returning the path.
# Create the directory if it doesn't exist
cassette_dir.mkdir(parents=True, exist_ok=True)
return str(cassette_dir)
mkdir(parents=True): This is like the command mkdir -p. If cassettes/tools/google is needed, it creates cassettes, then tools, then google automatically.vcr_config (from Chapter 2) can use it.
In this chapter, we learned about vcr_cassette_dir:
request.fspath) to see where the test is located.cassettes folder.Now we have a clean environment (Chapter 1), a configured recorder (Chapter 2), and an organized filing system (Chapter 3).
But there is still a major security risk. If we record a request to OpenAI, the recording file will contain your secret API Key: Authorization: Bearer sk-12345.... We cannot save this to a file!
In the next chapter, we will define a list of secrets that must be scrubbed from our recordings.
Next Chapter: HEADERS_TO_FILTER
Generated by Code IQ