From b94a285aada76413c73beaca0ad11a5e1625704f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Thu, 30 Apr 2026 15:52:33 +0100 Subject: [PATCH] ci: speed up pipeline (#341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Last CI run on a PR took **~16m 36s** wall (browser-tests dominated). Total CPU ~34 min. Several easy wins identified. ## Baseline (run 25169327223) | Job | Wall | Critical step | |---|---|---| | browser-tests | 16m 36s | Browser Tests 718s, Build 166s, Playwright 50s | | tests | 9m 38s | Tests 334s, Build 173s | | performance-tests | 4m 19s | Build 173s | | linter | 2m 54s | Pint 109s | | static-analysis | 36s | — | ## Changes (one commit each) **A. Shard browser tests 4×** — `pest --shard=N/4` matrix. ~12 min → ~3 min critical path. **B. Build assets once, share via artifact** — new `build-assets` job uploads `public/build`; tests/browser-tests/performance-tests download it. Saves ~340s of duplicated CPU. **C. Disable xdebug in tests job** — `coverage: xdebug` was set but `--coverage` never passed. Silent ~30-50% slowdown. **D. Parallel Pest with Testcontainers** — `pest --parallel --processes=4`, `TESTCONTAINERS=true` so each worker gets isolated MySQL. Drops job-level mysql service. **E. Cache composer + bun deps** — `actions/cache` for composer cache dir and `~/.bun/install/cache` keyed on lockfiles. Saves 30-60s/job warm. **F. Cache Playwright browsers** — cache `~/.cache/ms-playwright` keyed on bun.lock; on hit only runs `install-deps`. Saves ~50s. **G. Pint parallel + cache** — `--parallel --cache-file=.pint.cache` with persisted cache. ~109s → ~5-10s warm. ## Expected wall time after Browser shards become critical path: **~6-7 min** (down from 16-17 min). ## Risks - **D**: 4 paratest workers each spawn a MySQL container — watch RAM/runner load. Drop to `--processes=2` if flaky. - **A**: more concurrent runners. Adjust shard count if MySQL service init becomes the new bottleneck per shard. - **B**: `build-assets` is now a hard dep for tests/browser-tests/performance-tests. ## Test plan CI itself is the test. Watch this PR run, compare timings to baseline. --- .github/workflows/ci.yml | 192 +++++++++++++++++++++++++++++++++++---- .gitignore | 1 + 2 files changed, 176 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1735a7d8..7680dfa5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,59 @@ on: default: true jobs: + build-assets: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + tools: composer:v2 + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- + + - name: Install PHP Dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Cache bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: bun-${{ runner.os }}- + + - name: Install Node Dependencies + run: bun install --frozen-lockfile + + - name: Build Assets + run: bun run build + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: build-assets + path: public/build + retention-days: 1 + tests: runs-on: ubuntu-latest + needs: build-assets services: mysql: image: mysql:8.0 @@ -37,19 +88,27 @@ jobs: with: php-version: 8.4 tools: composer:v2 - coverage: xdebug + coverage: none - - name: Setup Bun - uses: oven-sh/setup-bun@v2 + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - - name: Install Node Dependencies - run: bun install --frozen-lockfile + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- - name: Install Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - - name: Build Assets - run: bun run build + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: build-assets + path: public/build - name: Copy Environment File run: cp .env.example .env @@ -69,8 +128,26 @@ jobs: DB_PASSWORD: password browser-tests: + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + needs: browser-tests-matrix + steps: + - name: Aggregate shard results + run: | + if [ "${{ needs.browser-tests-matrix.result }}" != "success" ]; then + echo "One or more browser-tests shards failed: ${{ needs.browser-tests-matrix.result }}" + exit 1 + fi + echo "All browser-tests shards passed." + + browser-tests-matrix: if: github.event_name == 'pull_request' runs-on: ubuntu-latest + needs: build-assets + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] services: mysql: image: mysql:8.0 @@ -98,17 +175,51 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 + - name: Cache bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: bun-${{ runner.os }}- + - name: Install Node Dependencies run: bun install --frozen-lockfile + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: playwright-${{ runner.os }}- + - name: Install Playwright Browsers run: npx playwright install --with-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' + + - name: Install Playwright system deps + run: npx playwright install-deps + if: steps.playwright-cache.outputs.cache-hit == 'true' + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- - name: Install PHP Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - - name: Build Assets - run: bun run build + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: build-assets + path: public/build - name: Copy Environment File run: cp .env.example .env @@ -117,7 +228,7 @@ jobs: run: php artisan key:generate - name: Browser Tests - run: ./vendor/bin/pest --testsuite=Browser --ci + run: ./vendor/bin/pest --testsuite=Browser --ci --shard=${{ matrix.shard }}/4 env: TESTCONTAINERS: false DB_CONNECTION: mysql @@ -137,6 +248,17 @@ jobs: with: php-version: '8.4' + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- + - name: Install PHP Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist @@ -156,13 +278,40 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- + + - name: Cache bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: bun-${{ runner.os }}- + - name: Install Dependencies run: | composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist bun install --frozen-lockfile + - name: Cache Pint results + uses: actions/cache@v4 + with: + path: .pint.cache + key: pint-${{ runner.os }}-${{ hashFiles('composer.lock', 'pint.json') }}-${{ github.sha }} + restore-keys: | + pint-${{ runner.os }}-${{ hashFiles('composer.lock', 'pint.json') }}- + pint-${{ runner.os }}- + - name: Run Pint - run: vendor/bin/pint --test + run: vendor/bin/pint --test --parallel --cache-file=.pint.cache - name: Format Frontend run: bun run format @@ -175,6 +324,7 @@ jobs: performance-tests: runs-on: ubuntu-latest + needs: build-assets services: mysql: image: mysql:8.0 @@ -194,17 +344,25 @@ jobs: php-version: 8.4 tools: composer:v2 - - name: Setup Bun - uses: oven-sh/setup-bun@v2 + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - - name: Install Node Dependencies - run: bun install --frozen-lockfile + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.4-${{ hashFiles('composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.4- - name: Install Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - - name: Build Assets - run: bun run build + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: build-assets + path: public/build - name: Copy Environment File run: cp .env.example .env diff --git a/.gitignore b/.gitignore index 99782d89..3d226cb1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ yarn-error.log .php-cs-fixer.cache .claude/settings.local.json .php-version +.pint.cache