This page describes the testing stack, test suite categories, CI filter logic, shared test utilities, and the E2E infrastructure used across the n8n monorepo. It covers how different test tiers are structured, which frameworks run them, and how the CI pipeline decides what to execute on a given pull request.
For a detailed walkthrough of Playwright E2E patterns and page objects, see 8.2. For unit and integration test patterns, database helpers, and mock conventions, see 8.3.
The monorepo uses three distinct test runners, each targeting a different layer of the application.
| Layer | Framework | Location | CI job |
|---|---|---|---|
| Backend unit | Jest | packages/cli/src/**/__tests__/, packages/@n8n/*/test/ | unit-test |
| Frontend unit | Vitest | packages/frontend/**/**.test.ts | unit-test |
| Integration / DB | Jest | packages/cli/test/integration/, packages/cli/test/migration/ | db-tests |
| E2E browser | Playwright + Currents | packages/testing/playwright/ | e2e-tests |
| Visual regression | Chromatic | packages/frontend/@n8n/storybook/ | chromatic |
The backend and frontend unit tests are bundled under the same unit-test CI job but run through different test runners. Integration tests that require a live database are separated into the db-tests job, which uses Testcontainers to spin up real Postgres and SQLite instances.
Sources: .github/workflows/ci-pull-requests.yml86-157 CONTRIBUTING.md27-32
Every pull request runs a ci-filter action before executing any test suites. The action reads the set of changed files and emits a JSON object of boolean flags that downstream jobs consume.
CI filter pipeline:
The filter rules mapping changed file paths to output flags are defined inline in the workflow:
| Flag | Triggers when changes touch |
|---|---|
ci | Any file except task-runner-python/** and .github/** |
unit | Any file except playwright and task-runner-python paths |
e2e | packages/testing/playwright/**, packages/testing/containers/**, E2E workflow files |
db | packages/cli/src/databases/**, packages/@n8n/db/**, migration/integration test files |
design-system | packages/frontend/@n8n/design-system/**, @n8n/chat/**, @n8n/storybook/** |
workflows | .github/** |
The required-checks job at the end re-runs the same ci-filter action in validate mode, which reads the results of all upstream jobs and fails if any required check is missing or failed. This is the branch protection gate.
Sources: .github/workflows/ci-pull-requests.yml1-175 .github/workflows/ci-master.yml1-61
Backend packages use Jest as the test runner with jest-mock-extended for typed mocking.
Package-level Jest config locations:
packages/@n8n/config/ ā tests GlobalConfig env var loading; see packages/@n8n/config/test/config.test.tspackages/cli/src/**/__tests__/ ā unit tests for services and controllerspackages/cli/test/integration/ ā integration tests (require database)A typical backend unit test constructs a class under test by supplying mock<T>() instances for its dependencies directly, bypassing the DI container:
// packages/cli/src/__tests__/license.test.ts
import { mock } from 'jest-mock-extended';
const license = new License(mockLogger(), instanceSettings, mock(), mock(), globalConfig);
The @n8n/backend-test-utils package exports two utilities used across backend tests:
mockInstance(ServiceClass) ā registers a mocked instance in the DI containermockLogger() ā returns a typed mock of the Logger classSources: packages/cli/src/__tests__/license.test.ts1-50 packages/cli/test/integration/commands/worker.cmd.test.ts1-65 packages/cli/src/services/__tests__/frontend.service.test.ts1-65
Frontend packages (editor-ui, design-system, @n8n/chat) use Vitest with vitest-mock-extended.
Shared mock factories and default fixture objects live in the __tests__ directory of editor-ui:
Shared test utilities:
defaultSettings in packages/frontend/editor-ui/src/__tests__/defaults.ts1-177 is a fully-typed FrontendSettings object with safe test defaults (SQLite, no auth, no enterprise features). Component tests import and spread-override it to simulate specific configurations.
Sources: packages/frontend/editor-ui/src/__tests__/mocks.ts1-310 packages/frontend/editor-ui/src/__tests__/defaults.ts1-177
Integration tests in packages/cli/test/integration/ start a real HTTP server and may connect to a real database. The db-tests CI job provisions ephemeral Postgres and SQLite instances using Testcontainers, via configuration in packages/testing/containers/.
The setupTestCommand helper found at packages/cli/test/integration/utils/test-command.ts (referenced as @test-integration/utils/test-command) wires up a command class against the DI container and runs it in-process, allowing assertion of which service methods were called.
Pattern from the worker integration test:
Sources: packages/cli/test/integration/commands/worker.cmd.test.ts1-65
E2E tests live in packages/testing/playwright/. They run against a fully started n8n instance.
The file packages/testing/playwright/currents.config.ts1-14 configures Currents for cloud-parallel test orchestration. When BUILD_WITH_COVERAGE=true, coverage collection is enabled per Playwright project.
The script packages/testing/playwright/scripts/generate-coverage-report.js merges NYC output from multiple Playwright projects (sqlite:e2e, postgres:e2e, queue:e2e) into a single HTML report. The coverage data is written by an Istanbul-instrumented build of editor-ui.
When E2E_TESTS=true, the backend registers an additional E2EController at /rest/e2e:
E2EController replaces license.isLicensed and license.getValue with in-memory maps so tests can toggle any enterprise feature without a real license.
The list of tables truncated on each reset is declared at packages/cli/src/controllers/e2e.controller.ts36-57
Sources: packages/cli/src/controllers/e2e.controller.ts1-407 packages/cli/src/constants.ts13-14
The config module in packages/cli/src/config/index.ts15-26 adjusts runtime configuration when tests are detected:
| Mode | Detected via | Adjustments applied |
|---|---|---|
| E2E | process.env.E2E_TESTS === 'true' (inE2ETests) | Disables diagnostics, clears EXTERNAL_FRONTEND_HOOKS_URLS, disables personalization, enables AI |
| Unit/integration | inTest from @n8n/backend-common | Sets log level to silent, disables public API, sets auth.cookie.secure = false, skips statistics events |
The inE2ETests flag is also exported from packages/cli/src/constants.ts14 and consumed by FrontendService to set settings.inE2ETests in the frontend settings payload, allowing the Vue app to suppress certain telemetry and survey prompts during E2E runs.
Sources: packages/cli/src/config/index.ts1-71 packages/cli/src/constants.ts13-14 packages/cli/src/services/frontend.service.ts184-186
GlobalConfig Unit TestsThe @n8n/config package has its own Jest test suite at packages/@n8n/config/test/config.test.ts that validates the GlobalConfig class against:
_FILE env var pattern (reads a value from a file path)console.warnThis suite resets the DI container (Container.reset()) in beforeEach so each test starts with a clean instance.
Sources: packages/@n8n/config/test/config.test.ts1-620
Sources: .github/workflows/ci-pull-requests.yml1-175 packages/cli/src/controllers/e2e.controller.ts1-50 packages/testing/playwright/currents.config.ts1-14 packages/frontend/editor-ui/src/__tests__/mocks.ts1-30
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.