Repository navigation
Protected staging E2E #852
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
| name: Protected staging E2E | |
| # Daily, and on demand from main. It used to follow every push to main, and | |
| # about half of each run's billed minutes went to waiting for the staging | |
| # sync. A frontend merge is checked live by the production canary; before a | |
| # backend production deploy, dispatch this from main once staging runs the | |
| # merge: `gh workflow run staging-e2e.yml --ref main`. | |
| on: | |
| schedule: | |
| - cron: "11 7 * * *" | |
| workflow_dispatch: | |
| permissions: | |
| # Reads the Quality runs for the main commit under test. | |
| actions: read | |
| contents: read | |
| # Waits for the staging-sync commit status set by the VPS sync. | |
| statuses: read | |
| concurrency: | |
| group: protected-staging-e2e-main | |
| cancel-in-progress: false | |
| jobs: | |
| # Staging is one shared backend that the VPS sync (docs/staging.md) moves to | |
| # each new main commit and marks with a staging-sync commit status. Test a | |
| # commit only once staging runs it, and skip a commit that main has already | |
| # moved past: staging runs the newer backend, and the newer commit's run | |
| # tests it. | |
| staging-sync-gate: | |
| # A scheduled run's ref is main; a dispatch must choose main. | |
| if: github.ref == 'refs/heads/main' && vars.STAGING_E2E_ENABLED == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| outputs: | |
| current: ${{ steps.wait.outputs.current }} | |
| steps: | |
| - name: Wait for staging to sync this commit | |
| id: wait | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| SHA: ${{ github.sha }} | |
| shell: bash | |
| run: | | |
| for _ in $(seq 1 40); do | |
| main="$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha)" | |
| if [ "$main" != "$SHA" ]; then | |
| echo "::notice::main moved on to ${main}; staging runs that backend, and its own run tests it. Skipping ${SHA}." | |
| echo "current=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| state="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${SHA}/statuses" \ | |
| --jq '[.[] | select(.context == "staging-sync")][0].state // "missing"')" | |
| case "$state" in | |
| success) | |
| echo "Staging is synced with ${SHA}." | |
| echo "current=true" >> "$GITHUB_OUTPUT" | |
| exit 0 ;; | |
| failure|error) | |
| echo "::error::Staging sync failed for ${SHA}. See the open 'Staging is out of sync with main' issue." | |
| exit 1 ;; | |
| esac | |
| sleep 30 | |
| done | |
| echo "::error::No staging-sync status for ${SHA} after 20 minutes. Is debatelab-staging-sync.timer running on the VPS? See docs/staging.md." | |
| exit 1 | |
| staging-e2e: | |
| needs: staging-sync-gate | |
| if: needs.staging-sync-gate.outputs.current == 'true' | |
| runs-on: ubuntu-latest | |
| # Bounds how long the VPS sync holds off for this run (ops/staging-sync/run.sh). | |
| timeout-minutes: 30 | |
| environment: staging | |
| steps: | |
| # "Re-run failed jobs" skips the gate, so check again on every attempt. | |
| - name: Confirm staging still runs this commit | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| SHA: ${{ github.sha }} | |
| shell: bash | |
| run: | | |
| main="$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha)" | |
| state="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${SHA}/statuses" \ | |
| --jq '[.[] | select(.context == "staging-sync")][0].state // "missing"')" | |
| if [ "$main" != "$SHA" ] || [ "$state" != "success" ]; then | |
| echo "::error::Staging no longer runs ${SHA} (main is ${main}, staging-sync is ${state}). Rerun the newest main commit's run instead." | |
| exit 1 | |
| fi | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # A commit merged just before the schedule can reach staging while its | |
| # Quality run is still queued or running, so wait for it to finish. | |
| - name: Verify the commit passed the quality workflow | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| for _ in $(seq 1 30); do | |
| read -r status conclusion < <(gh api \ | |
| "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ | |
| --jq '.workflow_runs[0] // {} | "\(.status // "missing") \(.conclusion // "none")"') || true | |
| case "$status" in | |
| completed) | |
| if [ "$conclusion" = success ]; then echo "Quality passed for ${GITHUB_SHA}."; exit 0; fi | |
| echo "::error::Quality ended in ${conclusion} for main commit ${GITHUB_SHA}" | |
| exit 1 ;; | |
| missing) | |
| echo "::error::Main commit ${GITHUB_SHA} has no push run of the Quality workflow" | |
| exit 1 ;; | |
| esac | |
| sleep 20 | |
| done | |
| echo "::error::Quality did not finish for ${GITHUB_SHA} within 10 minutes" | |
| exit 1 | |
| - name: Set up Node | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Chromium | |
| run: npx playwright install --with-deps chromium | |
| - name: Verify protected variables are present | |
| shell: bash | |
| env: | |
| STAGING_SUPABASE_URL: ${{ secrets.STAGING_SUPABASE_URL }} | |
| STAGING_SUPABASE_ANON_KEY: ${{ secrets.STAGING_SUPABASE_ANON_KEY }} | |
| STAGING_DEMO_CODE: ${{ secrets.STAGING_DEMO_CODE }} | |
| run: | | |
| missing=0 | |
| for name in STAGING_SUPABASE_URL STAGING_SUPABASE_ANON_KEY STAGING_DEMO_CODE; do | |
| if [ -z "${!name}" ]; then | |
| echo "::error::$name is not configured in the staging environment" | |
| missing=1 | |
| fi | |
| done | |
| exit "$missing" | |
| - name: Protected staging browser tests | |
| env: | |
| CI: 1 | |
| PORT: 18080 | |
| PLAYWRIGHT_BASE_URL: http://127.0.0.1:18080 | |
| STAGING_SUPABASE_URL: ${{ secrets.STAGING_SUPABASE_URL }} | |
| STAGING_SUPABASE_ANON_KEY: ${{ secrets.STAGING_SUPABASE_ANON_KEY }} | |
| STAGING_DEMO_CODE: ${{ secrets.STAGING_DEMO_CODE }} | |
| run: npm run test:e2e:staging |