se/selenium-server-standalone
A PHP/Composer package that simplifies Selenium Server Standalone setup for browser testing. It downloads and manages the Selenium JAR, starts and stops the local server, and lets you run automated UI tests with minimal configuration.
This package does one thing: it gets the Selenium Server Standalone JAR into your project through Composer instead of a manual download from selenium-release.storage.googleapis.com. It is not a PHP client, not a driver installer, and not a Laravel testing layer — treat it purely as a vendored binary.
Confirm the runtime dependency first. Selenium 3.x (what this ships) needs a JRE on PATH:
java -version # aim for Java 8–11; see Gotchas for 17+
Install as a dev dependency:
composer require --dev se/selenium-server-standalone
After install, look in vendor/bin/ — that is where the JAR (and a thin launcher) lands. That directory is the only thing this package contributes to your project; there is nothing to configure, no service provider, no config file.
Smoke-test it before touching any test suite:
java -jar vendor/bin/selenium-server-standalone.jar -port 4444
curl http://127.0.0.1:4444/wd/hub/status
A JSON payload with "ready": true means you are in business.
First real use case — and the honest one for a Laravel dev: you probably do not need this just to drive a browser. Laravel Dusk bundles its own ChromeDriver and talks to Chrome directly; Docker images (selenium/standalone-chrome) are the maintained way to get a Selenium node. Reach for this package when you (a) already have a php-webdriver/webdriver or phpunit/phpunit-selenium test suite, (b) need a Selenium Grid hub with multiple browser nodes, or (c) have a constrained CI environment where adding a Docker service is harder than installing a JAR.
Lifecycle-managed local server. Do not leave a java -jar running by hand. Start it with Symfony's Process (already in every Laravel install) and stop it deterministically:
use Symfony\Component\Process\Process;
protected static Process $server;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
static::$server = new Process([
'java', '-jar', base_path('vendor/bin/selenium-server-standalone.jar'),
'-port', '4444',
'-Dwebdriver.chrome.driver=' . base_path('vendor/bin/chromedriver'),
]);
static::$server->start();
// poll /wd/hub/status rather than sleeping blindly
retry(30, fn () => file_get_contents('http://127.0.0.1:4444/wd/hub/status'), 200);
}
public static function tearDownAfterClass(): void
{
static::$server->stop(0);
parent::tearDownAfterClass();
}
RemoteWebDriver base test case. Pair the server with php-webdriver/webdriver and a shared static driver so you pay browser startup once per class, not per test:
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Chrome\ChromeOptions;
protected static RemoteWebDriver $driver;
$options = new ChromeOptions();
$options->addArguments(['--headless=new', '--no-sandbox', '--disable-dev-shm-usage']);
$caps = DesiredCapabilities::chrome();
$caps->setCapability(ChromeOptions::CAPABILITY_W3C, $options);
static::$driver = RemoteWebDriver::create('http://127.0.0.1:4444/wd/hub', $caps);
Composer script escape hatch. For day-to-day local work, a named script beats remembering the flags:
"scripts": {
"selenium": "java -jar vendor/bin/selenium-server-standalone.jar -port 4444"
}
Then composer selenium in one terminal, your test suite in another.
App server coordination. Selenium drives a real HTTP client, so php artisan serve must be running against the test environment. Start it in setUpBeforeClass alongside the JAR (or, more cleanly, use Laravel's own Illuminate\Foundation\Testing\Concerns server helpers if you are on a recent framework) and point the driver at http://127.0.0.1:8000.
Grid mode. The same JAR doubles as hub and node, which is where this package earns its keep over a bare ChromeDriver:
java -jar vendor/bin/selenium-server-standalone.jar -role hub -port 4444
java -jar vendor/bin/selenium-server-standalone.jar -role node \
-hub http://hub:4444/grid/register \
-browser "browserName=chrome,maxInstances=4"
CI pattern. On GitHub Actions, prefer a services: container running selenium/standalone-chrome; keep this package for the case where you want the JAR versioned in composer.lock and launched by your own PHP bootstrap (e.g. a Pest beforeAll hook), so local and CI share one code path.
InaccessibleObjectException or obscure NoClassDefFoundErrors. Pin JAVA_HOME to 11 in CI. This is the single most common "it worked on my machine" failure.session not created: This version of ChromeDriver only supports Chrome version N. This is what makes the package feel stale in practice — the JAR is pinned, the drivers are not.RefreshDatabase breaks with remote browsers. The browser hits your app over HTTP in a separate PHP process, so it never sees the test process's open transaction. Use DatabaseMigrations or DatabaseTruncation in Selenium test classes. This trips up almost everyone coming from Dusk-less feature tests.--headless=new (or --headless on older Chrome), --no-sandbox, --disable-dev-shm-usage; without them you get blank pages and /dev/shm OOM crashes that look like app bugs.tearDownAfterClass doesn't run (fatal error, Ctrl-C), the JAR and its child browsers keep port 4444 and pile up. Register a shutdown function or run through symfony/process with setTimeout; also Process::stop() only kills the parent, so consider ->stop(0) plus a pkill -f selenium-server-standalone in CI cleanup.bin proxies work via copies, not symlinks; path separators in -Dwebdriver.chrome.driver need escaping. Prefer base_path()-built absolute paths.-port is overridable; always call --version on the JAR to know what you actually got (java -jar ... --version), because the Composer constraint history is not obvious from composer.json..env block (SELENIUM_PORT, SELENIUM_BROWSER, SELENIUM_HUB) so your test base class, your composer script, and your CI workflow can't drift apart.composer install. Pre-warm the Composer cache in your image build if you hit this.How can I help you explore Laravel packages today?