Skip to content

About

πŸ§ͺ A collection of Behat steps

Topics

Resources

Contributing

Security policy

Stars

27 stars

Watchers

5 watching

Forks

Latest commit

Β 

History

767 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Behat Steps logo

A collection of Behat steps

GitHub Issues GitHub Pull Requests Test codecov GitHub release (latest by date) LICENSE Renovate

Total Downloads

Vortex Ecosystem

Join our community


πŸ’‘ What is it?

This library is a collection of reusable testing actions and assertions for automated testing with Behat. It's designed to help you write reliable, readable, and maintainable tests faster.

We provide a set of generic traits that can be used in any PHP project, with special support for Drupal through additional step definitions. Every method a trait contributes starts with the trait's own name, so it won't clash with the methods of your existing custom step definitions.

Our goal is to make this a go-to library for Behat steps. We maintain solid test coverage to avoid false positives and negatives, and follow clear guidelines to keep the step language consistent.

We actively maintain this package and welcome contributions from the community.

✨ Features

  • 🧩 57 step traits: 28 that work on any website, and 29 that only mean something on a Drupal site.
  • 🧰 Every step is a thin wrapper over named helpers, so a project's own domain steps call the same code. HELPERS.md lists the public ones.
  • πŸ”Œ A step names the capability it needs rather than a backend, so a step whose capability Drush provides, such as clearing caches or creating users, runs through Drush as well as in-process.
  • βœ… Behat 3.33+ and Behat 4, Drupal 11 and 12, and PHP 8.3 to 8.5.
  • 🌐 BrowserKit, Selenium and Chrome DevTools browser drivers.
  • 🧹 Entities a scenario creates through its steps are deleted after it, in reverse creation order.
  • πŸ“ AI-maintained architecture documentation in docs/architecture/ via the update-architecture-docs skill.

πŸ“… Supported versions

Version Behat Drupal PHP Support
4.x 3.33+, 4 11, 12 8.3, 8.4, 8.5 The current major. Receives new features, bugfixes and security updates.
3.x 3 10, 11 8.2, 8.3, 8.4, 8.5 The current minor is LTS until 1 July 2027 and receives bugfixes and security updates only.
2.x 3 9, 10 8.2+ Unsupported

Migrating from 3.x

MIGRATION.md takes a project from 3.14 to 4.0, in the order an upgrade meets each change. An AI coding agent can apply it for you with the migrate-behat-steps skill: it reads the guide, plans every change the guide calls for in your project, and asks before it changes anything. Once you say yes, it applies the plan, runs your checks and reports what's left for you.

Composer installs this package without the repository's .claude/ directory, so the skill isn't in vendor/. Paste this into your agent from your project's root, on a clean branch, so git diff shows the whole migration:

Fetch
https://raw.githubusercontent.com/drevops/behat-steps/main/.claude/skills/migrate-behat-steps/SKILL.md
and follow it to migrate this project from drevops/behat-steps 3.x to 4.x.
If you support skills, save it as .claude/skills/migrate-behat-steps/SKILL.md
first, so the migration can run again.

πŸ“š Documentation

  • πŸ“– STEPS.md - the vocabulary: every step, with an example for each.
  • 🧰 HELPERS.md - the toolbox: every helper the steps are built on, which your own step definitions call the same way.
  • 🧱 Usage - the extension-plus-context model: composing a context, registering it, configuring the traits it holds and switching their hooks off, and what happens to the entities a scenario creates.
  • πŸ”§ Configuration - the 4 channels a project configures this package through, and the suite layout to start from.
  • πŸ“ Scenario styles - the imperative and declarative scenario styles, the job each one does, and how to graduate from the shipped steps to your own domain steps built on the same helpers.
  • 🌐 HTTP clients - the 3 clients a step sends its own requests through, where their settings come from, and how to change them.
  • πŸ“ Architecture - how the layers fit together and how a step travels from a feature file to a browser or a Drupal API, with UML diagrams.
  • 🀝 CONTRIBUTING.md - conventions, layers and the local development setup.

Component architecture: the 3 library layers, the consuming project and the runtime around them

πŸ“¦ Installation

composer require --dev drevops/behat-steps:^4

With minimum-stability: stable, Composer's default, that installs Behat 3. Behat 4 needs friends-of-behat/mink-extension 3, which has only an alpha release so far, and Composer takes a stability flag such as @alpha only from your own composer.json. To install on Behat 4, ask for both:

composer require --dev drevops/behat-steps:^4 behat/behat:^4 \
  friends-of-behat/mink-extension:^3.0@alpha

