These directories contain TOML process definitions and JSON evidence files — no code changes that need testing. Also excludes docs/ and *.md from the main CI pipeline (e2e already excluded these). Prepares for formula and evidence PRs landing without triggering unnecessary CI runs.
458 lines
17 KiB
YAML
458 lines
17 KiB
YAML
# E2E Testing Pipeline using Native Woodpecker Services
|
|
# No Docker-in-Docker - uses pre-built images for fast startup
|
|
|
|
kind: pipeline
|
|
type: docker
|
|
name: e2e
|
|
|
|
when:
|
|
event: pull_request
|
|
path:
|
|
exclude:
|
|
- "tools/**"
|
|
- "onchain/test/FitnessEvaluator*"
|
|
- "docs/**"
|
|
- "formulas/**"
|
|
- "evidence/**"
|
|
- ".codeberg/**"
|
|
- "*.md"
|
|
|
|
clone:
|
|
git:
|
|
image: woodpeckerci/plugin-git
|
|
settings:
|
|
depth: 50
|
|
reference: /git-mirrors/harb.git
|
|
netrc_machine: codeberg.org
|
|
netrc_username: johba
|
|
netrc_password:
|
|
from_secret: codeberg_token
|
|
|
|
# All background services - services get proper DNS resolution in Woodpecker
|
|
# Note: Services can't depend on steps, so they wait internally for contracts.env
|
|
services:
|
|
# PostgreSQL for Ponder
|
|
- name: postgres
|
|
image: postgres:16-alpine
|
|
environment:
|
|
POSTGRES_USER: ponder
|
|
POSTGRES_PASSWORD: ponder_local
|
|
POSTGRES_DB: ponder_local
|
|
|
|
# Anvil blockchain fork
|
|
- name: anvil
|
|
image: ghcr.io/foundry-rs/foundry:latest
|
|
entrypoint:
|
|
- anvil
|
|
- --host=0.0.0.0
|
|
- --port=8545
|
|
- --fork-url=https://sepolia.base.org
|
|
- --fork-block-number=20000000
|
|
- --chain-id=31337
|
|
- --accounts=10
|
|
- --balance=10000
|
|
|
|
# Ponder indexer - waits for contracts.env from bootstrap
|
|
- name: ponder
|
|
image: registry.niovi.voyage/harb/ponder-ci:latest
|
|
commands:
|
|
- |
|
|
set -eu
|
|
|
|
# Wait for contracts.env (bootstrap writes it after deploying)
|
|
echo "=== Waiting for contracts.env ==="
|
|
for i in $(seq 1 120); do
|
|
if [ -f /woodpecker/src/contracts.env ]; then
|
|
echo "Found contracts.env after $i attempts"
|
|
break
|
|
fi
|
|
echo "Waiting for contracts.env... ($i/120)"
|
|
sleep 3
|
|
done
|
|
|
|
if [ ! -f /woodpecker/src/contracts.env ]; then
|
|
echo "ERROR: contracts.env not found after 6 minutes"
|
|
exit 1
|
|
fi
|
|
|
|
# Source contract addresses from bootstrap
|
|
. /woodpecker/src/contracts.env
|
|
echo "=== Contract addresses ==="
|
|
echo "KRAIKEN=$KRAIKEN"
|
|
echo "STAKE=$STAKE"
|
|
echo "START_BLOCK=$START_BLOCK"
|
|
|
|
# Export env vars required by ponder
|
|
export DATABASE_URL="$DATABASE_URL"
|
|
export DATABASE_SCHEMA="ponder_ci_$START_BLOCK"
|
|
export START_BLOCK="$START_BLOCK"
|
|
export KRAIKEN_ADDRESS="$KRAIKEN"
|
|
export STAKE_ADDRESS="$STAKE"
|
|
export LM_ADDRESS="${LIQUIDITY_MANAGER:-0x0000000000000000000000000000000000000000}"
|
|
export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
|
|
export PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK="$PONDER_RPC_URL_1"
|
|
export PONDER_RPC_URL_1="$PONDER_RPC_URL_1"
|
|
|
|
# Overlay kraiken-lib and ponder source from workspace
|
|
# CI_WORKSPACE points to the repo checkout directory
|
|
WS="${CI_WORKSPACE:-$(pwd)}"
|
|
echo "=== Workspace: $WS ==="
|
|
|
|
echo "=== Overlaying kraiken-lib from workspace ==="
|
|
if [ -d "$WS/kraiken-lib/dist" ]; then
|
|
cp -r "$WS/kraiken-lib/dist/." /app/kraiken-lib/dist/
|
|
cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
|
|
echo "kraiken-lib updated from workspace (src + dist)"
|
|
elif [ -d "$WS/kraiken-lib/src" ]; then
|
|
cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
|
|
echo "kraiken-lib/src updated (dist not available — may need rebuild)"
|
|
else
|
|
echo "WARNING: kraiken-lib not found at $WS/kraiken-lib"
|
|
fi
|
|
|
|
echo "=== Overlaying ponder source from workspace ==="
|
|
# Copy individual source files (not the directory itself) to avoid nested src/src/
|
|
if [ -d "$WS/services/ponder/src" ]; then
|
|
cp -r "$WS/services/ponder/src/." /app/services/ponder/src/
|
|
echo "ponder/src files updated from workspace"
|
|
fi
|
|
for f in ponder.schema.ts ponder.config.ts; do
|
|
if [ -f "$WS/services/ponder/$f" ]; then
|
|
cp "$WS/services/ponder/$f" /app/services/ponder/"$f"
|
|
echo "ponder/$f updated from workspace"
|
|
fi
|
|
done
|
|
|
|
echo "=== Starting Ponder (pre-built image + workspace overlay) ==="
|
|
cd /app/services/ponder
|
|
{
|
|
echo "DATABASE_URL=${DATABASE_URL}"
|
|
echo "PONDER_RPC_URL_1=${PONDER_RPC_URL_1}"
|
|
echo "DATABASE_SCHEMA=${DATABASE_SCHEMA}"
|
|
echo "START_BLOCK=${START_BLOCK}"
|
|
} > .env.local
|
|
# Use 'start' mode in CI — 'dev' mode watches for file changes and causes
|
|
# a hot-restart loop when workspace overlay modifies source files
|
|
exec npm run start
|
|
|
|
# Webapp - waits for contracts.env from bootstrap
|
|
- name: webapp
|
|
image: registry.niovi.voyage/harb/webapp-ci:latest
|
|
environment:
|
|
CI: "true"
|
|
commands:
|
|
- |
|
|
set -eu
|
|
|
|
# Wait for contracts.env (bootstrap writes it after deploying)
|
|
echo "=== Waiting for contracts.env ==="
|
|
for i in $(seq 1 120); do
|
|
if [ -f /woodpecker/src/contracts.env ]; then
|
|
echo "Found contracts.env after $i attempts"
|
|
break
|
|
fi
|
|
echo "Waiting for contracts.env... ($i/120)"
|
|
sleep 3
|
|
done
|
|
|
|
if [ ! -f /woodpecker/src/contracts.env ]; then
|
|
echo "ERROR: contracts.env not found after 6 minutes"
|
|
exit 1
|
|
fi
|
|
|
|
# Source contract addresses from bootstrap
|
|
. /woodpecker/src/contracts.env
|
|
|
|
# Export environment variables for Vite
|
|
export VITE_KRAIKEN_ADDRESS="$KRAIKEN"
|
|
export VITE_STAKE_ADDRESS="$STAKE"
|
|
export VITE_DEFAULT_CHAIN_ID=31337
|
|
export VITE_LOCAL_RPC_PROXY_TARGET=http://anvil:8545
|
|
export VITE_LOCAL_GRAPHQL_PROXY_TARGET=http://ponder:42069
|
|
# Default is the Sepolia SwapRouter; override via VITE_SWAP_ROUTER env var for other networks.
|
|
export VITE_SWAP_ROUTER="${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4}"
|
|
export VITE_ENABLE_LOCAL_SWAP=true
|
|
export VITE_BASE_PATH=/app/
|
|
|
|
# Overlay kraiken-lib from workspace (may be newer than baked-in image)
|
|
WS="${CI_WORKSPACE:-$(pwd)}"
|
|
echo "=== Overlaying kraiken-lib from workspace ==="
|
|
if [ -d "$WS/kraiken-lib/dist" ]; then
|
|
cp -r "$WS/kraiken-lib/dist/." /app/kraiken-lib/dist/
|
|
cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
|
|
echo "kraiken-lib updated from workspace (src + dist)"
|
|
elif [ -d /app/kraiken-lib/src ]; then
|
|
echo "kraiken-lib/src found in image (using baked-in version)"
|
|
else
|
|
echo "ERROR: kraiken-lib/src not found!"
|
|
exit 1
|
|
fi
|
|
|
|
# Overlay webapp source from workspace (ensures CI tests current branch)
|
|
echo "=== Overlaying webapp source from workspace ==="
|
|
if [ -d "$WS/web-app/src" ]; then
|
|
cp -r "$WS/web-app/src/." /app/web-app/src/
|
|
echo "webapp/src updated from workspace"
|
|
fi
|
|
for f in vite.config.ts vite.config.js; do
|
|
if [ -f "$WS/web-app/$f" ]; then
|
|
cp "$WS/web-app/$f" /app/web-app/"$f"
|
|
echo "webapp/$f updated from workspace"
|
|
fi
|
|
done
|
|
|
|
# Overlay @harb/web3 shared package from workspace
|
|
if [ -d "$WS/packages/web3" ]; then
|
|
mkdir -p /app/packages/web3
|
|
cp -r "$WS/packages/web3/." /app/packages/web3/
|
|
# Link @harb/web3 into web-app node_modules
|
|
mkdir -p /app/web-app/node_modules/@harb
|
|
ln -sf /app/packages/web3 /app/web-app/node_modules/@harb/web3
|
|
# Symlink wagmi/viem into packages dir so @harb/web3 can resolve them
|
|
mkdir -p /app/packages/web3/node_modules
|
|
ln -sf /app/web-app/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi
|
|
ln -sf /app/web-app/node_modules/viem /app/packages/web3/node_modules/viem
|
|
echo "@harb/web3 linked with wagmi/viem deps"
|
|
fi
|
|
|
|
# Overlay @harb/utils shared package from workspace
|
|
if [ -d "$WS/packages/utils" ]; then
|
|
mkdir -p /app/packages/utils
|
|
cp -r "$WS/packages/utils/." /app/packages/utils/
|
|
# Link @harb/utils into web-app node_modules
|
|
mkdir -p /app/web-app/node_modules/@harb
|
|
ln -sf /app/packages/utils /app/web-app/node_modules/@harb/utils
|
|
# Symlink viem into packages dir so @harb/utils can resolve it
|
|
mkdir -p /app/packages/utils/node_modules
|
|
ln -sf /app/web-app/node_modules/viem /app/packages/utils/node_modules/viem
|
|
echo "@harb/utils linked with viem dep"
|
|
fi
|
|
|
|
echo "=== Starting webapp (pre-built image + source overlay) ==="
|
|
cd /app/web-app
|
|
# Explicitly set CI=true to disable Vue DevTools in vite.config.ts
|
|
# (prevents 500 errors from devtools path resolution in CI environment)
|
|
export CI=true
|
|
echo "CI=$CI (should be 'true' to disable Vue DevTools)"
|
|
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/
|
|
|
|
# Landing page - no contracts needed, starts immediately
|
|
- name: landing
|
|
image: registry.niovi.voyage/harb/landing-ci:latest
|
|
commands:
|
|
- |
|
|
set -eu
|
|
# Overlay landing source from workspace
|
|
WS="${CI_WORKSPACE:-$(pwd)}"
|
|
if [ -d "$WS/landing/src" ]; then
|
|
cp -r "$WS/landing/src/." /app/landing/src/
|
|
echo "landing/src updated from workspace"
|
|
fi
|
|
for f in vite.config.ts vite.config.js; do
|
|
if [ -f "$WS/landing/$f" ]; then
|
|
cp "$WS/landing/$f" /app/landing/"$f"
|
|
echo "landing/$f updated from workspace"
|
|
fi
|
|
done
|
|
|
|
# Overlay @harb/web3 shared package
|
|
if [ -d "$WS/packages/web3" ]; then
|
|
mkdir -p /app/packages/web3
|
|
cp -r "$WS/packages/web3/." /app/packages/web3/
|
|
# Landing CI image doesn't have wagmi — install it
|
|
cd /app/landing
|
|
npm install --no-audit --no-fund @wagmi/vue viem 2>/dev/null || true
|
|
# Link @harb/web3
|
|
mkdir -p /app/landing/node_modules/@harb
|
|
ln -sf /app/packages/web3 /app/landing/node_modules/@harb/web3
|
|
# Symlink wagmi/viem into packages dir for resolution
|
|
mkdir -p /app/packages/web3/node_modules
|
|
ln -sf /app/landing/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi 2>/dev/null || true
|
|
ln -sf /app/landing/node_modules/viem /app/packages/web3/node_modules/viem 2>/dev/null || true
|
|
echo "@harb/web3 linked for landing"
|
|
fi
|
|
|
|
# Overlay @harb/ui-shared shared package from workspace
|
|
if [ -d "$WS/packages/ui-shared" ]; then
|
|
mkdir -p /app/packages/ui-shared
|
|
cp -r "$WS/packages/ui-shared/." /app/packages/ui-shared/
|
|
# Link @harb/ui-shared into landing node_modules
|
|
mkdir -p /app/landing/node_modules/@harb
|
|
ln -sf /app/packages/ui-shared /app/landing/node_modules/@harb/ui-shared
|
|
# Symlink vue into packages dir so @harb/ui-shared can resolve it
|
|
mkdir -p /app/packages/ui-shared/node_modules
|
|
ln -sf /app/landing/node_modules/vue /app/packages/ui-shared/node_modules/vue 2>/dev/null || true
|
|
echo "@harb/ui-shared linked for landing"
|
|
fi
|
|
|
|
echo "=== Starting landing (pre-built image + source overlay) ==="
|
|
cd /app/landing
|
|
exec npm run dev -- --host 0.0.0.0 --port 5174
|
|
|
|
# Caddy proxy - waits for contracts.env to ensure other services are starting
|
|
- name: caddy
|
|
image: caddy:2.8-alpine
|
|
commands:
|
|
- |
|
|
# Wait briefly for other services to start
|
|
echo "=== Waiting for contracts.env before starting Caddy ==="
|
|
for i in $(seq 1 120); do
|
|
if [ -f /woodpecker/src/contracts.env ]; then
|
|
echo "Found contracts.env, starting Caddy..."
|
|
break
|
|
fi
|
|
echo "Waiting for contracts.env... ($i/120)"
|
|
sleep 3
|
|
done
|
|
|
|
printf '%s\n' ':8081 {' \
|
|
' route /app* {' \
|
|
' reverse_proxy webapp:5173' \
|
|
' }' \
|
|
' route /api/graphql* {' \
|
|
' uri strip_prefix /api' \
|
|
' reverse_proxy ponder:42069' \
|
|
' }' \
|
|
' route /api/rpc* {' \
|
|
' uri strip_prefix /api/rpc' \
|
|
' reverse_proxy anvil:8545' \
|
|
' }' \
|
|
' reverse_proxy landing:5174' \
|
|
'}' > /etc/caddy/Caddyfile
|
|
exec caddy run --config /etc/caddy/Caddyfile
|
|
|
|
steps:
|
|
# Step 0: Install dependencies for onchain compilation
|
|
- name: install-deps
|
|
image: node:20-alpine
|
|
commands:
|
|
- |
|
|
set -eu
|
|
apk add --no-cache git
|
|
echo "=== Installing uni-v3-lib dependencies ==="
|
|
git submodule update --init --recursive
|
|
cd onchain/lib/uni-v3-lib
|
|
npm install
|
|
|
|
# Step 1: Wait for base services and deploy contracts
|
|
# Uses pre-built node-ci image with Foundry pre-installed (saves ~60s)
|
|
- name: bootstrap
|
|
image: registry.niovi.voyage/harb/node-ci:latest
|
|
depends_on:
|
|
- install-deps
|
|
commands:
|
|
- |
|
|
# Create a bootstrap wrapper that runs under bash
|
|
# (Woodpecker uses /bin/sh which lacks 'source' and bash-isms)
|
|
export ANVIL_RPC=http://anvil:8545
|
|
export CONTRACT_ENV=/woodpecker/src/contracts.env
|
|
export LOG_FILE=/dev/null
|
|
export ONCHAIN_DIR="$PWD/onchain"
|
|
export TXNBOT_FUND_VALUE=10ether
|
|
export TXNBOT_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
|
export TXNBOT_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
|
exec bash scripts/ci-bootstrap.sh
|
|
|
|
# Step 2: Wait for stack to be healthy (services run in background)
|
|
- name: wait-for-stack
|
|
image: alpine:3.20
|
|
depends_on:
|
|
- bootstrap
|
|
commands:
|
|
- |
|
|
set -eu
|
|
apk add --no-cache curl bash
|
|
|
|
echo "=== Waiting for DNS resolution (Docker embedded DNS can be slow under load) ==="
|
|
for svc in ponder webapp landing caddy; do
|
|
for attempt in $(seq 1 60); do
|
|
if getent hosts "$svc" >/dev/null 2>&1; then
|
|
echo "[dns] $svc resolved after $attempt attempts"
|
|
break
|
|
fi
|
|
echo "[dns] ($attempt/60) waiting for $svc DNS..."
|
|
sleep 5
|
|
done
|
|
done
|
|
|
|
echo "=== Waiting for stack to be healthy (max 7 min) ==="
|
|
bash scripts/wait-for-service.sh http://ponder:42069/health 420 ponder
|
|
|
|
# Wait for ponder to finish historical indexing (not just respond)
|
|
# /ready returns 200 only when fully synced, 503 while indexing
|
|
echo "=== Waiting for Ponder indexing to complete ==="
|
|
for i in $(seq 1 120); do
|
|
HTTP_CODE=$(curl -sf -o /dev/null -w '%{http_code}' --max-time 3 http://ponder:42069/ready 2>/dev/null || echo "000")
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
echo "[wait] Ponder fully indexed after $((i * 3))s"
|
|
break
|
|
fi
|
|
if [ "$i" = "120" ]; then
|
|
echo "[wait] WARNING: Ponder not fully indexed after 360s, continuing anyway"
|
|
fi
|
|
echo "[wait] ($i/120) Ponder indexing... (HTTP $HTTP_CODE)"
|
|
sleep 3
|
|
done
|
|
bash scripts/wait-for-service.sh http://webapp:5173/app/ 420 webapp
|
|
bash scripts/wait-for-service.sh http://landing:5174/ 420 landing
|
|
bash scripts/wait-for-service.sh http://caddy:8081/app/ 420 caddy
|
|
echo "=== Stack is healthy ==="
|
|
|
|
# Step 3: Run E2E tests
|
|
- name: run-e2e-tests
|
|
image: mcr.microsoft.com/playwright:v1.55.1-jammy
|
|
depends_on:
|
|
- wait-for-stack
|
|
timeout: 600
|
|
environment:
|
|
STACK_BASE_URL: http://caddy:8081
|
|
STACK_RPC_URL: http://caddy:8081/api/rpc
|
|
STACK_WEBAPP_URL: http://caddy:8081
|
|
STACK_GRAPHQL_URL: http://caddy:8081/api/graphql
|
|
CI: "true"
|
|
commands:
|
|
- |
|
|
set -eux
|
|
|
|
echo "=== Checking system resources ==="
|
|
free -h || true
|
|
cat /proc/meminfo | grep -E 'MemTotal|MemAvail' || true
|
|
|
|
echo "=== Verifying Playwright browsers ==="
|
|
npx playwright install --dry-run 2>&1 || true
|
|
ls -la /ms-playwright/ 2>/dev/null || echo "No /ms-playwright directory"
|
|
|
|
echo "=== Installing test dependencies ==="
|
|
npm config set fund false
|
|
npm config set audit false
|
|
npm ci --no-audit --no-fund
|
|
|
|
echo "=== Running E2E tests (workers=1 to limit memory) ==="
|
|
npx playwright test --reporter=list --workers=1
|
|
|
|
# Step 4: Collect artifacts
|
|
- name: collect-artifacts
|
|
image: alpine:3.20
|
|
depends_on:
|
|
- run-e2e-tests
|
|
when:
|
|
status:
|
|
- success
|
|
- failure
|
|
commands:
|
|
- |
|
|
set -eu
|
|
apk add --no-cache tar gzip
|
|
mkdir -p artifacts
|
|
|
|
if [ -d playwright-report ]; then
|
|
tar -czf artifacts/playwright-report.tgz playwright-report
|
|
echo "Playwright report archived"
|
|
fi
|
|
|
|
if [ -d test-results ]; then
|
|
tar -czf artifacts/test-results.tgz test-results
|
|
echo "Test results archived"
|
|
fi
|
|
|
|
ls -lh artifacts/ 2>/dev/null || echo "No artifacts"
|