GitHub Actions study materials
GitHub Actions study resources

Playwright Sharding: Readiness, Artifact Collisions, and Cache Keys

GitHub Actions · Put your knowledge to work in context.

All case studies

Playwright Sharding: Readiness, Artifact Collisions, and Cache Keys

Your team runs 640 Playwright end-to-end tests against a Next.js app. The workflow uses a matrix of 8 shards (--shard=${{ matrix.shard }}/8) and service containers for PostgreSQL 15 and Redis 7. Each job uploads test-results/ with actions/upload-artifact@v4 using name: playwright-results. Runs take 22-35 minutes. Shard 7 frequently hits the 30-minute job timeout; its logs show waiting for http://localhost:3000 and occasional database connection resets. Artifact upload fails with 'an artifact with this name already exists'. The Playwright browser cache uses key: ${{ runner.os }}-playwright and is restored on every run, but npx playwright install --with-deps still downloads browsers. The Next.js server is started with npm run start after a build step in every shard; the build takes 4 minutes per shard. The PostgreSQL service is declared without health checks, and the app retries database connections for only 5 seconds. Shard 7 also runs the three slowest checkout specs because sharding is by file count, not timing. The team wants to reduce wall-clock time and flakiness without increasing runner count.

1. Diagnose why shard 7 times out and why the browser cache misses. Propose concrete fixes for service readiness and cache keys.

Read the worked answer

Shard 7 is not inherently broken; sharding by file count puts the three slowest checkout specs in one shard, and every shard rebuilds the app. The timeout is also a readiness problem: waiting for http://localhost:3000 means the app process is not accepting connections, often because PostgreSQL is not ready or the build is still running. Add service health checks (--health-cmd pg_isready, --health-interval 5s, --health-retries 10) and a readiness loop that checks both the app URL and a real DB query before Playwright starts. Reduce per-shard work by building the app once in a build job, uploading the build artifact, and having each shard download it. The browser cache key ${{ runner.os }}-playwright ignores the Playwright version and dependency lockfile; after an upgrade, the cache contains the wrong browser build. Use ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} plus the Playwright version, cache ~/.cache/ms-playwright, and add restore-keys. Also confirm npx playwright install --with-deps is not re-downloading because PLAYWRIGHT_BROWSERS_PATH differs between save and restore.

2. Artifact upload collisions: explain v4 artifact naming behavior and design artifact names and retention for sharded runs.

Read the worked answer

actions/upload-artifact@v4 creates immutable artifacts. Artifact names must be unique within a workflow run, not just within a job. All eight jobs use name: playwright-results, so the first upload succeeds and the others fail. Include the shard and attempt in the name: playwright-results-${{ matrix.shard }}-${{ github.run_attempt }}. A downstream report job can download all artifacts with pattern: playwright-results-* and merge-multiple: true, then publish one report. Set retention-days to control storage, and use if: always() on upload so failures still produce traces. Do not try to overwrite the same artifact with overwrite: true across jobs; that is still a race. If you need one artifact, upload from a single job after downloading shard outputs, but that adds a job. Name artifacts by purpose, not by run ID, because github.run_id is implicit in the artifact scope.

3. Propose a test distribution strategy to reduce wall-clock time without adding runners. Include matrix sharding, test splitting, and caching.

Read the worked answer

Keep eight runners but make each shard's work smaller and more balanced. Use Playwright's --shard with fullyParallel: true and enough workers per shard; if the suite is CPU-bound, workers above 2-4 may not help. Balance by timing: record per-test durations, sort into buckets, and generate shard lists, or split by project/tag so the slow checkout specs are distributed. Build once and share the build artifact; do not rebuild in every shard. Start PostgreSQL and Redis once per job as service containers; they cannot be shared across jobs. Cache npm dependencies and browsers, but do not cache node_modules across OS versions. Set timeout-minutes at the job level to 25 and rely on Playwright traces, not longer timeouts. Use fail-fast: false so one shard failure does not cancel the others. For flaky DB resets, add retries only for known transient errors and fix connection pooling; retries hide the readiness bug.