Flaky Network Simulation

Practice handling real-world network issues: timeouts, retries, and error recovery

๐ŸŒAPI Base URL & Endpoints

Base URL

https://www.passthenote.com/api/gateway

GET

/flaky-data

Standard 50/20/30 split

GET

/flaky-endpoint

Configurable via params

POST

/flaky-submit

Form submission test

๐ŸŽฏ Learning Objective

Real-world automation fails because of network issues, not just bad selectors. These endpoints simulate common network problems to help you implement robust retry logic.

  • 50% - Immediate success (200 OK)
  • 20% - Server error (500)
  • 30% - Delayed response (5 seconds)

๐ŸŽฏTDD-Friendly: Deterministic Headers

The Problem: Random responses make it impossible to write reliable tests. How do you test your error handling if you can't guarantee a 500 response?

The Solution: Use deterministic headers to force specific responses. Perfect for Test-Driven Development (TDD) where you need predictable behavior.

x-mock-response-code

Force a specific HTTP status code (200, 400, 500, etc.)

Example - cURL:

curl -H "x-mock-response-code: 500" \
  https://www.passthenote.com/api/gateway/flaky-data

Example - Playwright:

await request.get('/api/gateway/flaky-data', {
  headers: { 'x-mock-response-code': '500' }
});

x-mock-delay

Force a specific delay in milliseconds (useful for timeout testing)

Example - cURL:

curl -H "x-mock-delay: 10000" \
  https://www.passthenote.com/api/gateway/flaky-data

Example - Combine Both:

curl -H "x-mock-response-code: 500" \
     -H "x-mock-delay: 3000" \
  https://www.passthenote.com/api/gateway/flaky-data

๐Ÿ’ก Use Case: Write tests that verify your app handles 500 errors correctly, timeout scenarios, or validation errors - without waiting for random chance to give you the right response.

Endpoint Specifications

GET/flaky-data

Standard flaky endpoint with fixed probability distribution.

Response Behavior:

  • โ€ข 50% - Immediate 200 OK with data
  • โ€ข 20% - 500 Server Error
  • โ€ข 30% - 200 OK after 5 second delay

Success Response (200):

{
  "status": "success",
  "message": "Data retrieved successfully",
  "data": { "items": [...] },
  "responseTime": "immediate" | "delayed"
}

Error Response (500):

{
  "error": "Internal Server Error",
  "message": "The server encountered an unexpected condition",
  "code": "SERVER_ERROR"
}
GET/flaky-endpoint

Configurable endpoint for custom testing scenarios.

Query Parameters:

  • โ€ข successRate - Percentage of successful requests (default: 50)
  • โ€ข delay - Delay in milliseconds for slow responses (default: 5000)

Example URLs:

/flaky-endpoint?successRate=70&delay=3000 /flaky-endpoint?successRate=20&delay=10000
POST/flaky-submit

Simulates realistic form submission with various failure modes.

Request Body:

{
  "name": "string",
  "email": "string",
  "message": "string"
}

Response Behavior:

  • โ€ข 40% - Immediate 200 OK
  • โ€ข 30% - 400 Validation Error
  • โ€ข 20% - 500 Server Error
  • โ€ข 10% - 200 OK after 8 second delay

GET /flaky-data

Standard flaky endpoint with 50/20/30 split. Perfect for testing basic retry logic.

GET /flaky-endpoint

Configurable endpoint. Use query params: ?successRate=70&delay=3000

POST /flaky-submit

Form submission with validation errors, timeouts, and server errors. Tests complete retry workflows.

Testing Strategies

  • โœ… Implement exponential backoff
  • โœ… Set appropriate timeout values
  • โœ… Handle specific error codes
  • โœ… Add max retry limits
  • โœ… Test with cy.intercept() / page.route()

๐ŸงชHow to Test These Endpoints

Using cURL

# Test basic endpoint
curl -X GET "https://www.passthenote.com/api/gateway/flaky-data"

# Test with custom parameters
curl -X GET "https://www.passthenote.com/api/gateway/flaky-endpoint?successRate=70&delay=3000"

# Test POST endpoint
curl -X POST "https://www.passthenote.com/api/gateway/flaky-submit" \
  -H "Content-Type: application/json" \
  -d '{"name":"Test","email":"test@test.com","message":"Hello"}'

