n8n-trigger-testing-strategies

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

n8n Trigger Testing Strategies

n8n触发器测试策略

<default_to_action> When testing n8n triggers:
  1. IDENTIFY trigger type (webhook, schedule, polling, event)
  2. TEST with various valid payloads
  3. VERIFY authentication and authorization
  4. CHECK error handling for invalid inputs
  5. MEASURE response time and reliability
Quick Trigger Checklist:
  • Trigger activates workflow correctly
  • Payload parsed and validated
  • Authentication enforced (if configured)
  • Error responses are informative
  • Response time is acceptable
Critical Success Factors:
  • Test edge cases (empty payloads, large payloads)
  • Verify idempotency where needed
  • Check timeout handling
  • Monitor for missed triggers </default_to_action>
<default_to_action> 测试n8n触发器时:
  1. 识别触发器类型(webhook、调度、轮询、事件)
  2. 使用各种有效负载进行测试
  3. 验证认证与授权
  4. 检查无效输入的错误处理
  5. 测量响应时间与可靠性
快速触发器检查清单:
  • 触发器正确激活工作流
  • 负载已解析并验证
  • 已启用认证(若已配置)
  • 错误响应信息明确
  • 响应时间符合要求
关键成功因素:
  • 测试边缘情况(空负载、大负载)
  • 必要时验证幂等性
  • 检查超时处理
  • 监控是否有遗漏的触发器 </default_to_action>

Quick Reference Card

快速参考卡片

n8n Trigger Types

n8n触发器类型

TypeUse CaseTesting Focus
WebhookExternal HTTP callsPayloads, auth, methods
ScheduleTimed executionCron accuracy, timezone
PollingCheck for changesInterval, deduplication
EventService eventsEvent handling, filtering
类型使用场景测试重点
Webhook外部HTTP调用负载、认证、请求方法
Schedule(调度)定时执行Cron表达式准确性、时区
Polling(轮询)检查变更间隔时间、去重
Event(事件)服务事件事件处理、过滤

Common Webhook Configurations

常见Webhook配置

SettingOptionsImpact
HTTP MethodGET, POST, PUT, DELETERequest handling
AuthenticationNone, Basic, HeaderSecurity
Response ModeImmediately, Last Node, CustomResponse timing
PathCustom URL pathEndpoint identification

设置项选项影响
HTTP方法GET, POST, PUT, DELETE请求处理
认证方式无、基础认证、头部认证安全性
响应模式立即响应、最后节点响应、自定义响应响应时机
路径自定义URL路径端点识别

Webhook Testing

Webhook测试

Basic Webhook Test

基础Webhook测试

typescript
// Test webhook with various payloads
async function testWebhook(webhookUrl: string): Promise<WebhookTestResult> {
  const testPayloads = [
    // Valid JSON
    { type: 'json', data: { event: 'test', timestamp: Date.now() } },
    // Empty object
    { type: 'empty', data: {} },
    // Large payload
    { type: 'large', data: { items: Array(1000).fill({ id: 1, name: 'test' }) } },
    // Nested data
    { type: 'nested', data: { level1: { level2: { level3: { value: 'deep' } } } } },
    // Special characters
    { type: 'special', data: { text: 'Hello <script>alert("xss")</script>' } }
  ];

  const results: PayloadTestResult[] = [];

  for (const payload of testPayloads) {
    const startTime = Date.now();

    try {
      const response = await fetch(webhookUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload.data)
      });

      results.push({
        payloadType: payload.type,
        success: response.ok,
        status: response.status,
        responseTime: Date.now() - startTime,
        responseBody: await response.text()
      });
    } catch (error) {
      results.push({
        payloadType: payload.type,
        success: false,
        error: error.message
      });
    }
  }

  return { webhookUrl, results };
}
typescript
// Test webhook with various payloads
async function testWebhook(webhookUrl: string): Promise<WebhookTestResult> {
  const testPayloads = [
    // Valid JSON
    { type: 'json', data: { event: 'test', timestamp: Date.now() } },
    // Empty object
    { type: 'empty', data: {} },
    // Large payload
    { type: 'large', data: { items: Array(1000).fill({ id: 1, name: 'test' }) } },
    // Nested data
    { type: 'nested', data: { level1: { level2: { level3: { value: 'deep' } } } } },
    // Special characters
    { type: 'special', data: { text: 'Hello <script>alert("xss")</script>' } }
  ];

  const results: PayloadTestResult[] = [];

  for (const payload of testPayloads) {
    const startTime = Date.now();

    try {
      const response = await fetch(webhookUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload.data)
      });

      results.push({
        payloadType: payload.type,
        success: response.ok,
        status: response.status,
        responseTime: Date.now() - startTime,
        responseBody: await response.text()
      });
    } catch (error) {
      results.push({
        payloadType: payload.type,
        success: false,
        error: error.message
      });
    }
  }

  return { webhookUrl, results };
}

