synthetic-monitoring

Category: Monitoring observability
Tags:
testing api
For: Claude Code

Implement synthetic monitoring and automated testing to simulate user behavior and detect issues before users. Use when creating end-to-end test scenarios, monitoring API flows, or validating user workflows.

Installation

Copy to your project
cp -r skills/synthetic-monitoring/ /your-project/.claude/skills/synthetic-monitoring/

Synthetic Monitoring

Overview

Set up synthetic monitoring to automatically simulate real user journeys, API workflows, and critical business transactions to detect issues and validate performance.

When to Use

  • End-to-end workflow validation
  • API flow testing
  • User journey simulation
  • Transaction monitoring
  • Critical path validation

Instructions

1. Synthetic Tests with Playwright

// synthetic-tests.js
const { chromium } = require("playwright");

class SyntheticMonitor {
  constructor(config = {}) {
    this.baseUrl = config.baseUrl || "https://app.example.com";
    this.timeout = config.timeout || 30000;
  }

  async testUserFlow() {
    const browser = await chromium.launch();
    const page = await browser.newPage();
    const metrics = { steps: {} };
    const startTime = Date.now();

    try {
      // Step 1: Navigate to login
      let stepStart = Date.now();
      await page.goto(`${this.baseUrl}/login`, { waitUntil: "networkidle" });
      metrics.steps.navigation = Date.now() - stepStart;

      // Step 2: Perform login
      stepStart = Date.now();
      await page.fill('input[name="email"]', "test@example.com");
      await page.fill('input[name="password"]', "password123");
      await page.click('button[type="submit"]');
      await page.waitForNavigation({ waitUntil: "networkidle" });
      metrics.steps.login = Date.now() - stepStart;

      // Step 3: Navigate to dashboard
      stepStart = Date.now();
      await page.goto(`${this.baseUrl}/dashboard`, {
        waitUntil: "networkidle",
      });
      metrics.steps.dashboard = Date.now() - stepStart;

      // Step 4: Search for products
      stepStart = Date.now();
      await page.fill('input[placeholder="Search products"]', "laptop");
      await page.waitForSelector(".product-list");
      metrics.steps.search = Date.now() - stepStart;

      // Step 5: Add to cart
      stepStart = Date.now();
      const firstProduct = await page.$(".product-item");
      if (firstProduct) {
        await firstProduct.click();
        await page.click('button:has-text("Add to Cart")');
        await page.waitForSelector('[data-testid="cart-count"]');
      }
      metrics.steps.addToCart = Date.now() - stepStart;

      metrics.totalTime = Date.now() - startTime;
      metrics.status = "success";
    } catch (error) {
      metrics.status = "failed";
      metrics.error = error.message;
      metrics.totalTime = Date.now() - startTime;
    } finally {
      await browser.close();
    }

    return metrics;
  }

  async testMobileUserFlow() {
    const browser = await chromium.launch();
    const context = await browser.createBrowserContext({
      ...chromium.devices["iPhone 12"],
    });
    const page = await context.newPage();

    try {
      const metrics = { device: "iPhone 12", steps: {} };
      const startTime = Date.now();

      let stepStart = Date.now();
      await page.goto(this.baseUrl, { waitUntil: "networkidle" });
      metrics.steps.navigation = Date.now() - stepStart;

      const viewport = page.viewportSize();
      metrics.viewport = viewport;

      stepStart = Date.now();
      await page.click(".menu-toggle");
      await page.waitForSelector(".mobile-menu.open");
      metrics.steps.mobileInteraction = Date.now() - stepStart;

      metrics.totalTime = Date.now() - startTime;
      metrics.status = "success";

      return metrics;
    } catch (error) {
      return { status: "failed", error: error.message, device: "iPhone 12" };
    } finally {
      await browser.close();
    }
  }

