addArguments([ '--disable-gpu', '--headless=new', '--window-size=1920,1080', '--no-sandbox', '--disable-dev-shm-usage', '--disable-search-engine-choice-screen', '--ignore-certificate-errors', '--allow-insecure-localhost', '--disable-features=Translate,BackForwardCache,AcceptCHFrame', ]); return RemoteWebDriver::create( $_ENV['DUSK_DRIVER_URL'] ?? 'http://127.0.0.1:9515', DesiredCapabilities::chrome()->setCapability( ChromeOptions::CAPABILITY, $options ) ); } private static function configureDuskBaseUrl(): void { $url = rtrim((string) ($_ENV['DUSK_APP_URL'] ?? getenv('DUSK_APP_URL') ?: 'http://127.0.0.1:8000'), '/'); $_ENV['APP_URL'] = $url; $_SERVER['APP_URL'] = $url; putenv('APP_URL='.$url); if (property_exists(BaseTestCase::class, 'baseUrl')) { static::$baseUrl = $url; } } private static function ensureLaravelServerIsReachable(): void { $url = rtrim((string) ($_ENV['APP_URL'] ?? 'http://127.0.0.1:8000'), '/'); $parts = parse_url($url); $scheme = $parts['scheme'] ?? 'http'; $host = $parts['host'] ?? '127.0.0.1'; $port = (int) ($parts['port'] ?? ($scheme === 'https' ? 443 : 80)); if (static::canConnect($host, $port)) { return; } // Dusk should be deterministic in local/CI runs. If no reachable app // server is already running, start Laravel's built-in server for the // browser suite instead of failing every test with ERR_CONNECTION_REFUSED. if (! in_array($host, ['127.0.0.1', 'localhost'], true)) { $fallbackUrl = 'http://127.0.0.1:8000'; $_ENV['APP_URL'] = $fallbackUrl; $_SERVER['APP_URL'] = $fallbackUrl; putenv('APP_URL='.$fallbackUrl); if (property_exists(BaseTestCase::class, 'baseUrl')) { static::$baseUrl = $fallbackUrl; } $host = '127.0.0.1'; $port = 8000; } if (static::canConnect($host, $port)) { return; } static::startLaravelServer($host, $port); static::waitForTcpPort($host, $port, 15.0, 'Laravel Dusk application server'); } private static function startLaravelServer(string $host, int $port): void { $root = dirname(__DIR__); $logPath = $root.'/storage/logs/dusk-server.log'; if (! is_dir(dirname($logPath))) { @mkdir(dirname($logPath), 0775, true); } $php = escapeshellarg(PHP_BINARY); $artisan = escapeshellarg($root.'/artisan'); $hostArg = escapeshellarg($host); $portArg = (string) $port; $log = escapeshellarg($logPath); $command = "{$php} {$artisan} serve --host={$hostArg} --port={$portArg} > {$log} 2>&1"; self::$laravelServerProcess = proc_open($command, [], $pipes, $root); } private static function canConnect(string $host, int $port): bool { $connection = @fsockopen($host, $port, $errno, $errstr, 0.25); if (! is_resource($connection)) { return false; } fclose($connection); return true; } private static function waitForTcpPort(string $host, int $port, float $seconds, string $service): void { $deadline = microtime(true) + $seconds; do { if (static::canConnect($host, $port)) { return; } usleep(100_000); } while (microtime(true) < $deadline); $log = dirname(__DIR__).'/storage/logs/dusk-server.log'; $hint = is_file($log) ? " Check {$log}." : ''; throw new \RuntimeException("{$service} is not reachable at {$host}:{$port}.{$hint}"); } /** * Dusk may install a differently named macOS Apple Silicon binary. * * This method intentionally does not use base_path(), app(), or Artisan because * PHPUnit calls BeforeClass before Laravel has booted the application container. */ private static function ensureChromeDriverBinaryAlias(): void { $binDirectory = dirname(__DIR__).'/vendor/laravel/dusk/bin'; $expected = $binDirectory.'/chromedriver-mac-arm'; if (is_file($expected)) { @chmod($expected, 0755); return; } if (! is_dir($binDirectory)) { @mkdir($binDirectory, 0755, true); } $candidates = array_values(array_filter( glob($binDirectory.'/chromedriver*') ?: [], static function (string $path) use ($expected): bool { return is_file($path) && $path !== $expected && ! str_ends_with($path, '.zip') && ! str_ends_with($path, '.txt'); } )); usort($candidates, static function (string $left, string $right): int { $score = static fn (string $path): int => str_contains($path, 'mac-arm64') ? 0 : (str_contains($path, 'mac-arm') ? 1 : (str_contains($path, 'mac') ? 2 : 3)); return $score($left) <=> $score($right); }); if ($candidates !== []) { @copy($candidates[0], $expected); @chmod($expected, 0755); } } }