This document outlines the development roadmap for ReqCassette based on the Design Specification.
📌 Current Release: v0.3.0 - Simplified recording modes (3 modes instead of 4)
Breaking Change:
:record_missingmode removed,:recordmode now safely appends interactions. See Migration Guide v0.2 → v0.3 for details.
Goal: Eliminate dangerous :record mode behavior and simplify the API
- ✅ Removed
:record_missingmode - Use:recordinstead - ✅ Made
:recordmode safer - Now appends interactions instead of overwriting - ✅ Simplified API - 3 modes instead of 4 (
:record,:replay,:bypass) - ✅ Eliminated multi-request test bugs -
:recordmode now safe for all test types - ✅ Updated all documentation - Migration guide, README, and integration guides
| v0.2 Mode | v0.3 Mode | Notes |
|---|---|---|
:record_missing |
:record |
Exact same behavior |
:record |
(removed) | Delete cassette first, then use :record |
:replay |
:replay |
No change |
:bypass |
:bypass |
No change |
Migration: Replace all :record_missing with :record. See
Migration Guide.
Goal: Production-ready VCR library with clean API and essential features
All v0.2.0 features have been implemented. See below for historical reference.
- Plug-based Architecture - Built on Req.Test and Plug.Conn
- Basic Record/Replay - Simple GET/POST request handling
- MD5-based Cassette Matching - Hash of method + path + query + body
- JSON Persistence - Cassettes stored as
{status, headers, body}JSON - Async-Safe Testing - Process-isolated, no global mocking
- E2E Tests with Bypass - Basic test coverage
- ReqLLM Integration - Tested with LLM API calls
- Only implicit
:recordmode - No
use_cassette/2macro (manual:plugoption required) - Cryptic MD5 hash filenames (e.g.,
a1b2c3d4e5f6.json) - One cassette per HTTP request (can't group related interactions)
- Limited cassette metadata (missing request details, timestamp)
- No configurable request matching (always matches entire request)
- No data sanitization/filtering capabilities
- Basic error messages
This section documents the original planning for v0.2.0. All features have been implemented.
Original plan was four recording modes via the :mode option:
-
:replay(default for CI) - Replay from cassette, error if cassette missinguse_cassette "api_call", mode: :replay do # Must have cassette, network calls will error end
-
:record- Record or overwrite cassettes (force re-record)use_cassette "api_call", mode: :record do # Always hits network, overwrites existing cassette end
-
:record_missing(default for dev/test) - Record only if cassette doesn't existuse_cassette "api_call", mode: :record_missing do # First run: records, subsequent runs: replays end
-
:bypass- Ignore cassettes entirely, always use networkuse_cassette "api_call", mode: :bypass do # Cassettes disabled, direct network call end
Implementation:
- Update
ReqCassette.Plug.call/2with mode-based dispatch logic - Add mode validation
- Add comprehensive tests for each mode
Files:
lib/req_cassette/plug.ex- Mode handling logictest/req_cassette/modes_test.exs- Mode-specific tests
Implement two complementary APIs for different use cases: implicit
auto-injection (use_cassette) and explicit plug passing (with_cassette).
Before (v0.1):
response = Req.get!(
"https://api.example.com/users/1",
plug: {ReqCassette.Plug, %{cassette_dir: "test/cassettes"}}
)After (v0.2) - Two Styles:
Auto-injects plug into all Req calls within block.
use ReqCassette
test "fetches user" do
use_cassette "github_user" do
response = Req.get!("https://api.example.com/users/1")
assert response.status == 200
end
endBest for: Simple tests, less boilerplate, automatic behavior
Provides plug as argument to lambda - you control where it's used.
test "fetches user" do
with_cassette "github_user", fn plug ->
response = Req.get!("https://api.example.com/users/1", plug: plug)
assert response.status == 200
end
endBest for: Helper functions, explicit control, functional style
| Feature | use_cassette |
with_cassette |
|---|---|---|
| Type | Macro | Function |
| Plug injection | Automatic (implicit) | Manual (explicit) |
| Syntax | do ... end block |
Lambda with plug arg |
| Boilerplate | Less | Slightly more |
| Helper functions | Harder | Easier |
| Explicitness | Implicit | Explicit |
| Learning curve | Lower | Lower |
Use use_cassette when:
- ✅ Writing simple, focused tests
- ✅ All requests in one test block
- ✅ You prefer less boilerplate
Use with_cassette when:
- ✅ Passing plug to helper functions
- ✅ You prefer explicit, functional style
- ✅ Building reusable test utilities
- ✅ Need clear visibility of what's recorded
Both APIs support the same options:
- Human-readable cassette names -
github_user.jsoninstead ofa1b2c3d4.json - Cassette options:
mode,cassette_dir,match_requests_on - Filtering:
filter_sensitive_data,filter_request_headers,filter_response_headers - Callbacks:
before_record - Async-safe: Both work with
async: true
Basic usage (both styles):
# Implicit style
use_cassette "github_user" do
Req.get!("https://api.github.com/users/wojtekmach")
end
# Explicit style
with_cassette "github_user", fn plug ->
Req.get!("https://api.github.com/users/wojtekmach", plug: plug)
endWith helper functions (with_cassette shines here):
defmodule MyApp.API do
def fetch_user(id, opts \\ []) do
Req.get!("https://api.example.com/users/#{id}", plug: opts[:plug])
end
def create_user(data, opts \\ []) do
Req.post!("https://api.example.com/users", json: data, plug: opts[:plug])
end
end
# Test with explicit plug passing
test "API helper functions" do
with_cassette "api_operations", fn plug ->
user = MyApp.API.fetch_user(1, plug: plug)
assert user.body["id"] == 1
new_user = MyApp.API.create_user(%{name: "Bob"}, plug: plug)
assert new_user.status == 201
end
endWith options:
# use_cassette with options
use_cassette "api_call",
mode: :replay,
match_requests_on: [:method, :uri],
filter_request_headers: ["authorization"] do
Req.get!("https://api.example.com/data")
end
# with_cassette with options
with_cassette "api_call",
mode: :replay,
match_requests_on: [:method, :uri],
filter_request_headers: ["authorization"],
fn plug ->
Req.get!("https://api.example.com/data", plug: plug)
endNested cassettes (different APIs):
# Only possible with with_cassette (explicit plug control)
test "multiple API services" do
with_cassette "github", fn github_plug ->
user = Req.get!("https://api.github.com/users/alice",
plug: github_plug)
with_cassette "stripe", fn stripe_plug ->
charge = Req.post!("https://api.stripe.com/v1/charges",
json: %{amount: 1000},
plug: stripe_plug)
{user, charge}
end
end
endCassette naming:
# Old (v0.1): cassettes/3a7f2c9d8e1b.json
# New (v0.2): cassettes/github_user_wojtekmach.json
# Both APIs support human-readable names
use_cassette "github_user_wojtekmach" do
# ...
end
with_cassette "github_user_wojtekmach", fn plug ->
# ...
end
# Supports subdirectories for organization:
use_cassette "api/users/create" do
# Creates: cassettes/api/users/create.json
enduse_cassette/2 - Macro:
defmodule ReqCassette do
defmacro __using__(_opts) do
quote do
import ReqCassette, only: [use_cassette: 2, with_cassette: 2, with_cassette: 3]
end
end
defmacro use_cassette(name, opts \\ [], do: block) do
# Store context in process dictionary
# Transform Req calls in block to inject plug
# Implementation details TBD
end
endwith_cassette/3 - Function:
defmodule ReqCassette do
@doc """
Execute code with a cassette, providing the plug explicitly.
Unlike `use_cassette/2` which auto-injects the plug, `with_cassette/3`
provides the plug configuration as an argument to your function.
## Examples
with_cassette "my_test", fn plug ->
Req.get!("https://api.example.com/data", plug: plug)
end
# With options
with_cassette "my_test", mode: :replay, fn plug ->
Req.get!("https://api.example.com/data", plug: plug)
end
# Pass to helper functions
with_cassette "my_test", fn plug ->
MyApp.API.fetch_user(1, plug: plug)
end
"""
@spec with_cassette(String.t(), keyword(), (plug :: term() -> result)) :: result
when result: any()
def with_cassette(name, opts \\ [], fun) when is_function(fun, 1) do
plug_opts = %{
cassette_name: name,
cassette_dir: opts[:cassette_dir] || "test/cassettes",
mode: opts[:mode] || :record_missing,
match_requests_on: opts[:match_requests_on] || [:method, :uri, :query, :headers, :body],
filter_sensitive_data: opts[:filter_sensitive_data] || [],
filter_request_headers: opts[:filter_request_headers] || [],
filter_response_headers: opts[:filter_response_headers] || [],
before_record: opts[:before_record]
}
plug = {ReqCassette.Plug, plug_opts}
fun.(plug)
end
endKey differences:
use_cassette- Macro, auto-injects plug (implementation TBD)with_cassette- Simple function, passes plug to lambda
Files:
lib/req_cassette.ex- Both implementationstest/req_cassette/use_cassette_test.exs-use_cassettemacro teststest/req_cassette/with_cassette_test.exs-with_cassettefunction tests
Update JSON format to include full request metadata, pretty-printing, and intelligent body type handling.
Current format (v0.1):
{
"status": 200,
"headers": { "content-type": ["application/json"] },
"body": "{\"id\":1,\"name\":\"Alice\"}"
}New format (v0.2) - Pretty-printed with native JSON:
{
"version": "1.0",
"interactions": [
{
"request": {
"method": "GET",
"uri": "https://api.example.com/users/1",
"query_string": "filter=active",
"headers": {
"accept": ["application/json"],
"user-agent": ["req/0.5.15"]
},
"body_type": "text",
"body": ""
},
"response": {
"status": 200,
"headers": {
"content-type": ["application/json"],
"cache-control": ["max-age=300"]
},
"body_type": "json",
"body_json": {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"roles": ["admin", "user"]
}
},
"recorded_at": "2025-10-16T14:23:45.123456Z"
}
]
}Key improvements:
Use Jason.encode!(cassette, pretty: true) for human-readable cassettes.
Before (compact, single-line):
{
"version": "1.0",
"interactions": [
{ "request": { "method": "GET" }, "response": { "status": 200 } }
]
}After (pretty-printed):
{
"version": "1.0",
"interactions": [
{
"request": { "method": "GET" },
"response": { "status": 200 }
}
]
}Benefits:
- Human-readable with proper indentation
- Better code review experience
- Easier manual editing
- Clearer git diffs
Add body_type field with three types: "json", "text", "blob".
Body type: json - Store as native JSON object (no escaping,
pretty-printed)
Before (v0.1):
{
"response": {
"body": "{\"token\":\"abc123\",\"user\":{\"id\":1,\"name\":\"Alice\"}}"
}
}Problem: Double-encoded JSON, escaped quotes, unreadable
After (v0.2):
{
"response": {
"body_type": "json",
"body_json": {
"token": "abc123",
"user": {
"id": 1,
"name": "Alice"
}
}
}
}✅ Native JSON object, no escaping, readable!
Body type: blob - Store binary data as base64
{
"response": {
"status": 200,
"headers": {
"content-type": ["image/png"]
},
"body_type": "blob",
"body_blob": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ..."
}
}Use cases:
- Image downloads (PNG, JPEG, GIF)
- PDF documents
- Compressed data (gzip)
- Binary formats (protobuf, msgpack)
Body type: text - Store plain text as string
{
"response": {
"body_type": "text",
"body": "<html><body>Success!</body></html>"
}
}Use cases:
- HTML responses
- XML/SOAP
- Plain text
- CSV files
Benefits:
- Dramatically better readability - JSON as native objects, not escaped strings
- Better git diffs - Object property changes vs entire string changes
"body_json": { - "followers": 100 + "followers": 150 } - Binary support - Can test image/PDF downloads and binary APIs
- Smaller files - Native JSON ~40% smaller than escaped strings
- Before:
"body": "{\"users\":[{\"id\":1},{\"id\":2}]}"(106 bytes) - After:
"body_json": {"users":[{"id":1},{"id":2}]}(47 bytes)
- Before:
Overall benefits:
- Debugging - See exact request that generated response
- Auditing - Know when cassettes were recorded (identify stale data)
- Logical grouping - All requests from one test in a single cassette file
- Better organization - One file instead of many MD5 hash files
- Version field - Enable future format migrations
- Readability - Pretty-printed with native JSON objects
- Binary support - Handle any content type
Example workflow cassette:
# Single test with multiple API calls
test "complete user workflow" do
use_cassette "user_workflow" do
# Login
{:ok, auth} = Req.post!("https://api.example.com/auth/login",
json: %{username: "alice", password: "secret"})
# Fetch profile
{:ok, profile} = Req.get!("https://api.example.com/users/me",
headers: [{"authorization", "Bearer #{auth.body["token"]}"}])
# Update profile
{:ok, _} = Req.put!("https://api.example.com/users/me",
json: %{bio: "New bio"})
end
end
# All 3 interactions stored in cassettes/user_workflow.json:
# {
# "version": "1.0",
# "interactions": [
# {"request": {POST /auth/login}, "response": {...}},
# {"request": {GET /users/me}, "response": {...}},
# {"request": {PUT /users/me}, "response": {...}}
# ]
# }Implementation:
-
Body type detection:
- Create
detect_body_type(body, headers)function - Check
content-typeheader for type hints - Use
String.printable?/1to detect binary data - Try
Jason.decode/1to detect valid JSON
- Create
-
Serialization:
- Update
save_cassette/3to:- Detect request and response body types
- Store as
body_json,body_blob, orbodyaccordingly - Base64 encode binary data
- Use
Jason.encode!(cassette, pretty: true)for final output
- Update
-
Deserialization:
- Update
maybe_load_cassette/2to:- Read
body_typefield - Decode based on type (JSON, base64, or raw string)
- Handle backward compatibility (v0.1 format without
body_type)
- Read
- Update
-
Migration:
- Add
mix cassette.migratetask to convert old cassettes - Warn users when loading old format cassettes
- Add
Files:
lib/req_cassette/body_type.ex- Body type detection and encoding/decodinglib/req_cassette/plug.ex- Serialization updates (use body types, pretty print)lib/req_cassette/cassette.ex- Cassette schema and validationlib/mix/tasks/cassette.migrate.ex- Migration tasktest/req_cassette/body_type_test.exs- Body type detection tests
Implement data sanitization to prevent API keys, tokens, and PII from being committed.
Features:
use_cassette "api_call",
filter_sensitive_data: [
# Regex pattern replacement
{~r/api_key=[\w-]+/, "api_key=<REDACTED>"},
{~r/"token":"[^"]+"/, "\"token\":\"<REDACTED>\""}
],
filter_request_headers: ["authorization", "x-api-key"],
filter_response_headers: ["set-cookie"] do
# Filters applied before saving cassette
enduse_cassette "api_call",
before_record: fn interaction ->
# Programmatically modify interaction before save
interaction
|> update_in(["response", "body"], &redact_email/1)
|> put_in(["request", "headers", "authorization"], ["<REDACTED>"])
end do
# ...
endImplementation:
- Add filter application in
save_cassette/3 - Support regex-based replacements
- Support header removal/redaction
- Support callback functions for complex filtering
Files:
lib/req_cassette/filter.ex- Filtering enginelib/req_cassette/plug.ex- Apply filters before savetest/req_cassette/filter_test.exs- Filter tests
Implement the :match_requests_on option for flexible request matching (Design
§5.1).
Default behavior (match all):
use_cassette "api_call" do
# Matches on: method + uri + query + headers + body (default)
endCustom matching:
use_cassette "api_call", match_requests_on: [:method, :uri] do
# Ignores query params, headers, and body differences
# Useful when headers contain timestamps or request IDs
endAvailable matchers:
-
:method- HTTP method (GET, POST, etc.)match_requests_on: [:method, :uri]
-
:uri- Full URI including scheme, host, port, path (excludes query string)match_requests_on: [:method, :uri, :query]
-
:query- Query string parameters (order-insensitive)# Matches: ?a=1&b=2 and ?b=2&a=1 (same parameters, different order) match_requests_on: [:method, :uri, :query]
-
:headers- Request headers (exact match)match_requests_on: [:method, :uri, :headers]
-
:body- Request body# For JSON bodies: key order doesn't matter # {"a":1,"b":2} matches {"b":2,"a":1} match_requests_on: [:method, :uri, :body]
Common patterns:
# Ignore dynamic headers (timestamps, request IDs, etc.)
use_cassette "api", match_requests_on: [:method, :uri, :body] do
# Headers ignored - useful for APIs that add x-request-id
end
# Ignore request body (GET/DELETE with cache-busting params)
use_cassette "api", match_requests_on: [:method, :uri, :query] do
# Body ignored
end
# Match only method and path (most permissive)
use_cassette "api", match_requests_on: [:method, :uri] do
# Useful during development when request format is changing
endImplementation:
-
Add matcher modules in
lib/req_cassette/matchers/method.ex- Method comparisonuri.ex- URI comparisonquery.ex- Query string normalization and comparisonheaders.ex- Header comparisonbody.ex- Body comparison (content-type aware)
-
Update
ReqCassette.Plug.call/2to use matcher pipeline -
Support custom matcher functions (future: v0.3)
Query string normalization:
# Both match the same cassette interaction:
?filter=active&page=1&limit=10
?page=1&limit=10&filter=activeJSON body normalization:
# Both match the same cassette interaction:
{"user": "alice", "action": "login"}
{"action": "login", "user": "alice"}Files:
lib/req_cassette/matchers/method.exlib/req_cassette/matchers/uri.exlib/req_cassette/matchers/query.exlib/req_cassette/matchers/headers.exlib/req_cassette/matchers/body.exlib/req_cassette/matcher.ex- Matcher pipeline coordinatortest/req_cassette/matchers_test.exs- Individual matcher teststest/req_cassette/matching_test.exs- Integration tests
Expand test coverage to match Design Specification §6.0:
- Recording mode logic (replay, record, record_missing, bypass)
- Request matching (method, uri, query, headers, body matchers)
- Query string normalization (order-insensitive)
- JSON body normalization (key-order insensitive)
- Body type detection (json, text, blob)
- Detect JSON from content-type header
- Detect binary data with
String.printable?/1 - Fallback to JSON parsing attempt
- Handle already-decoded Req responses
- Body encoding/decoding
- JSON bodies stored as native objects
- Binary bodies base64 encoded/decoded
- Text bodies stored as strings
- Pretty-printing validation
- Cassettes saved with
pretty: true - Output is valid JSON
- Proper indentation
- Cassettes saved with
- Cassette matching algorithm with multiple interactions
- Filter functions (regex, headers, callbacks)
- Cassette serialization/deserialization (v1.0 format)
- Backward compatibility with v0.1 cassettes (no
body_typefield) -
use_cassettemacro functionality- Macro expansion and AST transformation
- Process dictionary handling for cassette context
- Auto-injection of plug into Req calls
-
with_cassettefunction functionality- Options parsing and plug configuration
- Lambda execution with plug argument
- Return value preservation
- Filename sanitization for cassette names
- Plug call with fixture cassettes (no network)
- Mode behavior with existing/missing cassettes
- Both API styles (
use_cassetteandwith_cassette)- Same cassette name works with both APIs
- Options applied correctly in both APIs
- Nested cassettes (only with
with_cassette)
- Request matching with different matcher combinations
- Multiple interactions per cassette file
- Body type handling in cassettes
- JSON body saves as native object, loads correctly
- Binary body saves as base64, loads correctly
- Text body saves as string, loads correctly
- Pretty-printed cassettes are valid and parseable
- Filter application pipeline
-
with_cassettespecific- Passing plug to helper functions
- Return value preservation
- Multiple cassettes with different plugs
- All HTTP methods (GET, POST, PUT, PATCH, DELETE)
- Request body types
- JSON request body (POST/PUT with
json: %{...}) - Form-encoded request body
- Binary request body
- Empty request body
- JSON request body (POST/PUT with
- Response body types
- JSON responses (most common)
- HTML/XML responses (text)
- Binary responses (images, PDFs)
- Empty responses (204 No Content)
- Mixed content types in single cassette
- First interaction: JSON response
- Second interaction: Binary response
- Third interaction: Text response
- Non-2xx status codes (4xx, 5xx errors)
- Query parameters and headers
- Multiple interactions per cassette
- Pretty-printed cassette validation
- File is human-readable
- Can be manually edited
- Git diff shows meaningful changes
- ReqLLM integration (streaming responses)
- Concurrent async tests (verify process isolation)
Files:
test/req_cassette/modes_test.exs- Recording mode teststest/req_cassette/use_cassette_test.exs-use_cassettemacro teststest/req_cassette/with_cassette_test.exs-with_cassettefunction teststest/req_cassette/filter_test.exs- Filtering teststest/req_cassette/body_type_test.exs- Body type detection teststest/req_cassette/cassette_format_test.exs- Format validation and pretty-printingtest/req_cassette/e2e_test.exs- End-to-end with various body types
Update all documentation for v0.2 API:
- README.md - Update with both API examples
- Show
use_cassettefor simple cases - Show
with_cassettefor helper functions - When to use which section
- Show
- Module docs - Complete @moduledoc for all modules
- Function docs - Complete @doc for all public functions
-
use_cassette/2macro documentation -
with_cassette/2andwith_cassette/3function documentation
-
- Guides - Create comprehensive guides:
- Getting Started Guide (show both APIs)
- API Styles Guide (use_cassette vs with_cassette)
- Recording Modes Guide
- Request Matching Guide
- Sensitive Data Filtering Guide
- Cassette Format Guide (body types, pretty-printing)
- Binary Data Handling Guide
- Helper Functions Guide (with_cassette examples)
- Migration Guide (v0.1 → v0.2)
- ReqLLM Integration Guide (update existing)
- Examples - Add example projects in
examples/directory- Simple test suite with
use_cassette - Test utilities with
with_cassette - Helper function patterns
- Simple test suite with
Files:
README.md- Show both API styleslib/req_cassette.ex- Both APIs with full documentationlib/req_cassette/plug.exlib/req_cassette/filter.exlib/req_cassette/matcher.exlib/req_cassette/body_type.exguides/getting_started.md- Introduction to both APIsguides/api_styles.md- When to use use_cassette vs with_cassetteguides/recording_modes.mdguides/request_matching.mdguides/filtering.mdguides/cassette_format.md- Body types and pretty-printingguides/binary_data.md- Handling images, PDFs, etc.guides/helper_functions.md- Patterns for with_cassetteguides/migration_v0.1_to_v0.2.md
The following features are deferred to future releases:
ReqCassette.Config.setup/1- Global cassette directory
- Global filter rules
- Global callbacks
- Custom matcher functions (beyond built-in matchers)
- Matcher plugins/extensibility
mix cassette.eject- Delete cassettesmix cassette.check- Validate cassettesmix cassette.show- Pretty-print cassettemix cassette.list- List all cassettes
- Dynamic response templating (EEx)
- Pluggable serializers (YAML, ETF)
- Request/response streaming support
- Atomic file writes
- Cassette encryption
Breaking Changes:
- New cassette format (v1.0 schema)
- Pretty-printed JSON (multi-line instead of single-line)
- Body types:
body_json,body_blob,body(was justbody) - Multiple interactions per cassette (was single response)
- Two new APIs:
use_cassette/2macro andwith_cassette/3function- Replaces manual
:plugoption from v0.1 - Choose implicit (
use_cassette) or explicit (with_cassette) style
- Replaces manual
- Default mode is
:record_missing(was implicit:record) - Human-readable filenames (was MD5 hash)
Migration Path:
- Run
mix cassette.migrateto convert v0.1 cassettes to v0.2 format - Choose your API style:
- Simple tests: Use
use_cassette/2macro - Helper functions: Use
with_cassette/3function
- Simple tests: Use
- Update tests to use chosen API (or mix both as needed)
- Set
mode: :record_missingfor development - Set
mode: :replayin CI environment
Success Criteria:
- All 6 core features implemented
- Recording modes (replay, record, record_missing, bypass)
- Two API styles
-
use_cassette/2macro (implicit, auto-inject) -
with_cassette/3function (explicit, lambda-based) - Human-readable cassette names for both
-
- Enhanced cassette format
- Pretty-printed JSON
- Body type discrimination (json/text/blob)
- Logical grouping (multiple interactions)
- Sensitive data filtering
- Request matching options (match_requests_on)
- Comprehensive testing
- Test coverage > 90%
- All documentation updated
- Migration guide complete
- Example projects added
We welcome contributions! To contribute to v0.2 features:
- Check Issues for open tasks
- Comment on issue to claim it
- Follow CONTRIBUTING.md guidelines
- Ensure tests pass:
mix test - Submit pull request
Priority areas for v0.2:
- Recording modes implementation
- Two API styles
-
use_cassettemacro with auto-injection -
with_cassettefunction with explicit plug - Human-readable filenames for both
-
- Enhanced cassette format
- Pretty-printed JSON output
- Body type detection (json/text/blob)
- Logical grouping (multiple interactions)
- Request matching (match_requests_on) implementation
- Sensitive data filtering
- Binary data support (base64 encoding)
- Test coverage improvements (both APIs)
- Documentation writing (show both API styles)
Open an issue or discussion on GitHub.