Detailed explanation of: - **Dependency Fix**: Added explicit Chromium installation to Firefox and WebKit security jobs. The authentication fixture depends on Chromium being present, even when testing other browsers, causing previous runs to fail setup. - **Workflow Isolation**: Explicitly routed `tests/security/` to the dedicated "Security Enforcement" jobs and removed them from the general shards. This prevents false negatives where security config tests fail because the middleware is intentionally disabled in standard test runs. - **Metadata**: Added `@security` tags to all security specs (`rate-limiting`, `waf-config`, etc.) to align metadata with the new execution strategy. - **References**: Fixes CI failures in PR
1191 lines
48 KiB
YAML
1191 lines
48 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:
|
||
push:
|
||
branches: [main, development, 'feature/**', 'hotfix/**']
|
||
paths:
|
||
- 'frontend/**'
|
||
- 'backend/**'
|
||
- 'tests/**'
|
||
- 'playwright.config.js'
|
||
- '.github/workflows/e2e-tests-split.yml'
|
||
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.7'
|
||
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/ \
|
||
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: |
|
||
(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/ \
|
||
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: |
|
||
(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/ \
|
||
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: |
|
||
(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/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 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: |
|
||
(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 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
|