Files
WMS/tests/DuskTestCase.php
T
2026-06-23 13:39:02 +01:00

211 lines
6.5 KiB
PHP

<?php
namespace Tests;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;
use PHPUnit\Framework\Attributes\BeforeClass;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
/**
* Keep the local Laravel server process alive for the Dusk run.
*
* @var resource|null
*/
private static $laravelServerProcess = null;
/**
* Prepare for Dusk test execution.
*/
#[BeforeClass]
public static function prepare(): void
{
static::configureDuskBaseUrl();
static::ensureLaravelServerIsReachable();
if (! static::runningInSail()) {
static::ensureChromeDriverBinaryAlias();
static::startChromeDriver(['--port=9515']);
static::waitForTcpPort('127.0.0.1', 9515, 8.0, 'ChromeDriver');
}
}
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
$options = (new ChromeOptions)->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);
}
}
}