Communityn8ngithub.com

ashtonfeliciano/n8n-evaluations-runner

Build and run zero-API-cost logic test suites for n8n workflows. Use when adding test coverage to filter logic, scoring functions, validators, keyword lists, resume maps, or any pure-JS code-node logic. Outputs grouped pass/fail/pending report via Telegram. Run before AND after any logic change to catch regressions without burning API credits.

What is n8n-evaluations-runner?

n8n-evaluations-runner is a Claude Code agent skill that build and run zero-API-cost logic test suites for n8n workflows. Use when adding test coverage to filter logic, scoring functions, validators, keyword lists, resume maps, or any pure-JS code-node logic. Outputs grouped pass/fail/pending report via Telegram. Run before AND after any logic change to catch regressions without burning API credits.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ashtonfeliciano/claude-skills-pack/tree/HEAD/skills/n8n-evaluations-runner

Installed? Explore more n8n skills: ashtonfeliciano/n8n-workflow-engineer, hubgunter4-ops/configurador-de-nodos-n8n, hubgunter4-ops/experto-en-n8n-mcp · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

n8n Evaluations Runner

When to trigger

  • Adding or modifying filter logic, keyword lists, validators, or resume maps
  • Refactoring a Code node's pure-function logic
  • Before merging a workflow change — regression guard
  • After a production incident — codify the failure into a test
  • "How do I unit-test n8n code?" / "How do I test without spending API credits?"

