Speeding Up Laravel Tests with tmpfs (MySQL in RAM)
If your Laravel test suite is painfully slow and does a lot of database work (multi-tenancy, migrations, seeding), the bottleneck is almost…
Laravel Sail provides a fantastic Docker-based development environment. Running browser tests with Laravel Dusk within Sail is straightforward, but requires one key configuration tweak to ensure the testing browser can connect to your application.
You run sail artisan dusk. This executes the test command inside your main application container (e.g., laravel.test). Dusk needs to launch a browser instance (likely driven by ChromeDriver running within that same container) and have it navigate to your application's URL.
By default, Dusk might try to use the APP_URL from your .env file (e.g., http://localhost:8081). However, inside the Docker container:
localhost refers to the container itself, not necessarily where the app is served for external access.docker-compose.yml (e.g., 8081:80) means the web server inside the container listens on port 80, while 8081 is the host port.This mismatch causes the browser to fail with a net::ERR_CONNECTION_REFUSED error.
composer require --dev laravel/dusk, sail artisan dusk:install).sail-8.x/app) includes ChromeDriver and a compatible Chrome/Chromium browser (most recent versions do).tests/DuskTestCase.phpThe fix involves telling Dusk the correct URL to use within the Docker network. We do this by overriding the APP_URL specifically for the test environment using the prepare() method.
// tests/DuskTestCase.php
namespace Tests;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use PHPUnit\Framework\Attributes\BeforeClass; // <-- Add this if missing
abstract class DuskTestCase extends BaseTestCase
{
/**
* Prepare for Dusk test execution.
* This static method runs once before all tests in the class.
*/
#[BeforeClass] // <-- Add this attribute
public static function prepare(): void
{
if (! static::runningInSail()) {
// Start local ChromeDriver if not using Sail
static::startChromeDriver(); // Add options if needed: ['--port=9515']
$host = 'localhost';
// Use the port defined in .env for local testing
$port = env('APP_PORT', 80); // Or your local dev port (e.g., 8000, 8081)
} else {
// We are running inside Sail's app container.
// Use the app's service name (from docker-compose.yml)
// and the internal port the webserver listens on.
$host = 'laravel.test'; // <<< Your app's service name from docker-compose.yml
$port = 80; // <<< The internal port (usually 80)
// Optional: If ChromeDriver isn't starting automatically within the container,
// you might need to start it here if needed, but often Dusk handles it.
// static::startChromeDriver();
}
// Set the APP_URL specifically for Dusk's browser instance
$appUrl = "http://{$host}:{$port}";
static::buildAppUrl($appUrl); // Use Dusk's helper method
}
/**
* Create the RemoteWebDriver instance.
* Usually, the default logic works fine if ChromeDriver runs locally (inside the container).
*/
protected function driver(): RemoteWebDriver
{
$options = (new ChromeOptions)->addArguments(array_filter([
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
'--disable-gpu',
$this->hasHeadlessDisabled() ? null : '--headless=new',
// Add WSL/Linux specific options if needed, sometimes required inside container:
'--no-sandbox',
'--disable-dev-shm-usage',
]));
// Default driver URL usually points to localhost:9515
// When running `sail dusk`, Dusk often manages starting ChromeDriver
// inside the container and connects to it automatically on this port.
return RemoteWebDriver::create(
env('DUSK_DRIVER_URL', 'http://localhost:9515'), // Default ChromeDriver port
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
/**
* Helper to detect if running in Sail
*/
protected static function runningInSail(): bool
{
return isset($_ENV['LARAVEL_SAIL']) || isset($_SERVER['LARAVEL_SAIL']);
}
/**
* Build the base application URL.
* Helper method using Dusk's internal logic (or similar).
*/
protected static function buildAppUrl(string $url): void
{
putenv("APP_URL=$url");
$_ENV['APP_URL'] = $url;
$_SERVER['APP_URL'] = $url;
// Ensure Dusk uses the overridden URL
static::$baseUrl = $url;
static::$appUrl = $url;
}
// Add shouldStartMaximized() and hasHeadlessDisabled() helpers if you use them
protected function shouldStartMaximized(): bool
{
return isset($_ENV['DUSK_START_MAXIMIZED']) || isset($_SERVER['DUSK_START_MAXIMIZED']);
}
protected function hasHeadlessDisabled(): bool
{
return isset($_ENV['DUSK_HEADLESS_DISABLED']) || isset($_SERVER['DUSK_HEADLESS_DISABLED']);
}
}
#[BeforeClass] Attribute: Ensures prepare() runs once before tests start.prepare() Method:
$host to your application's service name (e.g., laravel.test) and $port to 80.$host to localhost and uses the APP_PORT from your .env.static::buildAppUrl() to correctly configure the URL Dusk uses.driver() Method: This often doesn't need changes. It defaults to connecting to http://localhost:9515, which is where ChromeDriver usually runs, even when started automatically by Dusk inside the container. Added container-friendly options like --no-sandbox.Use the sail prefix:
sail artisan dusk
The essential step for running Dusk within Sail (without a separate selenium container) is overriding the application URL used by the test browser. By implementing the prepare() method in DuskTestCase.php to set the URL to <service-name>:<internal-port> (e.g., http://laravel.test:80), you ensure the browser can successfully connect to your app within the Docker network.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com