Extension layer
The extension layer, src/Extension, connects Behat Steps to Behat and Mink. BehatStepsExtension reads the behat_steps settings in your behat.php and registers the backends. It also plugs a small piece into each place where Behat and Mink let an extension in. Select the diagram to zoom in.
What it plugs in
Each entry names a place where Behat or Mink lets an extension in, then the piece Behat Steps adds there:
- Configuration:
BehatStepsExtension::configure()declares thebehat_stepskey. It holdsbackends,regions,stepsandblackbox, plus 4 login settings:login_field,login_wait,textandselectors.DrupalBackendProvideradds thedrupalanddrushsettings. - Context initializers:
BackendAwareInitializerhands each context Behat builds onWebRawContextthe services that What your context receives lists. - Event dispatcher:
SkipTagListenerand thenBackendListenerrun as each scenario starts. The first rejects a@behat-steps-skip:tag that names no composed trait, and the second sets the backend order and publishes the scenario's tags. - Attribute readers:
HookAttributeReaderturns#[BeforeNodeCreate]and the other creation hook attributes into hooks. - Class generator:
ClassGeneratorwrites the starter context ofbehat --init, which extendsWebRawContext. - Mink's browser driver factories:
BrowserKitFactorybuildsbrowserkit_httpsessions exactly as Mink does, and records theirhttp_client_parametersfor the HTTP clients. - Mink's selectors:
RegionSelectoradds theregionselector, which turns a region name fromregionsinto its CSS. - Mink's
DocumentElement: aDocumentElementof Behat Steps' own takes its place, so the page text a step reads matches what a browser renders.
What your context receives
WebRawContext implements BackendAwareInterface. BackendAwareInitializer hands these services to WebRawContext, and to every context built on it, through their set*() methods:
BackendRegistry, which holds the backends and each scenario's order, and resolves eachbackendFor()call.TraitOptionResolverFactory, which builds the context's option resolver.HttpClientFactory, which builds the detached and bare HTTP clients and the default REST session on 1 transport carrying yourbrowserkit_httpconnection settings.- The hook dispatcher, the basic authenticator and the extension's parameters.
DrupalContext also implements UserAwareInterface, so it gets the user registry and the authenticator its login steps need. Each service the extension builds sits behind an interface with 1 implementation, and Extending the extension layer shows how to put a class of your own in its place.
How a trait gets options without a constructor
A trait can't have a constructor of its own, and Behat builds a fresh context for every scenario, so a trait has no obvious place to receive its settings. Behat Steps solves this in 4 parts:
- The trait declares its options in a method named after its prefix,
<prefix>ConfigSchema(). The method returnsOptionobjects, each with a name, a default and a description, plus any tags that set the option for 1 scenario.WaitTraitdeclaresenabledandajax_timeoutinwaitConfigSchema(). The default carries the option's type, so5makesajax_timeoutan integer. ConfigSchemaReaderfinds the declarations. It looks through every method of the context's class, including the methods of its traits, for names ending inConfigSchema, and calls each of them. The group is the method's prefix in snake case, sowaitConfigSchema()declares thewaitgroup. Nothing registers a trait anywhere, so a trait of your own takes part the same way.- The resolver settles each value.
TraitOptionResolverstarts from the declared default and applies the extension'sstepssettings, then the context's ownconfigargument. As each scenario runs, it then applies the tags of the feature and of the scenario itself:- In
config, a group or an option no trait declares fails while Behat builds the context, before any step runs. So does a value of the wrong type. - In
steps, a group or an option the context's traits don't declare is skipped, so 1 profile can carry the defaults of every suite. A value of the wrong type fails when the context first reads its options.
- In
- The trait reads a value through a method
WebRawContextprovides, typed to the declared default, such as$this->getOptionInt('wait', 'ajax_timeout'). It reads each option in a public<prefix>Get...()method,waitGetAjaxTimeout()here, so a project can override that method when no option reaches far enough.
The trait stays a plain trait that holds steps, hooks and helpers. The context that composes it carries the settings. Every context that composes it offers the same options. The trait names WebRawContext as the class it needs with @phpstan-require-extends, so it can call getOptionInt() without declaring it. Configure a trait shows the 3 places you set a value, and Anatomy of a trait shows a trait of its own that declares options.
Prerequisites, browsers and HTTP clients
The layer also holds these pieces:
PrerequisiteandPrerequisiteReaderlet a trait declare what it needs from the site in a<prefix>Prerequisites()method.WebRawContext::assertPrerequisites()checks the declaration and fails the scenario with a message that names what's missing.Extension\Browserholds the browser capabilities, 1 adapter per browser driver and the resolver behindbrowserDriverFor(). Browsers compares what each browser driver provides.Extension\Httpholds the factory behind the detached and bare clients and the default REST session, which HTTP clients covers.
The Drupal half
Extension\Drupal holds what the extension needs for a Drupal site. DrupalBackendProvider adds the drupal and drush settings and the Drupal defaults of the login text and selectors. It also adds the Drupal and Drush backends, each registered only when its drupal or drush key is set. The node, term and language creation hooks sit beside it.
What the layer may reach
scripts/lint-layers.php keeps src/Extension free of step code and of the 3 contexts, apart from ClassGenerator, whose template names WebRawContext. The root of the layer references no Drupal code, apart from BehatStepsExtension, which lists the backend providers it ships. Layers lists every rule.
Where to next
- Steps layer covers the traits that call these services.
- Data flow shows when each piece runs.