prod-e2e #42
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: prod-e2e | |
| # Run the Playwright e2e suite against the production URL after every | |
| # production deploy. Sandbox deploys are exercised by humans and tested | |
| # by the PR-preview e2e job on each PR before merge — this workflow is | |
| # purely the post-prod regression guard. | |
| # | |
| # If e2e fails, file a GitHub issue tagged `prod-e2e-fail` so the | |
| # regression isn't silent — production failures can otherwise sit | |
| # undetected for days. | |
| on: | |
| workflow_run: | |
| workflows: ['deploy-ui'] | |
| types: [completed] | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| actions: read | |
| jobs: | |
| resolve-target: | |
| if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| target: ${{ steps.read.outputs.target }} | |
| steps: | |
| - name: Download deploy target artifact (workflow_run path) | |
| if: github.event_name == 'workflow_run' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: deploy-target | |
| path: deploy-meta | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| run-id: ${{ github.event.workflow_run.id }} | |
| continue-on-error: true | |
| - name: Read deploy target | |
| id: read | |
| run: | | |
| # workflow_dispatch runs are always treated as production — | |
| # the operator triggered this on purpose to re-run prod | |
| # regression. workflow_run runs read the artifact written by | |
| # deploy-ui (sandbox vs production). If the artifact is | |
| # missing (e.g. older deploy-ui run that pre-dates this | |
| # change), default to skipping rather than testing the wrong | |
| # URL. | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| target=production | |
| elif [ -f deploy-meta/target.txt ]; then | |
| target=$(tr -d '[:space:]' < deploy-meta/target.txt) | |
| else | |
| target=skip | |
| fi | |
| echo "target=$target" >> "$GITHUB_OUTPUT" | |
| echo "Resolved target: $target" | |
| e2e: | |
| needs: resolve-target | |
| if: needs.resolve-target.outputs.target == 'production' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| fetch-depth: 1 | |
| - name: Set up Node 20 + pnpm | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Enable pnpm | |
| run: npm install --global pnpm@10 | |
| - name: Install site deps | |
| working-directory: site | |
| run: pnpm install --frozen-lockfile | |
| - name: Install Playwright browsers | |
| working-directory: site | |
| run: pnpm exec playwright install chromium --with-deps | |
| - name: Wait for production deploy to be live | |
| run: | | |
| # gh-pages CDN propagation lags the deploy workflow. | |
| URL="https://abstractatlas.brainkb.org/ohbm2026/" | |
| echo "Probing $URL" | |
| for i in $(seq 1 30); do | |
| code=$(curl -s -o /dev/null -w '%{http_code}' "$URL" || true) | |
| if [ "$code" = "200" ]; then | |
| echo "Prod live after ${i} polls." | |
| break | |
| fi | |
| echo " [$i/30] HTTP $code — sleeping 10s" | |
| sleep 10 | |
| done | |
| - name: Run Playwright e2e against production | |
| id: e2e | |
| working-directory: site | |
| env: | |
| # `PLAYWRIGHT_BASE_URL` terminates at the conference home so | |
| # specs that use relative paths (`./about/`, `./abstract/...`) | |
| # don't accidentally land on the root redirect island. | |
| PLAYWRIGHT_BASE_URL: https://abstractatlas.brainkb.org/ohbm2026/ | |
| UI_DATA_AVAILABLE: '1' | |
| run: pnpm exec playwright test --project=chromium | |
| - name: Upload Playwright report on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| id: report | |
| with: | |
| name: playwright-report-prod-${{ github.run_id }} | |
| path: site/playwright-report/ | |
| retention-days: 30 | |
| - name: File issue on prod e2e failure | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| env: | |
| REPORT_NAME: playwright-report-prod-${{ github.run_id }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| DEPLOY_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| with: | |
| script: | | |
| const runUrl = process.env.RUN_URL; | |
| const deploySha = (process.env.DEPLOY_SHA || '').slice(0, 7); | |
| const title = `prod e2e regression on ${deploySha}`; | |
| const body = [ | |
| `Playwright e2e suite failed against production after the latest main-branch deploy.`, | |
| ``, | |
| `**Workflow run:** ${runUrl}`, | |
| `**Deploy SHA:** \`${process.env.DEPLOY_SHA}\``, | |
| `**Production URL:** https://abstractatlas.brainkb.org/ohbm2026/`, | |
| ``, | |
| `The Playwright report is attached as the \`${process.env.REPORT_NAME}\` artifact on the workflow run (30-day retention).`, | |
| ``, | |
| `Triage:`, | |
| `1. Open the workflow run, download the report artifact, and inspect the failed traces.`, | |
| `2. Determine whether the failure reflects a real regression or an environmental flake (CDN, Dropbox 5xx, etc.).`, | |
| `3. If real: open a fix PR. If transient: close this issue with the rationale recorded.`, | |
| ].join('\n'); | |
| // De-dupe: if there's already an open `prod-e2e-fail` issue | |
| // for the same SHA, comment instead of opening a duplicate. | |
| const { data: existing } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: 'prod-e2e-fail', | |
| state: 'open', | |
| per_page: 50, | |
| }); | |
| const dup = existing.find((iss) => iss.title.endsWith(deploySha)); | |
| if (dup) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: dup.number, | |
| body: `Another failed run on the same deploy SHA: ${runUrl}`, | |
| }); | |
| core.info(`Commented on existing issue #${dup.number}`); | |
| return; | |
| } | |
| const { data: issue } = await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| body, | |
| labels: ['prod-e2e-fail', 'priority:high'], | |
| }); | |
| core.info(`Filed issue #${issue.number}: ${issue.html_url}`); |