HTTP Method Testing

HTTP方法测试

typescript
// Test all HTTP methods
async function testWebhookMethods(webhookUrl: string): Promise<MethodTestResult[]> {
  const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
  const results: MethodTestResult[] = [];

  for (const method of methods) {
    try {
      const response = await fetch(webhookUrl, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: ['GET', 'HEAD', 'OPTIONS'].includes(method) ? undefined : '{}'
      });

      results.push({
        method,
        allowed: response.ok || response.status !== 405,
        status: response.status,
        statusText: response.statusText
      });
    } catch (error) {
      results.push({
        method,
        allowed: false,
        error: error.message
      });
    }
  }

  return results;
}
typescript
// Test all HTTP methods
async function testWebhookMethods(webhookUrl: string): Promise<MethodTestResult[]> {
  const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
  const results: MethodTestResult[] = [];

  for (const method of methods) {
    try {
      const response = await fetch(webhookUrl, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: ['GET', 'HEAD', 'OPTIONS'].includes(method) ? undefined : '{}'
      });

      results.push({
        method,
        allowed: response.ok || response.status !== 405,
        status: response.status,
        statusText: response.statusText
      });
    } catch (error) {
      results.push({
        method,
        allowed: false,
        error: error.message
      });
    }
  }

  return results;
}

Authentication Testing

认证测试

typescript
// Test webhook authentication
async function testWebhookAuth(webhookUrl: string, authConfig: AuthConfig): Promise<AuthTestResult> {
  const scenarios = [
    // No auth
    { name: 'no-auth', headers: {} },
    // Invalid auth
    { name: 'invalid-auth', headers: { 'Authorization': 'Bearer invalid-token' } },
    // Valid auth
    { name: 'valid-auth', headers: { 'Authorization': `Bearer ${authConfig.token}` } },
    // Expired auth
    { name: 'expired-auth', headers: { 'Authorization': `Bearer ${authConfig.expiredToken}` } }
  ];

  const results: AuthScenarioResult[] = [];

  for (const scenario of scenarios) {
    const response = await fetch(webhookUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...scenario.headers
      },
      body: '{}'
    });

    results.push({
      scenario: scenario.name,
      status: response.status,
      authenticated: response.ok,
      errorMessage: response.ok ? null : await response.text()
    });
  }

  return {
    authRequired: !results.find(r => r.scenario === 'no-auth')?.authenticated,
    invalidRejected: !results.find(r => r.scenario === 'invalid-auth')?.authenticated,
    validAccepted: results.find(r => r.scenario === 'valid-auth')?.authenticated,
    results
  };
}

typescript
// Test webhook authentication
async function testWebhookAuth(webhookUrl: string, authConfig: AuthConfig): Promise<AuthTestResult> {
  const scenarios = [
    // No auth
    { name: 'no-auth', headers: {} },
    // Invalid auth
    { name: 'invalid-auth', headers: { 'Authorization': 'Bearer invalid-token' } },
    // Valid auth
    { name: 'valid-auth', headers: { 'Authorization': `Bearer ${authConfig.token}` } },
    // Expired auth
    { name: 'expired-auth', headers: { 'Authorization': `Bearer ${authConfig.expiredToken}` } }
  ];

  const results: AuthScenarioResult[] = [];

  for (const scenario of scenarios) {
    const response = await fetch(webhookUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...scenario.headers
      },
      body: '{}'
    });

  results.push({
      scenario: scenario.name,
      status: response.status,
      authenticated: response.ok,
      errorMessage: response.ok ? null : await response.text()
    });
  }

  return {
    authRequired: !results.find(r => r.scenario === 'no-auth')?.authenticated,
    invalidRejected: !results.find(r => r.scenario === 'invalid-auth')?.authenticated,
    validAccepted: results.find(r => r.scenario === 'valid-auth')?.authenticated,
    results
  };
}

Schedule Testing

调度测试

Cron Expression Validation

Cron表达式验证

