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, withbackends,regions, the login text and selectors andsteps.DrupalBackendProvideradds thedrupalanddrushsettings. - Context initializers:
BackendAwareInitializerhands every context Behat builds the 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 page text reads the way a browser renders it.
What your context receives
WebRawContext implements BackendAwareInterface, so BackendAwareInitializer hands it, and every context built on it, these services through its 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 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 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 gives it 1 in 4 parts:
- The trait declares its options in a method named after its prefix,
<prefix>ConfigSchema(), which returnsOptionobjects: 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. - The context finds the declarations.
ConfigSchemaReaderlooks through every method of the context's class, its traits' included, 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, which is why a trait of your own takes part the same way. - The resolver settles each value. Starting from the declared default,
TraitOptionResolverapplies the extension'sstepssettings, then the context's ownconfigargument and then, as each scenario runs, the tags of its feature and of the scenario itself. Inconfig, a group or an option no trait declares fails while Behat builds the context, before any step runs, and so does a value of the wrong type. Insteps, a group or an option the context's traits don't declare is skipped, so 1 profile can carry the defaults of every suite, while a value of the wrong type fails as soon as the context reads its options. - 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: it holds steps, hooks and helpers, while the context it lands in 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, which is how it relies on getOptionInt() without declaring it. Configure a trait shows the 3 places you set a value, and Anatomy of a trait declares options in a trait of its own.
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, andWebRawContext::assertPrerequisites()checks it, failing 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, 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 login text and selector defaults. It also adds the 2 backends, each registered only when its key is set. The node, term and language creation hooks live 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.