sylius-labs/behat-chrome-extension
Behat extension for controlling Chrome without Selenium, communicating directly over HTTP and WebSockets for speed at least twice as fast. Supports headless Chrome 59+, with configurable SSL validation, socket timeout, and download behavior.
sylius-labs/behat-chrome-extension is a maintained fork of the classic dmore/behat-chrome-extension: a Behat extension that gives Mink a Chrome/Chromium session over the DevTools protocol (dmore/chrome-mink-driver), with no Selenium/ChromeDriver server to babysit. For a Laravel team it is only worth reaching for when you already write Gherkin (or are porting a suite from a Sylius/Symfony project) — otherwise Laravel Dusk is the lower-friction default.
composer require --dev sylius-labs/behat-chrome-extension behat/behat behat/mink-extension — the extension brings the Mink driver, not Behat itself.behat.yml snippet (exact extension FQCN for your installed tag) and then at vendor/sylius-labs/behat-chrome-extension/composer.json, because that's where the constraints live — including the new conflict entry added in v1.5.1.php artisan serve --port=8000 or Sail/nginx).vendor/bin/behat.# behat.yml
default:
extensions:
SyliusLabs\BehatChromeExtension\...\ChromeExtension: ~ # copy FQCN from README
suites:
default:
contexts: [FeatureContext, Behat\MinkExtension\Context\MinkContext]
# features/smoke.feature
Feature: App boots
Scenario: Home page renders
When I am on "/"
Then I should see "Welcome"
If that green scenario appears, the driver, browser and server are wired correctly and everything else is application-level work.
Profiles per environment. Keep one behat.yml with default, local and ci profiles. local can run headful Chrome so you can watch failures; ci runs headless with --no-sandbox --disable-dev-shm-usage --disable-gpu.
Point base_url at the same host Laravel thinks it's on. Set both Mink's base_url and the app's APP_URL to the identical origin (http://127.0.0.1:8000, not a mix of localhost and 127.0.0.1). Asset and redirect URLs generated by Laravel will otherwise send the browser somewhere your test server isn't listening, and you get confusing "element not found" failures rather than a clean error.
Use a dedicated environment file. Run Behat with APP_ENV=behat and a .env.behat pointing at a separate database. Because the browser hits a real HTTP server in a separate process, PHPUnit-style RefreshDatabase transactions do not isolate anything — the web process can't see them. Use migrate:fresh per suite via a @BeforeSuite hook and explicit seeders, or truncate per scenario if your suite is small.
Share factories and seeders with your PHPUnit/Pest tests. Fixtures created through Eloquent factories are the cheapest way to get deterministic state; create the user in a hook, then log in through a test-only route guarded by app()->environment('behat') rather than driving the login form in every scenario.
Page objects over fat contexts. MinkContext gives you I am on / I should see for free; put domain steps (Given I am a signed-in customer, When I add :product to my cart) in small contexts composed into the suite. This keeps step definitions reusable when the UI changes.
Screenshots and HTML on failure. In an @AfterScenario hook, if the scenario failed, $session->getScreenshot() and dump getPage()->getContent() into build/behat/. Without this, CI failures are unactionable.
CI shape. Build Vite assets (npm ci && npm run build) before running — the dev server isn't there, and a broken asset pipeline surfaces as a blank page. Start the app server as a background process with a readiness check, then run vendor/bin/behat -f progress --strict. Run Behat as its own job so it doesn't slow the PHPUnit matrix.
v1.5.1 is a compatibility patch, not a feature release. The only change is a Composer conflict against dmore/chrome-mink-driver 2.11.0. There is no code change, no BC break, no config change — but it will affect resolution:
dmore/chrome-mink-driver at 2.11.0, composer update to this version will either fail or silently downgrade the driver to 2.10.x. Run composer why-not dmore/chrome-mink-driver 2.11.0 to see the chain.composer.json so a fresh composer install on another machine can't drift.Chrome sandbox in Docker. --no-sandbox is effectively mandatory in most CI containers; --disable-dev-shm-usage prevents the classic /dev/shm crash on small instances. If the browser seems to hang with no log output, this is almost always the cause.
Leaked Chrome processes. The driver keeps a browser alive per session; if a scenario dies mid-step, you get orphaned chrome processes and eventually OOM. Stop sessions in an @AfterScenario hook, but capture the screenshot before stopping.
Waiting is manual. There's no implicit retry baked into every assertion; JavaScript-driven UI (Livewire, Inertia, Alpine) needs explicit spin:
$this->getSession()->wait(5000, "document.querySelector('.alert') !== null");
Sprinkle a spin() helper rather than sleep() — sleeps make the suite slow and still flaky.
Ports and parallelism. Each concurrent Chrome session needs its own debugging port. Don't naively --parallel; either shard by suite across CI jobs, or configure distinct ports per worker.
Tag semantics. In a Behat/Mink setup everything runs against a real browser, so @javascript is effectively vestigial — but keep the tags consistent if half your suite was ported from a Symfony project, or future readers will assume some scenarios are fast.
Extension points. The service container extension registers a Mink driver factory; the driver itself is dmore/chrome-mink-driver, so anything beyond basic config (custom binary path, extra Chrome args, proxy, CDP timeouts) is done through the driver's options in behat.yml, not by patching this package. Custom Mink drivers implementing Behat\Mink\Driver\DriverInterface can be registered as additional sessions if you need, say, a headless HTTP-only session for non-JS scenarios.
Debugging loop. vendor/bin/behat --definitions to inventory steps, --stop-on-failure -f pretty for a focused run, and a single feature path to keep iteration fast. When a step fails with an empty page, check server access logs before the step definition — the browser very often never reached Laravel at all.
How can I help you explore Laravel packages today?