typescript
// Validate cron expression
function validateCronExpression(expression: string): CronValidationResult {
  const parts = expression.trim().split(/\s+/);

  if (parts.length < 5 || parts.length > 6) {
    return {
      valid: false,
      error: `Expected 5-6 parts, got ${parts.length}`
    };
  }

  const [minute, hour, dayOfMonth, month, dayOfWeek, year] = parts;

  const validations = [
    { field: 'minute', value: minute, range: [0, 59] },
    { field: 'hour', value: hour, range: [0, 23] },
    { field: 'dayOfMonth', value: dayOfMonth, range: [1, 31] },
    { field: 'month', value: month, range: [1, 12] },
    { field: 'dayOfWeek', value: dayOfWeek, range: [0, 7] }
  ];

  for (const v of validations) {
    const result = validateCronField(v.value, v.range);
    if (!result.valid) {
      return { valid: false, error: `Invalid ${v.field}: ${result.error}` };
    }
  }

  return {
    valid: true,
    description: describeCronExpression(expression),
    nextExecutions: getNextCronExecutions(expression, 5)
  };
}

// Get human-readable description
function describeCronExpression(expression: string): string {
  // Common patterns
  const patterns: Record<string, string> = {
    '* * * * *': 'Every minute',
    '*/5 * * * *': 'Every 5 minutes',
    '0 * * * *': 'Every hour',
    '0 0 * * *': 'Every day at midnight',
    '0 9 * * 1-5': 'Weekdays at 9:00 AM',
    '0 0 1 * *': 'First day of every month',
    '0 0 * * 0': 'Every Sunday at midnight'
  };

  return patterns[expression] || 'Custom schedule';
}

// Calculate next execution times
function getNextCronExecutions(expression: string, count: number): Date[] {
  const executions: Date[] = [];
  let current = new Date();

  // Simple implementation - use cron-parser library in production
  for (let i = 0; i < count; i++) {
    const next = calculateNextCronExecution(expression, current);
    executions.push(next);
    current = new Date(next.getTime() + 60000); // Move past this execution
  }

  return executions;
}
typescript
// Validate cron expression
function validateCronExpression(expression: string): CronValidationResult {
  const parts = expression.trim().split(/\s+/);

  if (parts.length < 5 || parts.length > 6) {
    return {
      valid: false,
      error: `Expected 5-6 parts, got ${parts.length}`
    };
  }

  const [minute, hour, dayOfMonth, month, dayOfWeek, year] = parts;

  const validations = [
    { field: 'minute', value: minute, range: [0, 59] },
    { field: 'hour', value: hour, range: [0, 23] },
    { field: 'dayOfMonth', value: dayOfMonth, range: [1, 31] },
    { field: 'month', value: month, range: [1, 12] },
    { field: 'dayOfWeek', value: dayOfWeek, range: [0, 7] }
  ];

  for (const v of validations) {
    const result = validateCronField(v.value, v.range);
    if (!result.valid) {
      return { valid: false, error: `Invalid ${v.field}: ${result.error}` };
    }
  }

  return {
    valid: true,
    description: describeCronExpression(expression),
    nextExecutions: getNextCronExecutions(expression, 5)
  };
}

// Get human-readable description
function describeCronExpression(expression: string): string {
  // Common patterns
  const patterns: Record<string, string> = {
    '* * * * *': 'Every minute',
    '*/5 * * * *': 'Every 5 minutes',
    '0 * * * *': 'Every hour',
    '0 0 * * *': 'Every day at midnight',
    '0 9 * * 1-5': 'Weekdays at 9:00 AM',
    '0 0 1 * *': 'First day of every month',
    '0 0 * * 0': 'Every Sunday at midnight'
  };

  return patterns[expression] || 'Custom schedule';
}

// Calculate next execution times
function getNextCronExecutions(expression: string, count: number): Date[] {
  const executions: Date[] = [];
  let current = new Date();

  // Simple implementation - use cron-parser library in production
  for (let i = 0; i < count; i++) {
    const next = calculateNextCronExecution(expression, current);
    executions.push(next);
    current = new Date(next.getTime() + 60000); // Move past this execution
  }

  return executions;
}

Schedule Reliability Testing

调度可靠性测试

