Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 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  
 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, ' '), ' collapseexpand ')]" .
 150                  "[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
 151              // Else, look for the first expand fieldset link.
 152              $expandonlysection = "//legend[@class='ftoggler']" .
 153                      "//a[contains(concat(' ', @class, ' '), ' fheader ') and @aria-expanded = 'false']";
 154  
 155              $collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandonlysection,
 156                      false, false, behat_base::get_reduced_timeout());
 157              $collapseexpandlink->click();
 158  
 159          } catch (ElementNotFoundException $e) {
 160              // The behat_base::find() method throws an exception if there are no elements,
 161              // we should not fail a test because of this. We continue if there are not expandable fields.
 162          }
 163  
 164          // Different try & catch as we can have expanded fieldsets with advanced fields on them.
 165          try {
 166  
 167              // Expand all fields xpath.
 168              $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
 169                  "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
 170  
 171              // We don't wait here as we already waited when getting the expand fieldsets links.
 172              if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
 173                  return;
 174              }
 175  
 176              if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) {
 177                  // Chrome Driver produces unique xpaths for each element.
 178                  foreach ($showmores as $showmore) {
 179                      $showmore->click();
 180                  }
 181              } else {
 182                  // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern
 183                  // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it
 184                  // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0],
 185                  // that's why we always click on the first XPath match, will be always the next one.
 186                  $iterations = count($showmores);
 187                  for ($i = 0; $i < $iterations; $i++) {
 188                      $showmores[0]->click();
 189                  }
 190              }
 191  
 192          } catch (ElementNotFoundException $e) {
 193              // We continue with the test.
 194          }
 195  
 196      }
 197  
 198      /**
 199       * Sets the field to wwwroot plus the given path. Include the first slash.
 200       *
 201       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to local url "(?P<field_path_string>(?:[^"]|\\")*)"$/
 202       * @throws ElementNotFoundException Thrown by behat_base::find
 203       * @param string $field
 204       * @param string $path
 205       * @return void
 206       */
 207      public function i_set_the_field_to_local_url($field, $path) {
 208          global $CFG;
 209          $this->set_field_value($field, $CFG->wwwroot . $path);
 210      }
 211  
 212      /**
 213       * Sets the specified value to the field.
 214       *
 215       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 216       * @throws ElementNotFoundException Thrown by behat_base::find
 217       * @param string $field
 218       * @param string $value
 219       * @return void
 220       */
 221      public function i_set_the_field_to($field, $value) {
 222          $this->set_field_value($field, $value);
 223      }
 224  
 225      /**
 226       * Sets the specified value to the field.
 227       *
 228       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 229       * @throws ElementNotFoundException Thrown by behat_base::find
 230       * @param string $field
 231       * @param string $containerelement Element we look in
 232       * @param string $containerselectortype The type of selector where we look in
 233       * @param string $value
 234       */
 235      public function i_set_the_field_in_container_to($field, $containerelement, $containerselectortype, $value) {
 236          $this->set_field_value_in_container($field, $value, $containerselectortype, $containerelement);
 237      }
 238  
 239      /**
 240       * Press the key in the field to trigger the javascript keypress event
 241       *
 242       * Note that the character key will not actually be typed in the input field
 243       *
 244       * @Given /^I press key "(?P<key_string>(?:[^"]|\\")*)" in the field "(?P<field_string>(?:[^"]|\\")*)"$/
 245       * @throws ElementNotFoundException Thrown by behat_base::find
 246       * @param string $key either char-code or character itself,
 247       *          may optionally be prefixed with ctrl-, alt-, shift- or meta-
 248       * @param string $field
 249       * @return void
 250       */
 251      public function i_press_key_in_the_field($key, $field) {
 252          if (!$this->running_javascript()) {
 253              throw new DriverException('Key press step is not available with Javascript disabled');
 254          }
 255          $fld = behat_field_manager::get_form_field_from_label($field, $this);
 256          $modifier = null;
 257          $char = $key;
 258          if (preg_match('/-/', $key)) {
 259              list($modifier, $char) = preg_split('/-/', $key, 2);
 260          }
 261          if (is_numeric($char)) {
 262              $char = (int)$char;
 263          }
 264          $fld->key_press($char, $modifier);
 265      }
 266  
 267      /**
 268       * Sets the specified value to the field.
 269       *
 270       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to multiline:$/
 271       * @throws ElementNotFoundException Thrown by behat_base::find
 272       * @param string $field
 273       * @param PyStringNode $value
 274       * @return void
 275       */
 276      public function i_set_the_field_to_multiline($field, PyStringNode $value) {
 277          $this->set_field_value($field, (string)$value);
 278      }
 279  
 280      /**
 281       * Sets the specified value to the field with xpath.
 282       *
 283       * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 284       * @throws ElementNotFoundException Thrown by behat_base::find
 285       * @param string $field
 286       * @param string $value
 287       * @return void
 288       */
 289      public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
 290          $this->set_field_node_value($this->find('xpath', $fieldxpath), $value);
 291      }
 292  
 293      /**
 294       * Checks, the field matches the value.
 295       *
 296       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 297       * @throws ElementNotFoundException Thrown by behat_base::find
 298       * @param string $field
 299       * @param string $value
 300       * @return void
 301       */
 302      public function the_field_matches_value($field, $value) {
 303  
 304          // Get the field.
 305          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 306  
 307          // Checks if the provided value matches the current field value.
 308          if (!$formfield->matches($value)) {
 309              $fieldvalue = $formfield->get_value();
 310              throw new ExpectationException(
 311                  'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 312                  $this->getSession()
 313              );
 314          }
 315      }
 316  
 317      /**
 318       * Checks, the field matches the value.
 319       *
 320       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches multiline:$/
 321       * @throws ElementNotFoundException Thrown by behat_base::find
 322       * @param string $field
 323       * @param PyStringNode $value
 324       * @return void
 325       */
 326      public function the_field_matches_multiline($field, PyStringNode $value) {
 327          $this->the_field_matches_value($field, (string)$value);
 328      }
 329  
 330      /**
 331       * Checks, the field does not match the value.
 332       *
 333       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 334       * @throws ExpectationException
 335       * @throws ElementNotFoundException Thrown by behat_base::find
 336       * @param string $field
 337       * @param string $value
 338       */
 339      public function the_field_does_not_match_value($field, $value) {
 340  
 341          // Get the field.
 342          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 343  
 344          // Checks if the provided value matches the current field value.
 345          if ($formfield->matches($value)) {
 346              throw new ExpectationException(
 347                  'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 348                  $this->getSession()
 349              );
 350          }
 351      }
 352  
 353      /**
 354       * Checks, the field matches the value.
 355       *
 356       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 357       * @throws ElementNotFoundException Thrown by behat_base::find
 358       * @param string $field
 359       * @param string $containerelement Element we look in
 360       * @param string $containerselectortype The type of selector where we look in
 361       * @param string $value
 362       */
 363      public function the_field_in_container_matches_value($field, $containerelement, $containerselectortype, $value) {
 364  
 365          // Get the field.
 366          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 367          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 368  
 369          // Checks if the provided value matches the current field value.
 370          if (!$formfield->matches($value)) {
 371              $fieldvalue = $formfield->get_value();
 372              throw new ExpectationException(
 373                      'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 374                      $this->getSession()
 375              );
 376          }
 377      }
 378  
 379      /**
 380       * Checks, the field does not match the value.
 381       *
 382       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 383       * @throws ExpectationException
 384       * @throws ElementNotFoundException Thrown by behat_base::find
 385       * @param string $field
 386       * @param string $containerelement Element we look in
 387       * @param string $containerselectortype The type of selector where we look in
 388       * @param string $value
 389       */
 390      public function the_field_in_container_does_not_match_value($field, $containerelement, $containerselectortype, $value) {
 391  
 392          // Get the field.
 393          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 394          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 395  
 396          // Checks if the provided value matches the current field value.
 397          if ($formfield->matches($value)) {
 398              throw new ExpectationException(
 399                      'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 400                      $this->getSession()
 401              );
 402          }
 403      }
 404  
 405      /**
 406       * Checks, the field matches the value.
 407       *
 408       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 409       * @throws ExpectationException
 410       * @throws ElementNotFoundException Thrown by behat_base::find
 411       * @param string $fieldxpath
 412       * @param string $value
 413       * @return void
 414       */
 415      public function the_field_with_xpath_matches_value($fieldxpath, $value) {
 416  
 417          // Get the field.
 418          $fieldnode = $this->find('xpath', $fieldxpath);
 419          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 420  
 421          // Checks if the provided value matches the current field value.
 422          if (!$formfield->matches($value)) {
 423              $fieldvalue = $formfield->get_value();
 424              throw new ExpectationException(
 425                  'The \'' . $fieldxpath . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 426                  $this->getSession()
 427              );
 428          }
 429      }
 430  
 431      /**
 432       * Checks, the field does not match the value.
 433       *
 434       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 435       * @throws ExpectationException
 436       * @throws ElementNotFoundException Thrown by behat_base::find
 437       * @param string $fieldxpath
 438       * @param string $value
 439       * @return void
 440       */
 441      public function the_field_with_xpath_does_not_match_value($fieldxpath, $value) {
 442  
 443          // Get the field.
 444          $fieldnode = $this->find('xpath', $fieldxpath);
 445          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 446  
 447          // Checks if the provided value matches the current field value.
 448          if ($formfield->matches($value)) {
 449              throw new ExpectationException(
 450                  'The \'' . $fieldxpath . '\' value matches \'' . $value . '\' and it should not match it' ,
 451                  $this->getSession()
 452              );
 453          }
 454      }
 455  
 456      /**
 457       * Checks, the provided field/value matches.
 458       *
 459       * @Then /^the following fields match these values:$/
 460       * @throws ExpectationException
 461       * @param TableNode $data Pairs of | field | value |
 462       */
 463      public function the_following_fields_match_these_values(TableNode $data) {
 464  
 465          // Expand all fields in case we have.
 466          $this->expand_all_fields();
 467  
 468          $datahash = $data->getRowsHash();
 469  
 470          // The action depends on the field type.
 471          foreach ($datahash as $locator => $value) {
 472              $this->the_field_matches_value($locator, $value);
 473          }
 474      }
 475  
 476      /**
 477       * Checks that the provided field/value pairs don't match.
 478       *
 479       * @Then /^the following fields do not match these values:$/
 480       * @throws ExpectationException
 481       * @param TableNode $data Pairs of | field | value |
 482       */
 483      public function the_following_fields_do_not_match_these_values(TableNode $data) {
 484  
 485          // Expand all fields in case we have.
 486          $this->expand_all_fields();
 487  
 488          $datahash = $data->getRowsHash();
 489  
 490          // The action depends on the field type.
 491          foreach ($datahash as $locator => $value) {
 492              $this->the_field_does_not_match_value($locator, $value);
 493          }
 494      }
 495  
 496      /**
 497       * Checks, the provided field/value matches.
 498       *
 499       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" match these values:$/
 500       * @throws ExpectationException
 501       * @param string $containerelement Element we look in
 502       * @param string $containerselectortype The type of selector where we look in
 503       * @param TableNode $data Pairs of | field | value |
 504       */
 505      public function the_following_fields_in_container_match_these_values(
 506              $containerelement, $containerselectortype, TableNode $data) {
 507  
 508          // Expand all fields in case we have.
 509          $this->expand_all_fields();
 510  
 511          $datahash = $data->getRowsHash();
 512  
 513          // The action depends on the field type.
 514          foreach ($datahash as $locator => $value) {
 515              $this->the_field_in_container_matches_value($locator, $containerelement, $containerselectortype, $value);
 516          }
 517      }
 518  
 519      /**
 520       * Checks that the provided field/value pairs don't match.
 521       *
 522       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" do not match these values:$/
 523       * @throws ExpectationException
 524       * @param string $containerelement Element we look in
 525       * @param string $containerselectortype The type of selector where we look in
 526       * @param TableNode $data Pairs of | field | value |
 527       */
 528      public function the_following_fields_in_container_do_not_match_these_values(
 529              $containerelement, $containerselectortype, TableNode $data) {
 530  
 531          // Expand all fields in case we have.
 532          $this->expand_all_fields();
 533  
 534          $datahash = $data->getRowsHash();
 535  
 536          // The action depends on the field type.
 537          foreach ($datahash as $locator => $value) {
 538              $this->the_field_in_container_does_not_match_value($locator, $containerelement, $containerselectortype, $value);
 539          }
 540      }
 541  
 542      /**
 543       * Checks, that given select box contains the specified option.
 544       *
 545       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
 546       * @throws ExpectationException
 547       * @throws ElementNotFoundException Thrown by behat_base::find
 548       * @param string $select The select element name
 549       * @param string $option The option text/value. Plain value or comma separated
 550       *                       values if multiple. Commas in multiple values escaped with backslash.
 551       */
 552      public function the_select_box_should_contain($select, $option) {
 553  
 554          $selectnode = $this->find_field($select);
 555          $multiple = $selectnode->hasAttribute('multiple');
 556          $optionsarr = array(); // Array of passed value/text options to test.
 557  
 558          if ($multiple) {
 559              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 560              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 561                  $optionsarr[] = trim($opt);
 562              }
 563          } else {
 564              // Only one option has been passed.
 565              $optionsarr[] = trim($option);
 566          }
 567  
 568          // Now get all the values and texts in the select.
 569          $options = $selectnode->findAll('xpath', '//option');
 570          $values = array();
 571          foreach ($options as $opt) {
 572              $values[trim($opt->getValue())] = trim($opt->getText());
 573          }
 574  
 575          foreach ($optionsarr as $opt) {
 576              // Verify every option is a valid text or value.
 577              if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
 578                  throw new ExpectationException(
 579                      'The select box "' . $select . '" does not contain the option "' . $opt . '"',
 580                      $this->getSession()
 581                  );
 582              }
 583          }
 584      }
 585  
 586      /**
 587       * Checks, that given select box does not contain the specified option.
 588       *
 589       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
 590       * @throws ExpectationException
 591       * @throws ElementNotFoundException Thrown by behat_base::find
 592       * @param string $select The select element name
 593       * @param string $option The option text/value. Plain value or comma separated
 594       *                       values if multiple. Commas in multiple values escaped with backslash.
 595       */
 596      public function the_select_box_should_not_contain($select, $option) {
 597  
 598          $selectnode = $this->find_field($select);
 599          $multiple = $selectnode->hasAttribute('multiple');
 600          $optionsarr = array(); // Array of passed value/text options to test.
 601  
 602          if ($multiple) {
 603              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 604              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 605                  $optionsarr[] = trim($opt);
 606              }
 607          } else {
 608              // Only one option has been passed.
 609              $optionsarr[] = trim($option);
 610          }
 611  
 612          // Now get all the values and texts in the select.
 613          $options = $selectnode->findAll('xpath', '//option');
 614          $values = array();
 615          foreach ($options as $opt) {
 616              $values[trim($opt->getValue())] = trim($opt->getText());
 617          }
 618  
 619          foreach ($optionsarr as $opt) {
 620              // Verify every option is not a valid text or value.
 621              if (in_array($opt, $values) || array_key_exists($opt, $values)) {
 622                  throw new ExpectationException(
 623                      'The select box "' . $select . '" contains the option "' . $opt . '"',
 624                      $this->getSession()
 625                  );
 626              }
 627          }
 628      }
 629  
 630      /**
 631       * Generic field setter.
 632       *
 633       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 634       * could be created based on it.
 635       *
 636       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 637       * @param string $value
 638       * @return void
 639       */
 640      protected function set_field_value($fieldlocator, $value) {
 641          // We delegate to behat_form_field class, it will
 642          // guess the type properly as it is a select tag.
 643          $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
 644          $field->set_value($value);
 645      }
 646  
 647      /**
 648       * Generic field setter to be used by chainable steps.
 649       *
 650       * @param NodeElement $fieldnode
 651       * @param string $value
 652       */
 653      public function set_field_node_value(NodeElement $fieldnode, string $value): void {
 654          $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 655          $field->set_value($value);
 656      }
 657  
 658      /**
 659       * Generic field setter.
 660       *
 661       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 662       * could be created based on it.
 663       *
 664       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 665       * @param string $value the value to set
 666       * @param string $containerselectortype The type of selector where we look in
 667       * @param string $containerelement Element we look in
 668       */
 669      protected function set_field_value_in_container($fieldlocator, $value, $containerselectortype, $containerelement) {
 670          $node = $this->get_node_in_container('field', $fieldlocator, $containerselectortype, $containerelement);
 671          $this->set_field_node_value($node, $value);
 672      }
 673  
 674      /**
 675       * Select a value from single select and redirect.
 676       *
 677       * @Given /^I select "(?P<singleselect_option_string>(?:[^"]|\\")*)" from the "(?P<singleselect_name_string>(?:[^"]|\\")*)" singleselect$/
 678       */
 679      public function i_select_from_the_singleselect($option, $singleselect) {
 680  
 681          $this->execute('behat_forms::i_set_the_field_to', array($this->escape($singleselect), $this->escape($option)));
 682  
 683          if (!$this->running_javascript()) {
 684              // Press button in the specified select container.
 685              $containerxpath = "//div[" .
 686                  "(contains(concat(' ', normalize-space(@class), ' '), ' singleselect ') " .
 687                      "or contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')".
 688                  ") and (
 689                  .//label[contains(normalize-space(string(.)), '" . $singleselect . "')] " .
 690                      "or .//select[(./@name='" . $singleselect . "' or ./@id='". $singleselect . "')]" .
 691                  ")]";
 692  
 693              $this->execute('behat_general::i_click_on_in_the',
 694                  array(get_string('go'), "button", $containerxpath, "xpath_element")
 695              );
 696          }
 697      }
 698  
 699      /**
 700       * Select item from autocomplete list.
 701       *
 702       * @Given /^I click on "([^"]*)" item in the autocomplete list$/
 703       *
 704       * @param string $item
 705       */
 706      public function i_click_on_item_in_the_autocomplete_list($item) {
 707          $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]";
 708  
 709          $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
 710      }
 711  
 712      /**
 713       * Open the auto-complete suggestions list (Assuming there is only one on the page.).
 714       *
 715       * @Given I open the autocomplete suggestions list
 716       * @Given I open the autocomplete suggestions list in the :container :containertype
 717       */
 718      public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) {
 719          $csstarget = ".form-autocomplete-downarrow";
 720          if ($container && $containertype) {
 721              $this->execute('behat_general::i_click_on_in_the', [$csstarget, 'css_element', $container, $containertype]);
 722          } else {
 723              $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
 724          }
 725      }
 726  
 727      /**
 728       * Expand the given autocomplete list
 729       *
 730       * @Given /^I expand the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$/
 731       *
 732       * @param string $field Field name
 733       */
 734      public function i_expand_the_autocomplete($field) {
 735          $csstarget = '.form-autocomplete-downarrow';
 736          $node = $this->get_node_in_container('css_element', $csstarget, 'form_row', $field);
 737          $this->ensure_node_is_visible($node);
 738          $node->click();
 739      }
 740  
 741      /**
 742       * Assert the given option exist in the given autocomplete list
 743       *
 744       * @Given /^I should see "(?P<option_string>(?:[^"]|\\")*)" in the list of options for the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$$/
 745       *
 746       * @param string $option Name of option
 747       * @param string $field Field name
 748       */
 749      public function i_should_see_in_the_list_of_option_for_the_autocomplete($option, $field) {
 750          $xpathtarget = "//div[contains(@class, 'form-autocomplete-selection') and contains(.//div, '" . $option . "')]";
 751          $node = $this->get_node_in_container('xpath_element', $xpathtarget, 'form_row', $field);
 752          $this->ensure_node_is_visible($node);
 753      }
 754  }