home / skills / aj-geddes / useful-ai-prompts / synthetic-monitoring

synthetic-monitoring skill

/skills/synthetic-monitoring

This skill helps you implement synthetic monitoring and automated end-to-end tests to detect issues before users.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill synthetic-monitoring

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
11.8 KB
---
name: synthetic-monitoring
description: 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.
---

# 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**

```javascript
// 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"]', '[email protected]');
      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**

```javascript
// 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**

```javascript
// 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

Overview

This skill implements synthetic monitoring and automated tests that simulate user behavior and API flows to find issues before real users are affected. It provides end-to-end browser scripts, API scenario checks, load simulations, scheduling, and alerting hooks for continuous validation of critical paths.

How this skill works

The skill runs Playwright-based browser flows to exercise login, search, add-to-cart, and mobile interactions while collecting step timings and performance timing data. It also executes API scenarios for authentication, transactions, and health checks, plus configurable load tests. Results are recorded to a monitoring endpoint and alerts are sent when thresholds or failures occur.

When to use it

  • Validate end-to-end user journeys after deployments
  • Continuously monitor API authentication and transaction flows
  • Detect regressions in critical business transactions before users notice
  • Measure synthetic performance and Core Web Vitals from automation
  • Run scheduled load checks to uncover capacity issues

Best practices

  • Focus tests on critical paths and high-value user journeys
  • Run synthetic checks from multiple locations and device profiles
  • Record step-level timings and performance.timing for trend analysis
  • Rotate synthetic test data and avoid using production credentials
  • Set sensible frequency and alert thresholds to avoid noise

Example use cases

  • Hourly E2E smoke test that logs in, searches, and adds an item to cart
  • API health run that registers a user, logs in, calls protected endpoints, and logs out
  • Scheduled load test that simulates concurrent health checks to measure p95 latency
  • Mobile synthetic run emulating an iPhone viewport to validate mobile menu flows
  • Automated guard that posts results to a monitoring service and triggers alerts on failures

FAQ

How often should synthetic tests run?

Balance coverage and noise: common cadences are every 1–5 minutes for critical flows, every 2–5 minutes for API checks, and hourly for load or heavy performance runs.

Can synthetic tests measure real user performance?

Synthetic tests provide reproducible performance baselines and Core Web Vitals from controlled runs; combine them with real-user monitoring for complete visibility.