A project whose minimum-stability already admits alpha releases can get Behat 4 from the first command, but nothing in it pins the major. Require behat/behat:^4, as above, when the suite needs Behat 4.

This package replaces drupal/drupal-extension and drupal/drupal-driver and conflicts with both, so Composer won't install it while your project still requires either one. The migration guide shows how to remove them.

Optional dependencies

To keep installs lean, packages needed by only some traits are declared as suggest rather than hard requirements (the hard requirements are Behat, Mink with its BrowserKit driver and extension, and a set of Symfony components). Add the ones for the traits you use to your project's require-dev - run composer suggests to list them:

  • JsonTrait needs softcreatr/jsonpath for JSON path steps and justinrainbow/json-schema for JSON schema steps.
  • @javascript scenarios need a JavaScript-capable browser driver - see JavaScript browser drivers below.

JavaScript browser drivers

Steps that require a real browser (used by scenarios tagged @javascript) work with the 2 browser drivers this library ships adapters for: 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. Both are exercised by this library's own CI. Another browser driver needs an adapter registered for it.

To run @javascript scenarios without a Selenium server, add dmore/behat-chrome-extension (which pulls in dmore/chrome-mink-driver) and point it at a headless Chrome. It accepts Behat 3 and, from 1.5.0, Behat 4, and this library's own CI runs it on both.

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, [
    'browser_name' => 'chrome',
    'javascript_session' => 'chrome',
    'sessions' => [
      'chrome' => ['chrome' => ['api_url' => 'http://chrome:9222']],
    ],
  ]));

A profile holds 1 entry per extension, so a profile that already registers MinkExtension merges these settings into that entry rather than adding a second one.

Any image that exposes a DevTools endpoint works (for example chromedp/headless-shell). For local visual debugging you can override api_url at runtime via Behat's BEHAT_PARAMS environment variable - for instance to drive a headed Chrome on your host - as long as that browser can reach your site's base_url.

πŸš€ Quick start

1. Register the vocabulary you need

The vocabulary sits on 1 chain. WebContext carries every step that drives a page, and DrupalContext extends it with every step that reaches a Drupal site, so a suite registers exactly one of them:

$suite = (new Suite('default'))
  ->withPaths('%paths.base%/tests/behat/features')
  ->addContext(DrupalContext::class)
  ->addContext(MinkContext::class);

Registering both is fatal, because DrupalContext already carries the 28 web traits and each of their steps would register twice. MinkContext is Mink's own context: it sits beside either one and brings the base browser steps, such as I fill in and I press.

That needs no PHP of your own. To pick your own traits instead, extend the root context and compose them:

<?php

use DrevOps\BehatSteps\WebRawContext;
use DrevOps\BehatSteps\Steps\Web\CookieTrait;

/**
 * Defines application features from the specific context.
 */
class FeatureContext extends WebRawContext {

  use CookieTrait;

}

WebRawContext registers no steps of its own: it owns the backend access, the configuration and 3 of the web helper traits. A step trait brings the helper traits it needs, so composing ContentTrait also brings the entity teardown that removes what its steps created. Usage covers the 3 entry points and the rules that govern composing them.

2. Enable the extension

Ensure that your behat.php enables the extension:

<?php