What this is (and isn't)

This isThis is NOT
A logic-only n8n workflow with mock inputsAn integration test against live APIs
No external calls except the final report send — runs in ~3 secondsA performance test
Pass/fail/pending counts pushed to TelegramA replacement for manual smoke tests of end-to-end flow
A regression net for pure JS in Code nodesCoverage for HTTP / Gmail / Telegram node behavior

Cost: no API credits. Mock inputs, in-memory only. The Telegram report is the one outside call.

Architecture

[Manual Trigger] → [Run All Tests (Code node)] → [Format Report (Code node)] → [Send Report (Telegram)]

One workflow per logic surface. Re-run on demand.

Standard test runner Code node

const results = [];

function assert(suite, testName, condition, detail) {
  results.push({ suite, test: testName, pass: condition, detail: detail || '' });
}

// ============================================================
// Suite 1 — <name>
// ============================================================

// Copy the production function VERBATIM here (don't import — Code nodes don't share scope)
function passesJs10Filter(job) {
  // ... same code as production node ...
}

// Tests
assert('js-10', 'Clearance job rejected',
  !passesJs10Filter({
    title: 'Technical Writer',
    description: 'Clearance required.',
    location: 'remote'
  }),
  'clearance required must trigger rejection');

assert('js-10', 'Valid remote tech writer passes',
  passesJs10Filter({
    title: 'Technical Writer',
    description: 'Write API documentation for cloud platform.',
    location: 'remote'
  }),
  'Valid remote technical writer should pass all filters');

// For not-yet-implemented features:
results.push({
  suite: 'js-10',
  test: '30-day age cutoff (PENDING — backlog)',
  pass: null,
  detail: 'Feature not yet implemented. Add postedAt date check, then re-run.'
});

// ============================================================
// Summarize
// ============================================================

const passed  = results.filter(r => r.pass === true);
const failed  = results.filter(r => r.pass === false);
const pending = results.filter(r => r.pass === null);

return [{ json: {
  summary: { total: results.length, passed: passed.length, failed: failed.length, pending: pending.length },
  results, failed, pending
}}];

Report formatter

const { summary, results } = $input.first().json;

const passIcon = '✅';
const failIcon = '❌';
const pendIcon = '⏳';
const topEmoji = summary.failed === 0 ? '🟢' : (summary.failed <= 3 ? '🟡' : '🔴');

const lines = [
  `<b>${topEmoji} <NAME> — Logic Eval</b>`,
  `${passIcon} ${summary.passed} passed  ${failIcon} ${summary.failed} failed  ${pendIcon} ${summary.pending} pending`,
  ''
];

const suites = {};
for (const r of results) (suites[r.suite] = suites[r.suite] || []).push(r);

for (const [suite, tests] of Object.entries(suites)) {
  const suitePass = tests.every(t => t.pass !== false);
  lines.push(`<b>${suitePass ? passIcon : failIcon} ${suite}</b>`);
  for (const t of tests) {
    const icon = t.pass === true ? passIcon : (t.pass === false ? failIcon : pendIcon);
    lines.push(`  ${icon} ${t.test}`);
    if (t.pass !== true) lines.push(`    <i>→ ${(t.detail || '').substring(0, 140)}</i>`);
  }
  lines.push('');
}

const now = new Date().toLocaleString('en-US', {
  timeZone: 'UTC', dateStyle: 'short', timeStyle: 'short'
});
lines.push(`<i>Run: ${now} UTC</i>`);

return [{ json: { message: lines.join('\n') } }];

Telegram send node (env-var pattern, no n8n credential)

{
  "method": "POST",
  "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
  "sendBody": true,
  "specifyBody": "keypair",
  "bodyParameters": {
    "parameters": [
      { "name": "chat_id", "value": "={{ $env.TELEGRAM_CHAT_ID }}" },
      { "name": "text", "value": "={{ $json.message }}" },
      { "name": "parse_mode", "value": "HTML" }
    ]
  }
}

What to test (priority order)

  1. Reject path coverage — every reason a record can be filtered out
  2. Boundary conditions — at-threshold, just-below, just-above
  3. Invalid-input fallbacks — undefined, null, 0, 'None', wrong type → expected default
  4. Known-good real data — at least one example from a past confirmed-working run
  5. Known-bad real data — at least one example that caused a past incident
  6. Substring vs exact matchers — e.g., 'HR Dept' vs 'Human Resources Department'
  7. Pending features — written as pass: null so they show up in reports as backlog

Test naming convention

<suite>: <human-readable assertion> with the detail field naming the underlying rule or past-bug reference:

assert('js-22', 'Human Resources Department rejected',
  !isValidContact('Human Resources Department'),
  'BAD_CONTAINS_CONTACT substring match must catch this (Session 5 bug fix)');

The "convert past bugs to tests" rule

Every entry in your Lessons Learned Library that names a specific input → wrong behavior is a test waiting to be written.

Example from a real lesson:

"Aggregate Sector Quotes was computing 1-MONTH change instead of 1-DAY because meta.previousClose returned chart-start price on range=1mo."

→ Test:

assert('aggregate', '1-day change computed from closes[], not meta.previousClose',
  computeChange({
    chart: { result: [{ meta: { previousClose: 100 }, indicators: { quote: [{ close: [100, 98, 99, 105] }] } }] }
  }) === ((105 - 99) / 99) * 100,
  'meta.previousClose can be unreliable; must use closes[-2]');

When to run

  • Before any change to filter logic, keyword lists, validators, resume maps
  • After the change → confirm no regressions
  • After any LL Library entry is added → write the test that would have caught it
  • Pre-deploy of any new workflow version

Status icon meaning

IconMeaning
🟢 (green)0 failures — this suite passes; deploy only if your other checks pass too
🟡 (yellow)1-3 failures — deploy blocked until fixed
🔴 (red)4+ failures — deploy blocked; likely a broader break

Pending (⏳) does not count toward red/yellow — it's tracked separately as backlog. Pending tests are coverage gaps: list them in the deploy note.

Anti-patterns

Anti-patternWhat to do instead
Test against live APIsMock inputs only — eval suite is logic-only
Skip pending features (lose visibility)Add as pass: null with detail explaining what's missing
Import production codeCopy verbatim — Code nodes don't share scope
One giant test functionOne assert() per assertion — readable report
Drop the test after a bug is fixedKeep it forever — that's the regression guard
Only test happy pathReject path coverage is the highest-value tests
Run only after a changeRun before AND after — confirms you didn't introduce regressions
Hardcode chat ID / token in workflow JSONUse $env.TELEGRAM_BOT_TOKEN / $env.TELEGRAM_CHAT_ID

Pre-deploy checklist

  • Eval workflow runs in < 10 seconds
  • All suites grouped in report
  • Production code copied verbatim (not paraphrased)
  • At least 1 test per reject path
  • At least 1 "known-good real data" test per suite
  • Pending features represented as pass: null with detail
  • Report fits in one Telegram message (< 4000 chars)

Reference

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Related Skills

ashtonfeliciano/n8n-workflow-engineer

Build, debug, and harden n8n workflows. Use when the user is editing n8n workflow JSON, debugging silent failures, working with Code nodes, Telegram / Gmail / HTTP Request nodes, scheduled triggers, paired-item tracking, dedup with static data, or doing bulk operations via the n8n REST API. Covers v2.x UI specifics. Not for non-n8n automation.

community

hubgunter4-ops/configurador-de-nodos-n8n

Ayuda a configurar nodos de n8n con reglas de dependencia complejas y conexiones de IA. Agiliza la creación de flujos de trabajo automatizados gestionando dependencias de nodos intrincadas e integraciones de IA.

community

hubgunter4-ops/experto-en-n8n-mcp

Guía a los desarrolladores a seleccionar y configurar herramientas y formatos de nodo del Protocolo de Contexto de Modelo MCP dentro de los flujos de trabajo de n8n. Proporciona orientación estructurada para integrar herramientas MCP.

community

caiqueDeOliveira-dev/homelab

HomeLab Agent — Gerencia Home Assistant, ESP32 satellites, dispositivos Zigbee/Matter/Thread, automações físicas, cameras, sensores. Camada de decisão e execução no hardware (não orquestração de software = Automação/n8n).

community

hubgunter4-ops/experto-en-n8n-mcp

Guía a los desarrolladores a seleccionar y configurar herramientas y formatos de nodo del Protocolo de Contexto de Modelo MCP dentro de los flujos de trabajo de n8n. Proporciona orientación estructurada para integrar herramientas MCP.

community

hubgunter4-ops/experto-en-validacion-de-n8n

Corrige errores de validación de n8n consultando un catálogo de errores integrado. Optimiza el proceso de depuración, permitiendo identificar rápidamente problemas y resolver problemas de configuración del flujo de trabajo.

community