Files
Charon/.github/workflows/e2e-tests-split.yml
GitHub Actions e7f791044d chore: Refactor CI workflows for pipeline consolidation and manual dispatch triggers
- 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.
2026-02-08 05:36:29 +00:00

1363 lines
55 KiB
YAML
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'
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