  async testWithPerformanceMetrics() {
    const browser = await chromium.launch();
    const page = await browser.newPage();

    try {
      await page.goto(this.baseUrl, { waitUntil: "networkidle" });

      const perfMetrics = JSON.parse(
        await page.evaluate(() => JSON.stringify(window.performance.timing)),
      );

      const metrics = {
        navigationTiming: {
          domInteractive:
            perfMetrics.domInteractive - perfMetrics.navigationStart,
          domComplete: perfMetrics.domComplete - perfMetrics.navigationStart,
          loadComplete: perfMetrics.loadEventEnd - perfMetrics.navigationStart,
        },
        status: "success",
      };

      return metrics;
    } catch (error) {
      return { status: "failed", error: error.message };
    } finally {
      await browser.close();
    }
  }

  async recordMetrics(testName, metrics) {
    try {
      await axios.post("http://monitoring-service/synthetic-results", {
        testName,
        timestamp: new Date(),
        metrics,
        passed: metrics.status === "success",
      });
    } catch (error) {
      console.error("Failed to record metrics:", error);
    }
  }
}

module.exports = SyntheticMonitor;

2. API Synthetic Tests

// api-synthetic-tests.js
const axios = require("axios");

class APISyntheticTests {
  constructor(config = {}) {
    this.baseUrl = config.baseUrl || "https://api.example.com";
    this.client = axios.create({ baseURL: this.baseUrl });
  }

  async testAuthenticationFlow() {
    const results = { steps: {}, status: "success" };

    try {
      const registerStart = Date.now();
      const registerRes = await this.client.post("/auth/register", {
        email: `test-${Date.now()}@example.com`,
        password: "Test@123456",
      });
      results.steps.register = Date.now() - registerStart;

      if (registerRes.status !== 201) throw new Error("Registration failed");

      const loginStart = Date.now();
      const loginRes = await this.client.post("/auth/login", {
        email: registerRes.data.email,
        password: "Test@123456",
      });
      results.steps.login = Date.now() - loginStart;

      const token = loginRes.data.token;

      const authStart = Date.now();
      await this.client.get("/api/profile", {
        headers: { Authorization: `Bearer ${token}` },
      });
      results.steps.authenticatedRequest = Date.now() - authStart;

      const logoutStart = Date.now();
      await this.client.post(
        "/auth/logout",
        {},
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );
      results.steps.logout = Date.now() - logoutStart;

      return results;
    } catch (error) {
      results.status = "failed";
      results.error = error.message;
      return results;
    }
  }

  async testTransactionFlow() {
    const results = { steps: {}, status: "success" };

    try {
      const orderStart = Date.now();
      const orderRes = await this.client.post(
        "/api/orders",
        {
          items: [{ sku: "ITEM-001", quantity: 2 }],
        },
        {
          headers: { "X-Idempotency-Key": `order-${Date.now()}` },
        },
      );
      results.steps.createOrder = Date.now() - orderStart;

      const getStart = Date.now();
      const getRes = await this.client.get(`/api/orders/${orderRes.data.id}`);
      results.steps.getOrder = Date.now() - getStart;

      const paymentStart = Date.now();
      await this.client.post(`/api/orders/${orderRes.data.id}/payment`, {
        method: "credit_card",
        amount: getRes.data.total,
      });
      results.steps.processPayment = Date.now() - paymentStart;

      return results;
    } catch (error) {
      results.status = "failed";
      results.error = error.message;
      return results;
    }
  }

  async testUnderLoad(concurrentUsers = 10, duration = 60000) {
    const startTime = Date.now();
    const results = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      averageResponseTime: 0,
      p95ResponseTime: 0,
    };

    const responseTimes = [];

    const makeRequest = async () => {
      const reqStart = Date.now();
      try {
        await this.client.get("/api/health");
        results.successfulRequests++;
        responseTimes.push(Date.now() - reqStart);
      } catch {
        results.failedRequests++;
      }
      results.totalRequests++;
    };

