harb/.woodpecker/e2e.yml

398 lines
14 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
# 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
export VITE_SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
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
# 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
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
# 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
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"