typescript
// Test schedule trigger reliability
async function testScheduleReliability(triggerId: string, testDuration: number): Promise<ScheduleTestResult> {
  const startTime = Date.now();
  const expectedExecutions: Date[] = [];
  const actualExecutions: Date[] = [];

  // Calculate expected execution times
  const cronExpression = await getTriggerCronExpression(triggerId);
  let checkTime = new Date(startTime);
  while (checkTime.getTime() < startTime + testDuration) {
    const nextExec = calculateNextCronExecution(cronExpression, checkTime);
    if (nextExec.getTime() < startTime + testDuration) {
      expectedExecutions.push(nextExec);
    }
    checkTime = new Date(nextExec.getTime() + 60000);
  }

  // Monitor actual executions
  const executionListener = onExecutionStart(triggerId, (exec) => {
    actualExecutions.push(new Date(exec.startedAt));
  });

  // Wait for test duration
  await sleep(testDuration);
  executionListener.stop();

  // Compare expected vs actual
  const comparison = compareExecutions(expectedExecutions, actualExecutions);

  return {
    testDuration,
    expectedCount: expectedExecutions.length,
    actualCount: actualExecutions.length,
    missedExecutions: comparison.missed,
    extraExecutions: comparison.extra,
    timingAccuracy: comparison.timingAccuracy,
    reliability: (actualExecutions.length / expectedExecutions.length) * 100
  };
}

typescript
// Test schedule trigger reliability
async function testScheduleReliability(triggerId: string, testDuration: number): Promise<ScheduleTestResult> {
  const startTime = Date.now();
  const expectedExecutions: Date[] = [];
  const actualExecutions: Date[] = [];

  // Calculate expected execution times
  const cronExpression = await getTriggerCronExpression(triggerId);
  let checkTime = new Date(startTime);
  while (checkTime.getTime() < startTime + testDuration) {
    const nextExec = calculateNextCronExecution(cronExpression, checkTime);
    if (nextExec.getTime() < startTime + testDuration) {
      expectedExecutions.push(nextExec);
    }
    checkTime = new Date(nextExec.getTime() + 60000);
  }

  // Monitor actual executions
  const executionListener = onExecutionStart(triggerId, (exec) => {
    actualExecutions.push(new Date(exec.startedAt));
  });

  // Wait for test duration
  await sleep(testDuration);
  executionListener.stop();

  // Compare expected vs actual
  const comparison = compareExecutions(expectedExecutions, actualExecutions);

  return {
    testDuration,
    expectedCount: expectedExecutions.length,
    actualCount: actualExecutions.length,
    missedExecutions: comparison.missed,
    extraExecutions: comparison.extra,
    timingAccuracy: comparison.timingAccuracy,
    reliability: (actualExecutions.length / expectedExecutions.length) * 100
  };
}

Polling Trigger Testing

轮询触发器测试

typescript
// Test polling trigger behavior
async function testPollingTrigger(triggerId: string, testConfig: PollingTestConfig): Promise<PollingTestResult> {
  const { interval, testDuration, simulateDataChanges } = testConfig;

  const pollEvents: PollEvent[] = [];
  const triggeredExecutions: Execution[] = [];

  // Monitor polling events
  const pollListener = onPoll(triggerId, (event) => {
    pollEvents.push({
      timestamp: new Date(),
      dataFound: event.hasNewData,
      itemCount: event.items?.length || 0
    });
  });

  // Monitor triggered executions
  const execListener = onExecutionStart(triggerId, (exec) => {
    triggeredExecutions.push(exec);
  });

  // Optionally simulate data changes
  if (simulateDataChanges) {
    for (const change of simulateDataChanges) {
      setTimeout(() => {
        injectTestData(triggerId, change.data);
      }, change.at);
    }
  }

  // Wait for test duration
  await sleep(testDuration);

  pollListener.stop();
  execListener.stop();

  // Analyze results
  const expectedPolls = Math.floor(testDuration / interval);
  const actualPolls = pollEvents.length;

  return {
    interval,
    testDuration,
    expectedPolls,
    actualPolls,
    pollAccuracy: (actualPolls / expectedPolls) * 100,
    averageInterval: calculateAverageInterval(pollEvents),
    executionsTriggered: triggeredExecutions.length,
    deduplicationWorking: checkDeduplication(triggeredExecutions),
    pollEvents
  };
}

// Check if deduplication is working
function checkDeduplication(executions: Execution[]): boolean {
  const processedIds = new Set();

  for (const exec of executions) {
    const itemIds = exec.data?.resultData?.runData?.Trigger?.[0]?.data?.main?.[0]
      ?.map(item => item.json?.id);

    if (itemIds) {
      for (const id of itemIds) {
        if (processedIds.has(id)) {
          return false; // Duplicate found
        }
        processedIds.add(id);
      }
    }
  }

  return true;
}

