Skip to content

E2E Integration

E2E Integration #239

Workflow file for this run

name: E2E Integration
# Validates that the plugin works end-to-end against the latest published
# OpenClaw release. Installs OpenClaw fresh, builds and installs the plugin
# from source, starts the gateway with a mock LLM + mock Opik server, runs
# a real agent turn, and asserts that traces and spans were exported.
#
# This catches regressions caused by OpenClaw plugin lifecycle changes that
# unit tests cannot detect.
permissions:
contents: read
on:
push:
branches: [main]
paths:
- ".github/workflows/e2e.yml"
- "index.ts"
- "openclaw.plugin.json"
- "src/**"
- ".scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "tsconfig.build.json"
pull_request:
branches: [main]
paths:
- ".github/workflows/e2e.yml"
- "index.ts"
- "openclaw.plugin.json"
- "src/**"
- ".scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "tsconfig.build.json"
schedule:
- cron: '0 6 * * *' # daily at 6am UTC — catches new OpenClaw releases breaking the plugin
workflow_dispatch:
jobs:
e2e:
name: E2E against OpenClaw ${{ matrix.openclaw-version }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
openclaw-version:
- latest
env:
E2E_RESULT_FILE: e2e-result.json
E2E_LLM_RESULT_FILE: e2e-llm-result.json
E2E_OPIK_JOURNAL_FILE: e2e-opik-journal.json
E2E_LLM_JOURNAL_FILE: e2e-llm-journal.json
E2E_EXPECTED_OPIK_PROJECT: e2e-test
OPENCLAW_GATEWAY_TOKEN: e2e-test-token
steps:
- name: Checkout plugin source
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "22.x"
cache: "npm"
- name: Setup npm
run: npm install -g npm@11.6.2
- name: Setup npm global prefix for caching
run: |
npm config set prefix ~/.npm-global
echo "$HOME/.npm-global/bin" >> $GITHUB_PATH
- name: Resolve OpenClaw version
id: openclaw-version
run: |
if [ "${{ matrix.openclaw-version }}" = "latest" ]; then
version="$(npm view openclaw version)"
else
version="${{ matrix.openclaw-version }}"
fi
echo "value=$version" >> "$GITHUB_OUTPUT"
echo "Resolved OpenClaw version: $version"
- name: Cache OpenClaw install
id: cache-openclaw
uses: actions/cache@v5
with:
path: ~/.npm-global
key: openclaw-${{ steps.openclaw-version.outputs.value }}-${{ runner.os }}-${{ runner.arch }}
- name: Install OpenClaw (${{ steps.openclaw-version.outputs.value }})
if: steps.cache-openclaw.outputs.cache-hit != 'true'
run: npm install -g openclaw@${{ steps.openclaw-version.outputs.value }}
- name: Print OpenClaw version
run: openclaw --version
- name: Cache plugin dependencies
id: cache-node-modules
uses: actions/cache@v5
with:
path: node_modules
key: node-modules-${{ runner.os }}-node22-${{ hashFiles('package-lock.json') }}
- name: Install plugin dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Build plugin tarball
run: npm pack
- name: Write OpenClaw config
run: |
mkdir -p ~/.openclaw/agents/main/sessions
cat > ~/.openclaw/openclaw.json << 'EOF'
{
"gateway": {
"mode": "local",
"bind": "loopback",
"auth": { "mode": "token", "token": "e2e-test-token" },
"port": 18789
},
"agents": {
"defaults": {
"model": {
"primary": "mock-openai/gpt-4o-mini"
}
}
},
"models": {
"mode": "merge",
"providers": {
"mock-openai": {
"baseUrl": "http://127.0.0.1:18790/v1",
"apiKey": "mock-key",
"authHeader": true,
"api": "openai-responses",
"models": [
{
"id": "gpt-4o-mini",
"name": "Mock GPT-4o Mini",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 128000,
"maxTokens": 16384
}
]
}
}
},
"plugins": {
"allow": ["opik-openclaw"],
"entries": {
"opik-openclaw": {
"enabled": true,
"hooks": {
"allowConversationAccess": true
},
"config": {
"enabled": true,
"apiUrl": "http://127.0.0.1:18791",
"apiKey": "mock-key",
"projectName": "e2e-test",
"workspaceName": "default"
}
}
}
}
}
EOF
- name: Install plugin from tarball
run: openclaw plugins install ./opik-opik-openclaw-*.tgz
- name: Start mock Opik server
run: |
node .scripts/mock-opik-server.mjs > mock-opik.log 2>&1 &
echo $! > mock-opik.pid
env:
MOCK_OPIK_PORT: "18791"
- name: Start mock LLM server
run: |
node .scripts/mock-llm-server.mjs > mock-llm.log 2>&1 &
echo $! > mock-llm.pid
env:
MOCK_LLM_PORT: "18790"
- name: Wait for mock servers to be ready
run: |
for i in $(seq 1 10); do
curl -sf http://127.0.0.1:18791/health > /dev/null 2>&1 && \
curl -sf http://127.0.0.1:18790/v1/models > /dev/null 2>&1 && break
sleep 1
done
- name: Start OpenClaw gateway
run: |
openclaw gateway run > gateway.log 2>&1 &
echo $! > gateway.pid
- name: Wait for gateway to be ready
run: |
for i in $(seq 1 15); do
openclaw health > /dev/null 2>&1 && break
sleep 1
done
openclaw health
- name: Run agent turn
run: |
set -o pipefail
openclaw agent --agent main --message "ping" --deliver 2>&1 | tee agent-output.log
if grep -q "falling back to embedded" agent-output.log; then
echo "[e2e] FAIL: gateway turn fell back to embedded mode"
exit 1
fi
timeout-minutes: 2
- name: Stop gateway and flush traces
run: |
openclaw gateway stop || true
if [ -f gateway.pid ]; then
kill "$(cat gateway.pid)" > /dev/null 2>&1 || true
fi
sleep 2
- name: Stop mock servers and collect results
run: |
if [ -f mock-opik.pid ]; then
kill "$(cat mock-opik.pid)" > /dev/null 2>&1 || true
fi
if [ -f mock-llm.pid ]; then
kill "$(cat mock-llm.pid)" > /dev/null 2>&1 || true
fi
sleep 1
- name: Assert E2E results
run: node .scripts/check-e2e-result.mjs
- name: Upload E2E result on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: e2e-debug
path: |
e2e-result.json
e2e-llm-result.json
e2e-opik-journal.json
e2e-llm-journal.json
agent-output.log
gateway.log
mock-opik.log
mock-llm.log
if-no-files-found: ignore