# E2E Tests Workflow (Reorganized: Security Isolation + Parallel Sharding)
#
# Architecture: 15 Total Jobs
#   - 3 Security Enforcement Jobs (1 shard per browser, serial execution, 30min timeout)
#   - 12 Non-Security Jobs (4 shards per browser, parallel execution, 20min timeout)
#
# Problem Solved: Cross-shard contamination from security middleware state changes
# Solution: Isolate security enforcement tests in dedicated jobs with Cerberus enabled,
#           run all other tests with Cerberus OFF to prevent ACL/rate limit interference
#
# See docs/implementation/E2E_TEST_REORGANIZATION_IMPLEMENTATION.md for full details

name: 'E2E Tests'

on:
  workflow_call:
    inputs:
      browser:
        description: 'Browser to test'
        required: false
        default: 'all'
        type: string
      test_category:
        description: 'Test category'
        required: false
        default: 'all'
        type: string
      image_ref:
        description: 'Image reference (digest) to test, e.g. docker.io/wikid82/charon@sha256:...'
        required: false
        type: string
      image_tag:
        description: 'Local image tag for compose usage (default: charon:e2e-test)'
        required: false
        type: string
      playwright_coverage:
        description: 'Enable Playwright coverage (V8)'
        required: false
        default: false
        type: boolean
    secrets:
      CHARON_EMERGENCY_TOKEN:
        required: false
      DOCKERHUB_USERNAME:
        required: false
      DOCKERHUB_TOKEN:
        required: false
  workflow_dispatch:
    inputs:
      browser:
        description: 'Browser to test'
        required: false
        default: 'all'
        type: choice
        options:
          - chromium
          - firefox
          - webkit
          - all
      test_category:
        description: 'Test category'
        required: false
        default: 'all'
        type: choice
        options:
          - all
          - security
          - non-security
      image_ref:
        description: 'Image reference (digest) to test, e.g. docker.io/wikid82/charon@sha256:...'
        required: false
        type: string
      image_tag:
        description: 'Local image tag for compose usage (default: charon:e2e-test)'
        required: false
        type: string
      playwright_coverage:
        description: 'Enable Playwright coverage (V8)'
        required: false
        default: false
        type: boolean
  pull_request:

