Tutorials

Chaos Engineering for CAPTCHA Solving Pipelines

You built circuit breakers, retries, and bulkheads for your CAPTCHA solving pipeline. But do they actually work? Chaos engineering injects controlled failures — network timeouts, API errors, slow responses — into your pipeline to verify that resilience patterns behave correctly before production discovers the gaps.

What to Test

Failure What breaks Expected behaviour
API timeout Submit hangs forever Retry after timeout, then circuit breaker opens
429 rate limit Too many requests Backoff, reduce concurrency
ERROR_ZERO_BALANCE No budget Stop solving, alert, don't retry
Slow responses 30s+ solve times Timeout, retry with fresh task
Malformed response Invalid JSON Error handling, retry
Network disconnect Connection refused Retry with backoff, failover

Python: Chaos Middleware

Intercept API calls and inject failures based on configurable rules.

import requests
import random
import time
import json
from dataclasses import dataclass, field
from unittest.mock import patch

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


@dataclass
class ChaosRule:
    """Defines a chaos injection rule."""
    name: str
    probability: float  # 0.0 to 1.0
    enabled: bool = True


@dataclass
class ChaosConfig:
    """Configuration for chaos experiments."""
    timeout_probability: float = 0.0
    error_probability: float = 0.0
    slow_response_probability: float = 0.0
    slow_response_delay: float = 15.0
    malformed_probability: float = 0.0
    error_responses: list[str] = field(default_factory=lambda: [
        "ERROR_NO_SLOT_AVAILABLE",
        "ERROR_CAPTCHA_UNSOLVABLE",
        "ERROR_TOO_MUCH_REQUESTS",
    ])


class ChaosProxy:
    """Wraps HTTP requests to inject chaos."""

    def __init__(self, config: ChaosConfig):
        self.config = config
        self.injections: list[dict] = []

    def _maybe_inject(self, probability: float) -> bool:
        return random.random() < probability

    def _log(self, injection_type: str, details: str = ""):
        entry = {"type": injection_type, "time": time.monotonic(), "details": details}
        self.injections.append(entry)
        print(f"[CHAOS] Injected: {injection_type} {details}")

    def intercept_post(self, original_post, url, **kwargs):
        """Intercept POST requests with chaos."""
        if self.config.timeout_probability and self._maybe_inject(self.config.timeout_probability):
            self._log("timeout", f"POST {url}")
            raise requests.Timeout("Chaos: connection timed out")

        if self.config.slow_response_probability and self._maybe_inject(self.config.slow_response_probability):
            delay = self.config.slow_response_delay
            self._log("slow_response", f"{delay}s delay")
            time.sleep(delay)

        if self.config.error_probability and self._maybe_inject(self.config.error_probability):
            error = random.choice(self.config.error_responses)
            self._log("error_response", error)
            # Return a mock response with error
            mock_resp = requests.models.Response()
            mock_resp.status_code = 200
            mock_resp._content = json.dumps({"status": 0, "request": error}).encode()
            return mock_resp

        if self.config.malformed_probability and self._maybe_inject(self.config.malformed_probability):
            self._log("malformed_response")
            mock_resp = requests.models.Response()
            mock_resp.status_code = 200
            mock_resp._content = b"not json at all"
            return mock_resp

        return original_post(url, **kwargs)

    def intercept_get(self, original_get, url, **kwargs):
        """Intercept GET requests with chaos."""
        if self.config.timeout_probability and self._maybe_inject(self.config.timeout_probability):
            self._log("timeout", f"GET {url}")
            raise requests.Timeout("Chaos: connection timed out")

        return original_get(url, **kwargs)

    def report(self) -> dict:
        """Summary of injected failures."""
        types = {}
        for entry in self.injections:
            types[entry["type"]] = types.get(entry["type"], 0) + 1
        return {"total_injections": len(self.injections), "by_type": types}


# --- Run a chaos experiment ---

def run_experiment(solver_fn, chaos_config: ChaosConfig, iterations: int = 20):
    """Run a solver function under chaos conditions."""
    proxy = ChaosProxy(chaos_config)
    original_post = requests.post
    original_get = requests.get

    results = {"success": 0, "failure": 0, "errors": []}

    with patch.object(requests, "post",
                      side_effect=lambda url, **kw: proxy.intercept_post(original_post, url, **kw)):
        with patch.object(requests, "get",
                          side_effect=lambda url, **kw: proxy.intercept_get(original_get, url, **kw)):
            for i in range(iterations):
                try:
                    token = solver_fn()
                    results["success"] += 1
                except Exception as e:
                    results["failure"] += 1
                    results["errors"].append(str(e))

    results["chaos_report"] = proxy.report()
    return results


