agentic-qe 2.5.5 → 2.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/n8n/n8n-base-agent.md +376 -0
- package/.claude/agents/n8n/n8n-bdd-scenario-tester.md +613 -0
- package/.claude/agents/n8n/n8n-chaos-tester.md +654 -0
- package/.claude/agents/n8n/n8n-ci-orchestrator.md +850 -0
- package/.claude/agents/n8n/n8n-compliance-validator.md +685 -0
- package/.claude/agents/n8n/n8n-expression-validator.md +560 -0
- package/.claude/agents/n8n/n8n-integration-test.md +602 -0
- package/.claude/agents/n8n/n8n-monitoring-validator.md +589 -0
- package/.claude/agents/n8n/n8n-node-validator.md +455 -0
- package/.claude/agents/n8n/n8n-performance-tester.md +630 -0
- package/.claude/agents/n8n/n8n-security-auditor.md +786 -0
- package/.claude/agents/n8n/n8n-trigger-test.md +500 -0
- package/.claude/agents/n8n/n8n-unit-tester.md +633 -0
- package/.claude/agents/n8n/n8n-version-comparator.md +567 -0
- package/.claude/agents/n8n/n8n-workflow-executor.md +392 -0
- package/.claude/skills/n8n-expression-testing/SKILL.md +434 -0
- package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
- package/.claude/skills/n8n-security-testing/SKILL.md +599 -0
- package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
- package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
- package/CHANGELOG.md +111 -0
- package/README.md +7 -4
- package/dist/adapters/MemoryStoreAdapter.d.ts +75 -123
- package/dist/adapters/MemoryStoreAdapter.d.ts.map +1 -1
- package/dist/adapters/MemoryStoreAdapter.js +204 -219
- package/dist/adapters/MemoryStoreAdapter.js.map +1 -1
- package/dist/agents/AccessibilityAllyAgent.d.ts.map +1 -1
- package/dist/agents/AccessibilityAllyAgent.js +17 -1
- package/dist/agents/AccessibilityAllyAgent.js.map +1 -1
- package/dist/agents/BaseAgent.d.ts +18 -250
- package/dist/agents/BaseAgent.d.ts.map +1 -1
- package/dist/agents/BaseAgent.js +122 -520
- package/dist/agents/BaseAgent.js.map +1 -1
- package/dist/agents/n8n/N8nAPIClient.d.ts +121 -0
- package/dist/agents/n8n/N8nAPIClient.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAPIClient.js +367 -0
- package/dist/agents/n8n/N8nAPIClient.js.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts +120 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.js +473 -0
- package/dist/agents/n8n/N8nAuditPersistence.js.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts +159 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js +697 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts +126 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.js +446 -0
- package/dist/agents/n8n/N8nBaseAgent.js.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts +164 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js +610 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts +205 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js +729 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts +228 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js +986 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts +213 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js +989 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts +99 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js +632 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts +238 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js +956 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts +242 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js +992 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts +104 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js +653 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts +210 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js +669 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts +142 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js +1090 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts +198 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js +653 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts +245 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js +952 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts +325 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js +1187 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts +91 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js +825 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts +131 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.js +456 -0
- package/dist/agents/n8n/N8nTestHarness.js.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts +119 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js +652 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts +130 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js +522 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts +201 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js +645 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts +120 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js +347 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js.map +1 -0
- package/dist/agents/n8n/index.d.ts +119 -0
- package/dist/agents/n8n/index.d.ts.map +1 -0
- package/dist/agents/n8n/index.js +298 -0
- package/dist/agents/n8n/index.js.map +1 -0
- package/dist/agents/n8n/types.d.ts +486 -0
- package/dist/agents/n8n/types.d.ts.map +1 -0
- package/dist/agents/n8n/types.js +8 -0
- package/dist/agents/n8n/types.js.map +1 -0
- package/dist/agents/utils/generators.d.ts +30 -0
- package/dist/agents/utils/generators.d.ts.map +1 -0
- package/dist/agents/utils/generators.js +44 -0
- package/dist/agents/utils/generators.js.map +1 -0
- package/dist/agents/utils/index.d.ts +10 -0
- package/dist/agents/utils/index.d.ts.map +1 -0
- package/dist/agents/utils/index.js +19 -0
- package/dist/agents/utils/index.js.map +1 -0
- package/dist/agents/utils/validation.d.ts +72 -0
- package/dist/agents/utils/validation.d.ts.map +1 -0
- package/dist/agents/utils/validation.js +75 -0
- package/dist/agents/utils/validation.js.map +1 -0
- package/dist/cli/init/agents.d.ts.map +1 -1
- package/dist/cli/init/agents.js +29 -0
- package/dist/cli/init/agents.js.map +1 -1
- package/dist/cli/init/skills.d.ts.map +1 -1
- package/dist/cli/init/skills.js +7 -1
- package/dist/cli/init/skills.js.map +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/core/memory/SwarmMemoryManager.d.ts +114 -90
- package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
- package/dist/core/memory/SwarmMemoryManager.js +277 -235
- package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
- package/dist/learning/baselines/StandardTaskSuite.d.ts.map +1 -1
- package/dist/learning/baselines/StandardTaskSuite.js +38 -0
- package/dist/learning/baselines/StandardTaskSuite.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/types/memory-interfaces.d.ts +76 -68
- package/dist/types/memory-interfaces.d.ts.map +1 -1
- package/dist/types/memory-interfaces.js +3 -0
- package/dist/types/memory-interfaces.js.map +1 -1
- package/docs/reference/agents.md +91 -2
- package/docs/reference/skills.md +97 -2
- package/package.json +2 -2
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* N8nBDDScenarioTesterAgent
|
|
4
|
+
*
|
|
5
|
+
* Behavior-Driven Development testing for n8n workflows:
|
|
6
|
+
* - Gherkin scenario parsing
|
|
7
|
+
* - Feature file execution
|
|
8
|
+
* - Business requirement validation
|
|
9
|
+
* - Natural language test definitions
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.N8nBDDScenarioTesterAgent = void 0;
|
|
13
|
+
const N8nBaseAgent_1 = require("./N8nBaseAgent");
|
|
14
|
+
// Step definition patterns
|
|
15
|
+
const STEP_PATTERNS = [
|
|
16
|
+
{
|
|
17
|
+
pattern: /^the workflow "([^"]+)" is (active|inactive)$/i,
|
|
18
|
+
handler: 'checkWorkflowStatus',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /^I trigger the workflow with data:?$/i,
|
|
22
|
+
handler: 'triggerWorkflow',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /^I execute the workflow$/i,
|
|
26
|
+
handler: 'executeWorkflow',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
pattern: /^the workflow should complete (successfully|with errors?)$/i,
|
|
30
|
+
handler: 'checkCompletion',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
pattern: /^the output should contain "([^"]+)"$/i,
|
|
34
|
+
handler: 'checkOutputContains',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: /^the output field "([^"]+)" should equal "([^"]+)"$/i,
|
|
38
|
+
handler: 'checkOutputField',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: /^the node "([^"]+)" should be executed$/i,
|
|
42
|
+
handler: 'checkNodeExecuted',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
pattern: /^the execution time should be less than (\d+)ms$/i,
|
|
46
|
+
handler: 'checkExecutionTime',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pattern: /^no errors should occur$/i,
|
|
50
|
+
handler: 'checkNoErrors',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /^the item count should be (\d+)$/i,
|
|
54
|
+
handler: 'checkItemCount',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
class N8nBDDScenarioTesterAgent extends N8nBaseAgent_1.N8nBaseAgent {
|
|
58
|
+
constructor(config) {
|
|
59
|
+
const capabilities = [
|
|
60
|
+
{
|
|
61
|
+
name: 'gherkin-parsing',
|
|
62
|
+
version: '1.0.0',
|
|
63
|
+
description: 'Parse and execute Gherkin scenarios',
|
|
64
|
+
parameters: {},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'step-matching',
|
|
68
|
+
version: '1.0.0',
|
|
69
|
+
description: 'Match natural language steps to actions',
|
|
70
|
+
parameters: {},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'scenario-execution',
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
description: 'Execute BDD scenarios against workflows',
|
|
76
|
+
parameters: {},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'business-validation',
|
|
80
|
+
version: '1.0.0',
|
|
81
|
+
description: 'Validate business requirements',
|
|
82
|
+
parameters: {},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
super({
|
|
86
|
+
...config,
|
|
87
|
+
type: 'n8n-bdd-scenario-tester',
|
|
88
|
+
capabilities: [...capabilities, ...(config.capabilities || [])],
|
|
89
|
+
});
|
|
90
|
+
this.lastExecution = null;
|
|
91
|
+
this.lastOutput = null;
|
|
92
|
+
this.executedNodes = new Set();
|
|
93
|
+
}
|
|
94
|
+
async performTask(task) {
|
|
95
|
+
const bddTask = task;
|
|
96
|
+
if (bddTask.type !== 'bdd-test') {
|
|
97
|
+
throw new Error(`Unsupported task type: ${bddTask.type}`);
|
|
98
|
+
}
|
|
99
|
+
return this.runBDDTests(bddTask.target, bddTask.options);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Run BDD tests for workflow
|
|
103
|
+
*/
|
|
104
|
+
async runBDDTests(workflowId, options) {
|
|
105
|
+
const workflow = await this.getWorkflow(workflowId);
|
|
106
|
+
const startTime = Date.now();
|
|
107
|
+
// Get scenarios from options or generate from workflow
|
|
108
|
+
let scenarios = options?.scenarios || [];
|
|
109
|
+
if (scenarios.length === 0 && options?.featureFile) {
|
|
110
|
+
scenarios = this.parseFeatureFile(options.featureFile);
|
|
111
|
+
}
|
|
112
|
+
if (scenarios.length === 0) {
|
|
113
|
+
scenarios = this.generateScenariosFromWorkflow(workflow);
|
|
114
|
+
}
|
|
115
|
+
// Filter by tags if specified
|
|
116
|
+
if (options?.tags && options.tags.length > 0) {
|
|
117
|
+
scenarios = scenarios.filter(s => s.tags?.some(t => options.tags.includes(t)));
|
|
118
|
+
}
|
|
119
|
+
// Execute scenarios
|
|
120
|
+
const results = [];
|
|
121
|
+
for (const scenario of scenarios) {
|
|
122
|
+
if (scenario.examples && scenario.examples.length > 0) {
|
|
123
|
+
// Scenario Outline with examples
|
|
124
|
+
for (const example of scenario.examples) {
|
|
125
|
+
const result = await this.executeScenario(workflow, scenario, options?.dryRun, example.values);
|
|
126
|
+
result.example = example.values;
|
|
127
|
+
results.push(result);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const result = await this.executeScenario(workflow, scenario, options?.dryRun);
|
|
132
|
+
results.push(result);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Calculate summary
|
|
136
|
+
const summary = this.calculateSummary(results, Date.now() - startTime);
|
|
137
|
+
// Calculate coverage
|
|
138
|
+
const coverage = this.calculateCoverage(workflow, results);
|
|
139
|
+
// Generate report if requested
|
|
140
|
+
let report;
|
|
141
|
+
if (options?.generateReport) {
|
|
142
|
+
report = this.generateReport(workflow.name, results, summary);
|
|
143
|
+
}
|
|
144
|
+
const result = {
|
|
145
|
+
workflowId,
|
|
146
|
+
featureName: workflow.name,
|
|
147
|
+
scenarios: results,
|
|
148
|
+
summary,
|
|
149
|
+
coverage,
|
|
150
|
+
report,
|
|
151
|
+
};
|
|
152
|
+
// Store result
|
|
153
|
+
await this.storeTestResult(`bdd-test:${workflowId}`, result);
|
|
154
|
+
// Emit event
|
|
155
|
+
this.emitEvent('bdd.test.completed', {
|
|
156
|
+
workflowId,
|
|
157
|
+
scenariosPassed: summary.passed,
|
|
158
|
+
scenariosFailed: summary.failed,
|
|
159
|
+
coverage: coverage.coveragePercentage,
|
|
160
|
+
});
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Parse feature file content
|
|
165
|
+
*/
|
|
166
|
+
parseFeatureFile(content) {
|
|
167
|
+
const scenarios = [];
|
|
168
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
169
|
+
let currentScenario = null;
|
|
170
|
+
let currentSection = null;
|
|
171
|
+
let currentTags = [];
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
// Skip empty lines and comments
|
|
174
|
+
if (!line || line.startsWith('#'))
|
|
175
|
+
continue;
|
|
176
|
+
// Parse tags
|
|
177
|
+
if (line.startsWith('@')) {
|
|
178
|
+
currentTags = line.split(/\s+/).filter(t => t.startsWith('@'));
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Parse scenario
|
|
182
|
+
if (line.startsWith('Scenario:') || line.startsWith('Scenario Outline:')) {
|
|
183
|
+
if (currentScenario) {
|
|
184
|
+
scenarios.push(currentScenario);
|
|
185
|
+
}
|
|
186
|
+
currentScenario = {
|
|
187
|
+
name: line.replace(/Scenario( Outline)?:\s*/, ''),
|
|
188
|
+
tags: [...currentTags],
|
|
189
|
+
given: [],
|
|
190
|
+
when: [],
|
|
191
|
+
then: [],
|
|
192
|
+
};
|
|
193
|
+
currentTags = [];
|
|
194
|
+
currentSection = null;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Parse steps
|
|
198
|
+
if (currentScenario) {
|
|
199
|
+
if (line.startsWith('Given ')) {
|
|
200
|
+
currentSection = 'given';
|
|
201
|
+
currentScenario.given.push({
|
|
202
|
+
keyword: 'Given',
|
|
203
|
+
text: line.replace('Given ', ''),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else if (line.startsWith('When ')) {
|
|
207
|
+
currentSection = 'when';
|
|
208
|
+
currentScenario.when.push({
|
|
209
|
+
keyword: 'When',
|
|
210
|
+
text: line.replace('When ', ''),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else if (line.startsWith('Then ')) {
|
|
214
|
+
currentSection = 'then';
|
|
215
|
+
currentScenario.then.push({
|
|
216
|
+
keyword: 'Then',
|
|
217
|
+
text: line.replace('Then ', ''),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else if (line.startsWith('And ') || line.startsWith('But ')) {
|
|
221
|
+
const keyword = line.startsWith('And ') ? 'And' : 'But';
|
|
222
|
+
const text = line.replace(/^(And|But) /, '');
|
|
223
|
+
if (currentSection && currentScenario[currentSection]) {
|
|
224
|
+
currentScenario[currentSection].push({ keyword, text });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (line.startsWith('Examples:')) {
|
|
228
|
+
// Parse examples table (simplified)
|
|
229
|
+
currentScenario.examples = currentScenario.examples || [];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (currentScenario) {
|
|
234
|
+
scenarios.push(currentScenario);
|
|
235
|
+
}
|
|
236
|
+
return scenarios;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Generate scenarios from workflow
|
|
240
|
+
*/
|
|
241
|
+
generateScenariosFromWorkflow(workflow) {
|
|
242
|
+
const scenarios = [];
|
|
243
|
+
// Basic execution scenario
|
|
244
|
+
scenarios.push({
|
|
245
|
+
name: `Execute ${workflow.name}`,
|
|
246
|
+
description: 'Verify workflow executes successfully',
|
|
247
|
+
tags: ['@auto-generated', '@smoke'],
|
|
248
|
+
given: [
|
|
249
|
+
{ keyword: 'Given', text: `the workflow "${workflow.name}" is active` },
|
|
250
|
+
],
|
|
251
|
+
when: [
|
|
252
|
+
{ keyword: 'When', text: 'I execute the workflow' },
|
|
253
|
+
],
|
|
254
|
+
then: [
|
|
255
|
+
{ keyword: 'Then', text: 'the workflow should complete successfully' },
|
|
256
|
+
{ keyword: 'And', text: 'no errors should occur' },
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
// Node-specific scenarios
|
|
260
|
+
for (const node of workflow.nodes) {
|
|
261
|
+
if (node.type.includes('trigger'))
|
|
262
|
+
continue;
|
|
263
|
+
scenarios.push({
|
|
264
|
+
name: `Verify ${node.name} execution`,
|
|
265
|
+
tags: ['@auto-generated', '@node-test'],
|
|
266
|
+
given: [
|
|
267
|
+
{ keyword: 'Given', text: `the workflow "${workflow.name}" is active` },
|
|
268
|
+
],
|
|
269
|
+
when: [
|
|
270
|
+
{ keyword: 'When', text: 'I execute the workflow' },
|
|
271
|
+
],
|
|
272
|
+
then: [
|
|
273
|
+
{ keyword: 'Then', text: `the node "${node.name}" should be executed` },
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return scenarios;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Execute a single scenario
|
|
281
|
+
*/
|
|
282
|
+
async executeScenario(workflow, scenario, dryRun, exampleValues) {
|
|
283
|
+
const startTime = Date.now();
|
|
284
|
+
const stepResults = [];
|
|
285
|
+
let scenarioStatus = 'passed';
|
|
286
|
+
let scenarioError;
|
|
287
|
+
// Reset state
|
|
288
|
+
this.lastExecution = null;
|
|
289
|
+
this.lastOutput = null;
|
|
290
|
+
this.executedNodes.clear();
|
|
291
|
+
// Execute all steps
|
|
292
|
+
const allSteps = [
|
|
293
|
+
...scenario.given,
|
|
294
|
+
...scenario.when,
|
|
295
|
+
...scenario.then,
|
|
296
|
+
];
|
|
297
|
+
for (const step of allSteps) {
|
|
298
|
+
// Replace example placeholders
|
|
299
|
+
let stepText = step.text;
|
|
300
|
+
if (exampleValues) {
|
|
301
|
+
for (const [key, value] of Object.entries(exampleValues)) {
|
|
302
|
+
stepText = stepText.replace(new RegExp(`<${key}>`, 'g'), value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const stepResult = await this.executeStep(workflow, { ...step, text: stepText }, dryRun);
|
|
306
|
+
stepResults.push(stepResult);
|
|
307
|
+
if (stepResult.status === 'failed') {
|
|
308
|
+
scenarioStatus = 'failed';
|
|
309
|
+
scenarioError = stepResult.error;
|
|
310
|
+
// Skip remaining steps
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Mark remaining steps as skipped
|
|
315
|
+
if (scenarioStatus === 'failed') {
|
|
316
|
+
const executedCount = stepResults.length;
|
|
317
|
+
for (let i = executedCount; i < allSteps.length; i++) {
|
|
318
|
+
stepResults.push({
|
|
319
|
+
keyword: allSteps[i].keyword,
|
|
320
|
+
text: allSteps[i].text,
|
|
321
|
+
status: 'skipped',
|
|
322
|
+
duration: 0,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
name: scenario.name,
|
|
328
|
+
description: scenario.description,
|
|
329
|
+
tags: scenario.tags || [],
|
|
330
|
+
status: scenarioStatus,
|
|
331
|
+
duration: Date.now() - startTime,
|
|
332
|
+
steps: stepResults,
|
|
333
|
+
error: scenarioError,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Execute a single step
|
|
338
|
+
*/
|
|
339
|
+
async executeStep(workflow, step, dryRun) {
|
|
340
|
+
const startTime = Date.now();
|
|
341
|
+
if (dryRun) {
|
|
342
|
+
return {
|
|
343
|
+
keyword: step.keyword,
|
|
344
|
+
text: step.text,
|
|
345
|
+
status: 'pending',
|
|
346
|
+
duration: Date.now() - startTime,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
// Match step to handler
|
|
351
|
+
const matched = this.matchStep(step.text);
|
|
352
|
+
if (!matched) {
|
|
353
|
+
return {
|
|
354
|
+
keyword: step.keyword,
|
|
355
|
+
text: step.text,
|
|
356
|
+
status: 'pending',
|
|
357
|
+
duration: Date.now() - startTime,
|
|
358
|
+
error: 'No matching step definition found',
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// Execute handler
|
|
362
|
+
await this.executeHandler(workflow, matched.handler, matched.params, step);
|
|
363
|
+
return {
|
|
364
|
+
keyword: step.keyword,
|
|
365
|
+
text: step.text,
|
|
366
|
+
status: 'passed',
|
|
367
|
+
duration: Date.now() - startTime,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
return {
|
|
372
|
+
keyword: step.keyword,
|
|
373
|
+
text: step.text,
|
|
374
|
+
status: 'failed',
|
|
375
|
+
duration: Date.now() - startTime,
|
|
376
|
+
error: error instanceof Error ? error.message : String(error),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Match step text to pattern
|
|
382
|
+
*/
|
|
383
|
+
matchStep(text) {
|
|
384
|
+
for (const { pattern, handler } of STEP_PATTERNS) {
|
|
385
|
+
const match = text.match(pattern);
|
|
386
|
+
if (match) {
|
|
387
|
+
return {
|
|
388
|
+
handler,
|
|
389
|
+
params: match.slice(1),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Execute step handler
|
|
397
|
+
*/
|
|
398
|
+
async executeHandler(workflow, handler, params, step) {
|
|
399
|
+
switch (handler) {
|
|
400
|
+
case 'checkWorkflowStatus':
|
|
401
|
+
this.checkWorkflowStatus(workflow, params[0], params[1]);
|
|
402
|
+
break;
|
|
403
|
+
case 'triggerWorkflow':
|
|
404
|
+
await this.triggerWorkflowStep(workflow, step.dataTable);
|
|
405
|
+
break;
|
|
406
|
+
case 'executeWorkflow':
|
|
407
|
+
await this.executeWorkflowStep(workflow);
|
|
408
|
+
break;
|
|
409
|
+
case 'checkCompletion':
|
|
410
|
+
this.checkCompletion(params[0]);
|
|
411
|
+
break;
|
|
412
|
+
case 'checkOutputContains':
|
|
413
|
+
this.checkOutputContains(params[0]);
|
|
414
|
+
break;
|
|
415
|
+
case 'checkOutputField':
|
|
416
|
+
this.checkOutputField(params[0], params[1]);
|
|
417
|
+
break;
|
|
418
|
+
case 'checkNodeExecuted':
|
|
419
|
+
this.checkNodeExecuted(params[0]);
|
|
420
|
+
break;
|
|
421
|
+
case 'checkExecutionTime':
|
|
422
|
+
this.checkExecutionTime(parseInt(params[0], 10));
|
|
423
|
+
break;
|
|
424
|
+
case 'checkNoErrors':
|
|
425
|
+
this.checkNoErrors();
|
|
426
|
+
break;
|
|
427
|
+
case 'checkItemCount':
|
|
428
|
+
this.checkItemCount(parseInt(params[0], 10));
|
|
429
|
+
break;
|
|
430
|
+
default:
|
|
431
|
+
throw new Error(`Unknown handler: ${handler}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Step handler implementations
|
|
435
|
+
checkWorkflowStatus(workflow, expectedName, expectedStatus) {
|
|
436
|
+
if (!workflow.name.includes(expectedName)) {
|
|
437
|
+
throw new Error(`Workflow name mismatch: expected "${expectedName}", got "${workflow.name}"`);
|
|
438
|
+
}
|
|
439
|
+
const isActive = workflow.active !== false;
|
|
440
|
+
const expectActive = expectedStatus === 'active';
|
|
441
|
+
if (isActive !== expectActive) {
|
|
442
|
+
throw new Error(`Workflow is ${isActive ? 'active' : 'inactive'}, expected ${expectedStatus}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async triggerWorkflowStep(workflow, dataTable) {
|
|
446
|
+
const inputData = dataTable?.[0] || {};
|
|
447
|
+
// Actually execute and wait for completion
|
|
448
|
+
const execution = await this.executeWorkflow(workflow.id, inputData, {
|
|
449
|
+
waitForCompletion: true,
|
|
450
|
+
timeout: 30000,
|
|
451
|
+
});
|
|
452
|
+
this.lastExecution = await this.waitForExecution(execution.id, 30000);
|
|
453
|
+
this.extractExecutedNodes();
|
|
454
|
+
this.extractOutput();
|
|
455
|
+
}
|
|
456
|
+
async executeWorkflowStep(workflow) {
|
|
457
|
+
// Actually execute and wait for completion
|
|
458
|
+
const execution = await this.executeWorkflow(workflow.id, {}, {
|
|
459
|
+
waitForCompletion: true,
|
|
460
|
+
timeout: 30000,
|
|
461
|
+
});
|
|
462
|
+
this.lastExecution = await this.waitForExecution(execution.id, 30000);
|
|
463
|
+
this.extractExecutedNodes();
|
|
464
|
+
this.extractOutput();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Wait for workflow execution to complete
|
|
468
|
+
*/
|
|
469
|
+
async waitForExecution(executionId, timeoutMs) {
|
|
470
|
+
const startTime = Date.now();
|
|
471
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
472
|
+
const execution = await this.getExecution(executionId);
|
|
473
|
+
if (execution.status !== 'running' && execution.status !== 'waiting') {
|
|
474
|
+
return execution;
|
|
475
|
+
}
|
|
476
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
477
|
+
}
|
|
478
|
+
throw new Error(`Execution ${executionId} timed out after ${timeoutMs}ms`);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Extract output from last execution for assertions
|
|
482
|
+
*/
|
|
483
|
+
extractOutput() {
|
|
484
|
+
if (this.lastExecution?.data?.resultData?.runData) {
|
|
485
|
+
// Get the last node's output
|
|
486
|
+
const lastNode = this.lastExecution.data.resultData.lastNodeExecuted;
|
|
487
|
+
if (lastNode) {
|
|
488
|
+
const nodeRuns = this.lastExecution.data.resultData.runData[lastNode];
|
|
489
|
+
if (nodeRuns && nodeRuns.length > 0) {
|
|
490
|
+
const lastRun = nodeRuns[nodeRuns.length - 1];
|
|
491
|
+
const outputItems = lastRun.data?.main?.[0];
|
|
492
|
+
if (outputItems && outputItems.length > 0) {
|
|
493
|
+
this.lastOutput = outputItems[0].json;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
extractExecutedNodes() {
|
|
500
|
+
if (this.lastExecution?.data?.resultData?.runData) {
|
|
501
|
+
for (const nodeName of Object.keys(this.lastExecution.data.resultData.runData)) {
|
|
502
|
+
this.executedNodes.add(nodeName);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
checkCompletion(expectedResult) {
|
|
507
|
+
if (!this.lastExecution) {
|
|
508
|
+
throw new Error('No execution found');
|
|
509
|
+
}
|
|
510
|
+
const expectSuccess = expectedResult === 'successfully';
|
|
511
|
+
const isSuccess = this.lastExecution.status === 'success';
|
|
512
|
+
if (isSuccess !== expectSuccess) {
|
|
513
|
+
throw new Error(`Workflow ${isSuccess ? 'succeeded' : 'failed'}, expected to ${expectSuccess ? 'succeed' : 'fail'}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
checkOutputContains(expectedContent) {
|
|
517
|
+
const outputStr = JSON.stringify(this.lastExecution?.data || {});
|
|
518
|
+
if (!outputStr.includes(expectedContent)) {
|
|
519
|
+
throw new Error(`Output does not contain "${expectedContent}"`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
checkOutputField(field, expectedValue) {
|
|
523
|
+
const data = this.lastExecution?.data?.resultData?.runData;
|
|
524
|
+
if (!data) {
|
|
525
|
+
throw new Error('No output data found');
|
|
526
|
+
}
|
|
527
|
+
// Simplified field check
|
|
528
|
+
const outputStr = JSON.stringify(data);
|
|
529
|
+
if (!outputStr.includes(`"${field}":`) || !outputStr.includes(expectedValue)) {
|
|
530
|
+
throw new Error(`Field "${field}" does not equal "${expectedValue}"`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
checkNodeExecuted(nodeName) {
|
|
534
|
+
if (!this.executedNodes.has(nodeName)) {
|
|
535
|
+
throw new Error(`Node "${nodeName}" was not executed`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
checkExecutionTime(maxMs) {
|
|
539
|
+
if (!this.lastExecution) {
|
|
540
|
+
throw new Error('No execution found');
|
|
541
|
+
}
|
|
542
|
+
const startTime = new Date(this.lastExecution.startedAt).getTime();
|
|
543
|
+
const endTime = this.lastExecution.stoppedAt
|
|
544
|
+
? new Date(this.lastExecution.stoppedAt).getTime()
|
|
545
|
+
: Date.now();
|
|
546
|
+
const duration = endTime - startTime;
|
|
547
|
+
if (duration > maxMs) {
|
|
548
|
+
throw new Error(`Execution took ${duration}ms, expected less than ${maxMs}ms`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
checkNoErrors() {
|
|
552
|
+
if (this.lastExecution?.status === 'failed' || this.lastExecution?.status === 'crashed') {
|
|
553
|
+
throw new Error('Execution had errors');
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
checkItemCount(expectedCount) {
|
|
557
|
+
let actualCount = 0;
|
|
558
|
+
if (this.lastExecution?.data?.resultData?.runData) {
|
|
559
|
+
for (const nodeData of Object.values(this.lastExecution.data.resultData.runData)) {
|
|
560
|
+
if (Array.isArray(nodeData)) {
|
|
561
|
+
for (const run of nodeData) {
|
|
562
|
+
if (run.data?.main) {
|
|
563
|
+
for (const output of run.data.main) {
|
|
564
|
+
actualCount += output?.length || 0;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (actualCount !== expectedCount) {
|
|
572
|
+
throw new Error(`Item count is ${actualCount}, expected ${expectedCount}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Calculate summary
|
|
577
|
+
*/
|
|
578
|
+
calculateSummary(results, duration) {
|
|
579
|
+
const totalSteps = results.reduce((sum, r) => sum + r.steps.length, 0);
|
|
580
|
+
const stepsPassed = results.reduce((sum, r) => sum + r.steps.filter(s => s.status === 'passed').length, 0);
|
|
581
|
+
const stepsFailed = results.reduce((sum, r) => sum + r.steps.filter(s => s.status === 'failed').length, 0);
|
|
582
|
+
return {
|
|
583
|
+
totalScenarios: results.length,
|
|
584
|
+
passed: results.filter(r => r.status === 'passed').length,
|
|
585
|
+
failed: results.filter(r => r.status === 'failed').length,
|
|
586
|
+
skipped: results.filter(r => r.status === 'skipped').length,
|
|
587
|
+
pending: results.filter(r => r.status === 'pending').length,
|
|
588
|
+
totalSteps,
|
|
589
|
+
stepsPassed,
|
|
590
|
+
stepsFailed,
|
|
591
|
+
duration,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Calculate coverage
|
|
596
|
+
*/
|
|
597
|
+
calculateCoverage(workflow, results) {
|
|
598
|
+
const allNodes = new Set(workflow.nodes.map(n => n.name));
|
|
599
|
+
const coveredNodes = new Set();
|
|
600
|
+
for (const result of results) {
|
|
601
|
+
if (result.status === 'passed') {
|
|
602
|
+
// Extract node names from steps
|
|
603
|
+
for (const step of result.steps) {
|
|
604
|
+
const nodeMatch = step.text.match(/node "([^"]+)"/);
|
|
605
|
+
if (nodeMatch) {
|
|
606
|
+
coveredNodes.add(nodeMatch[1]);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const nodesCovered = [...coveredNodes];
|
|
612
|
+
const nodesUncovered = [...allNodes].filter(n => !coveredNodes.has(n));
|
|
613
|
+
return {
|
|
614
|
+
nodesCovered,
|
|
615
|
+
nodesUncovered,
|
|
616
|
+
pathsCovered: [], // Would require more complex path analysis
|
|
617
|
+
coveragePercentage: allNodes.size > 0
|
|
618
|
+
? (coveredNodes.size / allNodes.size) * 100
|
|
619
|
+
: 0,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Generate report
|
|
624
|
+
*/
|
|
625
|
+
generateReport(featureName, results, summary) {
|
|
626
|
+
const lines = [
|
|
627
|
+
`# BDD Test Report: ${featureName}`,
|
|
628
|
+
'',
|
|
629
|
+
`**Date:** ${new Date().toISOString()}`,
|
|
630
|
+
'',
|
|
631
|
+
'## Summary',
|
|
632
|
+
'',
|
|
633
|
+
`| Metric | Value |`,
|
|
634
|
+
`|--------|-------|`,
|
|
635
|
+
`| Total Scenarios | ${summary.totalScenarios} |`,
|
|
636
|
+
`| Passed | ${summary.passed} |`,
|
|
637
|
+
`| Failed | ${summary.failed} |`,
|
|
638
|
+
`| Skipped | ${summary.skipped} |`,
|
|
639
|
+
`| Duration | ${summary.duration}ms |`,
|
|
640
|
+
'',
|
|
641
|
+
'## Scenarios',
|
|
642
|
+
'',
|
|
643
|
+
];
|
|
644
|
+
for (const result of results) {
|
|
645
|
+
const statusIcon = result.status === 'passed' ? '✅' :
|
|
646
|
+
result.status === 'failed' ? '❌' :
|
|
647
|
+
result.status === 'skipped' ? '⏭️' : '⏸️';
|
|
648
|
+
lines.push(`### ${statusIcon} ${result.name}`);
|
|
649
|
+
if (result.example) {
|
|
650
|
+
lines.push(`**Example:** ${JSON.stringify(result.example)}`);
|
|
651
|
+
}
|
|
652
|
+
lines.push('');
|
|
653
|
+
for (const step of result.steps) {
|
|
654
|
+
const stepIcon = step.status === 'passed' ? '✅' :
|
|
655
|
+
step.status === 'failed' ? '❌' :
|
|
656
|
+
step.status === 'skipped' ? '⏭️' : '⏸️';
|
|
657
|
+
lines.push(`${stepIcon} **${step.keyword}** ${step.text}`);
|
|
658
|
+
if (step.error) {
|
|
659
|
+
lines.push(` > Error: ${step.error}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
lines.push('');
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
format: 'markdown',
|
|
666
|
+
content: lines.join('\n'),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Create scenario from natural language
|
|
671
|
+
*/
|
|
672
|
+
createScenarioFromDescription(description) {
|
|
673
|
+
// Simple parser for natural language descriptions
|
|
674
|
+
const scenario = {
|
|
675
|
+
name: 'Generated Scenario',
|
|
676
|
+
given: [],
|
|
677
|
+
when: [],
|
|
678
|
+
then: [],
|
|
679
|
+
};
|
|
680
|
+
const sentences = description.split(/[.!]\s*/);
|
|
681
|
+
for (const sentence of sentences) {
|
|
682
|
+
const lower = sentence.toLowerCase();
|
|
683
|
+
if (lower.includes('given') || lower.includes('assuming')) {
|
|
684
|
+
scenario.given.push({ keyword: 'Given', text: sentence });
|
|
685
|
+
}
|
|
686
|
+
else if (lower.includes('when') || lower.includes('if')) {
|
|
687
|
+
scenario.when.push({ keyword: 'When', text: sentence });
|
|
688
|
+
}
|
|
689
|
+
else if (lower.includes('then') || lower.includes('should') || lower.includes('expect')) {
|
|
690
|
+
scenario.then.push({ keyword: 'Then', text: sentence });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return scenario;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
exports.N8nBDDScenarioTesterAgent = N8nBDDScenarioTesterAgent;
|
|
697
|
+
//# sourceMappingURL=N8nBDDScenarioTesterAgent.js.map
|