HTTP request recording and replay system for API testing
npx @tessl/cli install tessl/npm-replay@2.4.0Replay is a comprehensive HTTP request recording and replay system designed for API testing. It allows developers to capture HTTP responses during initial test runs and replay them in subsequent runs without making actual network requests, creating deterministic, fast-running test suites.
npm install replayconst Replay = require('replay');The package exports a single Replay instance that provides all functionality.
const Replay = require('replay');
// Set mode to record new responses
Replay.mode = 'record';
// Configure fixtures directory
Replay.fixtures = __dirname + '/fixtures';
// Make HTTP requests - they will be recorded automatically
const http = require('http');
http.get('http://api.example.com/data', (response) => {
// Response will be saved for future replay
});
// Later, in test mode (default)
Replay.mode = 'replay'; // Only replay captured responsesReplay is built around several key components:
Controls how Replay handles HTTP requests with four distinct operational modes.
// Mode property
Replay.mode: string; // 'replay' | 'record' | 'cheat' | 'bloody'Flexible host routing system for localhost redirection, pass-through, and connection dropping.
// Host configuration methods
Replay.localhost(...hosts: string[]): Replay;
Replay.passThrough(...hosts: string[]): Replay;
Replay.drop(...hosts: string[]): Replay;
Replay.reset(...hosts: string[]): Replay;
// Host checking methods
Replay.isLocalhost(host: string): boolean;
Replay.isPassThrough(host: string): boolean;
Replay.isDropped(host: string): boolean;Recording and storage configuration for HTTP response fixtures.
// Fixture configuration
Replay.fixtures: string; // Directory path
Replay.headers: RegExp[]; // Headers to record/match
// Catalog access (internal fixture management)
Replay.catalog: Catalog;Extensible proxy chain system for custom request processing.
// Proxy chain methods
Replay.use(proxy: (request: any, callback: Function) => void): Replay;
// Built-in proxies
Replay.logger(): Function;
// Chain access (internal proxy chain management)
Replay.chain: Chain;// Environment variable configuration
process.env.REPLAY: 'replay' | 'record' | 'cheat' | 'bloody';
process.env.DEBUG: 'replay'; // Enable debug loggingReplay extends EventEmitter for error handling:
// Event handling
Replay.on(event: 'error', listener: (error: Error) => void): Replay;Usage Examples:
const Replay = require('replay');
// Handle errors from the replay system
Replay.on('error', (error) => {
console.error('Replay error:', error.message);
// Common error types:
if (error.code === 'ECONNREFUSED') {
console.log('Request blocked - no fixture found and not in record mode');
} else if (error.code === 'CORRUPT FIXTURE') {
console.log('Fixture file is corrupted or malformed');
}
});Common error scenarios and their handling:
// Connection refused - no fixture available in replay mode
class ReplayConnectionRefusedError extends Error {
code: 'ECONNREFUSED';
syscall: 'connect';
message: 'Connection to {url} refused: not recording and no network access';
}
// Corrupt fixture file
class ReplayCorruptFixtureError extends Error {
code: 'CORRUPT FIXTURE';
syscall: 'connect';
message: string;
}
// Unsupported mode
class ReplayUnsupportedModeError extends Error {
message: 'Unsupported mode \'{mode}\', must be one of bloody, cheat, record, replay.';
}// Operational modes
type ReplayMode = 'replay' | 'record' | 'cheat' | 'bloody';
// Proxy handler function
type ProxyHandler = (request: ReplayRequest, callback: (error?: Error, response?: ReplayResponse) => void) => void;
// Response control function
type ResponseControlFunction = (request: ReplayRequest, response: ReplayResponse) => boolean;
// Host pattern (supports wildcards)
type HostPattern = string; // e.g., 'api.example.com', '*.example.com', 'api.*'
// Request object structure
interface ReplayRequest {
url: URL;
method: string;
headers: { [key: string]: string | string[] };
body?: Array<[Buffer | string, string]>;
trailers?: { [key: string]: string | string[] };
}
// Response object structure
interface ReplayResponse {
version: string;
statusCode: number;
statusMessage: string;
rawHeaders: string[];
headers: { [key: string]: string | string[] };
body: Array<[Buffer | string, string]>;
trailers?: { [key: string]: string | string[] };
rawTrailers?: string[];
}
// Catalog class for fixture management
interface Catalog {
find(host: string): Matcher[] | null;
save(host: string, request: ReplayRequest, response: ReplayResponse, callback: (error?: Error) => void): void;
getFixturesDir(): string;
setFixturesDir(dir: string): void;
}
// Chain class for proxy management
interface Chain {
append(handler: ProxyHandler): Chain;
prepend(handler: ProxyHandler): Chain;
clear(): void;
start: ProxyHandler | null;
}
// Matcher class for request/response matching
interface Matcher {
(request: ReplayRequest): ReplayResponse | null;
}