# --- Example: Test your solver under chaos ---

def my_solver():
    """Your existing solver function."""
    params = {
        "key": API_KEY, "json": 1,
        "method": "turnstile",
        "sitekey": "0x4XXXXXXXXXXXXXXXXX",
        "pageurl": "https://example.com",
    }
    resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
    if resp.get("status") != 1:
        raise RuntimeError(f"Submit: {resp.get('request')}")

    task_id = resp["request"]
    start = time.monotonic()
    while time.monotonic() - start < 180:
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }, timeout=15).json()
        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return poll["request"]
        raise RuntimeError(f"Solve: {poll.get('request')}")
    raise RuntimeError("Timeout")


# Experiment 1: 20% timeout rate
print("=== Experiment: Timeout Injection ===")
results = run_experiment(
    my_solver,
    ChaosConfig(timeout_probability=0.2),
    iterations=10,
)
print(f"Success: {results['success']}, Failure: {results['failure']}")
print(f"Injections: {results['chaos_report']}")

# Experiment 2: Mixed failures
print("\n=== Experiment: Mixed Chaos ===")
results = run_experiment(
    my_solver,
    ChaosConfig(
        timeout_probability=0.1,
        error_probability=0.15,
        slow_response_probability=0.1,
        slow_response_delay=10.0,
    ),
    iterations=10,
)
print(f"Success: {results['success']}, Failure: {results['failure']}")

JavaScript: Chaos Wrapper

const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

class ChaosInjector {
  constructor(config = {}) {
    this.config = {
      timeoutRate: config.timeoutRate || 0,
      errorRate: config.errorRate || 0,
      slowRate: config.slowRate || 0,
      slowDelayMs: config.slowDelayMs || 15000,
      errors: config.errors || ["ERROR_NO_SLOT_AVAILABLE", "ERROR_CAPTCHA_UNSOLVABLE"],
    };
    this.injections = [];
  }

