Quick start
Follow this page to go from an empty project to a passing test on your own site. It's for anyone setting up Behat Steps, including developers new to Behat. In 7 parts, you install the package, set up a context and the configuration, then write a scenario and run it. The last part adds a step of your own.
Behat runs tests written as plain-English scenarios, with lines such as When I visit "/", line by line against your site. Every line needs PHP code behind it. Behat Steps is a library of that code, already written and tested, so you can start by writing the scenarios.
On this page, Behat loads pages through HTTP requests from PHP, so your scenarios don't run JavaScript. Browsers covers the real browsers that run JavaScript.
Before you begin
Make sure you have the following:
- PHP 8.3 or newer, and a PHP project that uses Composer. For a site built without PHP, create a project that holds only the tests.
- A site running at an address Behat can reach, such as
http://your-site.local. Any site works, and a Drupal site also gets the Drupal steps.
On a Drupal site, you also need the following:
- Drupal 11 or 12.
- A place to run Behat that reaches your site's code and database, such as the site's own container. The Drupal steps load Drupal inside the test run, and your scenarios then visit the pages they create. Run this page's commands there.
- A copy of the site you can write to. The Drupal steps create content and users in its database, and Behat Steps deletes them afterward, so never point Behat at your live site.
- The core Database Logging module (
dblog) enabled, because Behat Steps checks the site's log in every scenario. Part 3 shows how to switch the check off instead.
1. Install the package
In your project's root, the folder that holds composer.json, run the following command:
composer require --dev drevops/behat-steps:^4
The command also installs Behat and Mink, the library Behat uses to drive a browser. Behat Steps works with Behat 3 from 3.33 and with Behat 4, so Composer installs the newest Behat your project allows. A few steps need 1 more package, which Installation lists. The steps on this page work with what the command installs.
2. Pick your context
Behat reads steps from classes called contexts. In part 3 you create a context of your own, FeatureContext. Your own steps go in it later. FeatureContext extends 1 of the 2 contexts Behat Steps ships. Pick the context that matches your site:
| Your site | FeatureContext extends | What it brings |
|---|---|---|
| Built with Drupal | DrupalContext | Every web step, plus the Drupal steps, such as content, users, caches, email and queues |
| Built with anything else | WebContext | Every web step, such as pages, forms, links, cookies, JSON, XML and tables |
DrupalContext builds on WebContext, so extend only 1 of them. Contexts explains what each context includes and why it works this way.
3. Create your context and configuration
You add 2 files to your project. FeatureContext.php holds your context, and behat.php holds the configuration Behat reads. Behat 4 reads its configuration only from PHP, so use behat.php even though Behat 3 also accepts behat.yml. By the end of this page, your project looks like this:
your-project/
├── behat.php
├── composer.json
├── tests/
│ └── behat/
│ ├── bootstrap/
│ │ └── FeatureContext.php
│ └── features/
│ └── homepage.feature
└── vendor/
On a Drupal site, you add pages.feature beside homepage.feature in part 6.
To create the 2 files:
-
Create
tests/behat/bootstrap/FeatureContext.phpwith the class for your site:-
On a site built without Drupal, extend
WebContext:<?phpdeclare(strict_types=1);use DrevOps\BehatSteps\WebContext;class FeatureContext extends WebContext {} -
On a Drupal site, extend
DrupalContext:<?phpdeclare(strict_types=1);use DrevOps\BehatSteps\DrupalContext;class FeatureContext extends DrupalContext {}
The empty class already has every step of the context it extends.
-
-
Create
behat.phpin your project's root with the following content:<?phpdeclare(strict_types=1);use Behat\Config\Config;use Behat\Config\Extension;use Behat\Config\Profile;use Behat\Config\Suite;use Behat\MinkExtension\Context\MinkContext;use Behat\MinkExtension\ServiceContainer\MinkExtension;use DrevOps\BehatSteps\Extension\ServiceContainer\BehatStepsExtension;$suite = (new Suite('default'))->withPaths('%paths.base%/tests/behat/features')->addContext(FeatureContext::class)->addContext(MinkContext::class);$profile = (new Profile('default', ['autoload' => ['%paths.base%/tests/behat/bootstrap'],]))->withSuite($suite)->withExtension(new Extension(MinkExtension::class, ['base_url' => 'http://your-site.local','sessions' => ['browserkit_http' => ['browserkit_http' => NULL],],]))->withExtension(new Extension(BehatStepsExtension::class));return (new Config())->withProfile($profile); -
Replace
http://your-site.localinbase_urlwith your site's address. -
On a Drupal site, replace the
->withExtension(new Extension(BehatStepsExtension::class));line with the following lines, which set where Drupal lives:->withExtension(new Extension(BehatStepsExtension::class, ['drupal' => ['drupal_root' => 'web'],]));
behat.php sets up the following:
- The suite sets where your scenarios live and which contexts Behat loads: your
FeatureContextand Mink'sMinkContext.MinkContextadds Mink's own basic steps, such asI fill in "Title" with "About us".%paths.base%is the folder that holdsbehat.php. - The profile's
autoloadsetting makes Behat load your classes fromtests/behat/bootstrap, the folder that holdsFeatureContext.php. - The Mink extension sets up the browser session your scenarios use. The
browserkit_httpsession loads pages through HTTP requests from PHP. It's quick, but it doesn't run JavaScript. BehatStepsExtensionswitches Behat Steps on. A site built without Drupal uses it with no settings, and Configuration reference lists every setting.
Register only FeatureContext and MinkContext. Registering WebContext or DrupalContext beside FeatureContext defines steps twice, and Behat Steps stops the run with an error that says to keep FeatureContext.
On a Drupal site, drupal_root is the folder that holds Drupal's index.php, such as web or docroot. Write it as a path from the folder you run Behat in. Set base_url to the address of that same site, because the browser visits what the Drupal steps create. With drupal_root set, the Drupal steps load Drupal inside the test run and create content and users through Drupal's API. Behat Steps calls this way of reaching a site a backend. Key concepts explains backends, Drush included.
DrupalContext also checks Drupal's log. Its WatchdogTrait fails a scenario when a PHP warning or error is logged between the scenario's start and its end, even one that never shows on the page. WatchdogTrait reads the log through the core dblog module. Without dblog enabled, every scenario fails at its start with this error:
WatchdogTrait requires that the core "dblog" module is enabled, which does not
hold. Meet the prerequisite, or switch WatchdogTrait off with the
"watchdog.enabled" option or the "@behat-steps-skip:WatchdogTrait" tag.
To fix the error, enable dblog or set the watchdog.enabled option the error names to FALSE. To set the option, replace the lines from step 4 with these, which add it under steps:
->withExtension(new Extension(BehatStepsExtension::class, [
'drupal' => ['drupal_root' => 'web'],
'steps' => ['watchdog' => ['enabled' => FALSE]],
]));
Switch a trait off covers the other ways to do it. If your site uses a self-signed certificate, HTTP clients shows the settings that accept it.
4. Write your first scenario
A feature file describes 1 feature of your site. Each scenario in it is 1 example of how the feature should behave, written as steps. Given sets up what the scenario needs, When does something, Then checks the result and And continues the step before it.
Create tests/behat/features/homepage.feature with the following content:
Feature: Homepage
Scenario: Visitor opens the homepage
When I visit "/"
Then the response status code should be 200
And I should see the heading "Welcome!"
Change Welcome! to the main heading of your homepage. I should see the heading matches a heading's text exactly, so copy the heading as it appears on your homepage.
5. Run it
From your project's root, run Behat:
vendor/bin/behat
Behat prints each step and the method that ran it, then a summary:
Feature: Homepage
Scenario: Visitor opens the homepage # tests/behat/features/homepage.feature:3
When I visit "/" # FeatureContext::pathVisit()
Then the response status code should be 200 # Behat\MinkExtension\Context\MinkContext::assertResponseStatus()
And I should see the heading "Welcome!" # FeatureContext::elementAssertHeadingExists()
1 scenario (1 passed)
3 steps (3 passed)
Confirm that the summary reads 1 scenario (1 passed). Behat lists the Behat Steps methods under FeatureContext, the class you registered. When a step fails, Behat prints why under it and skips the rest of that scenario.
To list every step you can use, run the following command:
vendor/bin/behat --definitions=l
The --definitions option takes the following values:
llists every step's text.iadds each step's description and the method that runs it.- Any other value shows the same entries as
i, for the steps whose text contains that value, as part 7 shows.
The list also holds Mink's basic steps from MinkContext. Web steps and Drupal steps show the Behat Steps steps, with an example for each.
6. Try a Drupal step
On a site built without Drupal, skip this part.
Your FeatureContext extends DrupalContext, so it already has the Drupal steps. The following scenario uses the page content type and the administrator role, which a site installed with Drupal's Standard installation profile has. On a site without them, use a content type and a role your site has.
Create tests/behat/features/pages.feature with the following content:
Feature: Pages
Scenario: Administrator views a new page
Given the following "page" content exists:
| title |
| About us |
And I am logged in as a user with the role "administrator"
When I visit the "page" content page with the title "About us"
Then I should see the heading "About us"
The first step takes a table. The table's first row names the fields, and each row after it creates 1 page, here "About us". The second step creates a user with the administrator role and logs in through your site's login form. The scenario logs in because a new page might start unpublished, for example on a site that moderates its content. An administrator can see the page either way.
When the scenario ends, whether it passed or failed, Behat Steps deletes the page and the user.
Run vendor/bin/behat again, and confirm that the summary reads 2 scenarios (2 passed). Drupal steps lists the rest of the Drupal steps.
7. Add a step of your own
To check something no shipped step covers, write a step of your own in FeatureContext. Follow 2 rules for every step of your own:
- Give the method a name
FeatureContextdoesn't inherit. Every method a shipped trait adds starts with the trait's name withoutTrait, such aselementinelementAssertHeadingExists(). A name likefooterShouldMention()stays clear of them. - Use step text that no shipped step uses. When 2 step definitions share the same text, Behat stops with an error before it runs the first step.
vendor/bin/behat --definitions="footer"lists every step whose text contains "footer", so you can check a word before you use it.
To add a step that checks your site's footer:
-
Add the
use Behat\Step\Then;line and thefooterShouldMention()method toFeatureContext.php:<?phpdeclare(strict_types=1);use Behat\Step\Then;use DrevOps\BehatSteps\WebContext;class FeatureContext extends WebContext {#[Then('the footer should mention :text')]public function footerShouldMention(string $text): void {$this->assertSession()->elementTextContains('css', 'footer', $text);}}On a Drupal site, use
use DrevOps\BehatSteps\DrupalContext;andextends DrupalContextin place of the 2WebContextlines.The
#[Then(...)]attribute holds the step's text.:textis a placeholder: Behat captures the quoted value in that position and passes it to the method as$text. The method calls Mink'sassertSession(), which fails the step when the page'sfooterelement doesn't contain the text. Behat already loadsFeatureContextthrough theautoloadsetting, so the step is ready to use. -
Add a scenario that uses your step to
homepage.feature, after the first scenario. ReplacePowered by Drupalwith text from your own site's footer:Scenario: Visitor sees who runs the siteWhen I visit "/"Then the footer should mention "Powered by Drupal" -
Run
vendor/bin/behatagain, and confirm that the new scenario passes. When your footer doesn't contain the text, the scenario fails with Mink's message naming the text.
Where to next
- Key concepts explains each piece you used, from contexts to backends.
- Contexts covers what each context includes, and Your own steps in traits of your own shows how to group your steps when you have more than a few.
- HELPERS.md lists the helpers your own steps can call, the methods the shipped steps are built on. Your project has it at
vendor/drevops/behat-steps/HELPERS.md. - Scenario styles explains when to write whole scenarios in your site's own words, such as
When I publish a page titled "About us". - Usage covers configuring the traits and the cleanup after each scenario.
- Web steps and Drupal steps list every step, with an example for each.
- Configuration reference lists every option and tag, and behat.dist.php sets every extension option as a reference.
- Writing tests with AI assistants shows how to point a coding agent at
STEPS.md,HELPERS.mdand the skill that writes a step of your own.