Skip to content

Commit b935dc1

Browse files
shortstackedclaude
andauthored
test: Add Helm chart E2E testing via K3s + testcontainers (#26155)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3ed72a1 commit b935dc1

File tree

7 files changed

+995
-0
lines changed

7 files changed

+995
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
name: 'Test: E2E Helm Chart'
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/test-e2e-helm.yml'
7+
- 'packages/testing/containers/helm-stack.ts'
8+
- 'packages/testing/containers/helm-start-stack.ts'
9+
workflow_dispatch:
10+
inputs:
11+
branch:
12+
description: 'Branch to test'
13+
required: false
14+
default: ''
15+
helm-chart-ref:
16+
description: 'n8n-hosting branch/tag for Helm chart'
17+
required: false
18+
default: 'main'
19+
mode:
20+
description: 'Deployment mode to test'
21+
required: true
22+
type: choice
23+
options:
24+
- standalone
25+
- queue
26+
default: 'queue'
27+
28+
env:
29+
DOCKER_IMAGE: ghcr.io/${{ github.repository }}:ci-${{ github.run_id }}
30+
HELM_CHART_REF: ${{ inputs.helm-chart-ref || 'main' }}
31+
32+
jobs:
33+
build:
34+
name: 'Build Docker Image'
35+
runs-on: blacksmith-4vcpu-ubuntu-2204
36+
permissions:
37+
packages: write
38+
contents: read
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
42+
with:
43+
ref: ${{ inputs.branch || github.ref }}
44+
fetch-depth: 1
45+
46+
- name: Login to GHCR
47+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
48+
with:
49+
registry: ghcr.io
50+
username: ${{ github.actor }}
51+
password: ${{ secrets.GITHUB_TOKEN }}
52+
53+
- name: Build and push to GHCR
54+
uses: ./.github/actions/setup-nodejs
55+
with:
56+
build-command: 'pnpm build:docker'
57+
enable-docker-cache: true
58+
env:
59+
INCLUDE_TEST_CONTROLLER: 'true'
60+
IMAGE_BASE_NAME: ghcr.io/${{ github.repository }}
61+
IMAGE_TAG: ci-${{ github.run_id }}
62+
RUNNERS_IMAGE_BASE_NAME: ghcr.io/${{ github.repository_owner }}/runners
63+
64+
helm-e2e:
65+
name: "Helm E2E (${{ inputs.mode || 'queue' }})"
66+
needs: build
67+
runs-on: blacksmith-4vcpu-ubuntu-2204
68+
timeout-minutes: 30
69+
permissions:
70+
contents: read
71+
steps:
72+
- name: Checkout
73+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
74+
with:
75+
ref: ${{ inputs.branch || github.ref }}
76+
fetch-depth: 1
77+
78+
- name: Setup Environment
79+
uses: ./.github/actions/setup-nodejs
80+
with:
81+
build-command: 'pnpm build'
82+
83+
- name: Install Browsers
84+
working-directory: packages/testing/playwright
85+
run: pnpm exec playwright install chromium
86+
87+
- name: Install Helm
88+
uses: azure/setup-helm@bf6a7d304bc2fdb57e0331155b7ebf2c504acf0a # v4
89+
90+
- name: Install kubectl
91+
uses: azure/setup-kubectl@c0c8b32d33a5244f1e5947304550403b63930415 # v4
92+
93+
- name: Start K3s + Helm stack
94+
run: |
95+
npx tsx packages/testing/containers/helm-start-stack.ts \
96+
--mode "${{ inputs.mode || 'queue' }}" \
97+
--image "${{ env.DOCKER_IMAGE }}" \
98+
--chart-ref "${{ env.HELM_CHART_REF }}" \
99+
--url-file /tmp/n8n-helm-url.txt \
100+
--kubeconfig-file /tmp/n8n-helm-kubeconfig.txt &
101+
102+
echo "Waiting for Helm stack to be ready..."
103+
for i in $(seq 1 60); do
104+
if [ -f /tmp/n8n-helm-url.txt ]; then
105+
echo "Stack ready!"
106+
break
107+
fi
108+
if [ "$i" -eq 60 ]; then
109+
echo "Timeout waiting for Helm stack"
110+
exit 1
111+
fi
112+
sleep 10
113+
done
114+
115+
N8N_URL=$(cat /tmp/n8n-helm-url.txt)
116+
echo "n8n URL: ${N8N_URL}"
117+
echo "N8N_BASE_URL=${N8N_URL}" >> "$GITHUB_ENV"
118+
119+
KUBECONFIG_PATH=$(cat /tmp/n8n-helm-kubeconfig.txt)
120+
echo "KUBECONFIG=${KUBECONFIG_PATH}" >> "$GITHUB_ENV"
121+
122+
- name: Run Building Blocks E2E Tests
123+
working-directory: packages/testing/playwright
124+
run: |
125+
N8N_BASE_URL="${{ env.N8N_BASE_URL }}" \
126+
RESET_E2E_DB=true \
127+
npx playwright test \
128+
--project=e2e \
129+
tests/e2e/building-blocks/ \
130+
--workers=1 \
131+
--retries=2 \
132+
--reporter=list
133+
134+
- name: Debug K8s State
135+
if: failure()
136+
run: |
137+
echo "=== Pod Status ==="
138+
kubectl get pods -o wide
139+
echo ""
140+
echo "=== Pod Descriptions ==="
141+
kubectl describe pods -l app.kubernetes.io/name=n8n
142+
echo ""
143+
echo "=== Recent Events ==="
144+
kubectl get events --sort-by=.lastTimestamp | tail -30
145+
echo ""
146+
echo "=== n8n Pod Logs (last 100 lines) ==="
147+
kubectl logs -l app.kubernetes.io/name=n8n --tail=100 || true
148+
echo ""
149+
echo "=== Health Check ==="
150+
curl -s -o /dev/null -w "HTTP %{http_code}" "${{ env.N8N_BASE_URL }}/healthz/readiness" || echo "UNREACHABLE"
151+
152+
- name: Upload Failure Artifacts
153+
if: failure()
154+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
155+
with:
156+
name: helm-e2e-report-${{ inputs.mode || 'queue' }}
157+
path: |
158+
packages/testing/playwright/test-results/
159+
packages/testing/playwright/playwright-report/
160+
retention-days: 7
161+
162+
cleanup-ghcr:
163+
name: 'Cleanup GHCR Image'
164+
needs: [build, helm-e2e]
165+
if: always()
166+
runs-on: blacksmith-2vcpu-ubuntu-2204
167+
permissions:
168+
packages: write
169+
contents: read
170+
steps:
171+
- name: Checkout
172+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
173+
with:
174+
sparse-checkout: .github/scripts
175+
sparse-checkout-cone-mode: false
176+
177+
- name: Delete images from GHCR
178+
continue-on-error: true
179+
env:
180+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
181+
GHCR_ORG: ${{ github.repository_owner }}
182+
GHCR_REPO: ${{ github.event.repository.name }}
183+
run: node .github/scripts/cleanup-ghcr-images.mjs --tag ci-${{ github.run_id }}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Helm Chart E2E Testing
2+
3+
Test n8n Helm chart deployments using K3s (lightweight Kubernetes) inside Docker, powered by `@testcontainers/k3s`.
4+
5+
## Prerequisites
6+
7+
### Required
8+
9+
- **Docker Desktop** (macOS/Windows) or **Docker Engine** (Linux)
10+
- **Privileged container support** — K3s runs as a privileged container
11+
- **helm** CLI — runs on your host machine ([install](https://helm.sh/docs/intro/install/))
12+
- **kubectl** CLI — runs on your host machine ([install](https://kubernetes.io/docs/tasks/tools/))
13+
- **n8n Docker image** — built locally (`pnpm build:docker`) or pulled from Docker Hub
14+
15+
### Not Required
16+
17+
- Kind, Minikube, or any other K8s distribution
18+
- Kubernetes cluster access
19+
- Any K8s tooling beyond helm + kubectl
20+
21+
### Privileged Container Compatibility
22+
23+
| Environment | Supported | Notes |
24+
|---|---|---|
25+
| Docker Desktop (macOS/Windows) | Yes | Privileged enabled by default |
26+
| Docker Engine (Linux) | Yes | Standard daemon supports it |
27+
| GitHub Actions (ubuntu runners) | Yes | Docker socket available |
28+
| Blacksmith runners | Yes | Standard Docker-capable VMs |
29+
| Rootless Docker | **No** | K3s requires privileged mode |
30+
| Docker-in-Docker | Depends | Outer container needs `--privileged` |
31+
| Podman | **No** | K3s requires Docker-compatible runtime |
32+
33+
## How It Works
34+
35+
```
36+
Host Machine (helm, kubectl)
37+
├── KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
38+
└── Docker
39+
└── K3s Container (privileged, NodePort 30080 → host random port)
40+
├── containerd (K3s runtime)
41+
│ └── n8n image (preloaded from host Docker)
42+
└── Kubernetes control plane
43+
├── n8n Pod (Helm-deployed)
44+
└── Service (NodePort 30080 → Pod 5678)
45+
46+
Playwright ──── http://localhost:<host-port> ──→ NodePort ──→ n8n Pod
47+
```
48+
49+
1. `@testcontainers/k3s` starts K3s inside a Docker container with NodePort 30080 exposed
50+
2. The n8n Docker image is exported from host Docker and imported into K3s's containerd
51+
3. Host `helm` installs the n8n chart using a kubeconfig pointing at the K3s API
52+
4. The n8n service is patched to NodePort, routing traffic through K3s's exposed port
53+
5. Playwright tests connect via `N8N_BASE_URL=http://localhost:<host-port>`
54+
55+
## Local Usage
56+
57+
```bash
58+
# 1. Build the n8n Docker image (or use a Docker Hub image)
59+
pnpm build:docker
60+
61+
# 2. Start the Helm stack (takes ~60-120s)
62+
cd packages/testing/containers
63+
pnpm stack:helm
64+
65+
# 3. In another terminal, use the printed KUBECONFIG for debugging
66+
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
67+
kubectl get pods
68+
kubectl logs -l app.kubernetes.io/name=n8n
69+
70+
# 4. Run tests
71+
N8N_BASE_URL=http://localhost:<port> RESET_E2E_DB=true \
72+
npx playwright test tests/e2e/building-blocks/ --workers=1
73+
74+
# 5. Cleanup
75+
pnpm stack:helm:clean
76+
```
77+
78+
### Test a Specific Version Matrix
79+
80+
```bash
81+
# Test n8n 1.80.0 against chart version v1.2.0
82+
pnpm stack:helm --image n8nio/n8n:1.80.0 --chart-ref v1.2.0
83+
84+
# Test latest n8n against a chart PR branch
85+
pnpm stack:helm --chart-ref fix/pvc-permissions
86+
87+
# Test a GHCR image (e.g., from CI)
88+
pnpm stack:helm --image ghcr.io/n8n-io/n8n:ci-12345
89+
```
90+
91+
### CLI Options
92+
93+
```
94+
--mode <mode> standalone (SQLite, default) or queue (PostgreSQL + Redis + workers)
95+
--image <image> n8n Docker image (default: n8nio/n8n:local)
96+
--chart-ref <ref> Git branch/tag for n8n-hosting (default: main)
97+
--chart-repo <url> Git repo URL (default: https://github.com/n8n-io/n8n-hosting.git)
98+
--k3s-image <image> K3s image (default: rancher/k3s:v1.32.2-k3s1)
99+
--url-file <path> Write URL to file when ready (for CI automation)
100+
--help Show help
101+
```
102+
103+
## CI Usage
104+
105+
The `test-e2e-helm.yml` workflow handles everything:
106+
107+
- **Trigger:** Push to `helm-container-test` branch, or manual dispatch
108+
- **Build:** Creates n8n Docker image, pushes to GHCR
109+
- **Test:** Starts K3s, installs Helm chart, runs building-blocks E2E tests
110+
- **Cleanup:** Removes ephemeral GHCR image
111+
112+
Manual dispatch also accepts `helm-chart-ref` to test a specific chart version.
113+
114+
## Troubleshooting
115+
116+
### K3s won't start
117+
118+
Check that Docker supports privileged containers:
119+
```bash
120+
docker run --rm --privileged alpine echo "privileged works"
121+
```
122+
123+
### Image not found in K3s
124+
125+
Ensure the n8n Docker image exists locally:
126+
```bash
127+
docker images | grep n8nio/n8n
128+
```
129+
130+
If empty, run `pnpm build:docker` first.
131+
132+
### Helm install times out
133+
134+
Use kubectl to inspect the cluster:
135+
```bash
136+
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
137+
kubectl get pods -o wide
138+
kubectl describe pod -l app.kubernetes.io/name=n8n
139+
kubectl get events --sort-by=.lastTimestamp
140+
```
141+
142+
### Port not accessible
143+
144+
NodePort routing is stateless (kube-proxy), so connectivity issues typically indicate the pod is unhealthy. Check pod status:
145+
```bash
146+
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
147+
kubectl get pods -o wide
148+
kubectl describe pod -l app.kubernetes.io/name=n8n
149+
kubectl version --client
150+
helm version
151+
```
152+
153+
### Slow startup
154+
155+
First run is slower due to K3s image pull. Typical timings:
156+
157+
| Phase | First Run | Subsequent |
158+
|---|---|---|
159+
| K3s start | ~15-30s | ~5s (reuse) |
160+
| Image preload | ~10-20s | ~10-20s |
161+
| Helm install | ~5-10s | ~5-10s |
162+
| n8n boot | ~15-30s | ~15-30s |
163+
| **Total** | **~60-120s** | **~40-80s** |
164+
165+
## Comparison: Testcontainers vs K3s + Helm
166+
167+
| | Testcontainers (Stream 1) | K3s + Helm (Stream 2) |
168+
|---|---|---|
169+
| **Purpose** | Feature testing | Deployment validation |
170+
| **Speed** | 5-15s startup | 60-120s startup |
171+
| **K8s features** | None | PVC, RBAC, securityContext, NetworkPolicy |
172+
| **Custom services** | Kafka, Mailpit, OIDC, etc. | Only what the Helm chart defines |
173+
| **What it proves** | "n8n works with X" | "this chart config deploys correctly" |
174+
| **When to run** | Every PR | On demand, nightly, pre-release |
175+
| **Prerequisites** | Docker | Docker + helm + kubectl |

0 commit comments

Comments
 (0)