  async wrapFetch(originalFetch, url, options = {}) {
    // Timeout injection
    if (Math.random() < this.config.timeoutRate) {
      this.injections.push({ type: "timeout", url });
      throw new Error("Chaos: timeout");
    }

    // Slow response injection
    if (Math.random() < this.config.slowRate) {
      this.injections.push({ type: "slow", url });
      await new Promise((r) => setTimeout(r, this.config.slowDelayMs));
    }

    // Error response injection
    if (Math.random() < this.config.errorRate) {
      const error = this.config.errors[Math.floor(Math.random() * this.config.errors.length)];
      this.injections.push({ type: "error", error });
      return new Response(JSON.stringify({ status: 0, request: error }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }

    return originalFetch(url, options);
  }

  report() {
    const byType = {};
    for (const i of this.injections) {
      byType[i.type] = (byType[i.type] || 0) + 1;
    }
    return { total: this.injections.length, byType };
  }
}

async function runExperiment(solverFn, chaosConfig, iterations = 10) {
  const injector = new ChaosInjector(chaosConfig);
  const originalFetch = globalThis.fetch;
  globalThis.fetch = (url, opts) => injector.wrapFetch(originalFetch, url, opts);

  const results = { success: 0, failure: 0, errors: [] };

  for (let i = 0; i < iterations; i++) {
    try {
      await solverFn();
      results.success++;
    } catch (e) {
      results.failure++;
      results.errors.push(e.message);
    }
  }

  globalThis.fetch = originalFetch;
  results.chaosReport = injector.report();
  return results;
}

// Run experiment
const results = await runExperiment(
  () => solveCaptcha({ method: "turnstile", sitekey: "SITEKEY", pageurl: "https://example.com" }),
  { timeoutRate: 0.2, errorRate: 0.1 },
  10
);
console.log(`Success: ${results.success}, Failure: ${results.failure}`);
console.log("Injections:", results.chaosReport);

Chaos Experiment Checklist

Experiment Inject Verify
Timeout at submit 30% timeout on POST Retries succeed within budget
Timeout at poll 30% timeout on GET Fresh task submitted on timeout
Rate limit storm 50% return 429 Backoff slows retries, no storm
Balance exhausted Return ERROR_ZERO_BALANCE Stops immediately, alerts fired
Slow API 15s delay on responses Timeout kicks in, retries work
Total outage 100% connection refused Circuit breaker opens, graceful degradation

Troubleshooting

Issue Cause Fix
Chaos affects production No environment check Gate chaos behind CHAOS_ENABLED=true env var
Monkey-patching leaks across tests Global state not restored Use context managers or try/finally to restore originals
False positives in results Chaos rate too high Start at 5–10% injection rate, increase gradually
Experiment not reproducible Random seed not set Set random.seed() for reproducible experiments
Real API calls during chaos Not all paths intercepted Verify all HTTP methods are wrapped

FAQ

Should I run chaos experiments against the live CaptchaAI API?

Run chaos at the HTTP layer so failures are injected before reaching the API. This tests your error handling without consuming API credits. Only test against the live API when you specifically need to verify end-to-end behaviour under normal conditions.

How often should I run chaos experiments?

Run them after every change to retry logic, circuit breakers, or error handling code. Add them to your CI pipeline as integration tests. For ongoing monitoring, run weekly experiments against staging environments.

What's the right failure injection rate?

Start at 5–10% for exploratory tests. Increase to 20–30% to stress-test specific patterns. 50%+ is useful for verifying circuit breaker thresholds. Never run 100% injection in production — that's not chaos engineering, that's an outage.

Next Steps

Verify your CAPTCHA pipeline's resilience — get your CaptchaAI API key and start running chaos experiments.

Related guides:

Discussions (0)

No comments yet.

Related Posts

DevOps & Scaling Ansible Playbooks for CaptchaAI Worker Deployment
Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates, and health checks across your server fleet.

Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates...

Automation Python All CAPTCHA Types
Apr 07, 2026
DevOps & Scaling Blue-Green Deployment for CAPTCHA Solving Infrastructure
Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switching, and rollback strategies with Captcha AI.

Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switchin...

Automation Python All CAPTCHA Types
Apr 07, 2026
Troubleshooting CaptchaAI API Error Handling: Complete Decision Tree
Complete decision tree for every Captcha AI API error.

Complete decision tree for every Captcha AI API error. Learn which errors are retryable, which need parameter...

Automation Python All CAPTCHA Types
Mar 17, 2026
Tutorials Using Fiddler to Inspect CaptchaAI API Traffic
How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and responses — filters, breakpoints, and replay for tr...

How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and r...

Automation Python All CAPTCHA Types
Mar 05, 2026
Tutorials CAPTCHA Handling in Mobile Apps with Appium
Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web sitekeys, solve, and inject tokens on Android and i OS.

Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web View sitekeys, solve, and i...

Automation Python All CAPTCHA Types
Feb 13, 2026
Tutorials Streaming Batch Results: Processing CAPTCHA Solutions as They Arrive
Process CAPTCHA solutions the moment they arrive instead of waiting for tasks to complete — use async generators, event emitters, and callback patterns for stre...

Process CAPTCHA solutions the moment they arrive instead of waiting for all tasks to complete — use async gene...

Automation Python All CAPTCHA Types
Apr 07, 2026
Reference CaptchaAI CLI Tool: Command-Line CAPTCHA Solving and Testing
A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parameters, and integrate with shell scripts and CI/CD p...

A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parame...

Automation Python All CAPTCHA Types
Feb 26, 2026
DevOps & Scaling Auto-Scaling CAPTCHA Solving Workers
Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates.

Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates...

Automation Python All CAPTCHA Types
Mar 23, 2026
DevOps & Scaling CaptchaAI Monitoring with Datadog: Metrics and Alerts
Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve rate tracking for CAPTCHA solving pipelines.

Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve...

Automation Python All CAPTCHA Types
Feb 19, 2026
Tutorials Pytest Fixtures for CaptchaAI API Testing
Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI.

Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI. Covers mocking, live integra...

Automation Python reCAPTCHA v2
Apr 08, 2026
Tutorials GeeTest Token Injection in Browser Automation Frameworks
how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the three-value response, callback triggering, and form submissi...

Learn how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the thre...

Automation Python Testing
Jan 18, 2026