    const userSimulations = Array(concurrentUsers)
      .fill(null)
      .map(async () => {
        while (Date.now() - startTime < duration) {
          await makeRequest();
          await new Promise((r) => setTimeout(r, Math.random() * 1000));
        }
      });

    await Promise.all(userSimulations);

    responseTimes.sort((a, b) => a - b);
    results.averageResponseTime =
      responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
    results.p95ResponseTime =
      responseTimes[Math.floor(responseTimes.length * 0.95)];

    return results;
  }
}

module.exports = APISyntheticTests;

3. Scheduled Synthetic Monitoring

// scheduled-monitor.js
const cron = require("node-cron");
const SyntheticMonitor = require("./synthetic-tests");
const APISyntheticTests = require("./api-synthetic-tests");
const axios = require("axios");

class ScheduledSyntheticMonitor {
  constructor(config = {}) {
    this.eMonitor = new SyntheticMonitor(config);
    this.apiTests = new APISyntheticTests(config);
    this.alertThreshold = config.alertThreshold || 5000;
  }

  start() {
    cron.schedule("*/5 * * * *", () => this.runE2ETests());
    cron.schedule("*/2 * * * *", () => this.runAPITests());
    cron.schedule("0 * * * *", () => this.runLoadTest());
  }

  async runE2ETests() {
    try {
      const metrics = await this.eMonitor.testUserFlow();
      await this.recordResults("e2e-user-flow", metrics);

      if (metrics.totalTime > this.alertThreshold) {
        await this.sendAlert("e2e-user-flow", metrics);
      }
    } catch (error) {
      console.error("E2E test failed:", error);
    }
  }

  async runAPITests() {
    try {
      const authMetrics = await this.apiTests.testAuthenticationFlow();
      const transactionMetrics = await this.apiTests.testTransactionFlow();

      await this.recordResults("api-auth-flow", authMetrics);
      await this.recordResults("api-transaction-flow", transactionMetrics);

      if (
        authMetrics.status === "failed" ||
        transactionMetrics.status === "failed"
      ) {
        await this.sendAlert("api-tests", { authMetrics, transactionMetrics });
      }
    } catch (error) {
      console.error("API test failed:", error);
    }
  }

  async runLoadTest() {
    try {
      const results = await this.apiTests.testUnderLoad(10, 30000);
      await this.recordResults("load-test", results);

      if (results.failedRequests > 0) {
        await this.sendAlert("load-test", results);
      }
    } catch (error) {
      console.error("Load test failed:", error);
    }
  }

  async recordResults(testName, metrics) {
    try {
      await axios.post("http://monitoring-service/synthetic-results", {
        testName,
        timestamp: new Date(),
        metrics,
      });
      console.log(`Recorded: ${testName}`, metrics);
    } catch (error) {
      console.error("Failed to record results:", error);
    }
  }

  async sendAlert(testName, metrics) {
    try {
      await axios.post("http://alerting-service/alerts", {
        type: "synthetic_monitoring",
        testName,
        severity: "warning",
        message: `Synthetic test '${testName}' has issues`,
        metrics,
        timestamp: new Date(),
      });
      console.log(`Alert sent for ${testName}`);
    } catch (error) {
      console.error("Failed to send alert:", error);
    }
  }
}

module.exports = ScheduledSyntheticMonitor;

Best Practices

✅ DO

  • Test critical user journeys
  • Simulate real browser conditions
  • Monitor from multiple locations
  • Track response times
  • Alert on test failures
  • Rotate test data
  • Test mobile and desktop
  • Include error scenarios

❌ DON’T

  • Test with production data
  • Reuse test accounts
  • Skip timeout configurations
  • Ignore test maintenance
  • Test too frequently
  • Hard-code credentials
  • Ignore geographic variations
  • Test only happy paths

Key Metrics

  • Response time
  • Success rate
  • Availability
  • Core Web Vitals
  • Error rate