Nightly Suite #97
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
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Nightly Suite — Comprehensive test coverage + data integrity | |
| # CI Architecture Redesign — absorbs data-audit.yml into nightly schedule | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Runs all Playwright projects (including visual regression + authenticated), | |
| # full unit test coverage, and data integrity audit against live Supabase. | |
| # | |
| # Target runtime: < 15 minutes (jobs run in parallel) | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| name: Nightly Suite | |
| on: | |
| schedule: | |
| - cron: "0 2 * * *" # 2:00 UTC daily | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: nightly | |
| cancel-in-progress: true # Only latest nightly matters | |
| env: | |
| BASELINE_COMPREHENSIVE: 900 | |
| BASELINE_DATA_AUDIT: 300 | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────── | |
| # Job 1: Full frontend test suite (unit + all Playwright projects) | |
| # ───────────────────────────────────────────────────────────────────────── | |
| comprehensive: | |
| name: Full Test Suite | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 30 | |
| defaults: | |
| run: | |
| working-directory: frontend | |
| steps: | |
| - name: Record job start | |
| id: timer | |
| run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" | |
| working-directory: . | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - run: npm ci | |
| - name: Cache Next.js build | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: frontend/.next/cache | |
| key: next-cache-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }}-${{ hashFiles('frontend/src/**') }} | |
| restore-keys: | | |
| next-cache-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }}- | |
| next-cache-${{ runner.os }}- | |
| - name: Build | |
| run: npx next build | |
| timeout-minutes: 5 | |
| - name: Unit tests with coverage | |
| run: npx vitest run --coverage | |
| timeout-minutes: 5 | |
| - name: Get Playwright version | |
| id: pw-version | |
| run: echo "version=$(node -e "console.log(require('@playwright/test/package.json').version)")" >> "$GITHUB_OUTPUT" | |
| - name: Cache Playwright browsers | |
| id: pw-cache | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: pw-${{ runner.os }}-${{ steps.pw-version.outputs.version }} | |
| - name: Install Playwright browsers | |
| if: steps.pw-cache.outputs.cache-hit != 'true' | |
| run: npx playwright install --with-deps chromium | |
| timeout-minutes: 8 | |
| - name: Install Playwright system deps | |
| if: steps.pw-cache.outputs.cache-hit == 'true' | |
| run: npx playwright install-deps chromium | |
| timeout-minutes: 8 | |
| - name: Seed QA fixture data | |
| run: | | |
| OUTPUT=$(node tests/quality/seed-fixtures.mjs 2>&1 1>/tmp/qa-ids.txt || true) | |
| echo "$OUTPUT" | |
| if [ -f /tmp/qa-ids.txt ] && [ -s /tmp/qa-ids.txt ]; then | |
| cat /tmp/qa-ids.txt >> "$GITHUB_ENV" | |
| else | |
| echo "::warning::QA fixture seeding skipped or failed — tests may use fallback IDs" | |
| fi | |
| env: | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY_STAGING || secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| - name: Full Playwright (all projects including visual) | |
| run: npx playwright test 2>&1 | tee ../playwright-stdout.log | |
| timeout-minutes: 20 | |
| env: | |
| # Visual regression disabled until baseline screenshots are generated | |
| # and committed from a Linux CI run. Re-enable after running: | |
| # npx playwright test --project=visual-smoke --update-snapshots | |
| # VISUAL_REGRESSION: "true" | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY_STAGING || secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY_STAGING || secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: ${{ !cancelled() }} | |
| with: | |
| name: nightly-playwright-report | |
| path: | | |
| frontend/playwright-report/ | |
| frontend/test-results/ | |
| playwright-stdout.log | |
| retention-days: 30 | |
| - name: Alert on comprehensive failure | |
| if: failure() | |
| env: | |
| ALERT_WEBHOOK_URL: ${{ secrets.ALERT_WEBHOOK_URL }} | |
| run: | | |
| if [ -n "$ALERT_WEBHOOK_URL" ]; then | |
| curl -sf -X POST "$ALERT_WEBHOOK_URL" \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"text": "Nightly comprehensive tests FAILED \u2014 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' | |
| fi | |
| working-directory: . | |
| - name: CI runtime summary | |
| if: ${{ !cancelled() }} | |
| working-directory: . | |
| run: | | |
| end=$(date +%s); start=${{ steps.timer.outputs.start }}; d=$((end-start)) | |
| echo "## Nightly Comprehensive: $((d/60))m $((d%60))s" >> "$GITHUB_STEP_SUMMARY" | |
| pct=0; [ "$BASELINE_COMPREHENSIVE" -gt 0 ] && pct=$(( (d-BASELINE_COMPREHENSIVE)*100/BASELINE_COMPREHENSIVE )) | |
| [ "$pct" -gt 30 ] && echo "::warning::Nightly comprehensive regression: ${d}s (+${pct}% vs ${BASELINE_COMPREHENSIVE}s)" | |
| mkdir -p ci-timings | |
| echo '{"job":"comprehensive","duration_seconds":'$d',"baseline_seconds":'$BASELINE_COMPREHENSIVE',"delta_pct":'$pct'}' > ci-timings/comprehensive.json | |
| - name: Upload CI timings | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ci-timings-comprehensive | |
| path: ci-timings/ | |
| retention-days: 30 | |
| # ───────────────────────────────────────────────────────────────────────── | |
| # Job 2: Data Integrity Audit (runs in parallel with comprehensive) | |
| # Absorbed from the former data-audit.yml | |
| # ───────────────────────────────────────────────────────────────────────── | |
| data-audit: | |
| name: Data Integrity Audit | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Record job start | |
| id: timer | |
| run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| cache: "pip" | |
| - run: pip install -r requirements.txt | |
| - name: Run Data Integrity Audit | |
| env: | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL || secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY || secrets.SUPABASE_SERVICE_ROLE_KEY_STAGING || secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| run: | | |
| if [ -z "$SUPABASE_URL" ] || [ -z "$SUPABASE_SERVICE_KEY" ]; then | |
| echo "::warning::Data integrity audit skipped — SUPABASE_URL or SUPABASE_SERVICE_KEY secret not configured" | |
| exit 0 | |
| fi | |
| python run_data_audit.py | |
| - name: Upload Audit Report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: always() | |
| with: | |
| name: data-audit-report | |
| path: audit-reports/ | |
| retention-days: 90 | |
| - name: Post Summary | |
| if: always() | |
| run: | | |
| REPORT=$(cat audit-reports/audit_*.json 2>/dev/null || echo '{}') | |
| CRITICAL=$(echo "$REPORT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('summary',{}).get('critical',0))" 2>/dev/null || echo "?") | |
| WARNINGS=$(echo "$REPORT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('summary',{}).get('warnings',0))" 2>/dev/null || echo "?") | |
| INFO=$(echo "$REPORT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('summary',{}).get('info',0))" 2>/dev/null || echo "?") | |
| TOTAL=$(echo "$REPORT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('summary',{}).get('total_findings',0))" 2>/dev/null || echo "?") | |
| echo "## Data Integrity Audit" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| 🔴 Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY | |
| echo "| 🟠 Warning | $WARNINGS |" >> $GITHUB_STEP_SUMMARY | |
| echo "| 🔵 Info | $INFO |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| - name: Alert on Critical Findings | |
| if: failure() | |
| env: | |
| ALERT_WEBHOOK_URL: ${{ secrets.ALERT_WEBHOOK_URL }} | |
| run: | | |
| if [ -n "$ALERT_WEBHOOK_URL" ]; then | |
| curl -sf -X POST "$ALERT_WEBHOOK_URL" \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"text": "Nightly audit: CRITICAL issues — https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' | |
| fi | |
| - name: CI runtime summary | |
| if: ${{ !cancelled() }} | |
| run: | | |
| end=$(date +%s); start=${{ steps.timer.outputs.start }}; d=$((end-start)) | |
| echo "## Data Audit: $((d/60))m $((d%60))s" >> "$GITHUB_STEP_SUMMARY" | |
| pct=0; [ "$BASELINE_DATA_AUDIT" -gt 0 ] && pct=$(( (d-BASELINE_DATA_AUDIT)*100/BASELINE_DATA_AUDIT )) | |
| [ "$pct" -gt 30 ] && echo "::warning::Data audit regression: ${d}s (+${pct}% vs ${BASELINE_DATA_AUDIT}s)" | |
| mkdir -p ci-timings | |
| echo '{"job":"data-audit","duration_seconds":'$d',"baseline_seconds":'$BASELINE_DATA_AUDIT',"delta_pct":'$pct'}' > ci-timings/data-audit.json | |
| - name: Upload CI timings | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ci-timings-data-audit | |
| path: ci-timings/ | |
| retention-days: 30 |