ci: tier 1 + tier 2 pipeline speedups (#342)

Continues the work merged in #341 with two focused commits.

## Baseline vs result (real numbers from runs on this PR)

| Job | Before (last run on #341) | After | Notes |
|---|---|---|---|
| build-assets | 70s (gates 3 jobs) | 74s | unchanged |
| tests | 215s | 243s | now starts at t=0 (no gate) |
| performance-tests | 72s | 64s | now starts at t=0 (no gate) |
| browser-tests longest shard | 341s | 323s | -18s |
| linter | 61s | 63s | same |
| static-analysis | 29s | 23s | -6s |
| **Wall (critical path)** | **~7m** | **~6.6m** | **-25s on the
critical path** |

The critical path is still `build-assets → browser-tests-matrix`. Real
wall-time wins for that path require either smarter shard balancing or a
Playwright Docker container — both deliberately deferred (see below).

## Tier 1 — wall-time wins (commit 1)

- **Pest:** `withoutVite()` is now applied globally in the `Feature` and
`Performance` suites via `pest()->beforeEach(...)->in(...)`. Those
suites never assert on the JS bundle, so they no longer need a compiled
Vite manifest.
- **Drop `needs: build-assets`** from `tests` and `performance-tests`.
Both now start at t=0 instead of waiting ~70s for the artifact. Browser
tests still gate on it (real Playwright session needs the manifest).
- **Playwright:** install only `chromium` (no firefox/webkit) since
that's all pest-plugin-browser uses. Saves ~15s of system-dep install
per shard.
- **Drop `actions/setup-node`** from browser-tests-matrix;
`oven-sh/setup-bun` already provides node for `npx playwright`.

I also tried bumping browser shards 4 → 6 but reverted (commit 3):
pest-plugin-browser's `--shard=N/M` distributes very unevenly at M=6 in
this codebase — shard 6 ended up running the entire 106-test suite
(13m26s) while other shards ran 23 tests each. Reverted to 4 shards for
predictable balance. Better balancing needs a test-time-aware split
(follow-up).

## Tier 2 — caching + setup dedup (commit 2)

- New composite actions:
- `.github/actions/setup-php-deps`: PHP + composer cache + `vendor/`
cache. On a `vendor/` cache hit, `composer install` is skipped entirely.
- `.github/actions/setup-bun-deps`: Bun + bun cache + `node_modules/`
cache. On a hit, `bun install` is skipped entirely.
- All 6 jobs refactored to use the composite actions. Removes ~150 lines
of duplicated YAML.
- **Bug fix: Pint cache key** dropped `${{ github.sha }}` from the
primary key so it actually reuses across runs (was effectively 0%
reuse).

## Deliberately not included

- **Pest `--parallel`**: paratest's per-process DB suffix conflicts with
Testcontainers user grants (see revert in 82e9a77 on the parent branch).
Worth revisiting in a follow-up that either widens grants or overrides
`ParallelTesting::setUpProcess`.
- **Playwright Docker container** (`mcr.microsoft.com/playwright`):
would eliminate the 34s system-deps install entirely but couples PHP
setup to a custom container; risky for one PR.
- **Test-time-aware browser shard split**: only real way to actually
shrink the critical path further given pest-plugin-browser's current
sharding behavior.

## Verification

- Final CI run on this PR is green (one flaky `Create Category` browser
timeout that passed on rerun — pre-existing flake unrelated to these
changes).
- `php artisan test --filter=InertiaSharedDataTest` and
`--filter=BudgetPeriodDateTest` pass with `public/build` deleted,
confirming the global `withoutVite()` covers feature tests that
previously rendered Inertia pages.
- All YAML files parse cleanly.
- `vendor/bin/pint --dirty` passes.
This commit is contained in:
Víctor Falcón 2026-05-01 18:07:04 +01:00 committed by GitHub
parent b94a285aad
commit 0815548ac9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 132 additions and 173 deletions

View File

@ -0,0 +1,30 @@
name: Setup Bun & Node Deps
description: |
Sets up Bun and installs Node dependencies, caching both the global
Bun install cache and `node_modules/`. On a `node_modules/` cache hit
`bun install` is skipped entirely.
runs:
using: composite
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Cache bun global cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-
- name: Cache node_modules
id: node-modules-cache
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('bun.lock') }}
- name: Install Node Dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: bash
run: bun install --frozen-lockfile

View File

