- Updated quality-checks.yml to support manual dispatch with frontend checks. - Modified rate-limit-integration.yml to remove workflow_run triggers and adjust conditions for execution. - Removed pull request triggers from repo-health.yml, retaining only scheduled and manual dispatch. - Adjusted security-pr.yml and supply-chain-pr.yml to eliminate workflow_run dependencies and refine execution conditions. - Cleaned up supply-chain-verify.yml by removing workflow_run triggers and ensuring proper execution conditions. - Updated waf-integration.yml to remove workflow_run triggers, allowing manual dispatch only. - Revised current_spec.md to reflect the consolidation of CI workflows into a single pipeline, detailing objectives, research findings, and implementation plans.
1363 lines
55 KiB
YAML
1363 lines
55 KiB
YAML
# 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
|
||
|
||
env:
|
||
NODE_VERSION: '20'
|
||
GO_VERSION: '1.25.7'
|
||
GOTOOLCHAIN: auto
|
||
DOCKERHUB_REGISTRY: docker.io
|
||
IMAGE_NAME: ${{ github.repository_owner }}/charon
|
||
PLAYWRIGHT_COVERAGE: ${{ (inputs.playwright_coverage && '1') || (vars.PLAYWRIGHT_COVERAGE || '0') }}
|
||
DEBUG: 'charon:*,charon-test:*'
|
||
PLAYWRIGHT_DEBUG: '1'
|
||
CI_LOG_LEVEL: 'verbose'
|
||
|
||
concurrency:
|
||
group: e2e-split-${{ github.workflow }}-${{ github.ref_name }}-${{ github.run_id }}
|
||
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" >> "$GITHUB_OUTPUT"
|
||
echo "image_ref=$IMAGE_REF" >> "$GITHUB_OUTPUT"
|
||
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||
if [[ "$IMAGE_REF" == *@* ]]; then
|
||
echo "image_digest=${IMAGE_REF#*@}" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "image_digest=" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
exit 0
|
||
fi
|
||
echo "image_source=build" >> "$GITHUB_OUTPUT"
|
||
echo "image_ref=" >> "$GITHUB_OUTPUT"
|
||
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||
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@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # 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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Cache npm dependencies
|
||
if: steps.resolve-image.outputs.image_source == 'build'
|
||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # 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@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||
|
||
- name: Build Docker image
|
||
id: build-image
|
||
if: steps.resolve-image.outputs.image_source == 'build'
|
||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'chromium' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'security' || inputs.test_category == 'all')
|
||
timeout-minutes: 30
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 \
|
||
tests/security-enforcement/ \
|
||
tests/security/
|
||
|
||
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 "════════════════════════════════════════════"
|
||
env:
|
||
PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
|
||
CI: true
|
||
|
||
- name: Upload HTML report (Chromium Security)
|
||
if: always()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-chromium-security
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload Chromium Security coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: e2e-coverage-chromium-security
|
||
path: coverage/e2e/
|
||
retention-days: 7
|
||
|
||
- name: Upload test traces on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-chromium-security
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'firefox' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'security' || inputs.test_category == 'all')
|
||
timeout-minutes: 30
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 \
|
||
tests/security-enforcement/ \
|
||
tests/security/
|
||
|
||
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 "════════════════════════════════════════════"
|
||
env:
|
||
PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
|
||
CI: true
|
||
|
||
- name: Upload HTML report (Firefox Security)
|
||
if: always()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-firefox-security
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload Firefox Security coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: e2e-coverage-firefox-security
|
||
path: coverage/e2e/
|
||
retention-days: 7
|
||
|
||
- name: Upload test traces on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-firefox-security
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'webkit' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'security' || inputs.test_category == 'all')
|
||
timeout-minutes: 30
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 \
|
||
tests/security-enforcement/ \
|
||
tests/security/
|
||
|
||
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 "════════════════════════════════════════════"
|
||
env:
|
||
PLAYWRIGHT_BASE_URL: http://127.0.0.1:8080
|
||
CI: true
|
||
|
||
- name: Upload HTML report (WebKit Security)
|
||
if: always()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-webkit-security
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload WebKit Security coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: e2e-coverage-webkit-security
|
||
path: coverage/e2e/
|
||
retention-days: 7
|
||
|
||
- name: Upload test traces on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-webkit-security
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'chromium' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'non-security' || inputs.test_category == 'all')
|
||
timeout-minutes: 20
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 }} \
|
||
tests/core \
|
||
tests/dns-provider-crud.spec.ts \
|
||
tests/dns-provider-types.spec.ts \
|
||
tests/emergency-server \
|
||
tests/integration \
|
||
tests/manual-dns-provider.spec.ts \
|
||
tests/monitoring \
|
||
tests/settings \
|
||
tests/tasks
|
||
|
||
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 "════════════════════════════════════════════"
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-chromium-shard-${{ matrix.shard }}
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload Chromium coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-chromium-shard-${{ matrix.shard }}
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'firefox' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'non-security' || inputs.test_category == 'all')
|
||
timeout-minutes: 20
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 }} \
|
||
tests/core \
|
||
tests/dns-provider-crud.spec.ts \
|
||
tests/dns-provider-types.spec.ts \
|
||
tests/emergency-server \
|
||
tests/integration \
|
||
tests/manual-dns-provider.spec.ts \
|
||
tests/monitoring \
|
||
tests/settings \
|
||
tests/tasks
|
||
|
||
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 "════════════════════════════════════════════"
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-firefox-shard-${{ matrix.shard }}
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload Firefox coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-firefox-shard-${{ matrix.shard }}
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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 == 'webkit' || inputs.browser == 'all') &&
|
||
(inputs.test_category == 'non-security' || inputs.test_category == 'all')
|
||
timeout-minutes: 20
|
||
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@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Log in to Docker Hub
|
||
if: needs.build.outputs.image_source == 'registry' && secrets.DOCKERHUB_TOKEN != ''
|
||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||
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: |
|
||
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 }} \
|
||
tests/core \
|
||
tests/dns-provider-crud.spec.ts \
|
||
tests/dns-provider-types.spec.ts \
|
||
tests/emergency-server \
|
||
tests/integration \
|
||
tests/manual-dns-provider.spec.ts \
|
||
tests/monitoring \
|
||
tests/settings \
|
||
tests/tasks
|
||
|
||
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 "════════════════════════════════════════════"
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: playwright-report-webkit-shard-${{ matrix.shard }}
|
||
path: playwright-report/
|
||
retention-days: 14
|
||
|
||
- name: Upload WebKit coverage (if enabled)
|
||
if: always() && env.PLAYWRIGHT_COVERAGE == '1'
|
||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
with:
|
||
name: traces-webkit-shard-${{ matrix.shard }}
|
||
path: test-results/**/*.zip
|
||
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||
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)" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### Architecture: 15 Total Jobs" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "#### Security Enforcement (3 jobs)" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Browser | Status | Shards | Timeout | Cerberus |" >> $GITHUB_STEP_SUMMARY
|
||
echo "|---------|--------|--------|---------|----------|" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Chromium | ${{ needs.e2e-chromium-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Firefox | ${{ needs.e2e-firefox-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| WebKit | ${{ needs.e2e-webkit-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "#### Non-Security Tests (12 jobs)" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Browser | Status | Shards | Timeout | Cerberus |" >> $GITHUB_STEP_SUMMARY
|
||
echo "|---------|--------|--------|---------|----------|" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Chromium | ${{ needs.e2e-chromium.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Firefox | ${{ needs.e2e-firefox.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| WebKit | ${{ needs.e2e-webkit.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### Benefits" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ✅ **Isolation:** Security tests run independently without ACL/rate limit interference" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ✅ **Performance:** Non-security tests sharded 4-way for faster execution" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ✅ **Reliability:** Cerberus OFF by default prevents cross-shard contamination" >> $GITHUB_STEP_SUMMARY
|
||
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
|
||
run: |
|
||
CHROMIUM_SEC="${{ needs.e2e-chromium-security.result }}"
|
||
FIREFOX_SEC="${{ needs.e2e-firefox-security.result }}"
|
||
WEBKIT_SEC="${{ needs.e2e-webkit-security.result }}"
|
||
CHROMIUM="${{ needs.e2e-chromium.result }}"
|
||
FIREFOX="${{ needs.e2e-firefox.result }}"
|
||
WEBKIT="${{ needs.e2e-webkit.result }}"
|
||
|
||
echo "Security Enforcement Results:"
|
||
echo " Chromium Security: $CHROMIUM_SEC"
|
||
echo " Firefox Security: $FIREFOX_SEC"
|
||
echo " WebKit Security: $WEBKIT_SEC"
|
||
echo ""
|
||
echo "Non-Security Results:"
|
||
echo " Chromium: $CHROMIUM"
|
||
echo " Firefox: $FIREFOX"
|
||
echo " WebKit: $WEBKIT"
|
||
|
||
# Allow skipped jobs (workflow_dispatch with specific browser/category)
|
||
if [[ "$CHROMIUM_SEC" == "skipped" ]]; then CHROMIUM_SEC="success"; fi
|
||
if [[ "$FIREFOX_SEC" == "skipped" ]]; then FIREFOX_SEC="success"; fi
|
||
if [[ "$WEBKIT_SEC" == "skipped" ]]; then WEBKIT_SEC="success"; fi
|
||
if [[ "$CHROMIUM" == "skipped" ]]; then CHROMIUM="success"; fi
|
||
if [[ "$FIREFOX" == "skipped" ]]; then FIREFOX="success"; fi
|
||
if [[ "$WEBKIT" == "skipped" ]]; then WEBKIT="success"; fi
|
||
|
||
if [[ "$CHROMIUM_SEC" == "success" && "$FIREFOX_SEC" == "success" && "$WEBKIT_SEC" == "success" && \
|
||
"$CHROMIUM" == "success" && "$FIREFOX" == "success" && "$WEBKIT" == "success" ]]; then
|
||
echo "✅ All browser tests passed or were skipped"
|
||
exit 0
|
||
else
|
||
echo "❌ One or more browser tests failed"
|
||
exit 1
|
||
fi
|