Files
Charon/.github/workflows/e2e-tests-split.yml.backup
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

1171 lines
47 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (Split - Security + Sharded)'
on:
workflow_run:
workflows: ["Docker Build, Publish & Test"]
types: [completed]
branches: [main, development, 'feature/**', 'hotfix/**']
pull_request:
branches: [main, development, 'feature/**', 'hotfix/**']
paths:
- 'frontend/**'
- 'backend/**'
- 'tests/**'
- 'playwright.config.js'
- '.github/workflows/e2e-tests-split.yml'
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
env:
NODE_VERSION: '20'
GO_VERSION: '1.25.6'
GOTOOLCHAIN: auto
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/charon
PLAYWRIGHT_COVERAGE: ${{ vars.PLAYWRIGHT_COVERAGE || '0' }}
DEBUG: 'charon:*,charon-test:*'
PLAYWRIGHT_DEBUG: '1'
CI_LOG_LEVEL: 'verbose'
concurrency:
group: e2e-split-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# Build application once, share across all browser jobs
build:
name: Build Application
runs-on: ubuntu-latest
outputs:
image_digest: ${{ steps.build-image.outputs.digest }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Go
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
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Cache npm dependencies
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Install dependencies
run: npm ci
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build Docker image
id: build-image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
file: ./Dockerfile
push: false
load: true
tags: charon:e2e-test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Save Docker image
run: docker save charon:e2e-test -o charon-e2e-image.tar
- name: Upload Docker image artifact
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'chromium' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'security' || github.event.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: charon:e2e-test
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
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
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/
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'firefox' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'security' || github.event.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: charon:e2e-test
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
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
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/
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'webkit' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'security' || github.event.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: charon:e2e-test
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
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
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/
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'chromium' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'non-security' || github.event.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: charon:e2e-test
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
total-shards: [4]
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: docker-image
- name: Load Docker image
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/security \
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'firefox' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'non-security' || github.event.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: charon:e2e-test
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
total-shards: [4]
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: docker-image
- name: Load Docker image
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 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/security \
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: |
(github.event_name != 'workflow_dispatch') ||
(github.event.inputs.browser == 'webkit' || github.event.inputs.browser == 'all') &&
(github.event.inputs.test_category == 'non-security' || github.event.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: charon:e2e-test
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
total-shards: [4]
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download Docker image
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: docker-image
- name: Load Docker image
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 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/security \
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