Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Steps definitions related with forms.
  19   *
  20   * @package    core
  21   * @category   test
  22   * @copyright  2012 David MonllaĆ³
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
  27  
  28  require_once (__DIR__ . '/../../../lib/behat/behat_base.php');
  29  require_once (__DIR__ . '/../../../lib/behat/behat_field_manager.php');
  30  
  31  use Behat\Gherkin\Node\{TableNode, PyStringNode};
  32  use Behat\Mink\Element\NodeElement;
  33  use Behat\Mink\Exception\{ElementNotFoundException, ExpectationException};
  34  
  35  /**
  36   * Forms-related steps definitions.
  37   *
  38   * Note, Behat tests to verify that the steps defined here work as advertised
  39   * are kept in admin/tool/behat/tests/behat.
  40   *
  41   * @package    core
  42   * @category   test
  43   * @copyright  2012 David MonllaĆ³
  44   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class behat_forms extends behat_base {
  47  
  48      /**
  49       * Presses button with specified id|name|title|alt|value.
  50       *
  51       * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
  52       * @throws ElementNotFoundException Thrown by behat_base::find
  53       * @param string $button
  54       */
  55      public function press_button($button) {
  56          $this->execute('behat_general::i_click_on', [$button, 'button']);
  57      }
  58  
  59      /**
  60       * Press button with specified id|name|title|alt|value and switch to main window.
  61       *
  62       * @When /^I press "(?P<button_string>(?:[^"]|\\")*)" and switch to main window$/
  63       * @throws ElementNotFoundException Thrown by behat_base::find
  64       * @param string $button
  65       */
  66      public function press_button_and_switch_to_main_window($button) {
  67          // Ensures the button is present, before pressing.
  68          $buttonnode = $this->find_button($button);
  69          $buttonnode->press();
  70          $this->wait_for_pending_js();
  71          $this->look_for_exceptions();
  72  
  73          // Switch to main window.
  74          $this->execute('behat_general::switch_to_the_main_window');
  75      }
  76  
  77      /**
  78       * Fills a form with field/value data.
  79       *
  80       * @Given /^I set the following fields to these values:$/
  81       * @throws ElementNotFoundException Thrown by behat_base::find
  82       * @param TableNode $data
  83       */
  84      public function i_set_the_following_fields_to_these_values(TableNode $data) {
  85  
  86          // Expand all fields in case we have.
  87          $this->expand_all_fields();
  88  
  89          $datahash = $data->getRowsHash();
  90  
  91          // The action depends on the field type.
  92          foreach ($datahash as $locator => $value) {
  93              $this->set_field_value($locator, $value);
  94          }
  95      }
  96  
  97      /**
  98       * Fills a form with field/value data.
  99       *
 100       * @Given /^I set the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to these values:$/
 101       * @throws ElementNotFoundException Thrown by behat_base::find
 102       * @param string $containerelement Element we look in
 103       * @param string $containerselectortype The type of selector where we look in
 104       * @param TableNode $data
 105       */
 106      public function i_set_the_following_fields_in_container_to_these_values(
 107              $containerelement, $containerselectortype, TableNode $data) {
 108  
 109          // Expand all fields in case we have.
 110          $this->expand_all_fields();
 111  
 112          $datahash = $data->getRowsHash();
 113  
 114          // The action depends on the field type.
 115          foreach ($datahash as $locator => $value) {
 116              $this->set_field_value_in_container($locator, $value, $containerselectortype, $containerelement);
 117          }
 118      }
 119  
 120      /**
 121       * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present.
 122       * @Given /^I expand all fieldsets$/
 123       */
 124      public function i_expand_all_fieldsets() {
 125          $this->expand_all_fields();
 126      }
 127  
 128      /**
 129       * Expands all moodle form fieldsets if they exists.
 130       *
 131       * Externalized from i_expand_all_fields to call it from
 132       * other form-related steps without having to use steps-group calls.
 133       *
 134       * @throws ElementNotFoundException Thrown by behat_base::find_all
 135       * @return void
 136       */
 137      protected function expand_all_fields() {
 138          // Expand only if JS mode, else not needed.
 139          if (!$this->running_javascript()) {
 140              return;
 141          }
 142  
 143          // We already know that we waited for the DOM and the JS to be loaded, even the editor
 144          // so, we will use the reduced timeout as it is a common task and we should save time.
 145          try {
 146              $this->wait_for_pending_js();
 147              // Expand all fieldsets link - which will only be there if there is more than one collapsible section.
 148              $expandallxpath = "//div[@class='collapsible-actions']" .
 149                  "//a[contains(concat(' ', @class, ' '), ' collapsed ')]" .
 150                  "//span[contains(concat(' ', @class, ' '), ' expandall ')]";
 151              // Else, look for the first expand fieldset link (old theme structure).
 152              $expandsectionold = "//legend[@class='ftoggler']" .
 153                      "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']";
 154              // Else, look for the first expand fieldset link (current theme structure).
 155              $expandsectioncurrent = "//fieldset//div[contains(concat(' ', @class, ' '), ' ftoggler ')]" .
 156                      "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']";
 157  
 158              $collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandsectionold . '|' . $expandsectioncurrent,
 159                      false, false, behat_base::get_reduced_timeout());
 160              $collapseexpandlink->click();
 161              $this->wait_for_pending_js();
 162  
 163          } catch (ElementNotFoundException $e) {
 164              // The behat_base::find() method throws an exception if there are no elements,
 165              // we should not fail a test because of this. We continue if there are not expandable fields.
 166          }
 167  
 168          // Different try & catch as we can have expanded fieldsets with advanced fields on them.
 169          try {
 170  
 171              // Expand all fields xpath.
 172              $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
 173                  "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
 174  
 175              // We don't wait here as we already waited when getting the expand fieldsets links.
 176              if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
 177                  return;
 178              }
 179  
 180              if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) {
 181                  // Chrome Driver produces unique xpaths for each element.
 182                  foreach ($showmores as $showmore) {
 183                      $showmore->click();
 184                  }
 185              } else {
 186                  // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern
 187                  // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it
 188                  // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0],
 189                  // that's why we always click on the first XPath match, will be always the next one.
 190                  $iterations = count($showmores);
 191                  for ($i = 0; $i < $iterations; $i++) {
 192                      $showmores[0]->click();
 193                  }
 194              }
 195  
 196          } catch (ElementNotFoundException $e) {
 197              // We continue with the test.
 198          }
 199  
 200      }
 201  
 202      /**
 203       * Sets the field to wwwroot plus the given path. Include the first slash.
 204       *
 205       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to local url "(?P<field_path_string>(?:[^"]|\\")*)"$/
 206       * @throws ElementNotFoundException Thrown by behat_base::find
 207       * @param string $field
 208       * @param string $path
 209       * @return void
 210       */
 211      public function i_set_the_field_to_local_url($field, $path) {
 212          global $CFG;
 213          $this->set_field_value($field, $CFG->wwwroot . $path);
 214      }
 215  
 216      /**
 217       * Sets the specified value to the field.
 218       *
 219       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 220       * @throws ElementNotFoundException Thrown by behat_base::find
 221       * @param string $field
 222       * @param string $value
 223       * @return void
 224       */
 225      public function i_set_the_field_to($field, $value) {
 226          $this->set_field_value($field, $value);
 227      }
 228  
 229      /**
 230       * Sets the specified value to the field.
 231       *
 232       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 233       * @throws ElementNotFoundException Thrown by behat_base::find
 234       * @param string $field
 235       * @param string $containerelement Element we look in
 236       * @param string $containerselectortype The type of selector where we look in
 237       * @param string $value
 238       */
 239      public function i_set_the_field_in_container_to($field, $containerelement, $containerselectortype, $value) {
 240          $this->set_field_value_in_container($field, $value, $containerselectortype, $containerelement);
 241      }
 242  
 243      /**
 244       * Press the key in the field to trigger the javascript keypress event
 245       *
 246       * Note that the character key will not actually be typed in the input field
 247       *
 248       * @Given /^I press key "(?P<key_string>(?:[^"]|\\")*)" in the field "(?P<field_string>(?:[^"]|\\")*)"$/
 249       * @throws ElementNotFoundException Thrown by behat_base::find
 250       * @param string $key either char-code or character itself,
 251       *          may optionally be prefixed with ctrl-, alt-, shift- or meta-
 252       * @param string $field
 253       * @return void
 254       */
 255      public function i_press_key_in_the_field($key, $field) {
 256          if (!$this->running_javascript()) {
 257              throw new DriverException('Key press step is not available with Javascript disabled');
 258          }
 259          $fld = behat_field_manager::get_form_field_from_label($field, $this);
 260          $modifier = null;
 261          $char = $key;
 262          if (preg_match('/-/', $key)) {
 263              list($modifier, $char) = preg_split('/-/', $key, 2);
 264          }
 265          if (is_numeric($char)) {
 266              $char = (int)$char;
 267          }
 268          $fld->key_press($char, $modifier);
 269      }
 270  
 271      /**
 272       * Sets the specified value to the field.
 273       *
 274       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to multiline:$/
 275       * @throws ElementNotFoundException Thrown by behat_base::find
 276       * @param string $field
 277       * @param PyStringNode $value
 278       * @return void
 279       */
 280      public function i_set_the_field_to_multiline($field, PyStringNode $value) {
 281          $this->set_field_value($field, (string)$value);
 282      }
 283  
 284      /**
 285       * Sets the specified value to the field with xpath.
 286       *
 287       * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 288       * @throws ElementNotFoundException Thrown by behat_base::find
 289       * @param string $field
 290       * @param string $value
 291       * @return void
 292       */
 293      public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
 294          $this->set_field_node_value($this->find('xpath', $fieldxpath), $value);
 295      }
 296  
 297      /**
 298       * Checks, the field matches the value.
 299       *
 300       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 301       * @throws ElementNotFoundException Thrown by behat_base::find
 302       * @param string $field
 303       * @param string $value
 304       * @return void
 305       */
 306      public function the_field_matches_value($field, $value) {
 307  
 308          // Get the field.
 309          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 310  
 311          // Checks if the provided value matches the current field value.
 312          if (!$formfield->matches($value)) {
 313              $fieldvalue = $formfield->get_value();
 314              throw new ExpectationException(
 315                  'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 316                  $this->getSession()
 317              );
 318          }
 319      }
 320  
 321      /**
 322       * Checks, the field contains the value.
 323       *
 324       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" (?P<doesnot_bool>does not )?match(?:es)* expression "(?P<expression_string>(?:[^"]|\\")*)"$/
 325       * @throws ElementNotFoundException Thrown by behat_base::find
 326       * @param string $field The naem or reference to the field
 327       * @param bool $doesnot
 328       * @param string $expression The Perl-like regular expression, including any delimeters and flag
 329       * @return void
 330       */
 331      public function the_field_matches_expression(
 332          string $field,
 333          bool $doesnot,
 334          string $expression,
 335      ): void {
 336          // Get the field.
 337          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 338  
 339          // Checks if the provided value matches the current field value.
 340          $fieldvalue = $formfield->get_value();
 341          $matches = preg_match($expression, $fieldvalue);
 342          if ($matches === 1 && $doesnot) {
 343              throw new ExpectationException(
 344                  "The '{$field}' field matches the expression '{$expression}' and it should not",
 345                  $this->getSession()
 346              );
 347          } else if ($matches === 0 && !$doesnot) {
 348              throw new ExpectationException(
 349                  "The '{$field}' field does not match the expression '{$expression}'",
 350                  $this->getSession()
 351              );
 352          } else if ($matches === false) {
 353              throw new coding_exception(
 354                  "The expression '{$expression}' was not valid",
 355              );
 356          }
 357      }
 358  
 359      /**
 360       * Checks, the field matches the value.
 361       *
 362       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches multiline:$/
 363       * @throws ElementNotFoundException Thrown by behat_base::find
 364       * @param string $field
 365       * @param PyStringNode $value
 366       * @return void
 367       */
 368      public function the_field_matches_multiline($field, PyStringNode $value) {
 369          $this->the_field_matches_value($field, (string)$value);
 370      }
 371  
 372      /**
 373       * Checks, the field does not match the value.
 374       *
 375       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 376       * @throws ExpectationException
 377       * @throws ElementNotFoundException Thrown by behat_base::find
 378       * @param string $field
 379       * @param string $value
 380       */
 381      public function the_field_does_not_match_value($field, $value) {
 382  
 383          // Get the field.
 384          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 385  
 386          // Checks if the provided value matches the current field value.
 387          if ($formfield->matches($value)) {
 388              throw new ExpectationException(
 389                  'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 390                  $this->getSession()
 391              );
 392          }
 393      }
 394  
 395      /**
 396       * Checks, the field matches the value.
 397       *
 398       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 399       * @throws ElementNotFoundException Thrown by behat_base::find
 400       * @param string $field
 401       * @param string $containerelement Element we look in
 402       * @param string $containerselectortype The type of selector where we look in
 403       * @param string $value
 404       */
 405      public function the_field_in_container_matches_value($field, $containerelement, $containerselectortype, $value) {
 406  
 407          // Get the field.
 408          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 409          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 410  
 411          // Checks if the provided value matches the current field value.
 412          if (!$formfield->matches($value)) {
 413              $fieldvalue = $formfield->get_value();
 414              throw new ExpectationException(
 415                      'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 416                      $this->getSession()
 417              );
 418          }
 419      }
 420  
 421      /**
 422       * Checks, the field does not match the value.
 423       *
 424       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 425       * @throws ExpectationException
 426       * @throws ElementNotFoundException Thrown by behat_base::find
 427       * @param string $field
 428       * @param string $containerelement Element we look in
 429       * @param string $containerselectortype The type of selector where we look in
 430       * @param string $value
 431       */
 432      public function the_field_in_container_does_not_match_value($field, $containerelement, $containerselectortype, $value) {
 433  
 434          // Get the field.
 435          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 436          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 437  
 438          // Checks if the provided value matches the current field value.
 439          if ($formfield->matches($value)) {
 440              throw new ExpectationException(
 441                      'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 442                      $this->getSession()
 443              );
 444          }
 445      }
 446  
 447      /**
 448       * Checks, the field matches the value.
 449       *
 450       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 451       * @throws ExpectationException
 452       * @throws ElementNotFoundException Thrown by behat_base::find
 453       * @param string $fieldxpath
 454       * @param string $value
 455       * @return void
 456       */
 457      public function the_field_with_xpath_matches_value($fieldxpath, $value) {
 458  
 459          // Get the field.
 460          $fieldnode = $this->find('xpath', $fieldxpath);
 461          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 462  
 463          // Checks if the provided value matches the current field value.
 464          if (!$formfield->matches($value)) {
 465              $fieldvalue = $formfield->get_value();
 466              throw new ExpectationException(
 467                  'The \'' . $fieldxpath . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 468                  $this->getSession()
 469              );
 470          }
 471      }
 472  
 473      /**
 474       * Checks, the field does not match the value.
 475       *
 476       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 477       * @throws ExpectationException
 478       * @throws ElementNotFoundException Thrown by behat_base::find
 479       * @param string $fieldxpath
 480       * @param string $value
 481       * @return void
 482       */
 483      public function the_field_with_xpath_does_not_match_value($fieldxpath, $value) {
 484  
 485          // Get the field.
 486          $fieldnode = $this->find('xpath', $fieldxpath);
 487          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 488  
 489          // Checks if the provided value matches the current field value.
 490          if ($formfield->matches($value)) {
 491              throw new ExpectationException(
 492                  'The \'' . $fieldxpath . '\' value matches \'' . $value . '\' and it should not match it' ,
 493                  $this->getSession()
 494              );
 495          }
 496      }
 497  
 498      /**
 499       * Checks, the provided field/value matches.
 500       *
 501       * @Then /^the following fields match these values:$/
 502       * @throws ExpectationException
 503       * @param TableNode $data Pairs of | field | value |
 504       */
 505      public function the_following_fields_match_these_values(TableNode $data) {
 506  
 507          // Expand all fields in case we have.
 508          $this->expand_all_fields();
 509  
 510          $datahash = $data->getRowsHash();
 511  
 512          // The action depends on the field type.
 513          foreach ($datahash as $locator => $value) {
 514              $this->the_field_matches_value($locator, $value);
 515          }
 516      }
 517  
 518      /**
 519       * Checks that the provided field/value pairs don't match.
 520       *
 521       * @Then /^the following fields do not match these values:$/
 522       * @throws ExpectationException
 523       * @param TableNode $data Pairs of | field | value |
 524       */
 525      public function the_following_fields_do_not_match_these_values(TableNode $data) {
 526  
 527          // Expand all fields in case we have.
 528          $this->expand_all_fields();
 529  
 530          $datahash = $data->getRowsHash();
 531  
 532          // The action depends on the field type.
 533          foreach ($datahash as $locator => $value) {
 534              $this->the_field_does_not_match_value($locator, $value);
 535          }
 536      }
 537  
 538      /**
 539       * Checks, the provided field/value matches.
 540       *
 541       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" match these values:$/
 542       * @throws ExpectationException
 543       * @param string $containerelement Element we look in
 544       * @param string $containerselectortype The type of selector where we look in
 545       * @param TableNode $data Pairs of | field | value |
 546       */
 547      public function the_following_fields_in_container_match_these_values(
 548              $containerelement, $containerselectortype, TableNode $data) {
 549  
 550          // Expand all fields in case we have.
 551          $this->expand_all_fields();
 552  
 553          $datahash = $data->getRowsHash();
 554  
 555          // The action depends on the field type.
 556          foreach ($datahash as $locator => $value) {
 557              $this->the_field_in_container_matches_value($locator, $containerelement, $containerselectortype, $value);
 558          }
 559      }
 560  
 561      /**
 562       * Checks that the provided field/value pairs don't match.
 563       *
 564       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" do not match these values:$/
 565       * @throws ExpectationException
 566       * @param string $containerelement Element we look in
 567       * @param string $containerselectortype The type of selector where we look in
 568       * @param TableNode $data Pairs of | field | value |
 569       */
 570      public function the_following_fields_in_container_do_not_match_these_values(
 571              $containerelement, $containerselectortype, TableNode $data) {
 572  
 573          // Expand all fields in case we have.
 574          $this->expand_all_fields();
 575  
 576          $datahash = $data->getRowsHash();
 577  
 578          // The action depends on the field type.
 579          foreach ($datahash as $locator => $value) {
 580              $this->the_field_in_container_does_not_match_value($locator, $containerelement, $containerselectortype, $value);
 581          }
 582      }
 583  
 584      /**
 585       * Checks, that given select box contains the specified option.
 586       *
 587       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
 588       * @throws ExpectationException
 589       * @throws ElementNotFoundException Thrown by behat_base::find
 590       * @param string $select The select element name
 591       * @param string $option The option text/value. Plain value or comma separated
 592       *                       values if multiple. Commas in multiple values escaped with backslash.
 593       */
 594      public function the_select_box_should_contain($select, $option) {
 595  
 596          $selectnode = $this->find_field($select);
 597          $multiple = $selectnode->hasAttribute('multiple');
 598          $optionsarr = array(); // Array of passed value/text options to test.
 599  
 600          if ($multiple) {
 601              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 602              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 603                  $optionsarr[] = trim($opt);
 604              }
 605          } else {
 606              // Only one option has been passed.
 607              $optionsarr[] = trim($option);
 608          }
 609  
 610          // Now get all the values and texts in the select.
 611          $options = $selectnode->findAll('xpath', '//option');
 612          $values = array();
 613          foreach ($options as $opt) {
 614              $values[trim($opt->getValue())] = trim($opt->getText());
 615          }
 616  
 617          foreach ($optionsarr as $opt) {
 618              // Verify every option is a valid text or value.
 619              if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
 620                  throw new ExpectationException(
 621                      'The select box "' . $select . '" does not contain the option "' . $opt . '"',
 622                      $this->getSession()
 623                  );
 624              }
 625          }
 626      }
 627  
 628      /**
 629       * Checks, that given select box does not contain the specified option.
 630       *
 631       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
 632       * @throws ExpectationException
 633       * @throws ElementNotFoundException Thrown by behat_base::find
 634       * @param string $select The select element name
 635       * @param string $option The option text/value. Plain value or comma separated
 636       *                       values if multiple. Commas in multiple values escaped with backslash.
 637       */
 638      public function the_select_box_should_not_contain($select, $option) {
 639  
 640          $selectnode = $this->find_field($select);
 641          $multiple = $selectnode->hasAttribute('multiple');
 642          $optionsarr = array(); // Array of passed value/text options to test.
 643  
 644          if ($multiple) {
 645              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 646              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 647                  $optionsarr[] = trim($opt);
 648              }
 649          } else {
 650              // Only one option has been passed.
 651              $optionsarr[] = trim($option);
 652          }
 653  
 654          // Now get all the values and texts in the select.
 655          $options = $selectnode->findAll('xpath', '//option');
 656          $values = array();
 657          foreach ($options as $opt) {
 658              $values[trim($opt->getValue())] = trim($opt->getText());
 659          }
 660  
 661          foreach ($optionsarr as $opt) {
 662              // Verify every option is not a valid text or value.
 663              if (in_array($opt, $values) || array_key_exists($opt, $values)) {
 664                  throw new ExpectationException(
 665                      'The select box "' . $select . '" contains the option "' . $opt . '"',
 666                      $this->getSession()
 667                  );
 668              }
 669          }
 670      }
 671  
 672      /**
 673       * Generic field setter.
 674       *
 675       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 676       * could be created based on it.
 677       *
 678       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 679       * @param string $value
 680       * @return void
 681       */
 682      protected function set_field_value($fieldlocator, $value) {
 683          // We delegate to behat_form_field class, it will
 684          // guess the type properly as it is a select tag.
 685          $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
 686          $field->set_value($value);
 687      }
 688  
 689      /**
 690       * Generic field setter to be used by chainable steps.
 691       *
 692       * @param NodeElement $fieldnode
 693       * @param string $value
 694       */
 695      public function set_field_node_value(NodeElement $fieldnode, string $value): void {
 696          $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 697          $field->set_value($value);
 698      }
 699  
 700      /**
 701       * Generic field setter.
 702       *
 703       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 704       * could be created based on it.
 705       *
 706       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 707       * @param string $value the value to set
 708       * @param string $containerselectortype The type of selector where we look in
 709       * @param string $containerelement Element we look in
 710       */
 711      protected function set_field_value_in_container($fieldlocator, $value, $containerselectortype, $containerelement) {
 712          $node = $this->get_node_in_container('field', $fieldlocator, $containerselectortype, $containerelement);
 713          $this->set_field_node_value($node, $value);
 714      }
 715  
 716      /**
 717       * Select a value from single select and redirect.
 718       *
 719       * @Given /^I select "(?P<singleselect_option_string>(?:[^"]|\\")*)" from the "(?P<singleselect_name_string>(?:[^"]|\\")*)" singleselect$/
 720       */
 721      public function i_select_from_the_singleselect($option, $singleselect) {
 722  
 723          $this->execute('behat_forms::i_set_the_field_to', array($this->escape($singleselect), $this->escape($option)));
 724  
 725          if (!$this->running_javascript()) {
 726              // Press button in the specified select container.
 727              $containerxpath = "//div[" .
 728                  "(contains(concat(' ', normalize-space(@class), ' '), ' singleselect ') " .
 729                      "or contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')".
 730                  ") and (
 731                  .//label[contains(normalize-space(string(.)), '" . $singleselect . "')] " .
 732                      "or .//select[(./@name='" . $singleselect . "' or ./@id='". $singleselect . "')]" .
 733                  ")]";
 734  
 735              $this->execute('behat_general::i_click_on_in_the',
 736                  array(get_string('go'), "button", $containerxpath, "xpath_element")
 737              );
 738          }
 739      }
 740  
 741      /**
 742       * Select item from autocomplete list.
 743       *
 744       * @Given /^I click on "([^"]*)" item in the autocomplete list$/
 745       *
 746       * @param string $item
 747       */
 748      public function i_click_on_item_in_the_autocomplete_list($item) {
 749          $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]";
 750  
 751          $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
 752      }
 753  
 754      /**
 755       * Open the auto-complete suggestions list (Assuming there is only one on the page.).
 756       *
 757       * @Given I open the autocomplete suggestions list
 758       * @Given I open the autocomplete suggestions list in the :container :containertype
 759       */
 760      public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) {
 761          $csstarget = ".form-autocomplete-downarrow";
 762          if ($container && $containertype) {
 763              $this->execute('behat_general::i_click_on_in_the', [$csstarget, 'css_element', $container, $containertype]);
 764          } else {
 765              $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
 766          }
 767      }
 768  
 769      /**
 770       * Expand the given autocomplete list
 771       *
 772       * @Given /^I expand the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$/
 773       *
 774       * @param string $field Field name
 775       */
 776      public function i_expand_the_autocomplete($field) {
 777          $csstarget = '.form-autocomplete-downarrow';
 778          $node = $this->get_node_in_container('css_element', $csstarget, 'form_row', $field);
 779          $this->ensure_node_is_visible($node);
 780          $node->click();
 781      }
 782  
 783      /**
 784       * Assert the given option exist in the given autocomplete list
 785       *
 786       * @Given /^I should see "(?P<option_string>(?:[^"]|\\")*)" in the list of options for the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$$/
 787       *
 788       * @param string $option Name of option
 789       * @param string $field Field name
 790       */
 791      public function i_should_see_in_the_list_of_option_for_the_autocomplete($option, $field) {
 792          $xpathtarget = "//div[contains(@class, 'form-autocomplete-selection') and contains(.//div, '" . $option . "')]";
 793          $node = $this->get_node_in_container('xpath_element', $xpathtarget, 'form_row', $field);
 794          $this->ensure_node_is_visible($node);
 795      }
 796  
 797      /**
 798       * Checks whether the select menu contains an option with specified text or not.
 799       *
 800       * @Then the :name select menu should contain :option
 801       * @Then the :name select menu should :not contain :option
 802       *
 803       * @throws ExpectationException When the expectation is not satisfied
 804       * @param string $label The label of the select menu element
 805       * @param string $option The string that is used to identify an option within the select menu. If the string
 806       *                       has two items separated by '>' (ex. "Group > Option"), the first item ("Group") will be
 807       *                       used to identify a particular group within the select menu, while the second ("Option")
 808       *                       will be used to identify an option within that group. Otherwise, a string with a single
 809       *                       item (ex. "Option") will be used to identify an option within the select menu regardless
 810       *                       of any existing groups.
 811       * @param string|null $not If set, the select menu should not contain the specified option. If null, the option
 812       *                         should be present.
 813       */
 814      public function the_select_menu_should_contain(string $label, string $option, ?string $not = null) {
 815  
 816          $field = behat_field_manager::get_form_field_from_label($label, $this);
 817  
 818          if (!method_exists($field, 'has_option')) {
 819              throw new coding_exception('Field does not support the has_option function.');
 820          }
 821  
 822          // If the select menu contains the specified option but it should not.
 823          if ($field->has_option($option) && $not) {
 824              throw new ExpectationException(
 825                  "The select menu should not contain \"{$option}\" but it does.",
 826                  $this->getSession()
 827              );
 828          }
 829          // If the select menu does not contain the specified option but it should.
 830          if (!$field->has_option($option) && !$not) {
 831              throw new ExpectationException(
 832                  "The select menu should contain \"{$option}\" but it does not.",
 833                  $this->getSession()
 834              );
 835          }
 836      }
 837  }