Record & replay HTTP

Record-and-replay grew up.
Now it works for everything.

VCR (Ruby), Betamax (Python), Polly.js (JS) record HTTP calls inside one test runner in one language. Gostly does it at the proxy layer — language-agnostic, structurally redacted, stateful, multi-service.

Why this matters

“With scaffolding you can get deterministic outcomes.”

— Boris Cherny, Anthropic

The tradition

VCR shipped in 2010. It records HTTP interactions in YAML cassettes and replays them in rspec. Brilliant for Ruby.

Betamax does the same for Python. Polly.js for JavaScript. Smart, simple, language-local.

The pattern is right. The implementation is bound to one language, one test framework, and one process.

Where the tradition breaks

  • Multi-service tests. Your Python service talks to a Node service talks to Stripe. Three languages, three cassette formats, three configs to keep in sync.
  • Container tests.The HTTP call happens inside a Docker container, not in your test runner’s process. The library can’t monkey-patch what it can’t see.
  • Stateful flows.Most cassette libraries are stateless. POST creates a resource, GET returns 404 because there’s no resource store.
  • PII handling. Many cassettes capture auth headers in plain text. Filtering is opt-in, configured per-cassette, easy to forget.
  • Drift.Cassettes go stale silently. There’s no detector telling you the upstream you recorded last quarter doesn’t return what you recorded anymore.

What changes when you move it to the proxy

Language agnostic

Any HTTP client speaks to localhost:8080. Python service calling a Node service calling Stripe — one proxy, one recording, three runtimes. No cassette format per language.

Structural redaction at capture

Sixteen PII header classes — auth tokens, session cookies, API keys — stripped before any bytes touch disk. Bodies scrubbed for the obvious patterns. The redaction is structural; it can't be disabled per-cassette.

Stateful by default

POST a resource, GET it back. Most cassette libraries are stateless and return 404 on the second hop. Gostly tracks the resource and returns it on the next request.

Container-friendly

The HTTP call happens inside a Docker container, not in your test runner's process. The proxy lives on the network. The call gets captured either way.

Drift detection

Re-record nightly. Diff this week's capture against last week's. Drift is surfaced as a report, not a test failure you have to root-cause.

Migration from VCR / Betamax / Polly.js

If you have cassettes, you have most of what Gostly needs. Run your existing test suite once against the live upstream with Gostly in LEARN mode. The recording corpus is what your cassettes wanted to be.

Three steps to switch the wiring:

# 1. Start the proxy pointed at your real upstream
docker run --rm -p 8080:8080 \
  -e BACKEND_URL=https://api.stripe.com \
  -v $(pwd)/data:/app/data \
  gostly/proxy:latest

# 2. Point your code at localhost:8080 instead of the upstream
#    (one env var change — STRIPE_API_BASE, GITHUB_API_BASE, etc.)

# 3. Run your suite once in LEARN mode, then flip to MOCK
#    Your cassettes are now a single shared recording.

Your existing cassette tests can run against the proxy unchanged. The cassette library still works in-process; Gostly handles everything the cassette library never had access to.

One recording. Every language. Every service.

If you’re single-language and single-service, VCR / Betamax / Polly.js are a fine choice — and free. If you’re neither, move the recording to the proxy.

Try GostlyStay on VCR / Betamax / Polly

No shame in the cassette libraries. They work where they work.