fix(ci): use correct label name in screenshots workflow #4638
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: E2E Tests | |
| # Runs automatically on PRs. Uses mock SDK (no real API key needed). | |
| on: | |
| pull_request: | |
| branches: [ main, master ] | |
| push: | |
| branches: [ main ] | |
| env: | |
| KIND_VERSION: "v0.27.0" | |
| permissions: | |
| contents: read | |
| actions: write | |
| concurrency: | |
| group: e2e-tests-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| detect-changes: | |
| # Only run for same-repo PRs (not forks) and pushes to main. | |
| # Fork PRs build and execute untrusted code in the CI runner environment. | |
| if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| e2e-relevant: ${{ steps.filter.outputs.e2e-relevant }} | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| backend: ${{ steps.filter.outputs.backend }} | |
| operator: ${{ steps.filter.outputs.operator }} | |
| claude-runner: ${{ steps.filter.outputs.claude-runner }} | |
| api-server: ${{ steps.filter.outputs.api-server }} | |
| steps: | |
| - name: Checkout PR code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Check for component changes | |
| uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4 | |
| id: filter | |
| with: | |
| filters: | | |
| e2e-relevant: | |
| - 'components/**' | |
| - 'e2e/**' | |
| - '.github/workflows/e2e.yml' | |
| frontend: | |
| - 'components/frontend/**' | |
| backend: | |
| - 'components/backend/**' | |
| operator: | |
| - 'components/operator/**' | |
| claude-runner: | |
| - 'components/runners/**' | |
| api-server: | |
| - 'components/ambient-api-server/**' | |
| e2e: | |
| name: End-to-End Tests | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: | | |
| (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push') | |
| && needs.detect-changes.outputs.e2e-relevant == 'true' | |
| timeout-minutes: 25 | |
| steps: | |
| - name: Checkout PR code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Cleanup Diskspace | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL | |
| sudo docker image prune -af 2>/dev/null || true | |
| - name: Validate AGENTS.md symlink | |
| run: | | |
| echo "Validating AGENTS.md -> CLAUDE.md symlink..." | |
| [ -L AGENTS.md ] || (echo "AGENTS.md is not a symlink" && exit 1) | |
| [ "$(readlink AGENTS.md)" = "CLAUDE.md" ] || (echo "AGENTS.md points to wrong target" && exit 1) | |
| [ -f CLAUDE.md ] || (echo "CLAUDE.md does not exist" && exit 1) | |
| diff -q AGENTS.md CLAUDE.md > /dev/null || (echo "AGENTS.md content differs from CLAUDE.md" && exit 1) | |
| echo "AGENTS.md symlink is valid" | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: e2e/package-lock.json | |
| - name: Install Cypress dependencies | |
| working-directory: e2e | |
| run: npm ci | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| with: | |
| driver-opts: network=host | |
| - name: Build or pull frontend image | |
| if: needs.detect-changes.outputs.frontend == 'true' | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: components/frontend | |
| file: components/frontend/Dockerfile | |
| load: true | |
| tags: quay.io/ambient_code/vteam_frontend:e2e-test | |
| cache-from: | | |
| type=gha,scope=frontend-amd64 | |
| type=gha,scope=e2e-frontend | |
| cache-to: type=gha,mode=max,scope=e2e-frontend | |
| - name: Pull unchanged images (parallel) | |
| run: | | |
| pull_pids=() | |
| PULL_FRONTEND="${{ needs.detect-changes.outputs.frontend }}" | |
| PULL_BACKEND="${{ needs.detect-changes.outputs.backend }}" | |
| PULL_OPERATOR="${{ needs.detect-changes.outputs.operator }}" | |
| PULL_RUNNER="${{ needs.detect-changes.outputs.claude-runner }}" | |
| if [ "$PULL_FRONTEND" != "true" ]; then | |
| (docker pull quay.io/ambient_code/vteam_frontend:latest && \ | |
| docker tag quay.io/ambient_code/vteam_frontend:latest quay.io/ambient_code/vteam_frontend:e2e-test) & | |
| pull_pids+=($!) | |
| fi | |
| if [ "$PULL_BACKEND" != "true" ]; then | |
| (docker pull quay.io/ambient_code/vteam_backend:latest && \ | |
| docker tag quay.io/ambient_code/vteam_backend:latest quay.io/ambient_code/vteam_backend:e2e-test) & | |
| pull_pids+=($!) | |
| fi | |
| if [ "$PULL_OPERATOR" != "true" ]; then | |
| (docker pull quay.io/ambient_code/vteam_operator:latest && \ | |
| docker tag quay.io/ambient_code/vteam_operator:latest quay.io/ambient_code/vteam_operator:e2e-test) & | |
| pull_pids+=($!) | |
| fi | |
| if [ "$PULL_RUNNER" != "true" ]; then | |
| (docker pull quay.io/ambient_code/vteam_claude_runner:latest && \ | |
| docker tag quay.io/ambient_code/vteam_claude_runner:latest quay.io/ambient_code/vteam_claude_runner:e2e-test) & | |
| pull_pids+=($!) | |
| fi | |
| # api-server is always built from branch code (no pull fallback) | |
| # because RBAC e2e tests require the branch's middleware | |
| for pid in "${pull_pids[@]}"; do | |
| wait "$pid" || exit 1 | |
| done | |
| echo "All unchanged images pulled" | |
| - name: Build changed backend image | |
| if: needs.detect-changes.outputs.backend == 'true' | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: components/backend | |
| file: components/backend/Dockerfile | |
| load: true | |
| tags: quay.io/ambient_code/vteam_backend:e2e-test | |
| cache-from: | | |
| type=gha,scope=backend-amd64 | |
| type=gha,scope=e2e-backend | |
| cache-to: type=gha,mode=max,scope=e2e-backend | |
| - name: Build changed operator image | |
| if: needs.detect-changes.outputs.operator == 'true' | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: components/operator | |
| file: components/operator/Dockerfile | |
| load: true | |
| tags: quay.io/ambient_code/vteam_operator:e2e-test | |
| cache-from: | | |
| type=gha,scope=operator-amd64 | |
| type=gha,scope=e2e-operator | |
| cache-to: type=gha,mode=max,scope=e2e-operator | |
| - name: Build changed ambient-runner image | |
| if: needs.detect-changes.outputs.claude-runner == 'true' | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: components/runners/ambient-runner | |
| file: components/runners/ambient-runner/Dockerfile | |
| load: true | |
| tags: quay.io/ambient_code/vteam_claude_runner:e2e-test | |
| cache-from: | | |
| type=gha,scope=ambient-runner-amd64 | |
| type=gha,scope=e2e-ambient-runner | |
| cache-to: type=gha,mode=max,scope=e2e-ambient-runner | |
| - name: Build api-server image | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: components/ambient-api-server | |
| file: components/ambient-api-server/Dockerfile | |
| load: true | |
| tags: quay.io/ambient_code/vteam_api_server:e2e-test | |
| cache-from: | | |
| type=gha,scope=api-server-amd64 | |
| type=gha,scope=e2e-api-server | |
| cache-to: type=gha,mode=max,scope=e2e-api-server | |
| - name: Show built images | |
| run: docker images | grep e2e-test | |
| - name: Cache kind binary | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: ~/k8s-tools/kind | |
| key: kind-${{ runner.os }}-${{ env.KIND_VERSION }} | |
| - name: Install kind | |
| run: | | |
| mkdir -p ~/k8s-tools | |
| if [[ ! -f ~/k8s-tools/kind ]]; then | |
| echo "Downloading kind $KIND_VERSION..." | |
| curl -sLo ~/k8s-tools/kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64" | |
| chmod +x ~/k8s-tools/kind | |
| else | |
| echo "Using cached kind" | |
| fi | |
| sudo cp ~/k8s-tools/kind /usr/local/bin/kind | |
| kind version | |
| - name: Setup kind cluster | |
| working-directory: e2e | |
| run: | | |
| chmod +x scripts/*.sh | |
| ./scripts/setup-kind.sh | |
| - name: Load images into kind cluster | |
| run: | | |
| echo "Saving and loading all images into kind via archive..." | |
| docker save \ | |
| quay.io/ambient_code/vteam_frontend:e2e-test \ | |
| quay.io/ambient_code/vteam_backend:e2e-test \ | |
| quay.io/ambient_code/vteam_operator:e2e-test \ | |
| quay.io/ambient_code/vteam_claude_runner:e2e-test \ | |
| quay.io/ambient_code/vteam_api_server:e2e-test \ | |
| | kind load image-archive /dev/stdin --name ambient-local | |
| echo "All images loaded into kind cluster" | |
| - name: Deploy vTeam (mock SDK mode) | |
| working-directory: e2e | |
| env: | |
| IMAGE_FRONTEND: quay.io/ambient_code/vteam_frontend:e2e-test | |
| IMAGE_BACKEND: quay.io/ambient_code/vteam_backend:e2e-test | |
| IMAGE_OPERATOR: quay.io/ambient_code/vteam_operator:e2e-test | |
| IMAGE_RUNNER: quay.io/ambient_code/vteam_claude_runner:e2e-test | |
| DEFAULT_API_SERVER_IMAGE: quay.io/ambient_code/vteam_api_server:e2e-test | |
| run: ./scripts/deploy.sh | |
| - name: Verify deployment | |
| run: | | |
| echo "Checking pods..." | |
| kubectl get pods -n ambient-code | |
| echo "" | |
| echo "Checking services..." | |
| kubectl get svc -n ambient-code | |
| - name: Run Cypress E2E tests (legacy auth) | |
| working-directory: e2e | |
| run: ./scripts/run-tests.sh | |
| - name: Toggle SSO auth mode | |
| run: | | |
| # Verify Keycloak is healthy before toggling (backend needs it for OIDC discovery) | |
| echo "Verifying Keycloak is ready..." | |
| kubectl wait --for=condition=available --timeout=60s deployment/keycloak -n ambient-code | |
| # Enable SSO on frontend | |
| kubectl set env deployment/frontend -n ambient-code SSO_ENABLED=true NEXT_PUBLIC_SSO_ENABLED=true | |
| # Enable SSO feature flag in Unleash | |
| UNLEASH_ADMIN_TOKEN=$(kubectl get secret unleash-credentials -n ambient-code -o jsonpath='{.data.admin-api-token}' | base64 -d) | |
| kubectl port-forward -n ambient-code svc/unleash 4242:4242 & | |
| PF=$! | |
| sleep 3 | |
| # Create flag if it doesn't exist, then enable | |
| curl -sf -X POST "http://localhost:4242/api/admin/projects/default/features" \ | |
| -H "Authorization: $UNLEASH_ADMIN_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"name":"sso-authentication","type":"release"}' 2>/dev/null || true | |
| curl -sf -X POST "http://localhost:4242/api/admin/projects/default/features/sso-authentication/environments/development/strategies" \ | |
| -H "Authorization: $UNLEASH_ADMIN_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"name":"default","parameters":{}}' 2>/dev/null || true | |
| curl -sf -X POST "http://localhost:4242/api/admin/projects/default/features/sso-authentication/environments/development/on" \ | |
| -H "Authorization: $UNLEASH_ADMIN_TOKEN" 2>/dev/null || true | |
| kill $PF 2>/dev/null || true | |
| # Wait for frontend rollout | |
| kubectl rollout status deployment/frontend -n ambient-code --timeout=60s | |
| # Restart backend to pick up flag change faster | |
| kubectl rollout restart deployment/backend-api -n ambient-code | |
| kubectl rollout status deployment/backend-api -n ambient-code --timeout=60s | |
| # Verify backend JWT validator initialized (OIDC discovery reached Keycloak) | |
| echo "Verifying backend SSO initialization..." | |
| for i in $(seq 1 15); do | |
| if kubectl logs -n ambient-code -l app=backend-api --tail=50 2>/dev/null | grep -q "SSO: JWT validator initialized"; then | |
| echo "Backend JWT validator initialized" | |
| break | |
| fi | |
| if [ "$i" -eq 15 ]; then | |
| echo "WARNING: Backend JWT validator may not have initialized" | |
| kubectl logs -n ambient-code -l app=backend-api --tail=20 | grep -i sso || true | |
| fi | |
| sleep 2 | |
| done | |
| - name: Run Cypress E2E tests (SSO auth) | |
| working-directory: e2e | |
| env: | |
| E2E_USE_SSO: "true" | |
| run: | | |
| # Re-extract token using Keycloak client_credentials | |
| ./scripts/extract-token.sh | |
| # Verify frontend is healthy after SSO toggle before running tests | |
| echo "Waiting for frontend to be ready with SSO..." | |
| for i in $(seq 1 30); do | |
| if curl -sf -o /dev/null http://localhost/api/version 2>/dev/null; then | |
| echo "Frontend ready" | |
| break | |
| fi | |
| if [ "$i" -eq 30 ]; then | |
| echo "Frontend not ready after 60s" | |
| kubectl logs -n ambient-code -l app=frontend --tail=20 || true | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| ./scripts/run-tests.sh | |
| - name: Run RBAC enforcement E2E tests | |
| run: | | |
| # Port-forward api-server and keycloak for RBAC tests. | |
| # The test script (Phase 0.5) handles all deployment patching, | |
| # JWT/authz enablement, rollout, and port-forward re-establishment. | |
| kubectl port-forward -n ambient-code svc/ambient-api-server 13592:8000 & | |
| kubectl port-forward -n ambient-code svc/keycloak-service 18592:8080 & | |
| sleep 3 | |
| API_URL=http://localhost:13592/api/ambient/v1 \ | |
| KC_URL=http://localhost:18592 \ | |
| bash components/ambient-api-server/test/e2e/rbac_e2e_test.sh | |
| - name: Upload test results | |
| if: failure() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 | |
| with: | |
| name: cypress-screenshots-pr-${{ github.event.pull_request.number }} | |
| path: e2e/cypress/screenshots | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| - name: Upload test videos | |
| if: failure() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 | |
| with: | |
| name: cypress-videos-pr-${{ github.event.pull_request.number }} | |
| path: e2e/cypress/videos | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| - name: Debug logs on failure | |
| if: failure() | |
| run: | | |
| echo "=== Frontend logs ===" | |
| kubectl logs -n ambient-code -l app=frontend --tail=100 || true | |
| echo "" | |
| echo "=== Backend logs ===" | |
| kubectl logs -n ambient-code -l app=backend-api --tail=100 || true | |
| echo "" | |
| echo "=== Operator logs ===" | |
| kubectl logs -n ambient-code -l app=agentic-operator --tail=100 || true | |
| - name: Cleanup | |
| if: always() | |
| working-directory: e2e | |
| run: | | |
| CLEANUP_ARTIFACTS=true ./scripts/cleanup.sh || true |