From 4ab62b98c68515a325c84a156b9a68e58d36686f Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:35:10 +0200 Subject: [PATCH] ci: speed up PHP test jobs (disable Xdebug, drop frontend build, run parallel) (#657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: speed up the test job (disable Xdebug, drop frontend build, run parallel) The `tests` job in check.yaml carried three sources of wasted wall-clock, none of which it actually used: - `coverage: xdebug` loaded Xdebug into every PHP process, but no step ever passes `--coverage` — so it was pure tax (~2-3x slower execution). Switch to `coverage: none`. If coverage is wanted later, use pcov + `--coverage`. - The job ran `npm install` + `npm run build` before the PHP tests. The feature suite is API/JSON only (49/56 feature files use getJson/assertJson) and nothing renders the Vite blade, so the built assets are never needed. Drop the Node/Vite steps; release & docker workflows still build assets. - Tests ran single-process. brianium/paratest is already installed and the runner has 4 cores, so run `php artisan test --parallel`. Validated locally: full suite passes in parallel (exit 0), including repeated runs of the two filesystem-writing module tests — no races. docker.yaml carries the same pattern but only runs on release/nightly cron, so it is left for a follow-up. * ci: apply the same test-job speedups to docker.yaml The release/nightly `tests` job in docker.yaml carried the identical waste that check.yaml had: Xdebug loaded but never used for coverage, an unnecessary frontend build before the PHP tests, and serial execution. Mirror the check.yaml fix: coverage: none, drop the Node/Vite steps (the suite is API/JSON and the separate release_artifact_build job builds its own assets), and run php artisan test --parallel. * ci: run module-scaffolding tests serially under --parallel The Modules/* tests (module:make ScaffoldProbe + modules_statuses.json toggles) mutate shared on-disk module state. paratest isolates the DB per worker but NOT the filesystem, so concurrent workers boot with ScaffoldProbe enabled and fatal on the un-autoloaded ServiceProvider (31 failures). Tag them 'modules' (Pest group on Feature/Company/Modules) and split CI: parallel --exclude-group=modules, then serial --group=modules. * ci: stub Vite in tests + bump all actions to Node 24 versions Part A (fixes #657): the customer-portal entrypoint test renders the SPA shell (app.blade.php → @vite). With the frontend build dropped from CI there's no manifest, so it 500'd (ViteManifestNotFoundException). Call $this->withoutVite() in TestCase::setUp() so SPA-shell renders work without a built manifest; the build stays dropped. Part B: bump every Node-20 action to its node24 release — checkout v4->v6, setup-node v4->v6, paths-filter v3->v4, cancel-workflow-action 0.12.1->0.13.1, softprops/action-gh-release v2->v3, docker/{setup-buildx v3->v4, login v3->v4, metadata v5->v6, build-push v5->v7}. setup-php@v2, ramsey/composer-install@v2 (composite) and svenstaro/upload-release-action@v2 are already node24. * ci: bump ramsey/composer-install v2 -> 4.0.0 (node24 internal cache) composer-install@v2 is composite but internally calls actions/cache@v3 (Node 20), which still trips the deprecation. 4.0.0 uses actions/cache v5.0.3 (Node 24) and keeps the composer-options input we use. --- .github/workflows/check.yaml | 32 +++++++------------ .github/workflows/docker.yaml | 58 +++++++++++++++------------------- .github/workflows/release.yaml | 6 ++-- CLAUDE.md | 2 +- tests/Pest.php | 6 ++++ tests/TestCase.php | 5 +++ 6 files changed, 52 insertions(+), 57 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 5d3222b9..96833671 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -21,10 +21,10 @@ jobs: php: ${{ steps.filter.outputs.php }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Check for file changes - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@v4 id: filter with: filters: | @@ -45,7 +45,7 @@ jobs: if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.12.1 + uses: styfle/cancel-workflow-action@0.13.1 with: access_token: ${{ github.token }} @@ -63,10 +63,10 @@ jobs: php-version: 8.4 - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@4.0.0 - name: Check source code for syntax errors run: ./vendor/bin/pint --test @@ -87,29 +87,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP Action uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: ${{ env.extensions }} - coverage: xdebug + coverage: none tools: pecl, composer - name: Install Composer dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@4.0.0 - - name: Use Node.js 24 - uses: actions/setup-node@v4 - with: - node-version: 24 + - name: Apply tests ${{ matrix.php-version }} (parallel) + run: php artisan test --parallel --exclude-group=modules - - name: Install - run: npm install - - - name: Compile Front-end - run: npm run build - - - name: Apply tests ${{ matrix.php-version }} - run: php artisan test + - name: Apply module tests ${{ matrix.php-version }} (serial) + run: php artisan test --group=modules diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 6ae1344c..7ad238f6 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -25,10 +25,10 @@ jobs: php-version: 8.4 - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@4.0.0 - name: Check source code for syntax errors run: ./vendor/bin/pint --test @@ -48,32 +48,24 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP Action uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: ${{ env.extensions }} - coverage: xdebug + coverage: none tools: pecl, composer - name: Install Composer dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@4.0.0 - - name: Use Node.js 24 - uses: actions/setup-node@v4 - with: - node-version: 24 + - name: Apply tests ${{ matrix.php-version }} (parallel) + run: php artisan test --parallel --exclude-group=modules - - name: Install - run: npm install - - - name: Compile Front-end - run: npm run build - - - name: Apply tests ${{ matrix.php-version }} - run: php artisan test + - name: Apply module tests ${{ matrix.php-version }} (serial) + run: php artisan test --group=modules release_artifact_build: name: 🏗️ Build / Upload - Release File @@ -86,7 +78,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -96,12 +88,12 @@ jobs: coverage: none - name: Install Composer dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@4.0.0 with: composer-options: --no-dev - name: Use Node.js 24 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 @@ -132,20 +124,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Log in to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Extract metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: invoiceshelf/invoiceshelf tags: | @@ -155,7 +147,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: docker/production/Dockerfile @@ -172,19 +164,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Log in to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: docker/production/Dockerfile @@ -203,7 +195,7 @@ jobs: branch: [master, develop] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ matrix.branch }} fetch-depth: 2 @@ -222,11 +214,11 @@ jobs: - name: Set up Docker Buildx if: steps.changes.outputs.has_changes == 'true' - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Log in to Docker Hub if: steps.changes.outputs.has_changes == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} @@ -243,7 +235,7 @@ jobs: - name: Build and push Docker image if: steps.changes.outputs.has_changes == 'true' - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: docker/production/Dockerfile diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b9f359f2..d24b0de8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -32,7 +32,7 @@ jobs: run: composer install --no-dev --optimize-autoloader --no-interaction - name: Use Node.js 24 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 @@ -89,7 +89,7 @@ jobs: run: zip -r InvoiceShelf.zip InvoiceShelf/ - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: /tmp/InvoiceShelf.zip generate_release_notes: true diff --git a/CLAUDE.md b/CLAUDE.md index 794c55f9..7a65781d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -140,4 +140,4 @@ InvoiceShelf follows TDD development style: ## CI Pipeline -GitHub Actions (`check.yaml`): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4. +GitHub Actions (`check.yaml`): runs Pint style check, then runs Pest tests in parallel (`php artisan test --parallel`) on PHP 8.4 with Xdebug disabled (`coverage: none`). The test job does **not** build the frontend — the suite is API/JSON only and never renders the Vite blade, so no Node/Vite step is needed (release/docker workflows still build assets in their own jobs). diff --git a/tests/Pest.php b/tests/Pest.php index 3260e4c6..efe351cc 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -5,3 +5,9 @@ use Tests\TestCase; uses(TestCase::class, RefreshDatabase::class)->in('Feature'); uses(TestCase::class, RefreshDatabase::class)->in('Unit'); + +// The module-system tests scaffold real modules on disk (Modules/ScaffoldProbe) and +// toggle the shared modules_statuses.json — global, filesystem-level state that paratest +// does NOT isolate per worker (it only isolates the database). Tag them so CI can run +// this group serially, after the parallel pass, to avoid cross-worker collisions. +uses()->group('modules')->in('Feature/Company/Modules'); diff --git a/tests/TestCase.php b/tests/TestCase.php index e76d05e3..0faafe0d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,6 +15,11 @@ abstract class TestCase extends BaseTestCase { parent::setUp(); + // CI skips the frontend build, so a few routes that render the SPA shell + // (resources/views/app.blade.php → @vite) would throw ViteManifestNotFoundException. + // Stub Vite so those views render without a built manifest. + $this->withoutVite(); + Factory::guessFactoryNamesUsing(function (string $modelName) { // We can also customise where our factories live too if we want: $namespace = 'Database\\Factories\\';