typescript
// Test polling trigger behavior
async function testPollingTrigger(triggerId: string, testConfig: PollingTestConfig): Promise<PollingTestResult> {
  const { interval, testDuration, simulateDataChanges } = testConfig;

  const pollEvents: PollEvent[] = [];
  const triggeredExecutions: Execution[] = [];

  // Monitor polling events
  const pollListener = onPoll(triggerId, (event) => {
    pollEvents.push({
      timestamp: new Date(),
      dataFound: event.hasNewData,
      itemCount: event.items?.length || 0
    });
  });

  // Monitor triggered executions
  const execListener = onExecutionStart(triggerId, (exec) => {
    triggeredExecutions.push(exec);
  });

  // Optionally simulate data changes
  if (simulateDataChanges) {
    for (const change of simulateDataChanges) {
      setTimeout(() => {
        injectTestData(triggerId, change.data);
      }, change.at);
    }
  }

  // Wait for test duration
  await sleep(testDuration);

  pollListener.stop();
  execListener.stop();

  // Analyze results
  const expectedPolls = Math.floor(testDuration / interval);
  const actualPolls = pollEvents.length;

  return {
    interval,
    testDuration,
    expectedPolls,
    actualPolls,
    pollAccuracy: (actualPolls / expectedPolls) * 100,
    averageInterval: calculateAverageInterval(pollEvents),
    executionsTriggered: triggeredExecutions.length,
    deduplicationWorking: checkDeduplication(triggeredExecutions),
    pollEvents
  };
}

// Check if deduplication is working
function checkDeduplication(executions: Execution[]): boolean {
  const processedIds = new Set();

  for (const exec of executions) {
    const itemIds = exec.data?.resultData?.runData?.Trigger?.[0]?.data?.main?.[0]
      ?.map(item => item.json?.id);

    if (itemIds) {
      for (const id of itemIds) {
        if (processedIds.has(id)) {
          return false; // Duplicate found
        }
        processedIds.add(id);
      }
    }
  }

  return true;
}

Event Trigger Testing

事件触发器测试

typescript
// Test event-driven triggers
async function testEventTrigger(triggerId: string, eventConfig: EventTestConfig): Promise<EventTestResult> {
  const { eventType, testEvents, timeout } = eventConfig;

  const results: EventResult[] = [];

  for (const testEvent of testEvents) {
    // Emit test event
    const startTime = Date.now();
    await emitTestEvent(eventType, testEvent.payload);

    // Wait for trigger
    try {
      const execution = await waitForTrigger(triggerId, timeout);

      results.push({
        eventType: testEvent.type,
        triggered: true,
        latency: Date.now() - startTime,
        payloadReceived: execution.data?.inputData
      });
    } catch (error) {
      results.push({
        eventType: testEvent.type,
        triggered: false,
        error: error.message
      });
    }
  }

  return {
    eventType,
    testsRun: testEvents.length,
    triggered: results.filter(r => r.triggered).length,
    averageLatency: average(results.filter(r => r.triggered).map(r => r.latency)),
    results
  };
}

typescript
// Test event-driven triggers
async function testEventTrigger(triggerId: string, eventConfig: EventTestConfig): Promise<EventTestResult> {
  const { eventType, testEvents, timeout } = eventConfig;

  const results: EventResult[] = [];

  for (const testEvent of testEvents) {
    // Emit test event
    const startTime = Date.now();
    await emitTestEvent(eventType, testEvent.payload);

    // Wait for trigger
    try {
      const execution = await waitForTrigger(triggerId, timeout);

      results.push({
        eventType: testEvent.type,
        triggered: true,
        latency: Date.now() - startTime,
        payloadReceived: execution.data?.inputData
      });
    } catch (error) {
      results.push({
        eventType: testEvent.type,
        triggered: false,
        error: error.message
      });
    }
  }

  return {
    eventType,
    testsRun: testEvents.length,
    triggered: results.filter(r => r.triggered).length,
    averageLatency: average(results.filter(r => r.triggered).map(r => r.latency)),
    results
  };
}

Trigger Response Testing

触发器响应测试

