name: ocelot.social end-to-end test CI on: push jobs: # Calculate smart content-based cache keys calculate_cache_keys: name: Calculate Smart Cache Keys runs-on: ubuntu-latest outputs: docker-key: ${{ steps.keys.outputs.docker-key }} deps-key: ${{ steps.keys.outputs.deps-key }} cypress-key: ${{ steps.keys.outputs.cypress-key }} steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Calculate content-based cache keys id: keys run: | # Docker infrastructure key (changes very rarely) DOCKER_FILES=$(find . -name "Dockerfile*" -o -name "docker-compose*.yml" -o -name ".dockerignore" 2>/dev/null | sort) if [ -n "$DOCKER_FILES" ]; then DOCKER_HASH=$(echo "$DOCKER_FILES" | xargs sha256sum | sha256sum | cut -d' ' -f1) else DOCKER_HASH="no-docker-files" fi DOCKER_KEY="docker-v2-$DOCKER_HASH" # Dependencies key (changes occasionally) DEPS_FILES=$(find . -name "package*.json" -o -name "yarn.lock" 2>/dev/null | sort) if [ -n "$DEPS_FILES" ]; then DEPS_HASH=$(echo "$DEPS_FILES" | xargs sha256sum | sha256sum | cut -d' ' -f1) else DEPS_HASH="no-deps-files" fi DEPS_KEY="deps-v2-$DEPS_HASH" # Cypress key (very stable) if [ -f "cypress.config.js" ]; then CYPRESS_HASH=$(sha256sum cypress.config.js | cut -d' ' -f1) else CYPRESS_HASH="no-cypress-config" fi CYPRESS_KEY="cypress-v2-$CYPRESS_HASH" echo "docker-key=$DOCKER_KEY" >> $GITHUB_OUTPUT echo "deps-key=$DEPS_KEY" >> $GITHUB_OUTPUT echo "cypress-key=$CYPRESS_KEY" >> $GITHUB_OUTPUT echo "๐Ÿ”‘ Smart cache keys generated:" echo " Docker: $DOCKER_KEY" echo " Dependencies: $DEPS_KEY" echo " Cypress: $CYPRESS_KEY" # Optimized backend environment preparation prepare_backend_environment: name: Fullstack | prepare backend environment needs: calculate_cache_keys runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Copy backend env file run: | cp backend/.env.test_e2e backend/.env cp webapp/.env.template webapp/.env # Set up Docker Buildx for faster builds - name: Set up Docker Buildx uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 with: driver-opts: | network=host # Smart Docker images cache - name: Restore Docker images cache id: docker-cache uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/docker-images/ key: ${{ needs.calculate_cache_keys.outputs.docker-key }} restore-keys: | docker-v2- docker- # Verify cached images and load them - name: Load and verify cached Docker images if: steps.docker-cache.outputs.cache-hit == 'true' run: | echo "๐Ÿ” Loading and verifying cached Docker images..." CACHE_VALID=true # Load each cached image with verification for img_file in /tmp/docker-images/*.tar; do if [ -f "$img_file" ]; then IMG_NAME=$(basename "$img_file" .tar) echo "Loading $IMG_NAME..." if docker load < "$img_file" 2>/dev/null; then # Quick integrity check if docker image inspect "$IMG_NAME" >/dev/null 2>&1; then echo "โœ… $IMG_NAME verified" else echo "โŒ $IMG_NAME failed inspection" CACHE_VALID=false break fi else echo "โŒ Failed to load $IMG_NAME" CACHE_VALID=false break fi fi done if [ "$CACHE_VALID" = "true" ]; then echo "โœ… All cached Docker images verified and loaded" echo "DOCKER_CACHE_VALID=true" >> $GITHUB_ENV else echo "โŒ Cache verification failed, will rebuild from scratch" docker system prune -f 2>/dev/null || true rm -rf /tmp/docker-images/ echo "DOCKER_CACHE_VALID=false" >> $GITHUB_ENV fi # Build images only if needed (cache miss or verification failed) - name: Build backend and dependencies with Buildx if: steps.docker-cache.outputs.cache-hit != 'true' || env.DOCKER_CACHE_VALID == 'false' run: | echo "๐Ÿ—๏ธ Building Docker images with optimizations..." mkdir -p /tmp/docker-images/ # Build with Docker Buildx for better caching and speed if docker buildx version >/dev/null 2>&1; then echo "Using Docker Buildx for optimized builds..." export DOCKER_BUILDKIT=1 export COMPOSE_DOCKER_CLI_BUILD=1 fi # Build and start all required images docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach neo4j backend --build # Save images efficiently echo "๐Ÿ’พ Saving built images..." docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" | gzip > /tmp/docker-images/backend.tar.gz docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" | gzip > /tmp/docker-images/neo4j.tar.gz docker save "quay.io/minio/minio:latest" | gzip > /tmp/docker-images/minio.tar.gz docker save "quay.io/minio/mc:latest" | gzip > /tmp/docker-images/minio-mc.tar.gz docker save "maildev/maildev:latest" | gzip > /tmp/docker-images/mailserver.tar.gz # Verify all images were saved successfully for img_file in /tmp/docker-images/*.tar.gz; do if [ ! -s "$img_file" ]; then echo "โŒ Failed to save $(basename $img_file)" exit 1 fi done echo "โœ… All Docker images built and compressed successfully" # Clean shutdown docker compose -f docker-compose.yml -f docker-compose.test.yml down # Save Docker images to cache - name: Save Docker images cache if: steps.docker-cache.outputs.cache-hit != 'true' || env.DOCKER_CACHE_VALID == 'false' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/docker-images/ key: ${{ needs.calculate_cache_keys.outputs.docker-key }} # Optimized webapp preparation prepare_webapp_image: name: Fullstack | prepare webapp image needs: calculate_cache_keys runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Copy backend env file run: | cp backend/.env.test_e2e backend/.env cp webapp/.env.template webapp/.env # Set up Docker Buildx - name: Set up Docker Buildx uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 # Smart webapp cache - name: Restore webapp image cache id: webapp-cache uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/webapp.tar.gz key: webapp-${{ needs.calculate_cache_keys.outputs.docker-key }} # Build webapp only if needed - name: Build webapp Docker image if: steps.webapp-cache.outputs.cache-hit != 'true' run: | echo "๐Ÿ—๏ธ Building webapp Docker image..." # Use Buildx if available if docker buildx version >/dev/null 2>&1; then export DOCKER_BUILDKIT=1 export COMPOSE_DOCKER_CLI_BUILD=1 fi docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach webapp --build --no-deps docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" | gzip > /tmp/webapp.tar.gz # Verify image was saved if [ ! -s "/tmp/webapp.tar.gz" ]; then echo "โŒ Failed to save webapp image" exit 1 fi echo "โœ… Webapp image built and compressed" # Save webapp cache - name: Save webapp cache if: steps.webapp-cache.outputs.cache-hit != 'true' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/webapp.tar.gz key: webapp-${{ needs.calculate_cache_keys.outputs.docker-key }} # Optimized Cypress preparation prepare_cypress: name: Fullstack | prepare cypress needs: calculate_cache_keys runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Setup Node.js uses: actions/setup-node@08f58d1471bff7f3a07d167b4ad7df25d5fcfcb6 # v4.4.0 with: node-version-file: 'backend/.tool-versions' cache: 'yarn' - name: Copy env files run: | cp webapp/.env.template webapp/.env cp backend/.env.test_e2e backend/.env # Comprehensive Cypress and build cache - name: Restore comprehensive build cache id: build-cache uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: | /opt/cucumber-json-formatter /home/runner/.cache/Cypress node_modules/ backend/node_modules/ backend/build/ webapp/node_modules/ webapp/.nuxt/ key: build-${{ needs.calculate_cache_keys.outputs.deps-key }}-${{ needs.calculate_cache_keys.outputs.cypress-key }} restore-keys: | build-${{ needs.calculate_cache_keys.outputs.deps-key }}- build- # Install and build everything efficiently - name: Smart install and build if: steps.build-cache.outputs.cache-hit != 'true' run: | echo "๐Ÿ“ฆ Smart dependency installation and building..." # Download cucumber formatter if not cached if [ ! -x "/opt/cucumber-json-formatter" ]; then echo "๐Ÿ“ฅ Downloading cucumber-json-formatter..." sudo wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386" sudo chmod +x /opt/cucumber-json-formatter else echo "โœ… Cucumber formatter already cached" fi # Install root dependencies if needed if [ ! -d "node_modules" ] || [ -z "$(ls -A node_modules 2>/dev/null)" ]; then echo "๐Ÿ“ฆ Installing root dependencies..." yarn install --frozen-lockfile --prefer-offline else echo "โœ… Root dependencies already cached" fi # Backend dependencies and build cd backend if [ ! -d "node_modules" ] || [ -z "$(ls -A node_modules 2>/dev/null)" ]; then echo "๐Ÿ“ฆ Installing backend dependencies..." yarn install --frozen-lockfile --prefer-offline fi if [ ! -d "build" ] || [ -z "$(ls -A build 2>/dev/null)" ]; then echo "๐Ÿ—๏ธ Building backend..." yarn build else echo "โœ… Backend build already cached" fi cd .. # Webapp dependencies (for completeness) cd webapp if [ ! -d "node_modules" ] || [ -z "$(ls -A node_modules 2>/dev/null)" ]; then echo "๐Ÿ“ฆ Installing webapp dependencies..." yarn install --frozen-lockfile --prefer-offline fi cd .. # Verify Cypress installation echo "๐Ÿงช Verifying Cypress..." if ! npx cypress verify; then echo "โš ๏ธ Cypress verify failed, attempting to install..." npx cypress install || echo "โš ๏ธ Cypress install failed, but continuing..." else echo "โœ… Cypress verified successfully" fi echo "โœ… All dependencies installed and built" # Save comprehensive cache - name: Save build cache if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: | /opt/cucumber-json-formatter /home/runner/.cache/Cypress node_modules/ backend/node_modules/ backend/build/ webapp/node_modules/ webapp/.nuxt/ key: build-${{ needs.calculate_cache_keys.outputs.deps-key }}-${{ needs.calculate_cache_keys.outputs.cypress-key }} # Ultra-fast test execution fullstack_tests: name: Fullstack | tests if: success() needs: [calculate_cache_keys, prepare_backend_environment, prepare_webapp_image, prepare_cypress] runs-on: ubuntu-latest env: jobs: 8 strategy: matrix: job: [1, 2, 3, 4, 5, 6, 7, 8] steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Setup Node.js uses: actions/setup-node@08f58d1471bff7f3a07d167b4ad7df25d5fcfcb6 # v4.4.0 with: node-version-file: 'backend/.tool-versions' cache: 'yarn' # Ultra-fast cache restoration - name: Restore Docker images cache id: docker-restore uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/docker-images/ key: ${{ needs.calculate_cache_keys.outputs.docker-key }} restore-keys: | docker-v2- docker- - name: Restore webapp cache id: webapp-restore uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: /tmp/webapp.tar.gz key: webapp-${{ needs.calculate_cache_keys.outputs.docker-key }} - name: Restore build cache id: build-restore uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 with: path: | /opt/cucumber-json-formatter /home/runner/.cache/Cypress node_modules/ backend/node_modules/ backend/build/ webapp/node_modules/ webapp/.nuxt/ key: build-${{ needs.calculate_cache_keys.outputs.deps-key }}-${{ needs.calculate_cache_keys.outputs.cypress-key }} restore-keys: | build-${{ needs.calculate_cache_keys.outputs.deps-key }}- build- # Lightning-fast environment setup - name: Lightning-fast environment setup run: | echo "โšก Lightning-fast environment setup starting..." # Ensure cucumber formatter is ready sudo chmod +x /opt/cucumber-json-formatter 2>/dev/null || true sudo ln -sf /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter 2>/dev/null || true # Quick load of all Docker images echo "๐Ÿ“ฆ Loading Docker images..." IMAGES_LOADED=0 # Load backend environment images if [ -d "/tmp/docker-images" ]; then for img_file in /tmp/docker-images/*.tar.gz; do if [ -f "$img_file" ]; then echo "Loading $(basename $img_file)..." if gunzip -c "$img_file" | docker load; then IMAGES_LOADED=$((IMAGES_LOADED + 1)) else echo "โš ๏ธ Failed to load $(basename $img_file), continuing..." fi fi done fi # Load webapp image if [ -f "/tmp/webapp.tar.gz" ]; then echo "Loading webapp image..." if gunzip -c /tmp/webapp.tar.gz | docker load; then IMAGES_LOADED=$((IMAGES_LOADED + 1)) fi fi echo "โœ… Loaded $IMAGES_LOADED Docker images" # Quick dependency check if [ ! -d "node_modules" ] && [ ! -d "backend/node_modules" ]; then echo "โš ๏ธ Dependencies not cached, doing quick install..." yarn install --frozen-lockfile --prefer-offline cd backend && yarn install --frozen-lockfile --prefer-offline && cd .. fi # Ultra-smart service startup - name: Ultra-smart service startup run: | echo "๐Ÿš€ Ultra-smart service startup..." # Start all services docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach backend mailserver webapp # Parallel health checks with intelligent timeouts echo "๐Ÿ” Intelligent service health checks..." # Load environment variables for health check URLs set -a [ -f backend/.env ] && source backend/.env [ -f webapp/.env ] && source webapp/.env set +a # Define health check function check_service() { local name="$1" local url="$2" local max_attempts="$3" for i in $(seq 1 $max_attempts); do if curl -sf --max-time 3 "$url" >/dev/null 2>&1; then echo "โœ… $name ready (attempt $i/$max_attempts)" return 0 fi [ $i -lt $max_attempts ] && sleep 1 done echo "โš ๏ธ $name not responding after $max_attempts attempts, but continuing..." return 0 # Never fail the pipeline } # Smart wait strategy: Start with minimal wait, then parallel health checks echo "โณ Brief stability wait (15s instead of 90s)..." sleep 15 # Parallel health checks with reasonable timeouts (check_service "backend" "${BACKEND_HEALTH:-http://localhost:4000/health}" 20) & (check_service "webapp" "${CLIENT_URI:-http://localhost:3000}" 20) & (check_service "mailserver" "http://localhost:1080" 10) & (check_service "minio" "http://localhost:9000/minio/health/live" 10) & # Wait for all health checks (with timeout) wait echo "๐ŸŽ‰ All services checked - environment ready!" # Execute tests with proper error handling - name: Execute E2E tests id: e2e-tests run: | echo "๐Ÿงช Starting E2E test execution (job ${{ matrix.job }}/${{ env.jobs }})..." # Ensure we're in the right directory and have dependencies if [ ! -x "cypress/parallel-features.sh" ]; then chmod +x cypress/parallel-features.sh 2>/dev/null || true fi # Execute tests for this matrix job yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }}) # Error reporting (unchanged) - name: Compile HTML report on test failure if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} run: | cd cypress/ node create-cucumber-html-report.js - name: Upload test report on failure if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ocelot-e2e-test-report-job-${{ matrix.job }} path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report # Smart cache cleanup cleanup_cache: name: Smart Cache Cleanup needs: fullstack_tests runs-on: ubuntu-latest continue-on-error: true if: always() steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 - name: Smart cache cleanup and monitoring run: | echo "๐Ÿงน Smart cache cleanup and monitoring..." # Monitor cache usage (informational) echo "๐Ÿ“Š Current cache status:" gh cache list | head -20 || echo "Could not list caches" # Cleanup strategy: Keep recent caches, remove very old ones # GitHub automatically handles cache limits, so we keep this minimal echo "โœ… Cache cleanup complete (GitHub manages cache limits automatically)" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}