n8n-trigger-testing-strategies
关于
This skill provides comprehensive testing strategies for n8n workflow triggers, including webhooks, schedules, and polling mechanisms. It helps developers verify trigger activation, payload handling, authentication, and error responses. Use it when you need to ensure your n8n workflows are triggered reliably and correctly.
快速安装
Claude Code
推荐/plugin add https://github.com/proffesor-for-testing/agentic-qegit clone https://github.com/proffesor-for-testing/agentic-qe.git ~/.claude/skills/n8n-trigger-testing-strategies在 Claude Code 中复制并粘贴此命令以安装该技能
技能文档
n8n Trigger Testing Strategies
<default_to_action> When testing n8n triggers:
- IDENTIFY trigger type (webhook, schedule, polling, event)
- TEST with various valid payloads
- VERIFY authentication and authorization
- CHECK error handling for invalid inputs
- 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>
Quick Reference Card
n8n Trigger Types
| Type | Use Case | Testing Focus |
|---|---|---|
| Webhook | External HTTP calls | Payloads, auth, methods |
| Schedule | Timed execution | Cron accuracy, timezone |
| Polling | Check for changes | Interval, deduplication |
| Event | Service events | Event handling, filtering |
Common Webhook Configurations
| Setting | Options | Impact |
|---|---|---|
| HTTP Method | GET, POST, PUT, DELETE | Request handling |
| Authentication | None, Basic, Header | Security |
| Response Mode | Immediately, Last Node, Custom | Response timing |
| Path | Custom URL path | Endpoint identification |
Webhook Testing
Basic Webhook Test
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
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
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.
GitHub 仓库
相关推荐技能
content-collections
元Content Collections 是一个 TypeScript 优先的构建工具,可将本地 Markdown/MDX 文件转换为类型安全的数据集合。它专为构建博客、文档站和内容密集型 Vite+React 应用而设计,提供基于 Zod 的自动模式验证。该工具涵盖从 Vite 插件配置、MDX 编译到生产环境部署的完整工作流。
evaluating-llms-harness
测试该Skill通过60+个学术基准测试(如MMLU、GSM8K等)评估大语言模型质量,适用于模型对比、学术研究及训练进度追踪。它支持HuggingFace、vLLM和API接口,被EleutherAI等行业领先机构广泛采用。开发者可通过简单命令行快速对模型进行多任务批量评估。
cloudflare-turnstile
元这个Skill提供完整的Cloudflare Turnstile集成知识,用于在表单、登录页面和API端点中实现无验证码的机器人防护。它支持React/Next.js/Hono等框架集成,涵盖令牌验证、错误代码调试和端到端测试等场景。通过运行后台不可见挑战,在保持用户体验的同时有效阻止自动化流量和垃圾信息。
webapp-testing
测试该Skill为开发者提供了基于Playwright的本地Web应用测试工具集,支持自动化测试前端功能、调试UI行为、捕获屏幕截图和查看浏览器日志。它包含管理服务器生命周期的辅助脚本,可直接作为黑盒工具运行而无需阅读源码。适用于需要快速验证本地Web应用界面和交互功能的开发场景。
