n8n-workflow-testing-fundamentals
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesen8n Workflow Testing Fundamentals
n8n工作流测试基础
<default_to_action>
When testing n8n workflows:
- VALIDATE workflow structure before execution
- TEST with realistic test data
- VERIFY node-to-node data flow
- CHECK error handling paths
- MEASURE execution performance
Quick n8n Testing Checklist:
- All nodes properly connected (no orphans)
- Trigger node correctly configured
- Data mappings between nodes valid
- Error workflows defined
- Credentials properly referenced
Critical Success Factors:
- Test each execution path separately
- Validate data transformations at each node
- Check retry and error handling behavior
- Verify integrations with external services </default_to_action>
<default_to_action>
测试n8n工作流时:
- 执行前验证工作流结构
- 使用真实测试数据进行测试
- 验证节点间的数据流
- 检查错误处理路径
- 评估执行性能
n8n测试快速检查表:
- 所有节点已正确连接(无孤立节点)
- 触发节点配置正确
- 节点间的数据映射有效
- 已定义错误处理工作流
- 凭证引用正确
关键成功因素:
- 单独测试每条执行路径
- 验证每个节点的数据转换
- 检查重试和错误处理行为
- 验证与外部服务的集成 </default_to_action>
Quick Reference Card
快速参考卡片
When to Use
适用场景
- Testing new n8n workflows
- Validating workflow changes
- Debugging failed executions
- Performance optimization
- Pre-deployment validation
- 测试新的n8n工作流
- 验证工作流变更
- 调试失败的执行任务
- 性能优化
- 部署前验证
n8n Workflow Components
n8n工作流组件
| Component | Purpose | Testing Focus |
|---|---|---|
| Trigger | Starts workflow | Reliable activation, payload handling |
| Action Nodes | Process data | Configuration, data mapping |
| Logic Nodes | Control flow | Conditional routing, branches |
| Integration Nodes | External APIs | Auth, rate limits, errors |
| Error Workflow | Handle failures | Recovery, notifications |
| 组件 | 用途 | 测试重点 |
|---|---|---|
| Trigger | 启动工作流 | 可靠触发、负载处理 |
| Action Nodes | 处理数据 | 配置、数据映射 |
| Logic Nodes | 控制流程 | 条件路由、分支处理 |
| Integration Nodes | 外部API集成 | 认证、速率限制、错误处理 |
| Error Workflow | 处理故障 | 恢复机制、通知 |
Workflow Execution States
工作流执行状态
| State | Meaning | Test Action |
|---|---|---|
| Currently executing | Monitor progress |
| Completed successfully | Validate outputs |
| Execution failed | Analyze error |
| Waiting for trigger | Test trigger mechanism |
| 状态 | 含义 | 测试操作 |
|---|---|---|
| 正在执行 | 监控进度 |
| 执行成功完成 | 验证输出结果 |
| 执行失败 | 分析错误原因 |
| 等待触发 | 测试触发机制 |
Workflow Structure Validation
工作流结构验证
typescript
// Validate workflow structure before execution
async function validateWorkflowStructure(workflowId: string) {
const workflow = await getWorkflow(workflowId);
// Check for trigger node
const triggerNode = workflow.nodes.find(n =>
n.type.includes('trigger') || n.type.includes('webhook')
);
if (!triggerNode) {
throw new Error('Workflow must have a trigger node');
}
// Check for orphan nodes (no connections)
const connectedNodes = new Set();
for (const [source, targets] of Object.entries(workflow.connections)) {
connectedNodes.add(source);
for (const outputs of Object.values(targets)) {
for (const connections of outputs) {
for (const conn of connections) {
connectedNodes.add(conn.node);
}
}
}
}
const orphans = workflow.nodes.filter(n => !connectedNodes.has(n.name));
if (orphans.length > 0) {
console.warn('Orphan nodes detected:', orphans.map(n => n.name));
}
// Validate credentials
for (const node of workflow.nodes) {
if (node.credentials) {
for (const [type, ref] of Object.entries(node.credentials)) {
if (!await credentialExists(ref.id)) {
throw new Error(`Missing credential: ${type} for node ${node.name}`);
}
}
}
}
return { valid: true, orphans, triggerNode };
}typescript
// Validate workflow structure before execution
async function validateWorkflowStructure(workflowId: string) {
const workflow = await getWorkflow(workflowId);
// Check for trigger node
const triggerNode = workflow.nodes.find(n =>
n.type.includes('trigger') || n.type.includes('webhook')
);
if (!triggerNode) {
throw new Error('Workflow must have a trigger node');
}
// Check for orphan nodes (no connections)
const connectedNodes = new Set();
for (const [source, targets] of Object.entries(workflow.connections)) {
connectedNodes.add(source);
for (const outputs of Object.values(targets)) {
for (const connections of outputs) {
for (const conn of connections) {
connectedNodes.add(conn.node);
}
}
}
}
const orphans = workflow.nodes.filter(n => !connectedNodes.has(n.name));
if (orphans.length > 0) {
console.warn('Orphan nodes detected:', orphans.map(n => n.name));
}
// Validate credentials
for (const node of workflow.nodes) {
if (node.credentials) {
for (const [type, ref] of Object.entries(node.credentials)) {
if (!await credentialExists(ref.id)) {
throw new Error(`Missing credential: ${type} for node ${node.name}`);
}
}
}
}
return { valid: true, orphans, triggerNode };
}Execution Testing
执行测试
typescript
// Test workflow execution with various inputs
async function testWorkflowExecution(workflowId: string, testCases: TestCase[]) {
const results: TestResult[] = [];
for (const testCase of testCases) {
const startTime = Date.now();
// Execute workflow
const execution = await executeWorkflow(workflowId, testCase.input);
// Wait for completion
const result = await waitForCompletion(execution.id, testCase.timeout || 30000);
// Validate output
const outputValid = validateOutput(result.data, testCase.expected);
results.push({
testCase: testCase.name,
success: result.status === 'success' && outputValid,
duration: Date.now() - startTime,
actualOutput: result.data,
expectedOutput: testCase.expected
});
}
return results;
}
// Example test cases
const testCases = [
{
name: 'Valid customer data',
input: { name: 'John Doe', email: 'john@example.com' },
expected: { processed: true, customerId: /^cust_/ },
timeout: 10000
},
{
name: 'Missing email',
input: { name: 'Jane Doe' },
expected: { error: 'Email required' },
timeout: 5000
},
{
name: 'Invalid email format',
input: { name: 'Bob', email: 'not-an-email' },
expected: { error: 'Invalid email' },
timeout: 5000
}
];typescript
// Test workflow execution with various inputs
async function testWorkflowExecution(workflowId: string, testCases: TestCase[]) {
const results: TestResult[] = [];
for (const testCase of testCases) {
const startTime = Date.now();
// Execute workflow
const execution = await executeWorkflow(workflowId, testCase.input);
// Wait for completion
const result = await waitForCompletion(execution.id, testCase.timeout || 30000);
// Validate output
const outputValid = validateOutput(result.data, testCase.expected);
results.push({
testCase: testCase.name,
success: result.status === 'success' && outputValid,
duration: Date.now() - startTime,
actualOutput: result.data,
expectedOutput: testCase.expected
});
}
return results;
}
// Example test cases
const testCases = [
{
name: 'Valid customer data',
input: { name: 'John Doe', email: 'john@example.com' },
expected: { processed: true, customerId: /^cust_/ },
timeout: 10000
},
{
name: 'Missing email',
input: { name: 'Jane Doe' },
expected: { error: 'Email required' },
timeout: 5000
},
{
name: 'Invalid email format',
input: { name: 'Bob', email: 'not-an-email' },
expected: { error: 'Invalid email' },
timeout: 5000
}
];Data Flow Validation
数据流验证
typescript
// Trace data through workflow nodes
async function validateDataFlow(executionId: string) {
const execution = await getExecution(executionId);
const nodeResults = execution.data.resultData.runData;
const dataFlow: DataFlowStep[] = [];
for (const [nodeName, runs] of Object.entries(nodeResults)) {
for (const run of runs) {
dataFlow.push({
node: nodeName,
input: run.data?.main?.[0]?.[0]?.json || {},
output: run.data?.main?.[0]?.[0]?.json || {},
executionTime: run.executionTime,
status: run.executionStatus
});
}
}
// Validate data transformations
for (let i = 1; i < dataFlow.length; i++) {
const prev = dataFlow[i - 1];
const curr = dataFlow[i];
// Check if expected data passed through
validateDataMapping(prev.output, curr.input);
}
return dataFlow;
}
// Validate data mapping between nodes
function validateDataMapping(sourceOutput: any, targetInput: any) {
// Check all required fields are present
const missingFields: string[] = [];
for (const [key, value] of Object.entries(targetInput)) {
if (value === undefined && sourceOutput[key] === undefined) {
missingFields.push(key);
}
}
if (missingFields.length > 0) {
console.warn('Missing fields in data mapping:', missingFields);
}
return missingFields.length === 0;
}typescript
// Trace data through workflow nodes
async function validateDataFlow(executionId: string) {
const execution = await getExecution(executionId);
const nodeResults = execution.data.resultData.runData;
const dataFlow: DataFlowStep[] = [];
for (const [nodeName, runs] of Object.entries(nodeResults)) {
for (const run of runs) {
dataFlow.push({
node: nodeName,
input: run.data?.main?.[0]?.[0]?.json || {},
output: run.data?.main?.[0]?.[0]?.json || {},
executionTime: run.executionTime,
status: run.executionStatus
});
}
}
// Validate data transformations
for (let i = 1; i < dataFlow.length; i++) {
const prev = dataFlow[i - 1];
const curr = dataFlow[i];
// Check if expected data passed through
validateDataMapping(prev.output, curr.input);
}
return dataFlow;
}
// Validate data mapping between nodes
function validateDataMapping(sourceOutput: any, targetInput: any) {
// Check all required fields are present
const missingFields: string[] = [];
for (const [key, value] of Object.entries(targetInput)) {
if (value === undefined && sourceOutput[key] === undefined) {
missingFields.push(key);
}
}
if (missingFields.length > 0) {
console.warn('Missing fields in data mapping:', missingFields);
}
return missingFields.length === 0;
}Error Handling Testing
错误处理测试
typescript
// Test error handling paths
async function testErrorHandling(workflowId: string) {
const errorScenarios = [
{
name: 'API timeout',
inject: { delay: 35000 }, // Trigger timeout
expectedError: 'timeout'
},
{
name: 'Invalid data',
inject: { invalidField: true },
expectedError: 'validation'
},
{
name: 'Missing credentials',
inject: { removeCredentials: true },
expectedError: 'authentication'
}
];
const results: ErrorTestResult[] = [];
for (const scenario of errorScenarios) {
// Execute with error injection
const execution = await executeWithErrorInjection(workflowId, scenario.inject);
// Check error was caught
const result = await waitForCompletion(execution.id);
// Validate error handling
results.push({
scenario: scenario.name,
errorCaught: result.status === 'failed',
errorType: result.data?.resultData?.error?.type,
expectedError: scenario.expectedError,
errorWorkflowTriggered: await checkErrorWorkflowTriggered(execution.id),
alertSent: await checkAlertSent(execution.id)
});
}
return results;
}
// Verify error workflow was triggered
async function checkErrorWorkflowTriggered(executionId: string): Promise<boolean> {
const errorExecutions = await getExecutions({
filter: {
metadata: { errorTriggeredBy: executionId }
}
});
return errorExecutions.length > 0;
}typescript
// Test error handling paths
async function testErrorHandling(workflowId: string) {
const errorScenarios = [
{
name: 'API timeout',
inject: { delay: 35000 }, // Trigger timeout
expectedError: 'timeout'
},
{
name: 'Invalid data',
inject: { invalidField: true },
expectedError: 'validation'
},
{
name: 'Missing credentials',
inject: { removeCredentials: true },
expectedError: 'authentication'
}
];
const results: ErrorTestResult[] = [];
for (const scenario of errorScenarios) {
// Execute with error injection
const execution = await executeWithErrorInjection(workflowId, scenario.inject);
// Check error was caught
const result = await waitForCompletion(execution.id);
// Validate error handling
results.push({
scenario: scenario.name,
errorCaught: result.status === 'failed',
errorType: result.data?.resultData?.error?.type,
expectedError: scenario.expectedError,
errorWorkflowTriggered: await checkErrorWorkflowTriggered(execution.id),
alertSent: await checkAlertSent(execution.id)
});
}
return results;
}
// Verify error workflow was triggered
async function checkErrorWorkflowTriggered(executionId: string): Promise<boolean> {
const errorExecutions = await getExecutions({
filter: {
metadata: { errorTriggeredBy: executionId }
}
});
return errorExecutions.length > 0;
}Node Connection Patterns
节点连接模式
Linear Flow
线性流程
Trigger → Process → Transform → OutputTesting: Execute once, validate each node output
Trigger → Process → Transform → Output测试方法: 执行一次,验证每个节点的输出
Branching Flow
分支流程
Trigger → IF → [Branch A] → Merge → Output
→ [Branch B] →Testing: Test both branches separately, verify merge behavior
Trigger → IF → [Branch A] → Merge → Output
→ [Branch B] →测试方法: 分别测试两个分支,验证合并行为
Parallel Flow
并行流程
Trigger → Split → [Process A] → Merge → Output
→ [Process B] →Testing: Validate parallel execution, check merge timing
Trigger → Split → [Process A] → Merge → Output
→ [Process B] →测试方法: 验证并行执行,检查合并时序
Loop Flow
循环流程
Trigger → SplitInBatches → Process → [Loop back until done] → OutputTesting: Test with varying batch sizes, verify all items processed
Trigger → SplitInBatches → Process → [Loop back until done] → Output测试方法: 使用不同批次大小测试,验证所有项均已处理
Common Testing Patterns
常见测试模式
Test Data Generation
测试数据生成
typescript
// Generate test data for common n8n patterns
const testDataGenerators = {
webhook: () => ({
body: { event: 'test', timestamp: new Date().toISOString() },
headers: { 'Content-Type': 'application/json' },
query: { source: 'test' }
}),
slack: () => ({
type: 'message',
channel: 'C123456',
user: 'U789012',
text: 'Test message'
}),
github: () => ({
action: 'opened',
issue: {
number: 1,
title: 'Test Issue',
body: 'Test body'
},
repository: {
full_name: 'test/repo'
}
}),
stripe: () => ({
type: 'payment_intent.succeeded',
data: {
object: {
id: 'pi_test123',
amount: 1000,
currency: 'usd'
}
}
})
};typescript
// Generate test data for common n8n patterns
const testDataGenerators = {
webhook: () => ({
body: { event: 'test', timestamp: new Date().toISOString() },
headers: { 'Content-Type': 'application/json' },
query: { source: 'test' }
}),
slack: () => ({
type: 'message',
channel: 'C123456',
user: 'U789012',
text: 'Test message'
}),
github: () => ({
action: 'opened',
issue: {
number: 1,
title: 'Test Issue',
body: 'Test body'
},
repository: {
full_name: 'test/repo'
}
}),
stripe: () => ({
type: 'payment_intent.succeeded',
data: {
object: {
id: 'pi_test123',
amount: 1000,
currency: 'usd'
}
}
})
};Execution Assertions
执行断言
typescript
// Common assertions for workflow execution
const workflowAssertions = {
// Assert workflow completed
assertCompleted: (execution) => {
expect(execution.finished).toBe(true);
expect(execution.status).toBe('success');
},
// Assert specific node executed
assertNodeExecuted: (execution, nodeName) => {
const nodeData = execution.data.resultData.runData[nodeName];
expect(nodeData).toBeDefined();
expect(nodeData[0].executionStatus).toBe('success');
},
// Assert data transformation
assertDataTransformed: (execution, nodeName, expectedData) => {
const nodeOutput = execution.data.resultData.runData[nodeName][0].data.main[0][0].json;
expect(nodeOutput).toMatchObject(expectedData);
},
// Assert execution time
assertExecutionTime: (execution, maxMs) => {
const duration = new Date(execution.stoppedAt) - new Date(execution.startedAt);
expect(duration).toBeLessThan(maxMs);
}
};typescript
// Common assertions for workflow execution
const workflowAssertions = {
// Assert workflow completed
assertCompleted: (execution) => {
expect(execution.finished).toBe(true);
expect(execution.status).toBe('success');
},
// Assert specific node executed
assertNodeExecuted: (execution, nodeName) => {
const nodeData = execution.data.resultData.runData[nodeName];
expect(nodeData).toBeDefined();
expect(nodeData[0].executionStatus).toBe('success');
},
// Assert data transformation
assertDataTransformed: (execution, nodeName, expectedData) => {
const nodeOutput = execution.data.resultData.runData[nodeName][0].data.main[0][0].json;
expect(nodeOutput).toMatchObject(expectedData);
},
// Assert execution time
assertExecutionTime: (execution, maxMs) => {
const duration = new Date(execution.stoppedAt) - new Date(execution.startedAt);
expect(duration).toBeLessThan(maxMs);
}
};Agent Coordination Hints
Agent协调提示
Memory Namespace
内存命名空间
aqe/n8n/
├── workflows/* - Cached workflow definitions
├── test-results/* - Test execution results
├── validations/* - Validation reports
├── patterns/* - Discovered testing patterns
└── executions/* - Execution trackingaqe/n8n/
├── workflows/* - 缓存的工作流定义
├── test-results/* - 测试执行结果
├── validations/* - 验证报告
├── patterns/* - 已发现的测试模式
└── executions/* - 执行跟踪Fleet Coordination
集群协调
typescript
// Comprehensive n8n testing with fleet
const n8nFleet = await FleetManager.coordinate({
strategy: 'n8n-testing',
agents: [
'n8n-workflow-executor', // Execute and validate
'n8n-node-validator', // Validate configurations
'n8n-trigger-test', // Test triggers
'n8n-expression-validator', // Validate expressions
'n8n-integration-test' // Test integrations
],
topology: 'parallel'
});typescript
// Comprehensive n8n testing with fleet
const n8nFleet = await FleetManager.coordinate({
strategy: 'n8n-testing',
agents: [
'n8n-workflow-executor', // Execute and validate
'n8n-node-validator', // Validate configurations
'n8n-trigger-test', // Test triggers
'n8n-expression-validator', // Validate expressions
'n8n-integration-test' // Test integrations
],
topology: 'parallel'
});Related Skills
相关技能
- n8n-expression-testing - Expression validation
- n8n-trigger-testing-strategies - Trigger testing
- n8n-integration-testing-patterns - Integration testing
- n8n-security-testing - Security validation
- n8n表达式测试 - 表达式验证
- n8n触发测试策略 - 触发测试
- n8n集成测试模式 - 集成测试
- n8n安全测试 - 安全验证
Remember
注意事项
n8n workflows are JSON-based execution flows that connect 400+ services. Testing requires validating:
- Workflow structure (nodes, connections)
- Trigger reliability (webhooks, schedules)
- Data flow (transformations between nodes)
- Error handling (retry, fallback, notifications)
- Performance (execution time, resource usage)
With Agents: Use n8n-workflow-executor for execution testing, n8n-node-validator for configuration validation, and coordinate multiple agents for comprehensive workflow testing.
n8n工作流是基于JSON的执行流,可连接400+服务。测试需要验证以下内容:
- 工作流结构(节点、连接)
- 触发可靠性(Webhook、调度)
- 数据流(节点间的转换)
- 错误处理(重试、回退、通知)
- 性能(执行时间、资源占用)
使用Agent时:使用n8n-workflow-executor进行执行测试,n8n-node-validator进行配置验证,并协调多个Agent以实现全面的工作流测试。