Behat Steps 4.0.0(Opens in a new tab/window) is out, ready for Drupal 12 and Behat 4. It brings Drupal Extension and Drupal Driver into Behat Steps(Opens in a new tab/window), so the step definitions, the Behat extension and the backends that reach Drupal all come in 1 package, tested together on Drupal 11 and 12, Behat 3.33+ and Behat 4, and PHP 8.3 to 8.5.
Behat checks your website against scenarios written in plain English: given I am logged in as an editor, when I publish a news article, then visitors should see it on the home page. Behat Steps supplies the steps those scenarios are made of, and version 4 rebuilds how they work. Each step now asks for the capability it needs and runs in-process or over Drush, all 458 steps follow the same wording rules, a public helper stands behind every step for the steps you write yourself, and REST, JSON and XML steps run in a session of their own.
Everything about it is documented on a new site of its own, behat-steps.drevops.com(Opens in a new tab/window).
What's new in Behat Steps 4
Drupal 12 and Behat 4
Behat Steps 4 supports Drupal 11 and Drupal 12. Drupal 12 requires PHP 8.5 and Symfony 8, and Behat 4 is the Behat release that accepts Symfony 8, so a Drupal 12 suite runs on Behat 4. Behat Steps 4 runs on Behat 3.33+ as well, so a Drupal 11 site can move to Behat 4 at its own pace, ahead of its Drupal 12 upgrade.
The library's own CI runs the full suite against the latest Drupal 12 pre-release on PHP 8.5 and Behat 4, with both the newest and the lowest dependency versions it allows. The test site behind those runs installs the kinds of contrib modules a real site uses, from Webform to Paragraphs, patched where a module has no Drupal 12 release yet.
Behat 4 reads its configuration from PHP only, so a suite on Behat 4 keeps its settings in behat.php. The quick start(Opens in a new tab/window) builds one from scratch.
Backends picked by capability
In Behat Steps 3, the @api tag picked 1 driver for a whole scenario. In Behat Steps 4, each step asks for the capability it needs, 1 job such as clearing caches or creating a user, and the first backend in your list that provides it does the work:
| Backend | How it reaches the site | Capabilities |
|---|---|---|
drupal | Bootstraps Drupal inside the Behat process | 34 of 35 |
drush | Runs Drush commands against the site | 14 |
blackbox | Stands for a site you can only browse | None |
You set the order once in behat.php, and a @backend:drush tag on a scenario or a feature moves Drush to the front for it. The same step runs in-process on your machine and over Drush wherever a scenario asks for it, with no change to the step:
@backend:drush
Scenario: The home page loads after cron
Given the cache has been cleared
When cron runs
And I visit "/"
Then the response status code should be 200
A backend starts only when a step first needs it, so a scenario that never touches Drupal never bootstraps it. When no backend in the list provides a capability, the step fails with an error that names the capability and the order it searched. A backend of your own, such as one that creates content over your site's JSON:API, serves the shipped steps as soon as it implements their capabilities. The backends are Drupal Driver's drivers under a new name, which keeps them apart from Mink's browser drivers.
Every step reads the same way
All 458 steps, the ones from Drupal Extension included, follow 19 numbered wording rules, and a step that doesn't fit a rule is reworded. A few of the rules:
- Each step declares who acts. A step a person does through the browser starts with
I, and a step the site does on its own starts with its subject:When I press the button "Save",When cron runs. Givennames the state it sets up,Whenis the action, andThenchecks withshould:Given the module "node" is enabled,Then the queue "default" should be empty.- A value that says which one follows its noun:
the user "jane",the config "system.site",in the region "header".
A linter checks 13 of the 19 rules automatically, so new steps keep to them too. A scenario reads the same way whoever wrote it, and once you know a few steps, the rest are easier to guess. Some step text changed to get there:
| Drupal Extension and Behat Steps 3.14 | Behat Steps 4.0 |
|---|---|
Given I am logged in as a user with the "editor" role | Given I am logged in as a user with the role "editor" |
Then I should see "Welcome" in the "header" region | Then I should see the text "Welcome" in the region "header" |
Given I run cron | When cron runs |
Many steps keep their text, including Mink's own, such as I fill in and I press.
A single context, with a helper behind every step
Register DrupalContext, or WebContext on a site that isn't Drupal, and all 57 step traits are there: 28 for any website and 29 for Drupal. Your own FeatureContext extends it and inherits every step.
Every step is a thin wrapper over public helpers, 337 of them, listed in HELPERS.md. When your suite needs a step in your project's own words, such as given a news article is scheduled for tomorrow, you build it on the same helpers the shipped steps use. If you work with an AI coding agent, the create-behat-step skill that ships in the package writes the step for you.
API steps with a session of their own
The REST steps send their requests through a Mink session of their own, and the JSON and XML steps read its last response, so API calls run apart from the browser's cookies and page. A REST header can take its value from the last response, so a token flow runs on the shipped steps:
Given the REST header "Authorization" has the value from the JSON path "$.access_token" with the prefix "Bearer "
A request body can come from a fixture file, too.
Configuration and checks
Each trait declares its own options, and you set their defaults once, under steps in behat.php. A context's config argument changes them for 1 suite, and a tag changes them for 1 feature or scenario. A tag on the Feature: line applies to every scenario below it, and @behat-steps-skip:WatchdogTrait names the trait it switches off, so a misspelled skip tag stops the run before the scenario starts.
Some traits check every scenario as it runs. DrupalContext fails a scenario that logs a PHP error, read from the log of Drupal's core dblog module, and JavascriptTrait can fail one that throws a JavaScript error. Then the page should pass accessibility checks checks the page's accessibility, with a threshold you can set per scenario. After each scenario, the entities its steps created are deleted in reverse creation order, so your test database stays clean across long suites.
Tested on every change
Every change to Behat Steps runs the full suite in 17 test jobs: Drupal 11 on PHP 8.3, 8.4 and 8.5 with Behat 3 and Behat 4, Drupal 12 on PHP 8.5 with Behat 4, and headless Chrome beside Selenium for the scenarios that need a real browser. Every step has a scenario of its own that tests it, and every file has at least 95% line coverage.
We've already run the development version on client projects, and our own website(Opens in a new tab/window) moved its tests to Behat Steps 4 and Behat 4 the morning after the first release candidate.
The long road to 4.0
| Package | What it does | Created by |
|---|---|---|
| Drupal Driver | Reaches into a Drupal site to create users, content and configuration, in PHP or through Drush | Jonathan Hedstrom |
| Drupal Extension | Connects Behat to Drupal and ships the Drupal step definitions | Jonathan Hedstrom |
| Behat Steps | Adds ready-made steps for pages, forms, emails, queues and more | DrevOps |
Behat Steps started as a set of traits on top of Drupal Extension, and for years that split worked well: Drupal Extension handled the connection to Drupal, and we added the steps our projects kept needing. Behat Steps 3 still builds its Drupal steps on Drupal Extension 6, which runs on Drupal Driver 3, and Vortex(Opens in a new tab/window), our template for Drupal websites, requires both.
Then we started maintaining Drupal Extension and Drupal Driver in Jonathan's repositories. We wrote about that work: CI on current PHP and Drupal versions, a backlog of community pull requests merged, and a re-architecture that became Drupal Driver 3 and Drupal Extension 6.
Releasing that re-architecture meant moving all 3 packages together. Every round, from the alpha to the stable release, shipped as 3 releases in the same order: the driver, then the extension, then Behat Steps. The stable versions went out within 2 hours of each other.
When Drupal 12's first alpha came out, we started planning Drupal Driver 4. The plan needed 2 Drupal Extension releases to follow it, with Behat Steps moving after both, so we closed it the same day and began moving both projects into Behat Steps 4. 6 weeks later, the first release candidate shipped, and 4.0.0 followed with 162 changes since 3.14.4.
Why a single package?
With the 3 packages in one, a change is written, tested and released once, against a single test matrix. Drupal 12 support shows the difference: it went in as 1 change, 2 weeks after the first change on the 4.x branch, and it's tested in the same CI run as everything else.
It's simpler on your side too: your project installs and tracks 1 package, and a fix reaches you in 1 release.
Which version should your site use?
| Behat Steps 4.x | Behat Steps 3.x | Drupal Extension 6 | Drupal Driver 3 | |
|---|---|---|---|---|
| Drupal | 11, 12 | 10, 11 | 10, 11 | 10, 11 |
| PHP | 8.3 to 8.5 | 8.2 to 8.5 | 8.2+ | 8.2+ |
| Behat | 3.33+, 4 | 3 | 3 | Not needed |
| Support | New features, bug and security fixes | Bug and security fixes for the latest minor until 1 July 2027 | Security fixes until 1 July 2027 | Security fixes until 1 July 2027 |
On Drupal 11 or 12, we'd encourage you to move to Behat Steps 4. On Drupal 10, stay where you are for now, since Behat Steps 4 needs Drupal 11 or 12. Drupal 10 reaches end of life on 9 December 2026 and the security fixes above run past it, so you can move to Behat Steps 4 as part of your Drupal 11 upgrade.
Moving your project to 4.0
Behat Steps 4 conflicts with Drupal Extension and Drupal Driver, so remove them before you require it. Add drupal/drupal-driver to the first command if your composer.json requires it directly:
composer remove --dev drupal/drupal-extension
composer require --dev drevops/behat-steps:^4
In your Behat configuration, BehatStepsExtension replaces Drupal\DrupalExtension, and Mink's own Behat\MinkExtension replaces Drupal\MinkExtension. Drop @api and @drush from your feature files, and enable dblog on your test site or switch the PHP error check off.
MIGRATION.md(Opens in a new tab/window) maps every renamed step, method, class, setting and tag from 3.14 to 4.0, including a table of Drupal Extension's steps and their Behat Steps equivalents. Start from Behat Steps 3.14 with a passing suite, so any failure afterwards points at the move. If you work with an AI coding agent, the migrate-behat-steps(Opens in a new tab/window) skill applies the guide for you: it plans the changes and shows you a summary before it edits anything, then checks the result with a Behat dry run and your full suite.
A new documentation site
Everything about Behat Steps 4 is on behat-steps.drevops.com(Opens in a new tab/window):
- Installation(Opens in a new tab/window) and a quick start(Opens in a new tab/window) that takes you from an empty
behat.phpto a passing scenario. - Key concepts(Opens in a new tab/window), which explains Behat, Mink, contexts and backends for anyone new to them.
- Web steps(Opens in a new tab/window) and Drupal steps(Opens in a new tab/window): all 458 steps, with a page for every trait and an example for every step.
- Guides to configuration(Opens in a new tab/window), real browsers and writing tests with AI assistants.
- How to extend(Opens in a new tab/window) the library with your own steps and backends, and the architecture(Opens in a new tab/window) behind it.
- The guide to migrating from 3.x(Opens in a new tab/window).
What happens to Drupal Extension and Drupal Driver
With their work now part of Behat Steps, we're stepping down as maintainers of both projects. Since 19 October they receive security fixes only, and we'll keep releasing those until 1 July 2027, the day the Behat Steps 3.x LTS ends.
Both projects need a new maintainer. If that could be you, say so on the issue for Drupal Extension(Opens in a new tab/window) or Drupal Driver(Opens in a new tab/window). Jonathan owns both repositories, so maintainer access comes from him.
Thank you
Jonathan Hedstrom started Drupal Extension in 2012 and Drupal Driver in 2014. For more than a decade, Drupal teams have connected Behat to their websites through them, and each has passed 16 million downloads. Thank you, Jonathan, for creating both projects and for trusting us to look after them.
Thank you as well to everyone who has contributed to them, more than 80 people on Drupal Extension and more than 60 on Drupal Driver. The field handlers and steps you contributed are part of Behat Steps 4 now.
Try it
Install Behat Steps 4 with composer require --dev drevops/behat-steps:^4, run it on your suites and open an issue(Opens in a new tab/window) for anything that breaks, or for a step you rely on that has no equivalent.
What will Behat Steps 4 mean for your tests?
We are a Melbourne-based digital agency that builds and supports Drupal websites, and Behat Steps is part of how we test the websites we build. If your website's tests run on Drupal Extension and you'd like to know what the move to Behat Steps 4 involves, we're glad to talk it through.