Installation
Follow this page to install Behat Steps in your PHP project, add the packages only some steps need and set up a real browser for scenarios that run JavaScript. It doesn't write a scenario: the Quick start does that.
Before you begin
Check the following before you start:
- PHP 8.3 or newer, and Composer.
- Behat 3, from 3.33, or Behat 4, or no Behat at all yet, since Composer installs it with Behat Steps.
- Drupal 11 or 12, if you want the Drupal steps.
- Neither
drupal/drupal-extensionnordrupal/drupal-driver. Behat Steps does their job itself and declares a conflict with both, so Composer won't install it while either one is installed, whether your project requires it or another package pulls it in. The Requirements section of the migration guide shows the command that removes them, for any project, and the rest of the guide lists what replaces their steps and classes.
Supported versions lists the full matrix.
Install the package
Run this in your project's root:
composer require --dev drevops/behat-steps:^4
That's the only package you need to require. Composer installs the rest with it, because Behat Steps requires them:
- Behat, as
behat/behat - Mink, the library Behat uses to drive a browser, as
behat/mink - Mink's BrowserKit driver, as
behat/mink-browserkit-driver - Mink's Behat extension, as
friends-of-behat/mink-extension - The Symfony components the steps use
Confirm that Composer installed it: composer show drevops/behat-steps prints the release it installed and the packages it requires.
A few steps need 1 more package each, which Optional dependencies lists.
Optional dependencies
Behat Steps lists 4 packages under suggest in its composer.json rather than requiring them, because only some steps need them, so you don't install what your suite never uses. composer suggests drevops/behat-steps lists the ones your project doesn't have yet. Add the ones your suite needs to its require-dev:
| Package | What needs it |
|---|---|
softcreatr/jsonpath | The JSON path steps of JSON responses, such as Then the JSON path :path should exist |
justinrainbow/json-schema | The JSON schema steps of JSON responses, such as Then the response should match the JSON schema in the file :filename |
lullabot/mink-selenium2-driver | @javascript scenarios run through a Selenium server |
dmore/behat-chrome-extension | @javascript scenarios run in headless Chrome, with no Selenium server |
The Selenium driver and the Chrome extension for Behat, dmore/behat-chrome-extension, do the same job, so a suite that runs @javascript scenarios installs 1 of them, and a suite without any installs neither, as JavaScript browser drivers describes. A suite that uses every JSON step adds both JSON packages:
composer require --dev softcreatr/jsonpath justinrainbow/json-schema
JavaScript browser drivers
Mink, the library Behat uses to drive a browser, talks to the browser through a browser driver. The BrowserKit driver that comes with Behat Steps loads pages over plain HTTP from PHP: it's fast, but it never runs the page's JavaScript. When a scenario needs JavaScript, tag it @javascript, and Mink runs it in a real browser through a second driver instead.
The steps that need a real browser work with the 2 browser drivers this library ships adapters for, the classes that declare what each driver can do: the Selenium/WebDriver browser driver from lullabot/mink-selenium2-driver, or the Selenium-less dmore/chrome-mink-driver, which talks to Chrome directly over the Chrome DevTools Protocol. This library's own CI tests against both. Browsers compares them with BrowserKit and lists the steps that need a real browser. Another browser driver needs an adapter registered for it.
Run JavaScript scenarios in headless Chrome
These steps change the behat.php the Quick start creates, so if your project has none yet, start there and come back. To run @javascript scenarios in headless Chrome, with no Selenium server, follow these steps:
-
Add
dmore/behat-chrome-extension, which pulls indmore/chrome-mink-driver. It accepts Behat 3 and, from 1.5.0, Behat 4, and this library's own CI runs it on both:composer require --dev dmore/behat-chrome-extension -
In
behat.php, register its extension, and give Mink achromesession that points at a headless Chrome beside thebrowserkit_httpsession the Quick start sets up.$profileis thedefaultprofile of that file:use Behat\Config\Extension;use Behat\MinkExtension\ServiceContainer\MinkExtension;use DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension;$profile->withExtension(new Extension(ChromeExtension::class))->withExtension(new Extension(MinkExtension::class, ['base_url' => 'http://your-site.local','javascript_session' => 'chrome','sessions' => ['browserkit_http' => ['browserkit_http' => NULL],'chrome' => ['chrome' => ['api_url' => 'http://chrome:9222']],],]));This is the whole
MinkExtensionentry. A profile takes each extension once, and Behat stops with an error saying the extension already exists when a profile registers it twice. So if your profile already registersMinkExtension, addjavascript_sessionand thechromesession to that entry, and keep your ownbase_urland sessions.Mink runs every scenario tagged
@javascripton thejavascript_session, and every other scenario on the first session that can't run JavaScript,browserkit_httphere. Only the tagged scenarios run in Chrome.In
'chrome' => ['chrome' => [...]], the firstchromenames the session, whichjavascript_sessionpoints at, and the second picks the Chrome driver, whose settings follow.api_urlis the address of Chrome's DevTools endpoint. Inhttp://chrome:9222,chromeis the host name Chrome listens on, such as the name of its Docker Compose service, and 9222 is its port, so replace both with your own. Chrome loadsbase_urlitself, so set it to an address Chrome can reach, as step 3 explains. -
Start a headless Chrome that Behat reaches at
api_urland that reaches yourbase_urlin turn. Any image that exposes a DevTools endpoint works, such aschromedp/headless-shell. When your site runs in Docker Compose, a service like this one starts a headless Chrome beside it:services:chrome:image: chromedp/headless-shell:latestshm_size: 1gbA container in the same Compose project, such as the one Behat runs in, reaches it at
http://chrome:9222, theapi_urlin step 2. Chrome reaches the site by its service name in turn, sobase_urluses that name and the port the site listens on inside the Compose network, such ashttp://nginx:8080, rather than a port published to your machine.shm_sizegives Chrome more shared memory than Docker's 64 MB default, which a large page can run out of. This repository runs the same image as itschrome_headlessservice in docker-compose.yml, which its own tests reach athttp://chrome_headless:9222.
Confirm that Behat reaches Chrome the first time you run a scenario tagged @javascript. An error that starts Could not fetch version information from http://chrome:9222/json/version means Behat can't reach api_url.
Watch the browser in a headed Chrome
When Behat runs on your machine, you can watch the browser in a headed Chrome, one with a window, instead of a headless one. These steps build on the chrome session, the ChromeExtension registration and the use lines from Run JavaScript scenarios in headless Chrome. To set it up, follow these steps:
-
Start Chrome on your machine with
--remote-debugging-port=9222and a--user-data-dirof its own, since Chrome 136 and newer ignore the port on your everyday profile. -
In
behat.php, add a profile that pointsapi_urlat that Chrome, and replace the file'sreturnline with the snippet's, which registers both profiles:use Behat\Config\Config;use Behat\Config\Profile;// Runs with "vendor/bin/behat -p headed".$headed = (new Profile('headed'))->withExtension(new Extension(MinkExtension::class, ['sessions' => ['chrome' => ['chrome' => ['api_url' => 'http://localhost:9222']],],]));return (new Config())->withProfile($profile)->withProfile($headed);A profile other than
defaultstarts fromdefaultand overrides only what it sets, so registeringMinkExtensionagain inheaded, a profile of its own, is fine, and the headed run keeps every other setting.BEHAT_PARAMScan't make this change, because it can't override a settingbehat.phpmakes, as Environment variables explains. -
Run Behat with that profile:
vendor/bin/behat -p headed. Behat reaches the Chrome athttp://localhost:9222, and the Chrome has to reach your site'sbase_urlin turn. Chrome loads the pages of each@javascriptscenario in a window of its own.
When Behat runs in a container, localhost is the container itself, and a Chrome on your machine can't open a Compose address such as http://nginx:8080. To watch a browser in that setup, run @javascript scenarios through Selenium on an image that shows its browser in a web page, such as selenium/standalone-chromium, as Run JavaScript scenarios through Selenium shows. Open the port its step 3 publishes in your own browser, with the image's default password, secret.
Run JavaScript scenarios through Selenium
To run @javascript scenarios through a Selenium server instead, follow these steps:
-
Add
lullabot/mink-selenium2-driver:composer require --dev lullabot/mink-selenium2-driverIf your project requires the original,
behat/mink-selenium2-driver, remove it and require the fork instead, since the fork replaces it and a project can't require both:composer remove --dev behat/mink-selenium2-drivercomposer require --dev lullabot/mink-selenium2-driver -
In
behat.php, give Mink aselenium2session that points at your Selenium server, and setjavascript_sessionto it:$profile->withExtension(new Extension(MinkExtension::class, ['base_url' => 'http://your-site.local','browser_name' => 'chrome','javascript_session' => 'selenium2','sessions' => ['browserkit_http' => ['browserkit_http' => NULL],'selenium2' => ['selenium2' => ['wd_host' => 'http://selenium:4444/wd/hub']],],]));wd_hostis the Selenium server's address, whereseleniumis its host name, such as the name of its Docker Compose service, and 4444 the portselenium/standalone-chromiumlistens on.browser_nameis the browser it starts. The browser loadsbase_urlitself, so set it to an address that browser can reach. behat.dist.php sets the session up the same way. -
Start a Selenium server that Behat reaches at
wd_host. When your site runs in Docker Compose, a service like this one starts it beside the site:services:selenium:image: selenium/standalone-chromium:latestshm_size: 1gbports:- "7900"portspublishes the page that shows the browser, on its port 7900, to a free port of your machine, whichdocker compose port selenium 7900prints. Leave it out when you don't watch the browser. This repository's Selenium service in docker-compose.yml runs the same image, which its own tests reach athttp://chrome:4444/wd/hub.
lullabot/mink-selenium2-driver is a fork of behat/mink-selenium2-driver that works with Selenium 4. The original, in every release up to and including 1.7.0, opens a session through the JSON Wire Protocol, which Selenium 4 dropped, so a Selenium 4 server refuses it with Could not start a new session. No nodes support the capabilities in the request. The fork speaks the W3C WebDriver protocol Selenium 4 expects.
The fork provides the same Selenium2Driver class and declares that it replaces the original, so Composer installs only 1 of them. A Drupal project that requires drupal/core-dev already has the fork, because core-dev requires it. composer show lullabot/mink-selenium2-driver prints the fork's details when your project has it, and Package "lullabot/mink-selenium2-driver" not found when it doesn't.
Where to next
- The Quick start takes you from an empty
behat.phpto a passing scenario. - If your suite already has contexts of its own, Contexts shows which Behat Steps context your
FeatureContextextends and where your own steps go.