From 61a8259e986a4aeaa6ac56f1cae8657844faad16 Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Tue, 27 Mar 2018 13:58:53 -0700 Subject: [PATCH] Adds a RandomContext for generating random strings in steps. ``` Given I am viewing an "Article" with the title "" Then I should see the header "<title>" ``` When the `RandomContext` is included, then `<title>` will be replaced with a random string during the scenario. It will have a consistent value throughout a given scenario, but a new value in subsequent scenarios. - Fixes #293 --- behat.yml.dist | 1 + doc/contexts.rst | 5 ++ features/random.feature | 20 +++++ .../Context/RandomContextSpec.php | 15 ++++ .../DrupalExtension/Context/RandomContext.php | 90 +++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 features/random.feature create mode 100644 spec/Drupal/DrupalExtension/Context/RandomContextSpec.php create mode 100644 src/Drupal/DrupalExtension/Context/RandomContext.php diff --git a/behat.yml.dist b/behat.yml.dist index 0fff888b..000eaeb6 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -115,6 +115,7 @@ drupal8: - Drupal\DrupalExtension\Context\MarkupContext - Drupal\DrupalExtension\Context\MessageContext - Drupal\DrupalExtension\Context\MailContext + - Drupal\DrupalExtension\Context\RandomContext filters: tags: "@d8&&~@d8wip" extensions: diff --git a/doc/contexts.rst b/doc/contexts.rst index 6a722135..2fbd56d0 100644 --- a/doc/contexts.rst +++ b/doc/contexts.rst @@ -31,6 +31,11 @@ following contexts: Step-definitions that are specific to Drupal messages that get displayed (notice, warning, and error). +*RandomContext* + Contains transforms that allow for use of placeholders such as `<?title>` that will be replaced with a random string + when the scenario is run: `Given I am viewing an "Article" with the title "<?title>"` + + *DrushContext* Allows steps to directly call drush commands. diff --git a/features/random.feature b/features/random.feature new file mode 100644 index 00000000..cd700314 --- /dev/null +++ b/features/random.feature @@ -0,0 +1,20 @@ +@api @d8 @random +Feature: RandomContext functionality + In order to prove the RandomContext is functional at transforming variables + As a developer + I need to use random variables in scenarios + + # This will fail on the second scenario if random transforms are not functional. + Scenario: Create a first user + Given I am at "/user/register" + And I fill in "Email address" with "<?user>@example.com" + And I fill in "Username" with "<?user>" + When I press "Create new account" + Then an email has been sent to "<?user>@example.com" with the subject "Account details for <?user>" + + Scenario: Create the second user + Given I am at "/user/register" + And I fill in "Email address" with "<?user>@example.com" + And I fill in "Username" with "<?user>" + When I press "Create new account" + Then an email has been sent to "<?user>@example.com" with the subject "Account details for <?user>" diff --git a/spec/Drupal/DrupalExtension/Context/RandomContextSpec.php b/spec/Drupal/DrupalExtension/Context/RandomContextSpec.php new file mode 100644 index 00000000..7390b02a --- /dev/null +++ b/spec/Drupal/DrupalExtension/Context/RandomContextSpec.php @@ -0,0 +1,15 @@ +<?php + +namespace spec\Drupal\DrupalExtension\Context; + +use Drupal\DrupalExtension\Context\RandomContext; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; + +class RandomContextSpec extends ObjectBehavior +{ + function it_is_initializable() + { + $this->shouldHaveType(RandomContext::class); + } +} diff --git a/src/Drupal/DrupalExtension/Context/RandomContext.php b/src/Drupal/DrupalExtension/Context/RandomContext.php new file mode 100644 index 00000000..dfd9390b --- /dev/null +++ b/src/Drupal/DrupalExtension/Context/RandomContext.php @@ -0,0 +1,90 @@ +<?php + +namespace Drupal\DrupalExtension\Context; + +use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; + +/** + * Class RandomContext + * @package Drupal\DrupalExtension\Context + * + * Transform tokens into random variables. + */ +class RandomContext extends RawDrupalContext +{ + /** + * Tracks variable names for consistent replacement during a given scenario. + * + * @var array + */ + protected $values = []; + + /** + * The regex to use for variable replacement. + * + * This matches placeholders in steps of the form `Given a string <?random>`. + */ + const VARIABLE_REGEX = '#(\<\?.*?\>)#'; + + /** + * Transform random variables. + * + * @Transform #([^<]*\<\?.*\>[^>]*)# + */ + public function transformVariables($message) + { + $patterns = []; + $replacements = []; + + preg_match_all(static::VARIABLE_REGEX, $message, $matches); + foreach ($matches[0] as $variable) { + $replacements[] = $this->values[$variable]; + $patterns[] = '#' . preg_quote($variable) . '#'; + } + $message = preg_replace($patterns, $replacements, $message); + + return $message; + } + + /** + * Set values for each random variable found in the current scenario. + * + * @BeforeScenario + */ + public function beforeScenarioSetVariables(BeforeScenarioScope $scope) + { + $steps = []; + if ($scope->getFeature()->hasBackground()) { + $steps = $scope->getFeature()->getBackground()->getSteps(); + } + $steps = array_merge($steps, $scope->getScenario()->getSteps()); + foreach ($steps as $step) { + preg_match_all(static::VARIABLE_REGEX, $step->getText(), $matches); + $variables_found = $matches[0]; + // Find variables in are TableNodes or PyStringNodes. + $step_argument = $step->getArguments(); + if (!empty($step_argument) && $step_argument[0] instanceof TableNode) { + preg_match_all(static::VARIABLE_REGEX, $step_argument[0]->getTableAsString(), $matches); + $variables_found = array_filter(array_merge($variables_found, $matches[0])); + } + foreach ($variables_found as $variable_name) { + if (!isset($this->values[$variable_name])) { + $value = $this->getDriver()->getRandom()->name(10); + // Value forced to lowercase to ensure it is machine-readable. + $this->values[$variable_name] = strtolower($value); + } + } + } + } + + /** + * Reset variables after the scenario. + * + * @AfterScenario + */ + public function afterScenarioResetVariables(AfterScenarioScope $scope) + { + $this->values = []; + } +}