Skip to content

prod-e2e

prod-e2e #43

Workflow file for this run

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}`);