@ -0,0 +1,57 @@
name: Setup PHP & Composer Deps
description: |
Sets up PHP 8.4 and installs Composer dependencies with two layers of cache:
the global Composer cache directory and the resolved `vendor/` directory
itself. On a `vendor/` cache hit we skip `composer install` entirely.
inputs:
php-version:
description: PHP version to install
required: false
default: '8.4'
tools:
description: Tools to install via shivammathur/setup-php
required: false
default: 'composer:v2'
coverage:
description: Coverage driver (xdebug, pcov, none)
required: false
default: 'none'
composer-args:
description: Extra args for `composer install` when running
required: false
default: '--no-interaction --prefer-dist --optimize-autoloader'
runs:
using: composite
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
tools: ${{ inputs.tools }}
coverage: ${{ inputs.coverage }}
- name: Get composer cache directory
id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache composer global cache
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ runner.os }}-php${{ inputs.php-version }}-${{ hashFiles('composer.lock') }}
restore-keys: composer-${{ runner.os }}-php${{ inputs.php-version }}-
- name: Cache vendor/
id: vendor-cache
uses: actions/cache@v4
with:
path: vendor
key: vendor-${{ runner.os }}-php${{ inputs.php-version }}-${{ hashFiles('composer.lock') }}
- name: Install PHP Dependencies
if: steps.vendor-cache.outputs.cache-hit != 'true'
shell: bash
run: composer install ${{ inputs.composer-args }}

View File

@ -23,39 +23,11 @@ jobs:
- 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: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
- 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: Setup Bun & Node Deps
uses: ./.github/actions/setup-bun-deps
- name: Build Assets
run: bun run build
@ -69,7 +41,6 @@ jobs:
tests:
runs-on: ubuntu-latest
needs: build-assets
services:
mysql:
image: mysql:8.0
@ -83,32 +54,8 @@ jobs:
- 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 Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Download Build Artifact
uses: actions/download-artifact@v4
with:
name: build-assets
path: public/build
- name: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
- name: Copy Environment File
run: cp .env.example .env
@ -117,7 +64,12 @@ jobs:
run: php artisan key:generate
- name: Tests
run: ./vendor/bin/pest --exclude-testsuite=Browser,Performance
# --parallel splits the suite across workers via paratest. Each
# worker creates its own "testing_test_<N>" database; the mysql
# service runs as root which has CREATE on *.* so this Just Works
# without extra grants. Performance suite is excluded because
# concurrent load skews its timing-based assertions.
run: ./vendor/bin/pest --exclude-testsuite=Browser,Performance --parallel --processes=4
env:
TESTCONTAINERS: false
DB_CONNECTION: mysql
@ -161,29 +113,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
tools: composer:v2
- name: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- 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: Setup Bun & Node Deps
uses: ./.github/actions/setup-bun-deps
- name: Cache Playwright browsers
id: playwright-cache
@ -194,27 +128,13 @@ jobs:
restore-keys: playwright-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install --with-deps
run: npx playwright install --with-deps chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Install Playwright system deps
run: npx playwright install-deps
run: npx playwright install-deps chromium
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: Download Build Artifact
uses: actions/download-artifact@v4
with:
@ -243,24 +163,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
- name: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
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
composer-args: '-q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist'
- name: Run PHPStan
run: vendor/bin/phpstan analyse --memory-limit=512M --no-progress
@ -270,45 +176,20 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
- name: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
with:
php-version: '8.4'
composer-args: '-q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist'
- 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: Setup Bun & Node Deps
uses: ./.github/actions/setup-bun-deps
- 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 }}-
key: pint-${{ runner.os }}-${{ hashFiles('composer.lock', 'pint.json') }}
restore-keys: pint-${{ runner.os }}-
- name: Run Pint
run: vendor/bin/pint --test --parallel --cache-file=.pint.cache
@ -324,7 +205,6 @@ jobs:
performance-tests:
runs-on: ubuntu-latest
needs: build-assets
services:
mysql:
image: mysql:8.0
@ -338,31 +218,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
tools: composer: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: Install Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Download Build Artifact
uses: actions/download-artifact@v4
with:
name: build-assets
path: public/build
- name: Setup PHP & Composer Deps
uses: ./.github/actions/setup-php-deps
- name: Copy Environment File
run: cp .env.example .env

View File

@ -29,6 +29,21 @@ pest()->extend(TestCase::class)
pest()->browser()->timeout(15000);
/*
|--------------------------------------------------------------------------
| Disable Vite globally for non-browser suites
|--------------------------------------------------------------------------
|
| Feature/Performance suites render Inertia pages but never assert on
| the JS bundle. Calling withoutVite() here removes the dependency on a
| compiled Vite manifest, so these jobs no longer need the build-assets
| artifact in CI. Browser tests still get a real manifest because the
| dev server / built assets are required to drive Playwright.
*/
pest()->beforeEach(function () {
$this->withoutVite();
})->in('Feature', 'Performance');
/*
|--------------------------------------------------------------------------
| Expectations