Documentation
¶
Overview ¶
Package cassette implements recording and replaying of HTTP traffic captured via CDP Fetch interception, for deterministic browser-automation runs.
A cassette is a versioned JSON file holding (request, response) pairs. The match key for serving a request from a cassette is the tuple (method, url, body-hash). Querystring is part of the URL and matters. Headers are NOT part of the match key (they vary too much for replay to be useful). Bodies are stored base64 — they may be binary.
Forward-compat: Version is bumped on any breaking format change. Readers should reject cassettes with a higher Version than they understand.
Index ¶
- Constants
- func DecodeBody(s string) ([]byte, error)
- func EncodeBody(b []byte) string
- func IsValidMode(m RecordMode) bool
- func Save(c *Cassette, path string) error
- type Cassette
- type Entry
- type Index
- type Key
- type RecordMode
- type Recorder
- type RecorderOptions
- type Replayer
- type ReplayerOptions
- type Request
- type Response
Constants ¶
const DefaultBodyCapBytes = 5 * 1024 * 1024
DefaultBodyCapBytes is the per-response body cap when not opted out. Responses larger than this are truncated; a stderr warning is emitted by the recording layer.
const FormatVersion = 1
FormatVersion is the cassette format version. Bump on breaking changes.
Variables ¶
This section is empty.
Functions ¶
func DecodeBody ¶
DecodeBody base64-decodes a stored body. Returns nil for empty input.
func EncodeBody ¶
EncodeBody base64-encodes a body for on-disk storage.
func IsValidMode ¶
func IsValidMode(m RecordMode) bool
IsValidMode reports whether m is a recognized record mode.
Types ¶
type Cassette ¶
type Cassette struct {
Version int `json:"version"`
RecordedAt time.Time `json:"recorded_at"`
BrzVersion string `json:"brz_version,omitempty"`
Workflow string `json:"workflow,omitempty"`
Entries []*Entry `json:"entries"`
}
Cassette is the on-disk format. Entries is intentionally a slice (not map) to preserve recording order — useful for human inspection and diffs.
func LoadCassetteForRecord ¶
LoadCassetteForRecord loads an existing cassette for ModeNew seeding. Returns (nil, nil) if the file does not exist (clean start). All other errors — corrupt JSON, future format version — are surfaced so the caller does not silently overwrite a recoverable cassette on save.
type Entry ¶
type Entry struct {
Request Request `json:"request"`
Response Response `json:"response"`
MatchedCount int `json:"matched_count"`
BodyTruncated bool `json:"body_truncated,omitempty"`
}
Entry is a single (request, response) pair plus bookkeeping.
type Index ¶
type Index struct {
// contains filtered or unexported fields
}
Index is an in-memory match-key → entries map built from a Cassette. On hash collision (same key, multiple entries), entries are returned in recording order — a round-robin across calls.
func (*Index) Lookup ¶
Lookup returns the next entry matching k, advancing a per-key cursor so repeated identical requests hit successive recorded responses (when the cassette has multiple entries for the same key). When the cursor exceeds the entry count, we wrap and continue serving the last entry — a request the page makes a 4th time gets the same recording as the 3rd.
type Key ¶
Key is the match-key tuple used to look up entries in replay mode. URL and method are normalized; bodyHash is sha256 hex of the raw body bytes.
type RecordMode ¶
type RecordMode string
RecordMode controls record-mode behavior when a request is already in the cassette (only relevant for ModeNew).
const ( // ModeAll records every intercepted request, even if the same key is // already in the cassette. The cassette grows on each run. ModeAll RecordMode = "all" // ModeNew passes through to the network for already-recorded requests // but does not append a new entry. New requests get appended. ModeNew RecordMode = "new" )
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder hooks rod.Browser.HijackRequests, fetches the upstream response via Go's http.Client, and appends entries to a Cassette. Call Stop() before saving the cassette — Stop drains in-flight handlers via a WaitGroup so concurrent intercepts cannot race the save.
func NewRecorder ¶
func NewRecorder(existing *Cassette, opts RecorderOptions) *Recorder
NewRecorder constructs a Recorder. existing may be nil; if non-nil, its entries seed the recorder so ModeNew can deduplicate against prior runs.
func (*Recorder) Attach ¶
Attach starts the interceptor on the given browser. Must be called after the browser is connected via CDP. Returns an error if Fetch.enable fails.
type RecorderOptions ¶
type RecorderOptions struct {
Mode RecordMode
// BodyCapBytes caps each captured response body. 0 = use DefaultBodyCapBytes;
// negative = no cap. The cap is enforced while reading the response (LimitReader),
// not after, so a 500MB response will not be fully buffered into memory.
BodyCapBytes int
Stderr io.Writer
Workflow string
BrzVersion string
}
RecorderOptions configures the Recorder.
type Replayer ¶
type Replayer struct {
// contains filtered or unexported fields
}
Replayer serves browser requests from a cassette index. Unmatched requests either pass through (default) or fail (strict). Strict mode is suitable for CI: a regression in workflow ordering surfaces as a clear failure.
func NewReplayer ¶
func NewReplayer(idx *Index, opts ReplayerOptions) *Replayer
NewReplayer constructs a Replayer over the given cassette index.
type ReplayerOptions ¶
type ReplayerOptions struct {
Strict bool // fail unmatched requests instead of passing through
Stderr io.Writer
}
ReplayerOptions configures the Replayer.