Po úspešnom skúsenosti s mojím sledovacím skriptom pre unit a feature testy som sa rozhodol postaviť niečo podobné aj pre Laravel Dusk E2E (end-to-end) testy. Po mnohých iteráciách som však zistil, že tentokrát to bude trochu iné.

Začal som jednoducho aktualizáciou sledovača pre dusk, s nádejou na najlepší výsledok:

#!/bin/bash

php artisan dusk --ansi "$@"

fswatch --recursive ./tests/Browser ./resources \
    --exclude="database.sqlite" | xargs -I% php artisan dusk --ansi "$@"

Ale objavilo sa veľa problémov. Prvým z nich bolo to, že sledovač bežal zúrivo a takmer sa nezastavoval. Vyriešil som to spustením fswatch ., aby som videl, čo sa skutočne deje, a následnou aktualizáciou vylúčení:

fswatch --recursive ./tests/Browser \
    --exclude="database.sqlite" \
    --exclude="screenshots" \
    --exclude="\.log$" | xargs -I% php artisan dusk --ansi "$@"

Hlavným vinníkom boli screenshots spolu s *.log súbormi, ktoré Dusk vytvára - tie ho dostávali do slučky. Môj editor tiež pridával niekoľko súborov do hry, takže upravte podľa potreby. Aj po vyriešení tohto problému však skript stále nebol dokonalý.

Ďalší problém nastal, keď beží npm run dev s vite dev serverom - testy nemôžu bežať. Prehliadač by nevidel nič, len prázdnu bielu stránku. Vyriešil som to jednoducho odmietnutím spustenia testov:

<?php

// ...

abstract class DuskTestCase extends BaseTestCase {
  use CreatesApplication;
  use DatabaseTruncation;

  protected function beforeTruncatingDatabase(): void {
    file_exists('public/hot') &&
      throw new Exception('❌ Vite dev server is running!');

    if (!RefreshDatabaseState::$migrated) {
      exec('npm run build 2>&1', $output, $returnCode);
      $returnCode === 0 || throw new Exception('❌ Asset build failed');
  }

  // ...
}

Ďalší problém v reťazci je priamym dôsledkom predchádzajúceho. Keďže dev server nemôže bežať, assets nie sú čerstvé medzi každým testom. Ak pridáte nejaký atribút data-dusk=, test ho bez npm run build neuvidí.

Použitie RefreshDatabaseState::$migrated vyzerá ako pekné riešenie, ak ide o prvý test v danej testovacej triede - rebuild sa teda spustí na začiatku každého takého súboru. Čím viac súborov s Dusk browser testmi, tým viac rebuildov. Stále lepšie ako buď nespúšťať rebuild automaticky, alebo ho spúšťať pred každým testom, ale aj tak je to zbytočné.

Preto som sa rozhodol opustiť riešenie s rebuildom v PHP a namiesto toho sa zamerať na bash sledovací skript.

<?php

// ...
use Illuminate\Foundation\Testing\RefreshDatabaseState;

abstract class DuskTestCase extends BaseTestCase {
  use CreatesApplication;
  use DatabaseTruncation;

  protected string $seeder = DatabaseSeeder::class;

  protected function beforeTruncatingDatabase(): void {
    file_exists('public/hot') &&
      throw new Exception('❌ Vite dev server is running!');
    }
  }

  // ...
}

Riešenie, s ktorým prišiel Claude, ma naučilo niečo nové a zdá sa, že funguje celkom efektívne:

#!/bin/bash

npm run build >/dev/null 2>&1
php artisan dusk --ansi "$@"

# Cleanup function to kill background processes on exit
cleanup() {
    kill $(jobs -p) 2>/dev/null
    exit
}

trap cleanup EXIT INT TERM

# Start the build watcher in background, suppress all output
fswatch --recursive ./resources | xargs -I% npm run build >/dev/null 2>&1 &

# Run the dusk watcher in foreground with full colored output
fswatch --recursive ./tests/Browser \
    --exclude="database.sqlite" \
    --exclude="screenshots" \
    --exclude="\.log$" | xargs -I% php artisan dusk --ansi "$@"

Za predpokladu, že aplikácia používa Blade alebo Inertia pre frontend, uvedený skript robí viacero vecí:

  1. sleduje priečinok resources/, recompiluje iba pri zmenách na FE
  2. zabraňuje tomu, aby výstup buildu znečisťoval výstup testov
  3. znovu spúšťa Dusk testy iba keď sa zmenia ich súbory
  4. správne zobrazuje výsledky testov
  5. ukončí oba sledovače pri ukončení skriptu

Užite si to!