name: e2e on: pull_request: branches: [main] push: branches: [main] permissions: contents: read # Cancel in-progress runs when a new commit lands on the same PR. concurrency: group: e2e-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # The suite outgrew the runner the same way the integration suite did: the # single job passed at ~18 minutes against a 20-minute cap, and the settings # geometry measurements pushed it over. Same remedy, split rather than raise: # Playwright shards, each with its own database and production build, and # a gate job below that carries the exact name branch protection requires. # Two shards grew to 14–19 minutes against the 20-minute cap once the # mobile route sweep landed, and a slow runner tipped them over; three # shards with a 30-minute cap leave room for both. shard: runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: fail-fast: false matrix: shard: [1, 2, 3] name: e2e shard ${{ matrix.shard }}/3 services: postgres: image: postgres:16-alpine env: POSTGRES_USER: healthlog POSTGRES_PASSWORD: healthlog POSTGRES_DB: healthlog ports: - 5432:5432 options: >- --health-cmd "pg_isready -U healthlog" --health-interval 5s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgresql://healthlog:healthlog@localhost:5432/healthlog?schema=public ENCRYPTION_KEYS: '{"v1":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' ENCRYPTION_ACTIVE_KEY_ID: v1 API_TOKEN_HMAC_KEY: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb AUTH_RP_NAME: HealthLog AUTH_RP_ID: localhost AUTH_RP_ORIGIN: http://localhost:3000 SESSION_COOKIE_SECURE: "false" NODE_ENV: production steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm db:generate - run: pnpm db:migrate:deploy - name: Cache Playwright browsers uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | playwright-${{ runner.os }}- - run: pnpm exec playwright install --with-deps chromium # `pnpm build` (not bare `next build`): the prebuild hook generates # the versioned service-worker stamp AND the static i18n catalog # assets (public/i18n/.js) the client boots from — without # them every UI interaction waits on missing catalogs. # The repository-wide Next type worker exceeds V8's ~4 GiB default # old-space ceiling. Keep the larger heap scoped to this production # build only; tests and the runtime retain their normal limits. - run: NODE_OPTIONS=--max-old-space-size=8192 pnpm build # Client-bundle budget gate — rides this job because it is the one # place CI already has a production build on disk. Catches the # regressions the dead webpack-era tooling (ANALYZE=1, the old # bundle-report) let through: a statically imported message catalog # (~100 KB gz on every route) or a chart import bypassing the shared # chart-runtime boundary (a duplicated ~92 KB gz recharts chunk). - run: pnpm bundle-check - run: pnpm exec playwright test --shard=${{ matrix.shard }}/3 env: E2E_BASE_URL: http://localhost:3000 - name: Upload Playwright HTML report if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: playwright-report-shard-${{ matrix.shard }} path: playwright-report/ retention-days: 7 if-no-files-found: ignore # The machine-readable report, for the skipped-spec check in the gate # job below. Only uploaded when the shard ran to completion, so a missing # artifact there means a shard that did not finish, not an empty suite. - name: Upload Playwright JSON report uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: playwright-json-shard-${{ matrix.shard }} path: playwright-json/results.json retention-days: 7 if-no-files-found: error # Branch protection requires a check named exactly "e2e". This job carries # that name and answers for both shards: it fails when any shard failed or # was cancelled, so a half-green matrix cannot satisfy the gate. e2e: runs-on: ubuntu-24.04 needs: shard if: always() timeout-minutes: 5 steps: - name: Every shard finished green run: | if [ "${{ needs.shard.result }}" != "success" ]; then echo "shard result: ${{ needs.shard.result }}" exit 1 fi # A spec file that skipped every one of its tests reports green. The # check aggregates both shards' reports, because Playwright splits a # file's tests across shards; see the script's header for the allowlist. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: scripts - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: playwright-json-shard-* path: playwright-json - name: No spec file skipped all of its tests run: node scripts/check-e2e-skipped-specs.mjs playwright-json/*/results.json