declare(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 DrevOps\BehatSteps\DrupalContext;
use DrevOps\BehatSteps\Extension\ServiceContainer\BehatStepsExtension;

$suite = (new Suite('default'))
  ->withPaths('%paths.base%/tests/behat/features')
  ->addContext(DrupalContext::class)
  ->addContext(MinkContext::class);

$profile = (new Profile('default'))
  ->withSuite($suite)
  ->withExtension(new Extension(BehatStepsExtension::class, [
    'backends' => ['drupal', 'blackbox'],
    'drupal' => ['drupal_root' => 'web'],
    'steps' => ['message' => ['selectors' => [
      'success' => '.messages.messages--status',
    ]]],
  ]));

return (new Config())->withProfile($profile);

The steps entry gives the message steps the selector of the success message, which the scenario below asserts on. Message selectors have no default.

The backends list says which backends a scenario may reach, and in what order. A step never names a backend - it names the capability it needs, and the first backend in the list providing that capability answers. See Backend resolution.

Browser sessions come from Mink's own extension, Behat\MinkExtension\ServiceContainer\MinkExtension, registered in the same profile as behat.dist.php shows. BehatStepsExtension reads the http_client_parameters a browserkit_http session declares and sends the requests steps make from PHP, such as a file download, with the same options. So the page and the download reach the site with the same settings, and HTTP clients covers the rest.

By default, DrupalContext fails any scenario that logs a PHP error, reading those errors from the table the core dblog module creates, through a backend that loads Drupal into the Behat process. On a site without dblog, or under a profile without such a backend, every scenario fails at its start until you provide what's missing or switch the Watchdog check off.

Behat 4 reads only PHP configuration, from behat.php or, when there is no behat.php, from behat.dist.php. Behat 3 also accepts the same settings in behat.yml.

behat.dist.php sets every extension option, plus the trait options a project almost always sets, as a reference, and docs/configuration.md documents all 4 configuration channels, including the suite layout to start from.

3. Write a scenario in the shipped vocabulary

The imperative style spells the interaction out and needs no PHP of your own, so the suite covers a flow the moment it is written:

Scenario: Editor publishes a page
  Given I am logged in as a user with the role "editor"
  When I visit "/node/add/page"
  And I fill in "Title" with "About us"
  And I press "Save"
  Then the path should be "/about-us"
  And I should see the success message "has been created"

4. Graduate the flows that matter to domain steps

The declarative style names the behavior instead of the mechanics:

Scenario: Editor publishes a page
  Given I am an editor
  When I publish a page titled "About us"
  Then the page "About us" should be publicly visible at "/about-us"

No shipped step matches those lines. You write them yourself, over the same helpers the shipped steps are built on:

#[When('I publish a page titled :title')]
public function publishPage(string $title): void {
  $this->entityLifecycleCreateNode(new EntityStub('node', 'page', [
    'title' => $title,
    'moderation_state' => 'published',
  ]));
}

That is the lifecycle this package is built for: start in the vocabulary for coverage on day one, then graduate the flows that matter into domain steps on the toolbox. The scenarios change language; the code underneath them does not.

Scenario styles explains when each style earns its keep, and HELPERS.md lists every helper a domain step can build on.

πŸ“‹ Available steps

Index of Web steps

Class Description
AccessibilityTrait Assess accessibility of rendered pages.
BasicAuthTrait Keep HTTP basic authentication applied across session resets.
CommandTrait Run local shell commands and assert on their result.
CookieTrait Verify and inspect browser cookies.
DateTrait Convert relative date expressions into timestamps or formatted dates.
DiagnosticsTrait Append on-failure diagnostics to the failure message of any failed step.
DropzoneTrait Simulate a real multi-file drag-and-drop gesture onto a Dropzone target.
ElementTrait Interact with HTML elements using CSS selectors and DOM attributes.
FieldTrait Manipulate form fields and verify widget functionality.
FileDownloadTrait Test file download functionality with content verification.
IframeTrait Switch between iframes and the root document.
JavascriptTrait Automatically detect JavaScript errors during test execution.
JsonTrait Assert JSON responses with path and schema checks.
KeyboardTrait Simulate keyboard interactions in the browser.
LinkTrait Verify link elements with attribute and content assertions.
MappingTrait Replace {{ Key }} tokens in step arguments and table cells.
MessageTrait Assert status, error, warning and success messages rendered on the page.
MetatagTrait Assert <meta> tags and head/SEO markup in page markup.
ModalTrait Interact with and assert modals.
PathTrait Navigate and verify paths with URL validation.
RandomTrait Replace random-value tokens in step arguments and table cells.
RegionTrait Interact with and assert against named page regions.
ResponseTrait Verify HTTP response headers.
ResponsiveTrait Test responsive layouts with viewport control.
RestTrait Test REST APIs with lightweight steps.
TableTrait Interact with HTML table elements and assert their content.
WaitTrait Wait for a period of time or for AJAX to finish.
XmlTrait Assert XML responses with element and attribute checks.

Index of Drupal steps

Class Description
Drupal\BatchTrait Wait for Drupal's Batch API to finish.
Drupal\BigPipeTrait Wait for Drupal BigPipe placeholders to be replaced on JavaScript scenarios.
Drupal\BlockTrait Manage Drupal blocks.
Drupal\CacheTrait Invalidate Drupal caches and run cron from within a scenario.
Drupal\ConfigOverrideTrait Disable Drupal config overrides from settings.php during a scenario.
Drupal\ConfigTrait Assert and set stored Drupal configuration values with automatic revert.
Drupal\ContentBlockTrait Manage Drupal content blocks.
Drupal\ContentTrait Manage Drupal content with workflow and moderation support.
Drupal\DraggableviewsTrait Order items in the Drupal Draggable Views.
Drupal\DrushTrait Run Drush commands and assert their output.
Drupal\EckTrait Manage Drupal ECK entities of an existing entity type and bundle.
Drupal\EmailTrait Test Drupal email functionality with content verification.
Drupal\EntityTrait Create entities of a type that has no dedicated trait.
Drupal\FileTrait Manage Drupal file entities with upload and storage operations.
Drupal\LanguageTrait Create the languages a scenario needs.
Drupal\MediaTrait Manage Drupal media entities with type-specific field handling.
Drupal\MenuTrait Manage Drupal menus and menu links.
Drupal\ModuleTrait Enable and disable Drupal modules with automatic state restoration.
Drupal\ParagraphsTrait Manage Drupal paragraphs entities with structured field data.
Drupal\QueueTrait Manage and assert Drupal queue state.
Drupal\RedirectTrait Manage Drupal redirect entities provided by the contrib redirect module.
Drupal\SearchApiTrait Run Drupal Search API indexing and cron hooks.
Drupal\StateTrait Manage and assert Drupal State API values with automatic revert.
Drupal\TaxonomyTrait Manage Drupal taxonomy terms with vocabulary organization.
Drupal\TestmodeTrait Configure Drupal Testmode module for controlled testing scenarios.
Drupal\TimeTrait Control system time in tests using Drupal state overrides.
Drupal\UserTrait Manage Drupal users with role and permission assignments.
Drupal\WatchdogTrait Assert Drupal does not trigger PHP errors during scenarios using Watchdog.
Drupal\WebformTrait Manage Drupal webforms.

🧭 Public API and versioning

This package follows semantic versioning, and 5 surfaces are covered by it. A breaking change to any of them waits for a major release:

  • Step text - the pattern a scenario matches, listed in STEPS.md.
  • Helpers - every public method the step traits, the helper traits and WebRawContext contribute that is not a step, a hook or a transform, listed in HELPERS.md. They sit on $this in your own context, so a domain step depends on them exactly as a scenario depends on step text. A protected method is an implementation detail and carries no such promise.
  • Configuration - the options under the behat_steps key and the tags, listed in docs/configuration.md, along with the contracts a class those options name implements: FieldHandlerInterface for drupal.field_handlers, and PreCreateAliasInterface or PostCreateAliasInterface for drupal.creation_aliases and drush.creation_aliases.
  • Exceptions - which exception type a failure reports, listed under Exceptions below.
  • Context base classes - WebRawContext, WebContext and DrupalContext, which a project extends.

2 things sit outside it: any member whose docblock carries @internal, and the internals of src/Backend and src/Extension that none of the surfaces above exposes.

A project adds or replaces a field handler through the drupal.field_handlers option, or from a context through FieldHandlerCapabilityInterface, and a creation alias through the drupal.creation_aliases and drush.creation_aliases options, or from a context through CreationAliasCapabilityInterface, rather than a subclass of Core or a backend: a subclass of Core, a backend or a field handler relies on protected methods a minor release can rename or stop calling.

Exceptions

This library reports failures with a small, fixed set of exception types, mostly Mink's:

Exception When thrown
ElementNotFoundException An element on the page or in an XML response is not found
ExpectationException Assertion fails (value mismatch, state verification)
AssertionException Assertion fails in a step with no Mink session
UnsupportedDriverActionException The browser driver lacks a capability the step needs
UnsupportedBackendActionException No backend the scenario lists provides a capability the step needs
\RuntimeException Invalid input or processing error (not an assertion)

ElementNotFoundException extends ExpectationException, so catching ExpectationException covers both. An attribute, a JSON path or a table column isn't an element, so a missing one throws ExpectationException.

DrevOps\BehatSteps\Steps\Exception\AssertionException is thrown by traits that never touch the browser, such as Steps\Web\CommandTrait and Steps\Drupal\ConfigTrait. ExpectationException needs a browser driver, which those traits do not have, so they report a failed assertion with this instead.

Example error messages:

Element matching css "#my-element" not found.
Link with title "My Link" not found.
Select with id|name|label "My Select" not found.
The cookie "session" was not set.

πŸ€– Writing tests with AI assistants

Copy and paste below into your project's CLAUDE.md or AGENTS.md file.

## Writing Behat Tests

Available step definitions are listed in `.behat-steps.txt`.
Read this file before writing or modifying Behat tests.
Use only step patterns from this file. Do not invent steps.

If `.behat-steps.txt` does not exist or is outdated, regenerate it:

    ./vendor/bin/behat --definitions=i > .behat-steps.txt

Regenerate after adding new step traits or updating dependencies.

For detailed examples, see: vendor/drevops/behat-steps/STEPS.md

🀝 Contributing

See CONTRIBUTING.md for local development setup and the linting and testing commands.


This repository was created using the Scaffold project template

About

πŸ§ͺ A collection of Behat steps

Topics

Resources

Contributing

Security policy

Stars

27 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages