Key concepts
The quick start had you write a behat.php, a feature file and a step of your own. This page explains what each of those pieces is, how Behat and Mink run them and what Behat Steps adds on top. If you've used Behat before, skip ahead to What Behat Steps adds.
How Behat runs a scenario
Behat is a PHP tool that runs tests written as plain-text scenarios. You keep them in feature files, which end in .feature and use a format called Gherkin:
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!"
Each line under Scenario: is a step. Given sets up what a scenario needs, When does the thing you're testing and Then checks the result. And and But continue whichever keyword came before them.
Behat doesn't read English, though. It matches each step against step definitions: PHP methods carrying an attribute with a pattern. This is the definition behind the first step of that scenario:
#[When('I visit :path')]
public function pathVisit(string $path): void {
$this->getSession()->visit($this->locatePath($path));
}
:path is a placeholder. Behat captures the quoted value in that position, "/" here, and passes it to the method. A step that matches no definition is reported as undefined.
Step definitions live in contexts. A context is a PHP class, and Behat builds a fresh instance of every context for every scenario, so each scenario starts from a clean object. A context can also hold hooks: methods that run before or after a scenario or a step. Behat Steps uses hooks to check for errors and to clean up after each scenario.
Your configuration file ties it all together:
- A suite pairs a folder of feature files with the contexts their steps come from. Most projects have 1, and Configuration shows when several pay off.
- A profile holds your suites and the extensions they need. Behat runs the
defaultprofile unless you name another with--profile. - An extension plugs into Behat itself. Mink's extension gives your scenarios a browser, and Behat Steps' extension connects its steps to your site.
Behat 4 reads this configuration only from PHP, in behat.php, or behat.dist.php when there's no behat.php. Behat 3 also reads behat.yml.
Where Mink comes in
Mink is the library Behat uses to drive a browser. A step that visits a page, clicks a link or reads text does it through a Mink session, a browser Mink controls for your scenarios.
Each session runs on a browser driver. Behat Steps works with 3 of them, and they come in 2 kinds:
- BrowserKit, behind the
browserkit_httpsession in the quick start, sends HTTP requests from PHP and reads the HTML that comes back. It's fast, and it doesn't run JavaScript. - Selenium2 and Chrome drive a real browser, so the page's JavaScript runs. A scenario tagged
@javascriptuses this kind of session, and Browsers compares the 3 and shows which steps need which.
Mink also ships MinkContext, a context with Mink's own basic steps, such as I fill in "Title" with "About us" and I press "Save". You register it beside a Behat Steps context.
What Behat Steps adds
Steps, grouped in traits
Behat Steps ships its step definitions as PHP traits, each holding the steps for 1 job, such as cookies, forms, links, content or users. 28 traits work on any website, and 29 only make sense on a Drupal site. Web steps and Drupal steps list every step with an example.
3 contexts, each building on the last
You don't register the traits 1 by 1. The library composes them into 3 contexts, and each one extends the one before it:
WebRawContextextends Mink'sRawMinkContextand adds backend access, configuration, hook dispatch and 3 web helper traits. It has no steps.WebContextextendsWebRawContextand adds every trait underSteps\Web, 28 in all.DrupalContextextendsWebContextand adds every trait underSteps\Drupal, 29 in all.
Because each context extends the one before it, DrupalContext already holds every web step. Your project extends exactly 1 of them in a class of its own, FeatureContext, and registers that class alone:
FeatureContext extends DrupalContexton a Drupal site.FeatureContext extends WebContexton any other site.WebRawContextis the base of a context that picks its own traits, as Compose your own context shows.
Your own steps then go in FeatureContext, or in traits of your own that it uses. If a suite registers 2 contexts built on WebContext, such as DrupalContext beside your FeatureContext, every web step would be defined twice. Behat Steps stops that run before the first scenario, with an error that names both contexts and says to keep only the one lowest in the chain, here your FeatureContext. Contexts explains what each context includes and why it's built this way.
Helpers behind every step
Every step is a thin wrapper over public methods called helpers. The step the cookie :name should exist is 1 line that calls cookieAssertExists(). Your own steps call the same helpers on $this, so a step in your project's own words takes a few lines of PHP. Scenario styles explains when that pays off.
Each helper's name starts with the name of its trait, such as cookieGetAll() in CookieTrait, so none collides with a method in your own context. HELPERS.md lists them all, and since it ships with the package, your project has it at vendor/drevops/behat-steps/HELPERS.md.
The extension and its settings
BehatStepsExtension connects the steps to Behat. It reads the following settings from your behat.php:
- Which backends the steps may use.
- What your login form looks like.
- Which CSS selector each named region of your pages has.
- The defaults of the traits' own options.
A plain website needs none of them, and Configuration reference lists them all.
Backends: reaching the site without the browser
Some steps need the site itself rather than a page. Given the following "page" content exists: creates pages directly in Drupal, without going through its forms. A step like that reaches the site through a backend:
drupalbootstraps Drupal inside the Behat process and calls its API.drushruns Drush commands against the site.blackboxstands for a site you can only browse, and does nothing on its own.
A step never names a backend. It requests a capability, such as creating content or clearing caches, and the first backend in your backends list that provides it does the work. So the same step can run in-process or through Drush, depending on that list. How a step picks a backend walks through it with a diagram, and Backend resolution states the rules.
Cleanup after each scenario
Whatever the library's steps create, such as content, users or roles, the library deletes when the scenario ends, newest first, so your test site stays clean across long runs. A step of your own gets the same cleanup when it creates entities through the entityLifecycleCreate*() helpers, and otherwise has to register what it creates. Entities a scenario creates covers keeping them when you're debugging.
Checks, tags and options
Some traits run checks around every scenario instead of waiting to be called as a step. WatchdogTrait, which DrupalContext includes, fails a scenario that logs a PHP error, and JavascriptTrait fails one that hits a JavaScript error.
Tags are Gherkin's labels for a scenario or a feature, such as @javascript. Behat Steps reads tags of its own, such as @behat-steps-skip:WatchdogTrait, which switches a trait's checks off for 1 scenario. Many traits also declare options, settings you can change for the whole profile, for 1 context or for 1 scenario. Configure a trait shows how to set them, and Configuration reference lists every tag. How a trait gets options without a constructor shows how a trait declares them.
The big picture
Your project registers its FeatureContext, which extends 1 of the shipped contexts, and the Behat Steps extension beside Mink's. The traits decide what each step does. A step that drives the page uses the browser session Mink's extension opens. A step that needs the site itself requests a capability, and the first backend in your backends list that provides it does the work.
Logical architecture walks through each layer of that diagram.
Words you'll see in these docs
The rest of these docs use the following terms, listed in alphabetical order:
| Term | What it means |
|---|---|
| Backend | How a step reaches the site without the browser: drupal, drush or blackbox |
| Browser driver | What runs behind a Mink session: BrowserKit for plain HTTP, Selenium2 or Chrome for a real browser |
| Capability | A job a backend can do, such as creating content. A step requests a capability, never a backend |
| Context | A PHP class holding step definitions and hooks. Behat builds a fresh one for every scenario |
| Extension | A package that plugs into Behat, such as Mink's extension or BehatStepsExtension |
| Feature file | A .feature file holding a feature and its scenarios, written in Gherkin |
| FeatureContext | Your own context: it extends DrupalContext or WebContext and holds your own steps, directly or through traits of your own |
| Gherkin | The plain-text format of feature files, built on Feature:, Scenario:, Given, When and Then |
| Helper | A public method a step is built on, which your own steps can call too |
| Hook | A method that runs before or after a scenario or a step |
| Mink session | A browser Mink controls for your scenarios |
| Profile | A named set of suites and extensions in behat.php |
| Scenario | 1 example of how your site should behave, as a list of steps |
| Step | 1 line of a scenario |
| Step definition | The PHP method behind a step, matched by the pattern in its attribute |
| Suite | A set of feature files and the contexts their steps come from |
| Tag | A label such as @javascript on a scenario or a feature, which changes how it runs |
| Trait | A PHP trait grouping the steps or the helpers for 1 job |
| Trait option | A setting a trait declares, such as ajax_timeout in the wait group |
| Vocabulary | The library's steps as a whole, which Extending calls the step vocabulary |