Raykao/sitoader agentic workflows #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Secret Santa - CI/CD Pipeline | |
| # ============================================================================ | |
| # Environment Strategy: | |
| # - PR: Ephemeral resource group per PR (auto-created and deleted) | |
| # - QA: Isolated resource group (`secretsanta-qa`) (deployed on main) | |
| # - Prod: Production environment (deployed on main after QA E2E tests pass) | |
| # | |
| # Note: Staging environments are ENABLED for GitHub Actions deployments. | |
| # When deploying via deployment token, the action requires staging support. | |
| # | |
| # Pipeline Flow: | |
| # 1. Build & Test (lint, build, unit tests) | |
| # 2. E2E Tests (runs against local dev server) | |
| # 3. Deploy Infrastructure (PR or QA) | |
| # 4. Deploy Application (Preview, QA, or Production) | |
| # 5. E2E Tests against QA (validates deployed environment) | |
| # 6. Deploy to Production (after QA E2E pass) | |
| # | |
| # Infrastructure is auto-provisioned with all environment variables configured: | |
| # - Cosmos DB connection | |
| # - Application Insights | |
| # - Azure Communication Services (if enabled) | |
| # ============================================================================ | |
| name: CI/CD | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| branches: [main] | |
| env: | |
| NODE_VERSION: '20' | |
| AZURE_LOCATION: 'centralus' | |
| PROJECT_NAME: 'secretsanta' | |
| AZURE_RESOURCE_GROUP: 'secretsanta' | |
| AZURE_QA_RESOURCE_GROUP: 'secretsanta-qa' | |
| permissions: | |
| id-token: write | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| # ========================================================================== | |
| # BUILD & TEST | |
| # ========================================================================== | |
| build: | |
| name: Build & Test | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' || github.event.action != 'closed' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: | | |
| npm ci | |
| cd api && npm ci | |
| - name: Lint | |
| run: npm run lint | |
| - name: Build frontend | |
| run: npm run build | |
| - name: Build API | |
| run: cd api && npm run build | |
| - name: Run API tests | |
| run: cd api && npm test | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build | |
| path: | | |
| dist/ | |
| api/dist/ | |
| retention-days: 1 | |
| # ========================================================================== | |
| # E2E TESTS (runs against local dev server) | |
| # ========================================================================== | |
| e2e: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name != 'pull_request' || github.event.action != 'closed' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Run E2E tests | |
| run: npx playwright test | |
| - name: Upload E2E test report | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: E2E Test Summary | |
| if: always() | |
| run: | | |
| echo "## 🎭 E2E Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f playwright-report/results.json ]; then | |
| # Parse results from JSON | |
| TOTAL=$(jq '.stats.expected + .stats.unexpected + .stats.flaky + .stats.skipped' playwright-report/results.json) | |
| PASSED=$(jq '.stats.expected' playwright-report/results.json) | |
| FAILED=$(jq '.stats.unexpected' playwright-report/results.json) | |
| FLAKY=$(jq '.stats.flaky' playwright-report/results.json) | |
| SKIPPED=$(jq '.stats.skipped' playwright-report/results.json) | |
| DURATION=$(jq '.stats.duration' playwright-report/results.json) | |
| DURATION_SEC=$(echo "scale=2; $DURATION / 1000" | bc) | |
| if [ "$FAILED" -eq 0 ]; then | |
| echo "### ✅ All Tests Passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ❌ Some Tests Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| ✅ Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ❌ Failed | $FAILED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⚠️ Flaky | $FLAKY |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⏭️ Skipped | $SKIPPED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "⏱️ **Duration:** ${DURATION_SEC}s" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # List failed tests if any | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.suites[].suites[]?.specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| jq -r '.suites[].specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo "❌ E2E tests may have failed. Check the logs above." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "📊 **Full Report:** Download the \`playwright-report\` artifact for detailed HTML report." >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # PR INFRASTRUCTURE DEPLOYMENT | |
| # ========================================================================== | |
| deploy-pr-infrastructure: | |
| name: Deploy PR Infrastructure | |
| runs-on: ubuntu-latest | |
| needs: e2e | |
| if: github.event_name == 'pull_request' && github.event.action != 'closed' | |
| outputs: | |
| resource_group: ${{ steps.rg.outputs.name }} | |
| static_web_app_token: ${{ steps.infra.outputs.deploymentToken }} | |
| static_web_app_url: ${{ steps.infra.outputs.staticWebAppUrl }} | |
| static_web_app_name: ${{ steps.infra.outputs.staticWebAppName }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Create Resource Group for PR | |
| id: rg | |
| run: | | |
| RG_NAME="${{ env.PROJECT_NAME }}-pr-${{ github.event.pull_request.number }}" | |
| echo "name=$RG_NAME" >> $GITHUB_OUTPUT | |
| az group create --name "$RG_NAME" --location "${{ env.AZURE_LOCATION }}" --tags "pr=${{ github.event.pull_request.number }}" "repo=${{ github.repository }}" | |
| echo "✅ Created resource group: $RG_NAME" | |
| - name: Validate PR Infrastructure | |
| run: | | |
| echo "Validating PR Bicep template..." | |
| az deployment group validate \ | |
| --resource-group ${{ steps.rg.outputs.name }} \ | |
| --template-file ./infra/main.bicep \ | |
| --parameters \ | |
| projectName=${{ env.PROJECT_NAME }} \ | |
| environment=pr \ | |
| prNumber=${{ github.event.pull_request.number }} \ | |
| staticWebAppSku=Free \ | |
| enableEmailService=false \ | |
| repositoryUrl=https://github.com/${{ github.repository }} \ | |
| repositoryBranch=${{ github.head_ref }} \ | |
| deploymentId=pr-${{ github.event.pull_request.number }} | |
| echo "✅ PR template validation passed" | |
| - name: Deploy Infrastructure | |
| id: infra | |
| uses: azure/arm-deploy@v2 | |
| with: | |
| scope: resourcegroup | |
| resourceGroupName: ${{ steps.rg.outputs.name }} | |
| template: ./infra/main.bicep | |
| parameters: > | |
| projectName=${{ env.PROJECT_NAME }} | |
| environment=pr | |
| prNumber=${{ github.event.pull_request.number }} | |
| staticWebAppSku=Free | |
| enableEmailService=false | |
| repositoryUrl=https://github.com/${{ github.repository }} | |
| repositoryBranch=${{ github.head_ref }} | |
| deploymentId=pr-${{ github.event.pull_request.number }} | |
| deploymentMode: Incremental | |
| failOnStdErr: true | |
| - name: Output Infrastructure Details | |
| run: | | |
| echo "## 🏗️ PR Infrastructure Deployed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource Group | \`${{ steps.rg.outputs.name }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Static Web App | ${{ steps.infra.outputs.staticWebAppUrl }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Cosmos DB | ${{ steps.infra.outputs.cosmosAccountName }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| App Insights | ${{ steps.infra.outputs.appInsightsName }} |" >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # PR PREVIEW DEPLOYMENT | |
| # ========================================================================== | |
| preview: | |
| name: Deploy Preview | |
| runs-on: ubuntu-latest | |
| needs: deploy-pr-infrastructure | |
| if: github.event_name == 'pull_request' && github.event.action != 'closed' | |
| outputs: | |
| preview_url: ${{ steps.deploy.outputs.static_web_app_url }} | |
| environment: | |
| name: pr | |
| url: ${{ steps.deploy.outputs.static_web_app_url }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build | |
| - name: Deploy to Preview | |
| id: deploy | |
| uses: Azure/static-web-apps-deploy@v1 | |
| with: | |
| azure_static_web_apps_api_token: ${{ needs.deploy-pr-infrastructure.outputs.static_web_app_token }} | |
| repo_token: ${{ secrets.GITHUB_TOKEN }} | |
| action: upload | |
| app_location: dist | |
| api_location: api | |
| output_location: . | |
| skip_app_build: true | |
| skip_api_build: false | |
| # ========================================================================== | |
| # E2E TESTS AGAINST PR PREVIEW ENVIRONMENT | |
| # ========================================================================== | |
| e2e-pr: | |
| name: E2E Tests (Preview) | |
| runs-on: ubuntu-latest | |
| needs: [preview] | |
| if: github.event_name == 'pull_request' && github.event.action != 'closed' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Wait for preview deployment to stabilize | |
| run: | | |
| echo "Waiting 30 seconds for preview deployment to stabilize..." | |
| sleep 30 | |
| - name: Run E2E tests against Preview | |
| env: | |
| BASE_URL: ${{ needs.preview.outputs.preview_url }} | |
| run: | | |
| echo "Running E2E tests against Preview: $BASE_URL" | |
| npx playwright test | |
| - name: Upload Preview E2E test report | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: playwright-report-preview | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: Preview E2E Test Summary | |
| if: always() | |
| run: | | |
| echo "## 🎭 Preview E2E Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tested URL:** ${{ needs.preview.outputs.preview_url }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f playwright-report/results.json ]; then | |
| # Parse results from JSON | |
| TOTAL=$(jq '.stats.expected + .stats.unexpected + .stats.flaky + .stats.skipped' playwright-report/results.json) | |
| PASSED=$(jq '.stats.expected' playwright-report/results.json) | |
| FAILED=$(jq '.stats.unexpected' playwright-report/results.json) | |
| FLAKY=$(jq '.stats.flaky' playwright-report/results.json) | |
| SKIPPED=$(jq '.stats.skipped' playwright-report/results.json) | |
| DURATION=$(jq '.stats.duration' playwright-report/results.json) | |
| DURATION_SEC=$(echo "scale=2; $DURATION / 1000" | bc) | |
| if [ "$FAILED" -eq 0 ]; then | |
| echo "### ✅ All Tests Passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ❌ Some Tests Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| ✅ Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ❌ Failed | $FAILED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⚠️ Flaky | $FLAKY |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⏭️ Skipped | $SKIPPED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "⏱️ **Duration:** ${DURATION_SEC}s" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # List failed tests if any | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.suites[].suites[]?.specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| jq -r '.suites[].specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo "❌ Preview E2E tests may have failed. Check the logs above." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "📊 **Full Report:** Download the \`playwright-report-preview\` artifact for detailed HTML report." >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # CLOSE PR - DELETE INFRASTRUCTURE | |
| # ========================================================================== | |
| close-preview: | |
| name: Close Preview & Delete Infrastructure | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
| steps: | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Delete Resource Group | |
| run: | | |
| RG_NAME="${{ env.PROJECT_NAME }}-pr-${{ github.event.pull_request.number }}" | |
| echo "🗑️ Deleting resource group: $RG_NAME" | |
| az group delete --name "$RG_NAME" --yes --no-wait | |
| echo "✅ Resource group deletion initiated (running in background)" | |
| - name: Comment PR | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const rg = '${{ env.PROJECT_NAME }}-pr-${{ github.event.pull_request.number }}'; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `## 🧹 Preview Environment Cleaned Up\n\nResource group \`${rg}\` is being deleted.\n\n_All Azure resources for this PR have been removed._` | |
| }); | |
| # ========================================================================== | |
| # DEPLOY QA INFRASTRUCTURE (Isolated resource group) | |
| # ========================================================================== | |
| deploy-qa-infrastructure: | |
| name: Deploy QA Infrastructure | |
| runs-on: ubuntu-latest | |
| needs: e2e | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| outputs: | |
| resource_group: ${{ steps.rg.outputs.name }} | |
| static_web_app_url: ${{ steps.infra.outputs.staticWebAppUrl }} | |
| static_web_app_name: ${{ steps.infra.outputs.staticWebAppName }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Create/Verify QA Resource Group | |
| id: rg | |
| run: | | |
| RG_NAME="${{ env.AZURE_QA_RESOURCE_GROUP }}" | |
| echo "name=$RG_NAME" >> $GITHUB_OUTPUT | |
| az group create --name "$RG_NAME" --location "${{ env.AZURE_LOCATION }}" --tags "environment=qa" "project=${{ env.PROJECT_NAME }}" | |
| echo "✅ QA resource group ready: $RG_NAME" | |
| - name: Validate QA Infrastructure | |
| id: validate | |
| run: | | |
| echo "Validating Bicep template for QA environment..." | |
| az deployment group validate \ | |
| --resource-group ${{ steps.rg.outputs.name }} \ | |
| --template-file ./infra/main.bicep \ | |
| --parameters ./infra/parameters.qa.json deploymentId=qa-stable | |
| echo "✅ Template validation passed" | |
| - name: Deploy QA Infrastructure | |
| id: infra | |
| uses: azure/arm-deploy@v2 | |
| with: | |
| scope: resourcegroup | |
| resourceGroupName: ${{ steps.rg.outputs.name }} | |
| template: ./infra/main.bicep | |
| parameters: ./infra/parameters.qa.json deploymentId=qa-stable | |
| deploymentMode: Incremental | |
| failOnStdErr: true | |
| - name: Verify QA Resources | |
| run: | | |
| echo "Verifying QA resources in dedicated resource group..." | |
| RG="${{ steps.rg.outputs.name }}" | |
| COSMOS=$(az cosmosdb show --name "${{ steps.infra.outputs.cosmosAccountName }}" --resource-group "$RG" 2>/dev/null && echo "✅ Cosmos DB (Free Tier)" || echo "❌ Cosmos DB missing") | |
| SWA=$(az staticwebapp show --name "${{ steps.infra.outputs.staticWebAppName }}" --resource-group "$RG" 2>/dev/null && echo "✅ Static Web App (Free)" || echo "❌ Static Web App missing") | |
| INSIGHTS=$(az monitor app-insights component show --app "${{ steps.infra.outputs.appInsightsName }}" --resource-group "$RG" 2>/dev/null && echo "✅ App Insights" || echo "❌ App Insights missing") | |
| echo "$COSMOS" | |
| echo "$SWA" | |
| echo "$INSIGHTS" | |
| echo "" | |
| echo "📋 QA deployment complete - isolated environment ready" | |
| - name: QA Infrastructure Summary | |
| run: | | |
| echo "## 🏗️ QA Infrastructure Deployed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource Group | \`${{ steps.rg.outputs.name }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Static Web App | ${{ steps.infra.outputs.staticWebAppUrl }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Cosmos DB | \`${{ steps.infra.outputs.cosmosAccountName }}\` (Free Tier) |" >> $GITHUB_STEP_SUMMARY | |
| echo "| App Insights | \`${{ steps.infra.outputs.appInsightsName }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tier:** Free (isolated from production)" >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # QA DEPLOYMENT | |
| # ========================================================================== | |
| deploy-qa: | |
| name: Deploy to QA | |
| runs-on: ubuntu-latest | |
| needs: deploy-qa-infrastructure | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| outputs: | |
| deployed_url: ${{ steps.deploy.outputs.static_web_app_url }} | |
| environment: | |
| name: qa | |
| url: ${{ steps.deploy.outputs.static_web_app_url }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Download build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build | |
| - name: Get Static Web App Token | |
| id: swa-token | |
| run: | | |
| TOKEN=$(az staticwebapp secrets list --name "${{ needs.deploy-qa-infrastructure.outputs.static_web_app_name }}" --resource-group "${{ needs.deploy-qa-infrastructure.outputs.resource_group }}" --query "properties.apiKey" -o tsv) | |
| echo "token=$TOKEN" >> $GITHUB_OUTPUT | |
| - name: Deploy to QA | |
| id: deploy | |
| uses: Azure/static-web-apps-deploy@v1 | |
| with: | |
| azure_static_web_apps_api_token: ${{ steps.swa-token.outputs.token }} | |
| repo_token: ${{ secrets.GITHUB_TOKEN }} | |
| action: upload | |
| app_location: dist | |
| api_location: api | |
| output_location: . | |
| skip_app_build: true | |
| skip_api_build: false | |
| - name: QA Deployment Summary | |
| run: | | |
| echo "## 🟢 QA Deployment Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**URL:** ${{ steps.deploy.outputs.static_web_app_url }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Resource Group:** \`${{ needs.deploy-qa-infrastructure.outputs.resource_group }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Resources (Free Tier):" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Static Web App (Free)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Cosmos DB (Free Tier)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Application Insights" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Azure Communication Services" >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # E2E TESTS AGAINST QA ENVIRONMENT | |
| # ========================================================================== | |
| e2e-qa: | |
| name: E2E Tests (QA) | |
| runs-on: ubuntu-latest | |
| needs: [deploy-qa, deploy-qa-infrastructure] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Wait for QA deployment to stabilize | |
| run: | | |
| echo "Waiting 30 seconds for QA deployment to stabilize..." | |
| sleep 30 | |
| - name: Run E2E tests against QA | |
| env: | |
| BASE_URL: ${{ needs.deploy-qa.outputs.deployed_url }} | |
| run: | | |
| echo "Running E2E tests against QA: $BASE_URL" | |
| npx playwright test | |
| - name: Upload QA E2E test report | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: playwright-report-qa | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: QA E2E Test Summary | |
| if: always() | |
| run: | | |
| echo "## 🎭 QA E2E Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tested URL:** ${{ needs.deploy-qa.outputs.deployed_url }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f playwright-report/results.json ]; then | |
| # Parse results from JSON | |
| TOTAL=$(jq '.stats.expected + .stats.unexpected + .stats.flaky + .stats.skipped' playwright-report/results.json) | |
| PASSED=$(jq '.stats.expected' playwright-report/results.json) | |
| FAILED=$(jq '.stats.unexpected' playwright-report/results.json) | |
| FLAKY=$(jq '.stats.flaky' playwright-report/results.json) | |
| SKIPPED=$(jq '.stats.skipped' playwright-report/results.json) | |
| DURATION=$(jq '.stats.duration' playwright-report/results.json) | |
| DURATION_SEC=$(echo "scale=2; $DURATION / 1000" | bc) | |
| if [ "$FAILED" -eq 0 ]; then | |
| echo "### ✅ All Tests Passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ❌ Some Tests Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| ✅ Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ❌ Failed | $FAILED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⚠️ Flaky | $FLAKY |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⏭️ Skipped | $SKIPPED |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "⏱️ **Duration:** ${DURATION_SEC}s" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # List failed tests if any | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.suites[].suites[]?.specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| jq -r '.suites[].specs[]? | select(.ok == false) | "- ❌ \(.title)"' playwright-report/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo "❌ QA E2E tests may have failed. Check the logs above." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "📊 **Full Report:** Download the \`playwright-report-qa\` artifact for detailed HTML report." >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # DEPLOY PRODUCTION INFRASTRUCTURE | |
| # ========================================================================== | |
| deploy-prod-infrastructure: | |
| name: Deploy Production Infrastructure | |
| runs-on: ubuntu-latest | |
| needs: e2e-qa | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| outputs: | |
| static_web_app_url: ${{ steps.prod-infra.outputs.staticWebAppUrl }} | |
| static_web_app_name: ${{ steps.prod-infra.outputs.staticWebAppName }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Create/Verify Production Resource Group | |
| run: | | |
| az group create --name "${{ env.AZURE_RESOURCE_GROUP }}" --location "${{ env.AZURE_LOCATION }}" --tags "environment=prod" "project=${{ env.PROJECT_NAME }}" | |
| echo "✅ Production resource group ready: ${{ env.AZURE_RESOURCE_GROUP }}" | |
| - name: Validate Production Infrastructure | |
| id: validate | |
| run: | | |
| echo "Validating Bicep template against current resources..." | |
| az deployment group validate \ | |
| --resource-group ${{ env.AZURE_RESOURCE_GROUP }} \ | |
| --template-file ./infra/main.bicep \ | |
| --parameters ./infra/parameters.prod.json deploymentId=prod-stable | |
| echo "✅ Template validation passed" | |
| - name: Deploy Production Infrastructure | |
| id: prod-infra | |
| uses: azure/arm-deploy@v2 | |
| with: | |
| scope: resourcegroup | |
| resourceGroupName: ${{ env.AZURE_RESOURCE_GROUP }} | |
| template: ./infra/main.bicep | |
| parameters: ./infra/parameters.prod.json deploymentId=prod-stable | |
| deploymentMode: Incremental | |
| failOnStdErr: true | |
| - name: Verify Production Resources | |
| run: | | |
| echo "Verifying Production resources..." | |
| COSMOS=$(az cosmosdb show --name "${{ steps.prod-infra.outputs.cosmosAccountName }}" --resource-group "${{ env.AZURE_RESOURCE_GROUP }}" 2>/dev/null && echo "✅ Cosmos DB (Serverless)" || echo "❌ Cosmos DB missing") | |
| SWA=$(az staticwebapp show --name "${{ steps.prod-infra.outputs.staticWebAppName }}" --resource-group "${{ env.AZURE_RESOURCE_GROUP }}" 2>/dev/null && echo "✅ Static Web App (Standard)" || echo "❌ Static Web App missing") | |
| INSIGHTS=$(az monitor app-insights component show --app "${{ steps.prod-infra.outputs.appInsightsName }}" --resource-group "${{ env.AZURE_RESOURCE_GROUP }}" 2>/dev/null && echo "✅ App Insights (90-day retention)" || echo "❌ App Insights missing") | |
| echo "$COSMOS" | |
| echo "$SWA" | |
| echo "$INSIGHTS" | |
| echo "" | |
| echo "📋 Production deployment complete - production-ready infrastructure verified" | |
| - name: Production Infrastructure Summary | |
| run: | | |
| echo "## 🏗️ Production Infrastructure Deployed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Resource Group | \`${{ env.AZURE_RESOURCE_GROUP }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Static Web App | ${{ steps.prod-infra.outputs.staticWebAppUrl }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Cosmos DB | \`${{ steps.prod-infra.outputs.cosmosAccountName }}\` (Serverless) |" >> $GITHUB_STEP_SUMMARY | |
| echo "| App Insights | \`${{ steps.prod-infra.outputs.appInsightsName }}\` (90-day retention) |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tier:** Production (Standard SWA, Serverless Cosmos DB)" >> $GITHUB_STEP_SUMMARY | |
| # ========================================================================== | |
| # PRODUCTION DEPLOYMENT (requires approval) | |
| # ========================================================================== | |
| deploy-production: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: deploy-prod-infrastructure | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| environment: | |
| name: production | |
| url: ${{ steps.deploy.outputs.static_web_app_url }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Download build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build | |
| - name: Get Static Web App Token | |
| id: swa-token | |
| run: | | |
| TOKEN=$(az staticwebapp secrets list --name "${{ needs.deploy-prod-infrastructure.outputs.static_web_app_name }}" --resource-group "${{ env.AZURE_RESOURCE_GROUP }}" --query "properties.apiKey" -o tsv) | |
| echo "token=$TOKEN" >> $GITHUB_OUTPUT | |
| - name: Deploy to Production | |
| id: deploy | |
| uses: Azure/static-web-apps-deploy@v1 | |
| with: | |
| azure_static_web_apps_api_token: ${{ steps.swa-token.outputs.token }} | |
| repo_token: ${{ secrets.GITHUB_TOKEN }} | |
| action: upload | |
| app_location: dist | |
| api_location: api | |
| output_location: . | |
| skip_app_build: true | |
| skip_api_build: false | |
| - name: Deployment Summary | |
| run: | | |
| echo "## 🎉 Production Deployment Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**URL:** ${{ steps.deploy.outputs.static_web_app_url }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Deployed by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Resources (Production Tier):" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Static Web App (Standard - SLA, custom domains)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Cosmos DB (Serverless - unlimited scaling)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Application Insights (90-day retention)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Azure Communication Services" >> $GITHUB_STEP_SUMMARY |