Using Postman

  1. Create a new request in Postman
  2. Set method to GET
  3. Enter URL: https://www.passthenote.com/api/gateway/flaky-data
  4. Click "Send" multiple times to see different responses
  5. Add a Test script to implement retry logic:
// Postman Test Script
pm.test("Status code or retry", function () {
    if (pm.response.code !== 200 && pm.info.iteration < 3) {
        postman.setNextRequest(pm.info.requestName);
    } else {
        pm.expect(pm.response.code).to.equal(200);
    }
});

Using JavaScript/Fetch with Retry Logic

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return await response.json();
      }
      
      // If not OK and not last attempt, continue to retry
      if (attempt < maxRetries) {
        console.log(`Attempt ${attempt} failed, retrying...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
        continue;
      }
      
      // Last attempt and still failing
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    } catch (error) {
      lastError = error;
      if (attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      }
    }
  }
  
  throw lastError;
}

// Usage
try {
  const data = await fetchWithRetry(
    'https://www.passthenote.com/api/gateway/flaky-data'
  );
  console.log('Success:', data);
} catch (error) {
  console.error('Failed after retries:', error);
}

Using Python with Requests

import requests
import time
from typing import Optional, Dict, Any

def fetch_with_retry(
    url: str,
    max_retries: int = 3,
    timeout: int = 10
) -> Optional[Dict[Any, Any]]:
    """Fetch URL with exponential backoff retry logic"""
    
    for attempt in range(1, max_retries + 1):
        try:
            response = requests.get(url, timeout=timeout)
            
            if response.status_code == 200:
                return response.json()
            
            print(f"Attempt {attempt} failed: {response.status_code}")
            
            if attempt < max_retries:
                wait_time = 2 ** attempt  # Exponential backoff
                time.sleep(wait_time)
                
        except requests.Timeout:
            print(f"Attempt {attempt} timed out")
            if attempt < max_retries:
                time.sleep(2 ** attempt)
        except requests.RequestException as e:
            print(f"Attempt {attempt} error: {e}")
            if attempt < max_retries:
                time.sleep(2 ** attempt)
    
    return None

# Usage
url = "https://www.passthenote.com/api/gateway/flaky-data"
result = fetch_with_retry(url, max_retries=3)

if result:
    print(f"Success: {result}")
else:
    print("Failed after all retries")

Example Test Code

Cypress with Retry Logic

describe('Flaky Network Handling', () => {
  it('should retry failed requests', () => {
    let attempts = 0;
    const maxRetries = 3;
    
    const makeRequest = () => {
      attempts++;
      return cy.request({
        method: 'GET',
        url: '/api/gateway/flaky-data',
        failOnStatusCode: false
      }).then((response) => {
        if (response.status !== 200 && attempts < maxRetries) {
          cy.wait(1000);
          return makeRequest();
        }
        expect(response.status).to.eq(200);
        expect(response.body).to.have.property('data');
      });
    };
    
    makeRequest();
  });
});

Playwright (Modern 2025 Syntax)

import { test, expect } from '@playwright/test';

test('handle flaky endpoint with deterministic headers', async ({ request }) => {
  // Force a 500 error to test error handling
  const response = await request.get('/api/gateway/flaky-data', {
    headers: { 'x-mock-response-code': '500' }
  });
  
  expect(response.status()).toBe(500);
  const data = await response.json();
  expect(data.error).toBe('Internal Server Error');
  expect(data.deterministic).toBe(true);
});

test('test UI with retry logic', async ({ page }) => {
  await page.goto('/practice-challenges/flaky-network');
  
  // Modern: Use getByTestId() instead of click()
  await page.getByTestId('test-with-retry').click();
  
  // Modern: Use expect().toBeVisible() with auto-retry
  await expect(
    page.getByTestId('success-result')
      .or(page.getByTestId('error-result'))
  ).toBeVisible({ timeout: 20000 });
  
  // Modern: Locator-based visibility check
  const success = page.getByTestId('success-result');
  await expect(success).toBeVisible();
});

test('verify retry counter appears', async ({ page }) => {
  await page.goto('/practice-challenges/flaky-network');
  await page.getByTestId('test-with-retry').click();
  
  // Check retry counter badge appears during retries
  await expect(page.getByTestId('retry-counter')).toBeVisible();
});

REST Assured with Retry

public Response retryRequest(String endpoint, int maxRetries) {
    int attempts = 0;
    Response response = null;
    
    while (attempts < maxRetries) {
        response = given()
            .baseUri("https://www.passthenote.com")
            .basePath("/api/gateway")
            .when()
            .get(endpoint);
            
        if (response.getStatusCode() == 200) {
            return response;
        }
        
        attempts++;
        try {
            Thread.sleep(1000); // Wait 1 second before retry
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    return response;
}
๐Ÿ”ฅ

Advanced Challenge: Circuit Breaker Pattern

Senior/Staff Engineer Interview Topic - Advanced resilience pattern for production systems

โšก Live Circuit Breaker Demo

Circuit Status:๐ŸŸข CLOSEDFailures: 0/3

Normal operation. Requests are flowing.

๐Ÿ’ก Try This:

  1. Click "Force Failure" 3 times โ†’ Circuit opens (๐Ÿ”ด OPEN)
  2. Try clicking any button โ†’ Request blocked
  3. Wait 30 seconds โ†’ Circuit enters HALF-OPEN (๐ŸŸก)
  4. Click "Successful Request" โ†’ Circuit closes (๐ŸŸข CLOSED)

๐ŸŽฏ The Scenario

You're testing a payment gateway integration. The service starts failing repeatedly. Your test suite hammers it with retries, making the problem worse and wasting time.

The Circuit Breaker Pattern prevents this:

  • After 3 consecutive failures โ†’ Open the circuit (stop making requests)
  • Wait 30 seconds โ†’ Enter "half-open" state
  • Try one test request โ†’ If it succeeds, close the circuit and resume
  • If it fails โ†’ Open circuit again for another 30 seconds

๐Ÿ’ก Implementation Strategy

State Management:

enum CircuitState { CLOSED, OPEN, HALF_OPEN }
let state = CircuitState.CLOSED;
let failureCount = 0;
let lastFailureTime = null;

Request Logic:

async function makeRequestWithCircuitBreaker(endpoint) {
  // OPEN: Check if timeout has passed
  if (state === OPEN) {
    if (Date.now() - lastFailureTime > 30000) {
      state = HALF_OPEN;  // Try one request
    } else {
      throw new Error('Circuit breaker is OPEN');
    }
  }
  
  try {
    const response = await fetch(endpoint);
    
    if (!response.ok) throw new Error('Request failed');
    
    // Success: Reset circuit
    state = CLOSED;
    failureCount = 0;
    return response;
    
  } catch (error) {
    failureCount++;
    lastFailureTime = Date.now();
    
    // Open circuit after 3 failures
    if (failureCount >= 3) {
      state = OPEN;
      console.log('Circuit breaker OPENED - no requests for 30s');
    }
    
    throw error;
  }
}

๐Ÿงช Testing Strategy

Use deterministic headers to test circuit breaker logic:

  1. Send 3 requests with x-mock-response-code: 500
  2. Verify circuit opens (4th request should fail fast without API call)
  3. Wait 30 seconds (or mock the timer in tests)
  4. Send request with x-mock-response-code: 200
  5. Verify circuit closes and normal operation resumes

โœ… Benefits

  • โ€ข Prevents cascading failures
  • โ€ข Saves test execution time
  • โ€ข Matches production patterns
  • โ€ข Demonstrates architectural knowledge
  • โ€ข Shows understanding of resilience patterns

๐Ÿ“š Interview Topics

  • โ€ข When to use circuit breakers vs retries
  • โ€ข Timeout vs failure count thresholds
  • โ€ข Monitoring and alerting strategies
  • โ€ข Half-open state rationale
  • โ€ข Integration with observability tools

๐Ÿ† Challenge: Implement a circuit breaker using /flaky-data and deterministic headers. Create a test suite that verifies all three states (CLOSED โ†’ OPEN โ†’ HALF_OPEN โ†’ CLOSED).

๐ŸŽจ Ready for Visual Testing?

Master visual regression testing with our Pixel Perfect challenge. Learn threshold tuning, masking strategies, and deterministic screenshot generation.

Next Challenge: Visual Regression โ†’