typescript
// Test trigger response modes
async function testTriggerResponses(webhookUrl: string): Promise<ResponseTestResult> {
  // Test immediate response
  const immediateStart = Date.now();
  const immediateResponse = await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: '{"test": "immediate"}'
  });
  const immediateTime = Date.now() - immediateStart;

  // Test with workflow execution
  const workflowStart = Date.now();
  const workflowResponse = await fetch(`${webhookUrl}?waitForResponse=true`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: '{"test": "workflow"}'
  });
  const workflowTime = Date.now() - workflowStart;

  return {
    immediateResponse: {
      status: immediateResponse.status,
      time: immediateTime,
      body: await immediateResponse.text()
    },
    workflowResponse: {
      status: workflowResponse.status,
      time: workflowTime,
      body: await workflowResponse.text()
    },
    responseMode: workflowTime > immediateTime + 100 ? 'workflow' : 'immediate'
  };
}

typescript
// Test trigger response modes
async function testTriggerResponses(webhookUrl: string): Promise<ResponseTestResult> {
  // Test immediate response
  const immediateStart = Date.now();
  const immediateResponse = await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: '{"test": "immediate"}'
  });
  const immediateTime = Date.now() - immediateStart;

  // Test with workflow execution
  const workflowStart = Date.now();
  const workflowResponse = await fetch(`${webhookUrl}?waitForResponse=true`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: '{"test": "workflow"}'
  });
  const workflowTime = Date.now() - workflowStart;

  return {
    immediateResponse: {
      status: immediateResponse.status,
      time: immediateTime,
      body: await immediateResponse.text()
    },
    workflowResponse: {
      status: workflowResponse.status,
      time: workflowTime,
      body: await workflowResponse.text()
    },
    responseMode: workflowTime > immediateTime + 100 ? 'workflow' : 'immediate'
  };
}

Test Scenarios

测试场景

yaml
Webhook Scenarios:
  - name: Valid JSON POST
    method: POST
    payload: {"event": "test"}
    expected: 200 OK

  - name: Invalid JSON
    method: POST
    payload: "not valid json"
    expected: 400 Bad Request

  - name: Missing auth
    method: POST
    headers: {}
    expected: 401 Unauthorized

  - name: Large payload
    method: POST
    payload: [10MB of data]
    expected: 413 Payload Too Large

Schedule Scenarios:
  - name: Every 5 minutes
    cron: "*/5 * * * *"
    verify: 12 executions per hour

  - name: Weekdays at 9 AM
    cron: "0 9 * * 1-5"
    verify: 5 executions per week

Polling Scenarios:
  - name: 1 minute interval
    interval: 60000
    verify: ~60 polls per hour

  - name: Deduplication
    interval: 60000
    inject_duplicate: true
    verify: No duplicate processing

yaml
Webhook Scenarios:
  - name: Valid JSON POST
    method: POST
    payload: {"event": "test"}
    expected: 200 OK

  - name: Invalid JSON
    method: POST
    payload: "not valid json"
    expected: 400 Bad Request

  - name: Missing auth
    method: POST
    headers: {}
    expected: 401 Unauthorized

  - name: Large payload
    method: POST
    payload: [10MB of data]
    expected: 413 Payload Too Large

Schedule Scenarios:
  - name: Every 5 minutes
    cron: "*/5 * * * *"
    verify: 12 executions per hour

  - name: Weekdays at 9 AM
    cron: "0 9 * * 1-5"
    verify: 5 executions per week

Polling Scenarios:
  - name: 1 minute interval
    interval: 60000
    verify: ~60 polls per hour

  - name: Deduplication
    interval: 60000
    inject_duplicate: true
    verify: No duplicate processing

Related Skills

相关技能

  • n8n-workflow-testing-fundamentals
  • n8n-integration-testing-patterns
  • n8n-security-testing

  • n8n工作流测试基础
  • n8n集成测试模式
  • n8n安全测试

Remember

注意事项

n8n triggers are the entry points to workflows. Testing requires:
  • Webhook: Payload handling, auth, HTTP methods
  • Schedule: Cron accuracy, timezone handling
  • Polling: Interval accuracy, deduplication
  • Event: Event handling, filtering
Key patterns: Test with various payloads (valid, invalid, edge cases). Verify authentication enforcement. Check response times and reliability over time.
n8n触发器是工作流的入口点,测试需要关注:
  • Webhook:负载处理、认证、HTTP方法
  • 调度:Cron表达式准确性、时区处理
  • 轮询:间隔准确性、去重
  • 事件:事件处理、过滤
关键模式: 使用各种负载(有效、无效、边缘情况)进行测试。验证认证是否强制启用。长期检查响应时间与可靠性。