Advanced Features

Chaos testing, HAR conversion, error replay, and more

Chaos Testing

Inject artificial latency and failures to test resilience.

# Script options

export const options = {
  workers: 10,
  duration: '1m',
  jitter: '500ms',   // Add 0-500ms random latency
  drop: 0.05,        // Drop 5% of requests
};

# CLI flags

$ fusillade run test.js --jitter 500ms --drop 0.05

jitter: Adds random delay (0 to specified value) before each request

drop: Probability (0.0-1.0) that a request will be silently dropped

HAR File Conversion

Convert browser recordings (HAR files) into Fusillade test scripts.

# Export HAR from browser

1. Open Chrome DevTools (F12)
2. Go to Network tab
3. Perform the user flow you want to test
4. Right-click → "Save all as HAR with content"

# Convert to script

$ fusillade convert --input recording.har --output test.js

# Preview without writing
$ fusillade convert --input recording.har --dry-run

# Generated script

// Auto-generated from recording.har
export const options = {
  workers: 1,
  iterations: 1,
};

export default function() {
  // GET https://example.com/
  http.get('https://example.com/', {
    headers: {
      'Accept': 'text/html',
      'User-Agent': 'Mozilla/5.0...',
    }
  });

  // POST https://example.com/api/login
  http.post('https://example.com/api/login', JSON.stringify({
    "username": "user",
    "password": "***"
  }), {
    headers: {
      'Content-Type': 'application/json',
    }
  });

  sleep(0.5);
}

Traffic Recording Proxy

Record HTTP traffic through a proxy to generate test scripts.

# Start recording proxy on port 8085
$ fusillade record -o recorded-test.js -p 8085

# Configure your app/browser to use http://localhost:8085 as proxy
# Perform actions you want to test
# Press Ctrl+C to stop and generate script

Error Capture & Replay

Capture failed requests during a test run for debugging and replay.

# Capture errors

# Save failed requests to file
$ fusillade run test.js --capture-errors errors.json

# errors.json contains full request details for debugging

# Replay failed requests

# Replay one by one
$ fusillade replay errors.json

# Replay in parallel
$ fusillade replay errors.json --parallel

# Export as cURL commands
$ fusillade export errors.json --format curl > debug_commands.sh

Test Comparison

Compare two test runs to detect performance regressions.

# Export baseline
$ fusillade run test.js --export-json baseline.json

# ... make changes ...

# Export current
$ fusillade run test.js --export-json current.json

# Compare
$ fusillade compare baseline.json current.json

# Output:
# Metric              Baseline    Current     Change
# http_req_duration   145ms       167ms       +15.2% ⚠️
# http_req_failed     0.5%        0.8%        +60.0% ⚠️
# http_reqs           15,234      14,891      -2.3%

Test History

Track test runs over time with the local history database.

# Enable history saving
$ fusillade run test.js --save-history

# View history
$ fusillade history
# ID   Date                  Script      Workers  Duration  P95
# 1    2024-01-15 10:30:00  test.js     10    30s       145ms
# 2    2024-01-15 11:00:00  test.js     50    1m        178ms
# 3    2024-01-15 14:22:00  stress.js   100   5m        234ms

# View specific run
$ fusillade history --show 3

# Limit results
$ fusillade history --limit 10

Memory Optimization

Options for running high-concurrency tests without exhausting memory.

export const options = {
  workers: 100000,
  duration: '10m',

  // Discard response bodies (only keep status/headers)
  response_sink: true,

  // Disable per-endpoint metrics (reduces cardinality)
  no_endpoint_tracking: true,

  // Auto-throttle spawning when memory is high
  memory_safe: true,

  // Smaller stack per worker (default 32KB)
  stack_size: 16384,
};

# CLI equivalent

$ fusillade run test.js -w 100000 \
  --response-sink \
  --no-endpoint-tracking \
  --memory-safe

Watch Mode

Automatically re-run tests when script files change.

# Watch for changes and re-run
$ fusillade run test.js --watch

# Great for development - edit script, see results immediately

Script Validation

Validate scripts without running them.

# Check script syntax and configuration
$ fusillade validate test.js

# Validate with config file
$ fusillade validate test.js -c config.yaml

# Output:
# ✓ Script syntax valid
# ✓ Options parsed successfully
# ✓ Thresholds valid
# ✓ 3 scenarios defined

Bandwidth Cost Estimation

# Estimate data transfer costs
$ fusillade run test.js --estimate-cost

# Set cost limit (abort if exceeded)
$ fusillade run test.js --estimate-cost 100  # $100 limit

# Output:
# Estimated data transfer: 15.2 GB
# Estimated cost (AWS): $1.37
# Estimated cost (GCP): $1.22

Utility Functions

sleep(seconds)

Pause execution for N seconds (fractional supported)

sleepRandom(min, max)

Pause for random duration between min and max seconds

print(message)

