After I had success with my unit and feature tests watcher script I decided to build something similar for a Laravel Dusk E2E (end-to-end) test too. However, it was a little bit different this time as I have found out after many iterations.
I started up simply updating the watcher but for dusk, hoping for the best:
#!/bin/bash
php artisan dusk --ansi "$@"
fswatch --recursive ./tests/Browser ./resources \
--exclude="database.sqlite" | xargs -I% php artisan dusk --ansi "$@"
But there were many problems. The first one was that the watcher was
running furiously and almost would not stop. I solved this via running
fswatch .
to see what was actually happening and then updating the
excludes:
fswatch --recursive ./tests/Browser \
--exclude="database.sqlite" \
--exclude="screenshots" \
--exclude="\.log$" | xargs -I% php artisan dusk --ansi "$@"
The main culprit was screenshots
along with *.log
files Dusk creates,
which sent it into the loop. My editor was also adding a few files into the
mix, so adjust accordingly. With this issue solved, the script was however
not perfect still, not at all.
Another problem was when the npm tun dev
is running a vite dev server,
the tests
cannot run.
The browser would see nothing, just a blank white page. I solved it by
simply refusing to run the tests:
<?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');
}
// ...
}
Next problem in the chain is the direct consequence of the previous one.
Since the dev server cannot run, the assets are not fresh between every
test. If you add some data-dusk=
attribute, the test would not see it
without a npm run build
.
Using RefreshDatabaseState::$migrated
appears to be a nice way if this is
the first test in a given test class, so rebuild happens at the beginning
of each such file. The more Dusk browser tests files, the more rebuilds.
Still better than either not running rebuild automatically or running it
before every test, but an overkill nevertheless.
Thus I decided to ditch the solution for a rebuild within PHP and instead focused on a bash watcher script.
<?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!');
}
}
// ...
}
The solution Claude came up with taught me something new and appears to be working quite efficiently:
#!/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 "$@"
Assuming the app is using Blade or Inertia for the front-end, the above script does multiple things;
- watches for
resources/
folder, rebuilding only when FE changes - prevents output of build process littering test output
- re-runs Dusk tests only when its files change
- correctly outputs tests results
- kills both watchers when script ends
Enjoy!