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/gatewayGET
/flaky-dataStandard 50/20/30 split
GET
/flaky-endpointConfigurable via params
POST
/flaky-submitForm 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
/flaky-dataStandard 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"
}/flaky-endpointConfigurable 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
/flaky-submitSimulates 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
- Create a new request in Postman
- Set method to
GET - Enter URL:
https://www.passthenote.com/api/gateway/flaky-data - Click "Send" multiple times to see different responses
- 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
Normal operation. Requests are flowing.
๐ก Try This:
- Click "Force Failure" 3 times โ Circuit opens (๐ด OPEN)
- Try clicking any button โ Request blocked
- Wait 30 seconds โ Circuit enters HALF-OPEN (๐ก)
- 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:
- Send 3 requests with
x-mock-response-code: 500 - Verify circuit opens (4th request should fail fast without API call)
- Wait 30 seconds (or mock the timer in tests)
- Send request with
x-mock-response-code: 200 - 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 โ