Export the agent e2e suite as a release bundle for downstream repos #13
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Agent E2E | |
| # Runs the agent e2e suites (e2e/) against the Conductor OSS server boot JAR | |
| # (Maven Central — it has no GitHub release assets), which ships the | |
| # /agent/* control plane + TaskDef.runtimeMetadata persistence (conductor-oss | |
| # PR #1255) and the agent runtime on by default from 3.32.0-rc.8 onward. | |
| # The Agentspan server JAR is no longer used as a server flavor here; the | |
| # Agentspan CLI binary is still downloaded (its own separate pin) since the | |
| # CLI/skill e2e suites still drive it against this same server. | |
| # Port of the Python SDK's proven agent-e2e workflow; JS deltas only | |
| # (Node toolchain, jest runner; Python kept solely for mcp-testkit). | |
| # | |
| # These tests call real LLMs via the OPENAI_API_KEY / ANTHROPIC_API_KEY | |
| # repo secrets. Fork PRs cannot see repo secrets, so for them the run | |
| # fails at the silently-empty guard rather than passing vacuously. | |
| on: [pull_request, workflow_dispatch] | |
| concurrency: | |
| group: agent-e2e-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| AGENTSPAN_CLI_VERSION: "0.4.4" # pinned CLI release — independent of the server | |
| CONDUCTOR_OSS_VERSION: "3.32.0-rc.8" # pinned conductor-oss release — bump deliberately | |
| jobs: | |
| agent-e2e: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| AGENTSPAN_SERVER_URL: http://localhost:8080/api | |
| AGENTSPAN_CLI_PATH: ${{ github.workspace }}/agentspan | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Set up Python (mcp-testkit only) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| # no `cache: pip` — it requires a requirements.txt/pyproject.toml | |
| # to key on, and this repo has neither (Python is mcp-testkit only) | |
| - name: Set up Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Cache conductor-oss server JAR | |
| id: conductor_oss_jar_cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: conductor-server-boot.jar | |
| key: conductor-server-${{ env.CONDUCTOR_OSS_VERSION }} | |
| - name: Download conductor-oss server JAR from Maven Central | |
| if: steps.conductor_oss_jar_cache.outputs.cache-hit != 'true' | |
| run: | | |
| curl -sfL "https://repo1.maven.org/maven2/org/conductoross/conductor-server/${CONDUCTOR_OSS_VERSION}/conductor-server-${CONDUCTOR_OSS_VERSION}-boot.jar" \ | |
| --output conductor-server-boot.jar | |
| # Agentspan CLI (CLI/skill suites) — kept on its own separate pin, | |
| # independent of which server JAR is under test. | |
| - name: Download CLI binary from release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release download "v${AGENTSPAN_CLI_VERSION}" --repo agentspan-ai/agentspan \ | |
| --pattern "agentspan_linux_amd64" --output agentspan | |
| chmod +x agentspan | |
| - name: Install SDK deps and build | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Install mcp-testkit | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install mcp-testkit | |
| - name: Start mcp-testkit | |
| run: | | |
| mcp-testkit --transport http --port 3001 & | |
| sleep 2 | |
| - name: Start server | |
| run: | | |
| java -jar conductor-server-boot.jar --server.port=8080 > server.log 2>&1 & | |
| - name: Wait for server health | |
| run: | | |
| for i in $(seq 1 45); do | |
| if curl -sf http://localhost:8080/health | grep -q '"healthy"[[:space:]]*:[[:space:]]*true'; then | |
| echo "server healthy after ~$((i*2))s"; exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "::error::server failed to become healthy within 90s" | |
| tail -100 server.log | |
| exit 1 | |
| - name: Run e2e suites | |
| run: | | |
| mkdir -p results | |
| npm run test:agent-e2e | |
| # The TS suites fail hard when the server is down (no session-skip), | |
| # so this guard is defense-in-depth against a future gate regression | |
| # or a secrets-less fork run skipping everything. | |
| - name: Guard against silently-empty runs | |
| if: always() | |
| run: | | |
| python - <<'EOF' | |
| import glob | |
| import sys | |
| import xml.etree.ElementTree as ET | |
| total = executed = 0 | |
| for path in glob.glob("results/junit-*.xml"): | |
| root = ET.parse(path).getroot() | |
| for suite in root.iter("testsuite"): | |
| t = int(suite.get("tests", 0)) | |
| sk = int(suite.get("skipped", 0)) | |
| total += t | |
| executed += t - sk | |
| print(f"executed {executed}/{total} tests") | |
| sys.exit(0 if executed > 0 else 1) | |
| EOF | |
| - name: Generate HTML report | |
| if: always() | |
| run: | | |
| npx tsx e2e/generate-report.ts results/junit-e2e.xml results/report.html || true | |
| - name: Upload results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: agent-e2e-results | |
| path: | | |
| results/ | |
| server.log | |
| retention-days: 14 |