env:
  NODE_VERSION: '20'
  GO_VERSION: '1.26.2'
  GOTOOLCHAIN: auto
  DOCKERHUB_REGISTRY: docker.io
  IMAGE_NAME: ${{ github.repository_owner }}/charon
  E2E_BROWSER: ${{ inputs.browser || 'all' }}
  E2E_TEST_CATEGORY: ${{ inputs.test_category || 'all' }}
  PLAYWRIGHT_COVERAGE: ${{ (inputs.playwright_coverage && '1') || (vars.PLAYWRIGHT_COVERAGE || '0') }}
  DEBUG: 'charon:*,charon-test:*'
  PLAYWRIGHT_DEBUG: '1'
  CI_LOG_LEVEL: 'verbose'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # Prepare application image once, share across all browser jobs
  build:
    name: Prepare Application Image
    runs-on: ubuntu-latest
    outputs:
      image_source: ${{ steps.resolve-image.outputs.image_source }}
      image_ref: ${{ steps.resolve-image.outputs.image_ref }}
      image_tag: ${{ steps.resolve-image.outputs.image_tag }}
      image_digest: ${{ steps.resolve-image.outputs.image_digest != '' && steps.resolve-image.outputs.image_digest || steps.build-image.outputs.digest }}
    steps:
      - name: Resolve image inputs
        id: resolve-image
        run: |
          IMAGE_REF="${{ inputs.image_ref }}"
          IMAGE_TAG="${{ inputs.image_tag || 'charon:e2e-test' }}"
          if [ -n "$IMAGE_REF" ]; then
            {
              echo "image_source=registry"
              echo "image_ref=$IMAGE_REF"
              echo "image_tag=$IMAGE_TAG"
              if [[ "$IMAGE_REF" == *@* ]]; then
                echo "image_digest=${IMAGE_REF#*@}"
              else
                echo "image_digest="
              fi
            } >> "$GITHUB_OUTPUT"
            exit 0
          fi
          {
            echo "image_source=build"
            echo "image_ref="
            echo "image_tag=$IMAGE_TAG"
            echo "image_digest="
          } >> "$GITHUB_OUTPUT"

      - name: Checkout repository
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Go
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
        with:
          go-version: ${{ env.GO_VERSION }}

          cache: true
          cache-dependency-path: backend/go.sum

      - name: Set up Node.js
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Cache npm dependencies
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('package-lock.json') }}
          restore-keys: npm-

      - name: Install dependencies
        if: steps.resolve-image.outputs.image_source == 'build'
        run: npm ci

      - name: Set up Docker Buildx
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4

      - name: Build Docker image
        id: build-image
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
        with:
          context: .
          file: ./Dockerfile
          push: false
          load: true
          tags: ${{ steps.resolve-image.outputs.image_tag }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Save Docker image
        if: steps.resolve-image.outputs.image_source == 'build'
        run: docker save ${{ steps.resolve-image.outputs.image_tag }} -o charon-e2e-image.tar

      - name: Upload Docker image artifact
        if: steps.resolve-image.outputs.image_source == 'build'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-image
          path: charon-e2e-image.tar
          retention-days: 1

  # ==================================================================================
  # SECURITY ENFORCEMENT TESTS (3 jobs: 1 per browser, serial execution)
  # ==================================================================================
  # These tests enable Cerberus middleware and verify security enforcement
  # Run serially to avoid cross-test contamination from global state changes
  # ==================================================================================

  e2e-chromium-security:
    name: E2E Chromium (Security Enforcement)
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'chromium' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "true"  # Cerberus ON for enforcement tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'


      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Validate Emergency Token Configuration
        run: |
          echo "🔐 Validating emergency token configuration..."
          if [ -z "$CHARON_EMERGENCY_TOKEN" ]; then
            echo "::error title=Missing Secret::CHARON_EMERGENCY_TOKEN secret not configured"
            exit 1
          fi
          TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN}
          if [ "$TOKEN_LENGTH" -lt 64 ]; then
            echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters"
            exit 1
          fi
          MASKED_TOKEN="${CHARON_EMERGENCY_TOKEN:0:8}...${CHARON_EMERGENCY_TOKEN: -4}"
          echo "::notice::Emergency token validated (length: $TOKEN_LENGTH, preview: $MASKED_TOKEN)"
        env:
          CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Security Tests Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml --profile security-tests up -d
          echo "✅ Container started for Chromium security enforcement tests"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium
        run: |
          echo "📦 Installing Chromium..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run Chromium Security Enforcement Tests
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "Chromium Security Enforcement Tests"
          echo "Cerberus: ENABLED"
          echo "Execution: SERIAL (no sharding)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=chromium \
            --output=playwright-output/security-chromium \
            tests/security-enforcement/ \
            tests/security/ \
            tests/integration/multi-feature-workflows.spec.ts || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "Chromium Security Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true

      - name: Upload HTML report (Chromium Security)
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report-chromium-security
          path: playwright-report/
          retention-days: 14

      - name: Upload Chromium Security coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-coverage-chromium-security
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: traces-chromium-security
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-diagnostics-chromium-security
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-chromium-security.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-logs-chromium-security
          path: docker-logs-chromium-security.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  e2e-firefox-security:
    name: E2E Firefox (Security Enforcement)
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'firefox' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "true"  # Cerberus ON for enforcement tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'


      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Validate Emergency Token Configuration
        run: |
          echo "🔐 Validating emergency token configuration..."
          if [ -z "$CHARON_EMERGENCY_TOKEN" ]; then
            echo "::error title=Missing Secret::CHARON_EMERGENCY_TOKEN secret not configured"
            exit 1
          fi
          TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN}
          if [ "$TOKEN_LENGTH" -lt 64 ]; then
            echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters"
            exit 1
          fi
          MASKED_TOKEN="${CHARON_EMERGENCY_TOKEN:0:8}...${CHARON_EMERGENCY_TOKEN: -4}"
          echo "::notice::Emergency token validated (length: $TOKEN_LENGTH, preview: $MASKED_TOKEN)"
        env:
          CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Security Tests Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml --profile security-tests up -d
          echo "✅ Container started for Firefox security enforcement tests"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium (required by security-tests dependency)
        run: |
          echo "📦 Installing Chromium (required by security-tests dependency)..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Install Playwright Firefox
        run: |
          echo "📦 Installing Firefox..."
          npx playwright install --with-deps firefox
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run Firefox Security Enforcement Tests
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "Firefox Security Enforcement Tests"
          echo "Cerberus: ENABLED"
          echo "Execution: SERIAL (no sharding)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=firefox \
            --output=playwright-output/security-firefox \
            tests/security-enforcement/ \
            tests/security/ \
            tests/integration/multi-feature-workflows.spec.ts || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "Firefox Security Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true

      - name: Upload HTML report (Firefox Security)
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report-firefox-security
          path: playwright-report/
          retention-days: 14

      - name: Upload Firefox Security coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-coverage-firefox-security
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: traces-firefox-security
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-diagnostics-firefox-security
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-firefox-security.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-logs-firefox-security
          path: docker-logs-firefox-security.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  e2e-webkit-security:
    name: E2E WebKit (Security Enforcement)
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'webkit' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "true"  # Cerberus ON for enforcement tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'


      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Validate Emergency Token Configuration
        run: |
          echo "🔐 Validating emergency token configuration..."
          if [ -z "$CHARON_EMERGENCY_TOKEN" ]; then
            echo "::error title=Missing Secret::CHARON_EMERGENCY_TOKEN secret not configured"
            exit 1
          fi
          TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN}
          if [ "$TOKEN_LENGTH" -lt 64 ]; then
            echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters"
            exit 1
          fi
          MASKED_TOKEN="${CHARON_EMERGENCY_TOKEN:0:8}...${CHARON_EMERGENCY_TOKEN: -4}"
          echo "::notice::Emergency token validated (length: $TOKEN_LENGTH, preview: $MASKED_TOKEN)"
        env:
          CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Security Tests Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml --profile security-tests up -d
          echo "✅ Container started for WebKit security enforcement tests"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium (required by security-tests dependency)
        run: |
          echo "📦 Installing Chromium (required by security-tests dependency)..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Install Playwright WebKit
        run: |
          echo "📦 Installing WebKit..."
          npx playwright install --with-deps webkit
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run WebKit Security Enforcement Tests
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "WebKit Security Enforcement Tests"
          echo "Cerberus: ENABLED"
          echo "Execution: SERIAL (no sharding)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=webkit \
            --output=playwright-output/security-webkit \
            tests/security-enforcement/ \
            tests/security/ \
            tests/integration/multi-feature-workflows.spec.ts || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "WebKit Security Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true

      - name: Upload HTML report (WebKit Security)
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report-webkit-security
          path: playwright-report/
          retention-days: 14

      - name: Upload WebKit Security coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-coverage-webkit-security
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: traces-webkit-security
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-diagnostics-webkit-security
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-webkit-security.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-logs-webkit-security
          path: docker-logs-webkit-security.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  # ==================================================================================
  # NON-SECURITY TESTS (12 jobs: 4 shards × 3 browsers, parallel execution)
  # ====================================================================================================
  # These tests run with Cerberus DISABLED to prevent ACL/rate limit interference
  # Sharded for performance: 4 shards per browser for faster execution
  # ==================================================================================

  e2e-chromium:
    name: E2E Chromium (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'chromium' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'non-security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "false"  # Cerberus OFF for non-security tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
        total-shards: [4]

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Preflight disk diagnostics (before cleanup)
        run: |
          echo "Disk usage before cleanup"
          df -h
          docker system df || true

      - name: Preflight cleanup (best effort)
        run: |
          echo "Best-effort cleanup for CI runner"
          docker system prune -af || true
          rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
          rm -f docker-logs-*.txt charon-e2e-image.tar || true

      - name: Preflight disk diagnostics and threshold gate
        run: |
          set -euo pipefail
          MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
          echo "Disk usage after cleanup"
          df -h
          docker system df || true

          WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
          FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
          FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')

          echo "Free bytes on /: $FREE_ROOT_BYTES"
          echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"

          if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
            echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
            exit 42
          fi

      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Non-Security Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml up -d
          echo "✅ Container started for Chromium non-security tests (Cerberus OFF)"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium
        run: |
          echo "📦 Installing Chromium..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run Chromium Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "Chromium Non-Security Tests - Shard ${{ matrix.shard }}/${{ matrix.total-shards }}"
          echo "Cerberus: DISABLED"
          echo "Execution: PARALLEL (sharded)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=chromium \
            --shard=${{ matrix.shard }}/${{ matrix.total-shards }} \
            --output=playwright-output/chromium-shard-${{ matrix.shard }} \
            tests/core \
            tests/dns-provider-crud.spec.ts \
            tests/dns-provider-types.spec.ts \
            tests/integration \
            tests/manual-dns-provider.spec.ts \
            tests/monitoring \
            tests/settings \
            tests/tasks || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "Chromium Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true
          TEST_WORKER_INDEX: ${{ matrix.shard }}

      - name: Upload HTML report (Chromium shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report-chromium-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

      - name: Upload Playwright output (Chromium shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-output-chromium-shard-${{ matrix.shard }}
          path: playwright-output/chromium-shard-${{ matrix.shard }}/
          retention-days: 7

      - name: Upload Chromium coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-coverage-chromium-shard-${{ matrix.shard }}
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: traces-chromium-shard-${{ matrix.shard }}
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-diagnostics-chromium-shard-${{ matrix.shard }}
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-chromium-shard-${{ matrix.shard }}.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-logs-chromium-shard-${{ matrix.shard }}
          path: docker-logs-chromium-shard-${{ matrix.shard }}.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  e2e-firefox:
    name: E2E Firefox (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'firefox' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'non-security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "false"  # Cerberus OFF for non-security tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
        total-shards: [4]

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Preflight disk diagnostics (before cleanup)
        run: |
          echo "Disk usage before cleanup"
          df -h
          docker system df || true

      - name: Preflight cleanup (best effort)
        run: |
          echo "Best-effort cleanup for CI runner"
          docker system prune -af || true
          rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
          rm -f docker-logs-*.txt charon-e2e-image.tar || true

      - name: Preflight disk diagnostics and threshold gate
        run: |
          set -euo pipefail
          MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
          echo "Disk usage after cleanup"
          df -h
          docker system df || true

          WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
          FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
          FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')

          echo "Free bytes on /: $FREE_ROOT_BYTES"
          echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"

          if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
            echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
            exit 42
          fi

      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Non-Security Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml up -d
          echo "✅ Container started for Firefox non-security tests (Cerberus OFF)"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium (required by security-tests dependency)
        run: |
          echo "📦 Installing Chromium (required by security-tests dependency)..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Install Playwright Firefox
        run: |
          echo "📦 Installing Firefox..."
          npx playwright install --with-deps firefox
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run Firefox Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "Firefox Non-Security Tests - Shard ${{ matrix.shard }}/${{ matrix.total-shards }}"
          echo "Cerberus: DISABLED"
          echo "Execution: PARALLEL (sharded)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=firefox \
            --shard=${{ matrix.shard }}/${{ matrix.total-shards }} \
            --output=playwright-output/firefox-shard-${{ matrix.shard }} \
            tests/core \
            tests/dns-provider-crud.spec.ts \
            tests/dns-provider-types.spec.ts \
            tests/integration \
            tests/manual-dns-provider.spec.ts \
            tests/monitoring \
            tests/settings \
            tests/tasks || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "Firefox Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true
          TEST_WORKER_INDEX: ${{ matrix.shard }}

      - name: Upload HTML report (Firefox shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report-firefox-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

      - name: Upload Playwright output (Firefox shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-output-firefox-shard-${{ matrix.shard }}
          path: playwright-output/firefox-shard-${{ matrix.shard }}/
          retention-days: 7

      - name: Upload Firefox coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-coverage-firefox-shard-${{ matrix.shard }}
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: traces-firefox-shard-${{ matrix.shard }}
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: e2e-diagnostics-firefox-shard-${{ matrix.shard }}
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-firefox-shard-${{ matrix.shard }}.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docker-logs-firefox-shard-${{ matrix.shard }}
          path: docker-logs-firefox-shard-${{ matrix.shard }}.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  e2e-webkit:
    name: E2E WebKit (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
    runs-on: ubuntu-latest
    needs: build
    if: |
      ((inputs.browser || 'all') == 'webkit' || (inputs.browser || 'all') == 'all') &&
      ((inputs.test_category || 'all') == 'non-security' || (inputs.test_category || 'all') == 'all')
    timeout-minutes: 60
    env:
      CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
      CHARON_EMERGENCY_SERVER_ENABLED: "true"
      CHARON_SECURITY_TESTS_ENABLED: "false"  # Cerberus OFF for non-security tests
      CHARON_E2E_IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
        total-shards: [4]

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ github.sha }}

      - name: Set up Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Preflight disk diagnostics (before cleanup)
        run: |
          echo "Disk usage before cleanup"
          df -h
          docker system df || true

      - name: Preflight cleanup (best effort)
        run: |
          echo "Best-effort cleanup for CI runner"
          docker system prune -af || true
          rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
          rm -f docker-logs-*.txt charon-e2e-image.tar || true

      - name: Preflight disk diagnostics and threshold gate
        run: |
          set -euo pipefail
          MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
          echo "Disk usage after cleanup"
          df -h
          docker system df || true

          WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
          FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
          FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')

          echo "Free bytes on /: $FREE_ROOT_BYTES"
          echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"

          if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
            echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
            exit 42
          fi

      - name: Log in to Docker Hub
        if: needs.build.outputs.image_source == 'registry'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Pull shared Docker image
        if: needs.build.outputs.image_source == 'registry'
        run: |
          docker pull "${{ needs.build.outputs.image_ref }}"
          docker tag "${{ needs.build.outputs.image_ref }}" "${{ needs.build.outputs.image_tag }}"
          docker images | grep charon

      - name: Download Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: docker-image

      - name: Load Docker image artifact
        if: needs.build.outputs.image_source == 'build'
        run: |
          docker load -i charon-e2e-image.tar
          docker images | grep charon

      - name: Generate ephemeral encryption key
        run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV"

      - name: Start test environment (Non-Security Profile)
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml up -d
          echo "✅ Container started for WebKit non-security tests (Cerberus OFF)"

      - name: Wait for service health
        run: |
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if curl -sf http://127.0.0.1:8080/api/v1/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              curl -s http://127.0.0.1:8080/api/v1/health | jq .
              exit 0
            fi
            sleep 2
          done
          echo "❌ Health check failed"
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
          exit 1

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Chromium (required by security-tests dependency)
        run: |
          echo "📦 Installing Chromium (required by security-tests dependency)..."
          npx playwright install --with-deps chromium
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Install Playwright WebKit
        run: |
          echo "📦 Installing WebKit..."
          npx playwright install --with-deps webkit
          EXIT_CODE=$?
          echo "✅ Install command completed (exit code: $EXIT_CODE)"
          exit "$EXIT_CODE"

      - name: Run WebKit Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
        run: |
          set -euo pipefail
          STATUS=0
          echo "════════════════════════════════════════════"
          echo "WebKit Non-Security Tests - Shard ${{ matrix.shard }}/${{ matrix.total-shards }}"
          echo "Cerberus: DISABLED"
          echo "Execution: PARALLEL (sharded)"
          echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          echo "════════════════════════════════════════════"

          SHARD_START=$(date +%s)
          echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV"

          npx playwright test \
            --project=webkit \
            --shard=${{ matrix.shard }}/${{ matrix.total-shards }} \
            --output=playwright-output/webkit-shard-${{ matrix.shard }} \
            tests/core \
            tests/dns-provider-crud.spec.ts \
            tests/dns-provider-types.spec.ts \
            tests/integration \
            tests/manual-dns-provider.spec.ts \
            tests/monitoring \
            tests/settings \
            tests/tasks || STATUS=$?

          SHARD_END=$(date +%s)
          echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV"
          SHARD_DURATION=$((SHARD_END - SHARD_START))
          echo "════════════════════════════════════════════"
          echo "WebKit Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s"
          echo "════════════════════════════════════════════"
          echo "PLAYWRIGHT_STATUS=$STATUS" >> "$GITHUB_ENV"
          exit "$STATUS"
        env:
          PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
          CI: true
          TEST_WORKER_INDEX: ${{ matrix.shard }}

      - name: Upload HTML report (WebKit shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: playwright-report-webkit-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

      - name: Upload Playwright output (WebKit shard ${{ matrix.shard }})
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: playwright-output-webkit-shard-${{ matrix.shard }}
          path: playwright-output/webkit-shard-${{ matrix.shard }}/
          retention-days: 7

      - name: Upload WebKit coverage (if enabled)
        if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: e2e-coverage-webkit-shard-${{ matrix.shard }}
          path: coverage/e2e/
          retention-days: 7

      - name: Upload test traces on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: traces-webkit-shard-${{ matrix.shard }}
          path: test-results/**/*.zip
          retention-days: 7

      - name: Collect diagnostics
        if: always()
        run: |
          mkdir -p diagnostics
          uptime > diagnostics/uptime.txt
          free -m > diagnostics/free-m.txt
          df -h > diagnostics/df-h.txt
          ps aux > diagnostics/ps-aux.txt
          docker ps -a > diagnostics/docker-ps.txt || true
          docker logs --tail 500 charon-e2e > diagnostics/docker-charon-e2e.log 2>&1 || true

      - name: Upload diagnostics
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: e2e-diagnostics-webkit-shard-${{ matrix.shard }}
          path: diagnostics/
          retention-days: 7

      - name: Collect Docker logs on failure
        if: failure()
        run: |
          docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-webkit-shard-${{ matrix.shard }}.txt 2>&1

      - name: Upload Docker logs on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: docker-logs-webkit-shard-${{ matrix.shard }}
          path: docker-logs-webkit-shard-${{ matrix.shard }}.txt
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true

  # Test summary job
  test-summary:
    name: E2E Test Summary
    runs-on: ubuntu-latest
    needs: [e2e-chromium-security, e2e-firefox-security, e2e-webkit-security, e2e-chromium, e2e-firefox, e2e-webkit]
    if: always()

    steps:
      - name: Generate job summary
        run: |
          {
            echo "## 📊 E2E Test Results (Split: Security + Sharded)"
            echo ""
            echo "### Architecture: 15 Total Jobs"
            echo ""
            echo "#### Security Enforcement (3 jobs)"
            echo "| Browser | Status | Shards | Timeout | Cerberus |"
            echo "|---------|--------|--------|---------|----------|"
            echo "| Chromium | ${{ needs.e2e-chromium-security.result }} | 1 | 30min | ON |"
            echo "| Firefox | ${{ needs.e2e-firefox-security.result }} | 1 | 30min | ON |"
            echo "| WebKit | ${{ needs.e2e-webkit-security.result }} | 1 | 30min | ON |"
            echo ""
            echo "#### Non-Security Tests (12 jobs)"
            echo "| Browser | Status | Shards | Timeout | Cerberus |"
            echo "|---------|--------|--------|---------|----------|"
            echo "| Chromium | ${{ needs.e2e-chromium.result }} | 4 | 20min | OFF |"
            echo "| Firefox | ${{ needs.e2e-firefox.result }} | 4 | 20min | OFF |"
            echo "| WebKit | ${{ needs.e2e-webkit.result }} | 4 | 20min | OFF |"
            echo ""
            echo "### Benefits"
            echo ""
            echo "- ✅ **Isolation:** Security tests run independently without ACL/rate limit interference"
            echo "- ✅ **Performance:** Non-security tests sharded 4-way for faster execution"
            echo "- ✅ **Reliability:** Cerberus OFF by default prevents cross-shard contamination"
            echo "- ✅ **Clarity:** Separate artifacts for security vs non-security test results"
          } >> "$GITHUB_STEP_SUMMARY"

  # Final status check
  e2e-results:
    name: E2E Test Results (Final)
    runs-on: ubuntu-latest
    needs: [e2e-chromium-security, e2e-firefox-security, e2e-webkit-security, e2e-chromium, e2e-firefox, e2e-webkit]
    if: always()

    steps:
      - name: Check test results
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          EFFECTIVE_BROWSER: ${{ inputs.browser || 'all' }}
          EFFECTIVE_CATEGORY: ${{ inputs.test_category || 'all' }}
          NEEDS_JSON: ${{ toJson(needs) }}
        with:
          script: |
            const needs = JSON.parse(process.env.NEEDS_JSON || '{}');
            const effectiveBrowser = process.env.EFFECTIVE_BROWSER || 'all';
            const effectiveCategory = process.env.EFFECTIVE_CATEGORY || 'all';

            const shouldRunSecurity = effectiveCategory === 'security' || effectiveCategory === 'all';
            const shouldRunNonSecurity = effectiveCategory === 'non-security' || effectiveCategory === 'all';

            const shouldRun = {
              chromiumSecurity: (effectiveBrowser === 'chromium' || effectiveBrowser === 'all') && shouldRunSecurity,
              firefoxSecurity: (effectiveBrowser === 'firefox' || effectiveBrowser === 'all') && shouldRunSecurity,
              webkitSecurity: (effectiveBrowser === 'webkit' || effectiveBrowser === 'all') && shouldRunSecurity,
              chromium: (effectiveBrowser === 'chromium' || effectiveBrowser === 'all') && shouldRunNonSecurity,
              firefox: (effectiveBrowser === 'firefox' || effectiveBrowser === 'all') && shouldRunNonSecurity,
              webkit: (effectiveBrowser === 'webkit' || effectiveBrowser === 'all') && shouldRunNonSecurity,
            };

            const results = {
              chromiumSecurity: needs['e2e-chromium-security']?.result || 'skipped',
              firefoxSecurity: needs['e2e-firefox-security']?.result || 'skipped',
              webkitSecurity: needs['e2e-webkit-security']?.result || 'skipped',
              chromium: needs['e2e-chromium']?.result || 'skipped',
              firefox: needs['e2e-firefox']?.result || 'skipped',
              webkit: needs['e2e-webkit']?.result || 'skipped',
            };

            core.info('Security Enforcement Results:');
            core.info(`  Chromium Security: ${results.chromiumSecurity}`);
            core.info(`  Firefox Security: ${results.firefoxSecurity}`);
            core.info(`  WebKit Security: ${results.webkitSecurity}`);
            core.info('');
            core.info('Non-Security Results:');
            core.info(`  Chromium: ${results.chromium}`);
            core.info(`  Firefox: ${results.firefox}`);
            core.info(`  WebKit: ${results.webkit}`);

            const failures = [];
            const invalidResults = new Set(['skipped', 'failure', 'cancelled']);

            const labels = {
              chromiumSecurity: 'Chromium Security',
              firefoxSecurity: 'Firefox Security',
              webkitSecurity: 'WebKit Security',
              chromium: 'Chromium',
              firefox: 'Firefox',
              webkit: 'WebKit',
            };

            for (const [key, shouldRunJob] of Object.entries(shouldRun)) {
              const result = results[key];
              if (shouldRunJob && invalidResults.has(result)) {
                failures.push(`${labels[key]} expected to run but result was ${result}`);
              }
            }

            if (failures.length > 0) {
              core.error('One or more expected browser jobs did not succeed:');
              failures.forEach((failure) => core.error(`- ${failure}`));
              core.setFailed('Expected E2E jobs did not complete successfully.');
            } else {
              core.info('All expected browser tests succeeded');
            }
