Combining Steps in Behat for Drupal
Lately I have found myself repeating several lines of behat steps over and over again. Here is a sample of the behat code I use to choose a specific checkbox from an entity browser popup:
1 2 3 4 5 6 7 8 |
And I press the "field_carousel_slides_0_subform_field_carousel_slide_img_entity_browser_entity_browser" button And I wait for AJAX to finish And I switch to the iframe "entity_browser_iframe_ifde_media_image_browser" Then I should see the button "Embed" When I click "Use existing image" And I check checkbox 3 in the element "#entity-browser-ifde-media-image-browser-form" And I press the "Embed" button And I wait for AJAX to finish |
There are a few steps in here that are custom, but explaining them would be beyond the scope of this post; just assume they do what they describe (switching focus to an iframe, and clicking a checkbox).
The issue I was having is that every time I wanted to do that one action (pick an image from an entity browser) I was repeating all 8 lines of code.
I began looking for ways of making this set of steps repeatable. After some failed attempts at extending MinkContext and MinkAwareContext I came across scenario hooks via this StackOverflow post. I discovered the @BeforeScenario, which is executed before every scenario in each feature (where it’s used). Using this hook to access contexts from other contexts was documented on behat.org but required a few tweaks for the Drupal implementation. Here is what I ended up doing (based on the default FeatureContext.php you get when you install behat for the project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?php use Drupal\DrupalExtension\Context\RawDrupalContext; use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Behat\Hook\Scope\BeforeScenarioScope; /** * Defines application features from the specific context. */ class FeatureContext extends RawDrupalContext implements SnippetAcceptingContext { /** @var Drupal\DrupalExtension\Context\MinkContext */ private $minkContext; /** * Initializes context. * * Every scenario gets its own context instance. * You can also pass arbitrary arguments to the * context constructor through behat.yml. */ public function __construct() { } /** @BeforeScenario */ public function gatherContexts(BeforeScenarioScope $scope) { $environment = $scope->getEnvironment(); $this->minkContext = $environment ->getContext('Drupal\DrupalExtension\Context\MinkContext'); } /** * Select an image from an IFDE image browser. * * @Given I select checkbox :checkbox_num via the ifde media image browser button :button */ public function selectImageFromIfdeImageBrowser($checkbox_num, $button) { $this->minkContext->pressButton($button); $this->minkContext->iWaitForAjaxToFinish(); $this->iSwitchToIframe('entity_browser_iframe_ifde_media_image_browser'); $this->minkContext->assertButton('Embed'); $this->minkContext->clickLink('Use existing image'); $this->iCheckCheckboxNthInTheElement($checkbox_num, '#entity-browser-ifde-media-image-browser-form'); $this->minkContext->pressButton('Embed'); $this->minkContext->iWaitForAjaxToFinish(); } } |
With this code, I’m able to use a single step in my feature files:
1 |
And I select checkbox 1 via the ifde media image browser button "field_carousel_slides_0_subform_field_carousel_slide_img_entity_browser_entity_browser" |
There a few more repeatable groups I’ll probably turn into single step definitions.
The only overhead I can see is that we’re adding $this->minkContext to every scenario even though some may not need it.