合成监控 synthetic-monitoring

合成监控是一种自动化测试技术,用于模拟真实用户行为,监控API流程和关键业务交易,以提前发现并解决性能问题。

测试 0 次安装 0 次浏览 更新于 3/4/2026

name: synthetic-monitoring description: 实施合成监控和自动化测试,模拟用户行为并在用户之前发现问题。用于创建端到端测试场景、监控API流程或验证用户工作流时。

合成监控

概览

设置合成监控以自动模拟真实用户旅程、API工作流和关键业务事务,以发现问题并验证性能。

何时使用

  • 端到端工作流验证
  • API流程测试
  • 用户旅程模拟
  • 事务监控
  • 关键路径验证

指南

1. 使用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 {
      // 第1步:导航到登录
      let stepStart = Date.now();
      await page.goto(`${this.baseUrl}/login`, { waitUntil: 'networkidle' });
      metrics.steps.navigation = Date.now() - stepStart;

      // 第2步:执行登录
      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;

      // 第3步:导航到仪表板
      stepStart = Date.now();
      await page.goto(`${this.baseUrl}/dashboard`, { waitUntil: 'networkidle' });
      metrics.steps.dashboard = Date.now() - stepStart;

      // 第4步:搜索产品
      stepStart = Date.now();
      await page.fill('input[placeholder="Search products"]', 'laptop');
      await page.waitForSelector('.product-list');
      metrics.steps.search = Date.now() - stepStart;

      // 第5步:添加到购物车
      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合成测试

// 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-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;

最佳实践

✅ DO

  • 测试关键用户旅程
  • 模拟真实浏览器条件
  • 从多个位置监控
  • 跟踪响应时间
  • 在测试失败时发出警报
  • 轮换测试数据
  • 测试移动和桌面
  • 包括错误场景

❌ DON’T

  • 使用生产数据进行测试
  • 重用测试账户
  • 跳过超时配置
  • 忽略测试维护
  • 测试过于频繁
  • 硬编码凭据
  • 忽略地理差异
  • 仅测试快乐路径

关键指标

  • 响应时间
  • 成功率
  • 可用性
  • 核心Web Vitals
  • 错误率