Print to console (visible in TUI)

utils.uuid()

Generate UUID v4

utils.randomString(length)

Random alphanumeric string

utils.randomInt(min, max)

Random integer in range

utils.randomItem(array)

Random array element

utils.randomEmail()

Generate random email address

utils.randomPhone()

Generate random US phone number

utils.randomName()

Generate random name {first, last, full}

utils.randomDate(startYear, endYear)

Generate random date (YYYY-MM-DD)

utils.sequentialId()

Unique sequential ID across workers

Crypto Module

crypto.md5(data)

MD5 hash as hex string

crypto.sha1(data)

SHA1 hash as hex string

crypto.sha256(data)

SHA256 hash as hex string

crypto.hmac(algorithm, key, data)

HMAC using md5/sha1/sha256

Encoding Module

encoding.b64encode(data)

Encode string to base64

encoding.b64decode(data)

Decode base64 to string

Console API

Structured logging with log levels.

console.log(msg)

Log at INFO level (default)

console.info(msg)

Log at INFO level

console.warn(msg)

Log at WARN level

console.error(msg)

Log at ERROR level

console.debug(msg)

Log at DEBUG level (hidden by default)

console.table(array)

Display array of objects as table

console.debug('Detailed debugging info');  // Only shown with --log-level debug
console.log('Normal info message');
console.warn('Warning: rate limit approaching');
console.error('Error: request failed');

// Pretty-print tabular data
console.table([
  { name: 'Alice', score: 95 },
  { name: 'Bob', score: 87 },
]);

Unit Testing

Built-in unit testing framework for verifying logic.

describe(name, fn)

Groups related tests

test(name, fn)

Defines a test case

expect(value).toBe(expected)

Strict equality check

expect(value).toEqual(expected)

Deep equality check (via JSON)

expect(value).toBeTruthy()

Checks if value is truthy

describe("Cart Logic", () => {
  test("calculates total correctly", () => {
    const total = calculateTotal(100, 2);
    expect(total).toBe(200);
  });

  test("applies discount", () => {
    const discounted = applyDiscount(100, 0.1);
    expect(discounted).toBe(90);
  });
});

Request Grouping (segment)

Group requests under named categories for cleaner metrics.

segment('Login Flow', () => {
  http.post('/login', credentials);

  segment('Dashboard', () => {
    http.get('/dashboard');  // Metric: "Login Flow::Dashboard::/dashboard"
  });
});

// Nested segments create hierarchical names

Stats API

Query collected statistics during the test for adaptive scenarios.

export default function() {
  http.get('https://api.example.com/data', { name: 'getData' });

  // Query stats for a named request
  const s = stats.get('getData');
  print(`p95: ${s.p95}ms, avg: ${s.avg}ms, count: ${s.count}`);

  // Adaptive behavior based on performance
  if (s.p95 > 500) {
    print('Warning: p95 latency exceeds 500ms');
  }
}

p95 - 95th percentile latency (ms)

p99 - 99th percentile latency (ms)

avg - Average latency (ms)

count - Total request count

min - Minimum latency (ms)

max - Maximum latency (ms)

Global Variables

__WORKER_ID - Current worker's numeric ID (0-indexed)

__ITERATION - Current iteration number for this worker (0-indexed)

__ENV - Object containing environment variables

__SCENARIO - Name of currently executing scenario

__WORKER_STATE - Per-worker state object that persists across iterations

export default function() {
  // Use worker ID to partition test data
  const userId = users[__WORKER_ID % users.length];

  // Access environment variables
  const apiKey = __ENV.API_KEY || 'default-key';

  // Track per-worker state across iterations
  __WORKER_STATE.loginCount = (__WORKER_STATE.loginCount || 0) + 1;
  console.log(`Worker ${__WORKER_ID} iteration ${__ITERATION}, logins: ${__WORKER_STATE.loginCount}`);

  // Store session data that persists
  if (!__WORKER_STATE.token) {
    const res = http.post('/login', credentials);
    __WORKER_STATE.token = res.json().token;
  }
  // Use cached token in subsequent iterations
  http.get('/api/data', { headers: { 'Authorization': `Bearer ${__WORKER_STATE.token}` } });
}

Automatic .env File Loading

Fusillade automatically loads .env files from the script's directory or current working directory.

# .env file format
API_KEY=your-secret-key
BASE_URL=https://api.example.com
DEBUG=true

Shell Completions

# Generate completions for your shell
$ fusillade completion bash >> ~/.bashrc
$ fusillade completion zsh >> ~/.zshrc
$ fusillade completion fish > ~/.config/fish/completions/fusillade.fish

# PowerShell
$ fusillade completion powershell >> $PROFILE

JSON Schema Support

Generate JSON Schema for config file validation in your IDE.

# Generate JSON schema for config validation
$ fusillade schema -o fusillade.schema.json

Use with VS Code YAML extension for autocomplete in YAML config files.