name: Deploy to Test Environment on: push: branches: [ develop ] workflow_dispatch: inputs: branch: description: 'Branch to deploy' required: true default: 'develop' env: NODE_VERSION: '22' TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }} TEST_SERVER_USER: ${{ secrets.TEST_SERVER_USER }} TEST_SERVER_PATH: ${{ secrets.TEST_SERVER_PATH }} TEST_APP_URL: ${{ secrets.TEST_APP_URL }} jobs: # ============================================ # Build & Test # ============================================ build: name: Build Application runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.branch || github.ref }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Generate version id: version run: | VERSION="test-$(date +'%Y%m%d-%H%M%S')-${GITHUB_SHA::7}" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Version: $VERSION" - name: Install dependencies run: | npm ci cd frontend && npm ci cd ../backend && npm ci - name: Run backend tests working-directory: backend run: npm test -- --passWithNoTests continue-on-error: true - name: Build frontend working-directory: frontend env: CI: false REACT_APP_ENV: test REACT_APP_VERSION: ${{ steps.version.outputs.version }} run: npm run build - name: Create deployment package run: | mkdir -p deploy-package cp -r frontend/build deploy-package/frontend-build cp -r backend deploy-package/backend cp ecosystem.config.js deploy-package/ cp package.json deploy-package/ rm -rf deploy-package/backend/node_modules echo "${{ steps.version.outputs.version }}" > deploy-package/VERSION - name: Upload deployment package uses: actions/upload-artifact@v7 with: name: deploy-package-test path: deploy-package retention-days: 3 # ============================================ # Deploy to Test Server # ============================================ deploy: name: Deploy to Test Server runs-on: ubuntu-latest needs: build environment: name: test url: ${{ env.TEST_APP_URL }} steps: - name: Download deployment package uses: actions/download-artifact@v8 with: name: deploy-package-test path: deploy-package - name: Setup SSH uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.TEST_SSH_PRIVATE_KEY }} - name: Add server to known hosts run: | mkdir -p ~/.ssh ssh-keyscan -H ${{ env.TEST_SERVER_HOST }} >> ~/.ssh/known_hosts - name: Deploy to test server run: | # Create deployment directory ssh ${{ env.TEST_SERVER_USER }}@${{ env.TEST_SERVER_HOST }} "mkdir -p ${{ env.TEST_SERVER_PATH }}/releases/${{ needs.build.outputs.version }}" # Upload deployment package scp -r deploy-package/* ${{ env.TEST_SERVER_USER }}@${{ env.TEST_SERVER_HOST }}:${{ env.TEST_SERVER_PATH }}/releases/${{ needs.build.outputs.version }}/ # Run deployment script on server ssh ${{ env.TEST_SERVER_USER }}@${{ env.TEST_SERVER_HOST }} << 'DEPLOY_SCRIPT' set -e DEPLOY_PATH="${{ env.TEST_SERVER_PATH }}" VERSION="${{ needs.build.outputs.version }}" RELEASE_PATH="$DEPLOY_PATH/releases/$VERSION" echo "=== Deploying version: $VERSION ===" # Install backend dependencies cd $RELEASE_PATH/backend npm ci --production # Copy environment file cp $DEPLOY_PATH/shared/.env $RELEASE_PATH/backend/.env # Copy frontend build to correct location mkdir -p $RELEASE_PATH/backend/../frontend mv $RELEASE_PATH/frontend-build $RELEASE_PATH/frontend/build # Update symlink ln -sfn $RELEASE_PATH $DEPLOY_PATH/current # Restart PM2 cd $DEPLOY_PATH/current pm2 restart ecosystem.config.js --env test || pm2 start ecosystem.config.js --env test # Cleanup old releases (keep last 5) cd $DEPLOY_PATH/releases ls -t | tail -n +6 | xargs -r rm -rf echo "=== Deployment complete ===" DEPLOY_SCRIPT # ============================================ # Post-Deploy Verification # ============================================ verify: name: Verify Deployment runs-on: ubuntu-latest needs: deploy steps: - name: Wait for server startup run: sleep 15 - name: Health check run: | MAX_RETRIES=5 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.TEST_APP_URL }}/api/health || echo "000") if [ "$HTTP_STATUS" = "200" ]; then echo "Health check passed!" exit 0 fi RETRY_COUNT=$((RETRY_COUNT + 1)) echo "Health check failed (HTTP $HTTP_STATUS), retry $RETRY_COUNT/$MAX_RETRIES..." sleep 10 done echo "Health check failed after $MAX_RETRIES retries" exit 1 - name: API smoke tests run: | echo "Testing API endpoints..." # Test health endpoint curl -f ${{ env.TEST_APP_URL }}/api/health # Test materials endpoint curl -f ${{ env.TEST_APP_URL }}/api/materials || true # Test smart packages endpoint curl -f ${{ env.TEST_APP_URL }}/api/smart-packages/ || true echo "Smoke tests completed!" - name: Notify deployment success if: success() run: | echo "Test deployment successful!" echo "Version: ${{ needs.build.outputs.version }}" echo "URL: ${{ env.TEST_APP_URL }}" - name: Notify deployment failure if: failure() run: | echo "Test deployment failed!" echo "Please check the logs for more details." exit 1 # ============================================ # Run E2E Tests on Test Environment # ============================================ e2e-tests: name: E2E Tests on Test Environment runs-on: ubuntu-latest needs: verify steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install test dependencies working-directory: tests run: npm ci - name: Install Playwright browsers working-directory: tests run: npx playwright install --with-deps chromium - name: Run Playwright tests working-directory: tests env: PLAYWRIGHT_BASE_URL: ${{ env.TEST_APP_URL }} run: npx playwright test --project=chromium --reporter=html continue-on-error: true - name: Upload test report uses: actions/upload-artifact@v7 if: always() # Artifact upload must never fail the job (storage quota can be full) continue-on-error: true with: name: e2e-test-report path: tests/playwright-report retention-days: 3 - name: Upload test screenshots uses: actions/upload-artifact@v7 if: failure() # Artifact upload must never fail the job (storage quota can be full) continue-on-error: true with: name: e2e-test-screenshots path: tests/test-results retention-days: 3