Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Running Laravel Dusk Tests within Your Sail Environment

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.

The Scenario

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.

The Challenge

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.
  • The port mapping in 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.

Prerequisites

  • Laravel Sail installed and running.
  • Laravel Dusk installed (composer require --dev laravel/dusk, sail artisan dusk:install).
  • Your Sail application container image (sail-8.x/app) includes ChromeDriver and a compatible Chrome/Chromium browser (most recent versions do).

The Solution: Configure tests/DuskTestCase.php

The 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']);
    }
}

Key Changes in the Code

  1. #[BeforeClass] Attribute: Ensures prepare() runs once before tests start.
  2. prepare() Method:
    • Detects if running inside Sail.
    • If in Sail: Sets $host to your application's service name (e.g., laravel.test) and $port to 80.
    • If not in Sail: Sets $host to localhost and uses the APP_PORT from your .env.
    • Calls static::buildAppUrl() to correctly configure the URL Dusk uses.
  3. 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.

Running Your Tests

Use the sail prefix:

sail artisan dusk

Conclusion

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.

What I'm building

Delegate tasks. Get software.

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

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *