Merge pull request #736 from MBombeck/ci/e2e-shards

Run the browser suite as two shards
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-08-08 05:18:30 +02:00
commit 9e89f3ec5d
2 files changed
+37 -6

No files matched your search

+29 -3
View File
@@ -15,9 +15,19 @@ concurrency:
cancel-in-progress: true
jobs:
e2e:
# 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:
# two Playwright shards, each with its own database and production build, and
# a gate job below that carries the exact name branch protection requires.
shard:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: [1, 2]
name: e2e shard ${{ matrix.shard }}/2
services:
postgres:
image: postgres:16-alpine
@@ -87,7 +97,7 @@ jobs:
# chart-runtime boundary (a duplicated ~92 KB gz recharts chunk).
- run: pnpm bundle-check
- run: pnpm exec playwright test
- run: pnpm exec playwright test --shard=${{ matrix.shard }}/2
env:
E2E_BASE_URL: http://localhost:3000
@@ -95,7 +105,23 @@ jobs:
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
name: playwright-report-shard-${{ matrix.shard }}
path: playwright-report/
retention-days: 7
if-no-files-found: ignore
# 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-latest
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
+8 -3
View File
@@ -77,14 +77,19 @@ describe("Playwright CI determinism", () => {
const workflow = parse(
readRepoFile(".github/workflows/e2e.yml"),
) as Workflow;
const upload = workflow.jobs.e2e?.steps?.find((step) =>
// The suite runs as a shard matrix since the single job outgrew its
// timeout; the running job is `shard`, while `e2e` is the gate that
// carries the required check name and runs no browser. The report
// therefore lives on the shard job, named per shard so the two
// artifacts cannot clobber each other.
const upload = workflow.jobs.shard?.steps?.find((step) =>
step.uses?.startsWith("actions/upload-artifact@"),
);
expect(upload).toBeDefined();
expect(upload?.if).toBe("always()");
expect(upload?.with).toMatchObject({
name: "playwright-report",
name: "playwright-report-shard-${{ matrix.shard }}",
path: "playwright-report/",
"retention-days": 7,
"if-no-files-found": "ignore",
@@ -95,7 +100,7 @@ describe("Playwright CI determinism", () => {
const workflow = parse(
readRepoFile(".github/workflows/e2e.yml"),
) as Workflow;
const env = workflow.jobs.e2e?.env;
const env = workflow.jobs.shard?.env;
expect(env?.API_TOKEN_HMAC_KEY).toMatch(/^[0-9a-f]{64}$/);
expect(env?.SESSION_COOKIE_SECURE).toBe("false");