Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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   * Behat grade related steps definitions.
  19   *
  20   * @package    core_grades
  21   * @category   test
  22   * @copyright  2014 Mark Nelson <markn@moodle.com>
  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  
  30  use Behat\Gherkin\Node\TableNode as TableNode;
  31  
  32  class behat_grade extends behat_base {
  33  
  34      /**
  35       * Enters a grade via the gradebook for a specific grade item and user when viewing the 'Grader report' with editing mode turned on.
  36       *
  37       * @Given /^I give the grade "(?P<grade_number>(?:[^"]|\\")*)" to the user "(?P<username_string>(?:[^"]|\\")*)" for the grade item "(?P<grade_activity_string>(?:[^"]|\\")*)"$/
  38       * @param int $grade
  39       * @param string $userfullname the user's fullname as returned by fullname()
  40       * @param string $itemname
  41       */
  42      public function i_give_the_grade($grade, $userfullname, $itemname) {
  43          $gradelabel = $userfullname . ' ' . $itemname;
  44          $fieldstr = get_string('useractivitygrade', 'gradereport_grader', $gradelabel);
  45  
  46          $this->execute('behat_forms::i_set_the_field_to', array($this->escape($fieldstr), $grade));
  47      }
  48  
  49      /**
  50       * Enters a quick feedback via the gradebook for a specific grade item and user when viewing
  51       * the 'Grader report' with editing mode turned on.
  52       *
  53       * @Given /^I give the feedback "(?P<grade_number>(?:[^"]|\\")*)" to the user "(?P<username_string>(?:[^"]|\\")*)" for the grade item "(?P<grade_activity_string>(?:[^"]|\\")*)"$/
  54       * @param string $feedback
  55       * @param string $userfullname the user's fullname as returned by fullname()
  56       * @param string $itemname
  57       */
  58      public function i_give_the_feedback($feedback, $userfullname, $itemname) {
  59          $gradelabel = $userfullname . ' ' . $itemname;
  60          $fieldstr = get_string('useractivityfeedback', 'gradereport_grader', $gradelabel);
  61  
  62          $this->execute('behat_forms::i_set_the_field_to', array($this->escape($fieldstr), $this->escape($feedback)));
  63      }
  64  
  65      /**
  66       * Changes the settings of a grade item or category or the course.
  67       *
  68       * Teacher must be either on the grade setup page or on the Grader report page with editing mode turned on.
  69       *
  70       * @Given /^I set the following settings for grade item "(?P<grade_item_string>(?:[^"]|\\")*)":$/
  71       * @param string $gradeitem
  72       * @param TableNode $data
  73       */
  74      public function i_set_the_following_settings_for_grade_item($gradeitem, TableNode $data) {
  75  
  76          $gradeitem = behat_context_helper::escape($gradeitem);
  77  
  78          if ($this->running_javascript()) {
  79              $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
  80              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
  81                  $this->execute("behat_action_menu::i_open_the_action_menu_in",
  82                          array("//tr[contains(.,$gradeitem)]",
  83                                  "xpath_element"));
  84              }
  85          }
  86  
  87          $savechanges = get_string('savechanges', 'grades');
  88          $edit = behat_context_helper::escape(get_string('edit') . '  ');
  89          $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
  90                  "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
  91  
  92          $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
  93          $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
  94          $this->execute('behat_forms::press_button', $this->escape($savechanges));
  95      }
  96  
  97      /**
  98       * Hids a grade item or category.
  99       *
 100       * Teacher must be on the grade setup page.
 101       *
 102       * @Given /^I hide the grade item "(?P<grade_item_string>(?:[^"]|\\")*)"$/
 103       * @param string $gradeitem
 104       */
 105      public function i_hide_the_grade_item($gradeitem) {
 106  
 107          $gradeitem = behat_context_helper::escape($gradeitem);
 108  
 109          if ($this->running_javascript()) {
 110              $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'dropdown-toggle')]";
 111              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
 112                  $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
 113              }
 114          }
 115  
 116          $this->execute("behat_general::i_click_on_in_the", array(get_string('hide'), 'link',
 117              "//tr[descendant::*[text() = " . $this->escape($gradeitem) . "]]", 'xpath_element'));
 118      }
 119  
 120      /**
 121       * Duplicates a grade item or category.
 122       *
 123       * Teacher must be on the grade setup page.
 124       *
 125       * @Given /^I duplicate the grade item "(?P<grade_item_string>(?:[^"]|\\")*)"$/
 126       * @param string $gradeitem
 127       */
 128      public function i_duplicate_the_grade_item($gradeitem) {
 129  
 130          $gradeitem = behat_context_helper::escape($gradeitem);
 131  
 132          if ($this->running_javascript()) {
 133              $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'dropdown-toggle')]";
 134              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
 135                  $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
 136              }
 137          }
 138  
 139          $this->execute("behat_general::i_click_on_in_the", array(get_string('duplicate'), 'link',
 140              "//tr[descendant::*[text() = " . $this->escape($gradeitem) . "]]", 'xpath_element'));
 141      }
 142  
 143      /**
 144       * Sets a calculated manual grade item. Needs a table with item name - idnumber relation.
 145       * The step requires you to be in the 'Gradebook setup' page.
 146       *
 147       * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade item "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
 148       * @param string $calculation The calculation.
 149       * @param string $gradeitem The grade item name.
 150       * @param TableNode $TableNode The grade item name - idnumbers relation.
 151       */
 152      public function i_set_calculation_for_grade_item_with_idnumbers($calculation, $gradeitem, TableNode $data) {
 153  
 154          $gradeitem = behat_context_helper::escape($gradeitem);
 155  
 156          if ($this->running_javascript()) {
 157              $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
 158              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
 159                  $this->execute("behat_action_menu::i_open_the_action_menu_in",
 160                          array("//tr[contains(.,$gradeitem)]",
 161                                  "xpath_element"));
 162              }
 163          }
 164  
 165          // Going to edit calculation.
 166          $savechanges = get_string('savechanges', 'grades');
 167          $edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
 168          $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
 169                  "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
 170          $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
 171  
 172          // Mapping names to idnumbers.
 173          $datahash = $data->getRowsHash();
 174          foreach ($datahash as $gradeitem => $idnumber) {
 175              // This xpath looks for course, categories and items with the provided name.
 176              // Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
 177              $inputxpath = "//input[@class='idnumber'][" .
 178                      "parent::li[@class='item'][text()='" . $gradeitem . "']" .
 179                      " or " .
 180                      "parent::li[@class='categoryitem' or @class='courseitem']" .
 181                      "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
 182                      "]";
 183              $this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
 184          }
 185  
 186          $this->execute('behat_forms::press_button', get_string('addidnumbers', 'grades'));
 187          $this->execute('behat_forms::i_set_the_field_to', array(get_string('calculation', 'grades'), $calculation));
 188          $this->execute('behat_forms::press_button', $savechanges);
 189  
 190      }
 191  
 192      /**
 193       * Sets a calculated manual grade category total. Needs a table with item name - idnumber relation.
 194       * The step requires you to be in the 'Gradebook setup' page.
 195       *
 196       * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade category "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
 197       * @param string $calculation The calculation.
 198       * @param string $gradeitem The grade item name.
 199       * @param TableNode $data The grade item name - idnumbers relation.
 200       */
 201      public function i_set_calculation_for_grade_category_with_idnumbers($calculation, $gradeitem, TableNode $data) {
 202  
 203          $gradecategorytotal = behat_context_helper::escape($gradeitem . ' total');
 204          $gradeitem = behat_context_helper::escape($gradeitem);
 205  
 206          if ($this->running_javascript()) {
 207              $xpath = "//tr[contains(.,$gradecategorytotal)]//*[contains(@class,'moodle-actionmenu')]";
 208              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
 209                  $xpath = "//tr[contains(.,$gradecategorytotal)]";
 210                  $this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
 211              }
 212          }
 213  
 214          // Going to edit calculation.
 215          $savechanges = get_string('savechanges', 'grades');
 216          $edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
 217          $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
 218                  "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
 219          $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
 220  
 221          // Mapping names to idnumbers.
 222          $datahash = $data->getRowsHash();
 223          foreach ($datahash as $gradeitem => $idnumber) {
 224              // This xpath looks for course, categories and items with the provided name.
 225              // Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
 226              $inputxpath = "//input[@class='idnumber'][" .
 227                      "parent::li[@class='item'][text()='" . $gradeitem . "']" .
 228                      " | " .
 229                      "parent::li[@class='categoryitem' or @class='courseitem']" .
 230                      "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
 231                      "]";
 232              $this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
 233          }
 234  
 235          $this->execute('behat_forms::press_button', get_string('addidnumbers', 'grades'));
 236  
 237          $this->execute('behat_forms::i_set_the_field_to', array(get_string('calculation', 'grades'), $calculation));
 238          $this->execute('behat_forms::press_button', $savechanges);
 239      }
 240  
 241      /**
 242       * Resets the weights for the grade category
 243       *
 244       * Teacher must be on the grade setup page.
 245       *
 246       * @Given /^I reset weights for grade category "(?P<grade_item_string>(?:[^"]|\\")*)"$/
 247       * @param $gradeitem
 248       */
 249      public function i_reset_weights_for_grade_category($gradeitem) {
 250  
 251          $steps = array();
 252  
 253          if ($this->running_javascript()) {
 254              $gradeitemliteral = behat_context_helper::escape($gradeitem);
 255              $xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]";
 256              if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
 257                  $xpath = "//tr[contains(.,$gradeitemliteral)]";
 258                  $this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
 259              }
 260          }
 261  
 262          $linktext = get_string('resetweights', 'grades', (object)array('itemname' => $gradeitem));
 263          $this->execute("behat_general::i_click_on", array($this->escape($linktext), "link"));
 264      }
 265  
 266      /**
 267       * Step allowing to test before-the-fix behaviour of the gradebook
 268       *
 269       * @Given /^gradebook calculations for the course "(?P<coursename_string>(?:[^"]|\\")*)" are frozen at version "(?P<version_string>(?:[^"]|\\")*)"$/
 270       * @param string $coursename
 271       * @param string $version
 272       */
 273      public function gradebook_calculations_for_the_course_are_frozen_at_version($coursename, $version) {
 274          global $DB;
 275          $courseid = $DB->get_field('course', 'id', array('shortname' => $coursename), MUST_EXIST);
 276          set_config('gradebook_calculations_freeze_' . $courseid, $version);
 277      }
 278  
 279      /**
 280       * Select the tab in the gradebook. We must be on one of the gradebook pages already.
 281       *
 282       * @deprecated since 4.0 - use behat_forms::i_set_the_field_to() instead.
 283       * @param string $gradepath examples: "View > User report", "Letters > View", "Scales"
 284       */
 285      protected function select_in_gradebook_tabs($gradepath) {
 286          debugging('The function select_in_gradebook_tabs() is deprecated, please use ' .
 287              'behat_forms::i_set_the_field_to() instead.', DEBUG_DEVELOPER);
 288  
 289          $gradepath = preg_split('/\s*>\s*/', trim($gradepath));
 290          if (count($gradepath) > 2) {
 291              throw new coding_exception('Grade path is too long (must have no more than two items separated with ">")');
 292          }
 293  
 294          $xpath = '//div[contains(@class,\'grade-navigation\')]';
 295  
 296          // If the first row of the grade-navigation tabs does not have $gradepath[0] as active tab, click on it.
 297          $link = '\'' . $this->escape($gradepath[0]) . '\'';
 298          $xpathrow1 = $xpath . '//ul[1]//*[contains(@class,\'active\') and contains(normalize-space(.), ' . $link . ')]';
 299          if (!$this->getSession()->getPage()->findAll('xpath', $xpathrow1)) {
 300              $this->find('xpath', $xpath . '//ul[1]/li/a[text()=' . $link . ']')->click();
 301              $this->wait_for_pending_js();
 302          }
 303  
 304          if (isset($gradepath[1])) {
 305              // If the second row of the grade-navigation tabs does not have $gradepath[1] as active tab, click on it.
 306              $link = '\'' . $this->escape($gradepath[1]) . '\'';
 307              $xpathrow2 = $xpath . '//ul[2]//*[contains(@class,\'active\') and contains(normalize-space(.), ' . $link . ')]';
 308              if (!$this->getSession()->getPage()->findAll('xpath', $xpathrow2)) {
 309                  $this->find('xpath', $xpath . '//ul[2]/li/a[text()=' . $link . ']')->click();
 310                  $this->wait_for_pending_js();
 311              }
 312          }
 313      }
 314  
 315      /**
 316       * Navigates to the course gradebook and selects the specified item from the general grade navigation selector.
 317       *
 318       * Examples:
 319       * - I navigate to "Setup > Gradebook setup" in the course gradebook
 320       * - I navigate to "Scales" in the course gradebook
 321       * - I navigate to "More > Grade letters" in the course gradebook
 322       *
 323       * @Given /^I navigate to "(?P<gradepath_string>(?:[^"]|\\")*)" in the course gradebook$/
 324       * @param string $gradepath The path string. If the path has two items (ex. "More > Grade letters"), the first item
 325       *                          ("More") will be used to identify an option group in the navigation selector, while the
 326       *                          second ("Grade letters") will be used to identify an option within that option group.
 327       *                          Otherwise, a single item in a path (ex. "Scales") will be used to identify an option in
 328       *                          the navigation selector regardless of the option group.
 329       */
 330      public function i_navigate_to_in_the_course_gradebook($gradepath) {
 331          // If we are not on one of the gradebook pages already, follow "Grades" link in the navigation drawer.
 332          $xpath = '//div[contains(@class,\'grade-navigation\')]';
 333          if (!$this->getSession()->getPage()->findAll('xpath', $xpath)) {
 334              $this->execute('behat_navigation::i_select_from_secondary_navigation', get_string('grades'));
 335          }
 336  
 337          $this->execute('behat_forms::i_set_the_field_to', [get_string('gradebooknavigationmenu', 'grades'), $gradepath]);
 338      }
 339  
 340      /**
 341       * Navigates to the imports page in the course gradebook and selects the specified import type from the grade
 342       * imports navigation selector.
 343       *
 344       * Examples:
 345       * - I navigate to "CSV file" import page in the course gradebook
 346       *
 347       * @Given /^I navigate to "(?P<importoption_string>(?:[^"]|\\")*)" import page in the course gradebook$/
 348       * @param string $gradeimportoption The name of an existing grade import option.
 349       */
 350      public function i_navigate_to_import_page_in_the_course_gradebook($gradeimportoption) {
 351          $this->i_navigate_to_in_the_course_gradebook("More > Import");
 352          $this->execute('behat_forms::i_set_the_field_to', [get_string('importas', 'grades'), $gradeimportoption]);
 353      }
 354  
 355      /**
 356       * Navigates to the exports page in the course gradebook and selects the specified export type from the grade
 357       * exports navigation selector.
 358       *
 359       * Examples:
 360       * - I navigate to "XML file" export page in the course gradebook
 361       *
 362       * @Given /^I navigate to "(?P<exportoption_string>(?:[^"]|\\")*)" export page in the course gradebook$/
 363       * @param string $gradeexportoption The name of an existing grade export option.
 364       */
 365      public function i_navigate_to_export_page_in_the_course_gradebook($gradeexportoption) {
 366          $this->i_navigate_to_in_the_course_gradebook("More > Export");
 367          $this->execute('behat_forms::i_set_the_field_to', [get_string('exportas', 'grades'), $gradeexportoption]);
 368      }
 369  
 370      /**
 371       * Select a given option from a navigation URL selector in the gradebook. We must be on one of the gradebook pages
 372       * already.
 373       *
 374       * @deprecated since 4.1 - use behat_forms::i_set_the_field_to() instead.
 375       * @param string $path The string path that is used to identify an item within the navigation selector. If the path
 376       *                     has two items (ex. "More > Grade letters"), the first item ("More") will be used to identify
 377       *                     an option group in the navigation selector, while the second ("Grade letters") will be used to
 378       *                     identify an option within that option group. Otherwise, a single item in a path (ex. "Scales")
 379       *                     will be used to identify an option in the navigation selector regardless of the option group.
 380       * @param string $formid The ID of the form element which contains the navigation URL selector element.
 381       */
 382      protected function select_in_gradebook_navigation_selector(string $path, string $formid) {
 383          debugging('The function select_in_gradebook_navigation_selector() is deprecated, please use ' .
 384              'behat_forms::i_set_the_field_to() instead.', DEBUG_DEVELOPER);
 385  
 386          // Split the path string by ">".
 387          $path = preg_split('/\s*>\s*/', trim($path));
 388  
 389          // Make sure that the path does not have more than two items separated with ">".
 390          if (count($path) > 2) {
 391              throw new coding_exception('The path is too long (must have no more than two items separated with ">")');
 392          }
 393  
 394          // Get the select element.
 395          $selectxpath = "//form[contains(@id,'{$formid}')]//select";
 396          $select = $this->find('xpath', $selectxpath);
 397  
 398          // Define the xpath to the option element depending on the provided path.
 399          // If two items are provided in the path, the first item will be considered as an identifier of an existing
 400          // option group in the select select element, while the second item will identify an existing option within
 401          // that option group.
 402          // If one item is provided in the path, this item will identify any existing option in the select element
 403          // regardless of the option group. Also, this is useful when option elements are not a part of an option group
 404          // which is possible.
 405          if (count($path) === 2) {
 406              $optionxpath = $selectxpath . '/optgroup[@label="' . $this->escape($path[0]) . '"]' .
 407                  '/option[contains(.,"' . $this->escape($path[1]) . '")]';
 408          } else {
 409              $optionxpath = $selectxpath . '//option[contains(.,"' . $this->escape($path[0]) . '")]';
 410          }
 411  
 412          // Get the option element that we are looking to select.
 413          $option = $this->find('xpath', $optionxpath);
 414  
 415          // Select the given option in the select element.
 416          $field = behat_field_manager::get_field_instance('select', $select, $this->getSession());
 417          $field->set_value($this->escape($option->getValue()));
 418  
 419          if (!$this->running_javascript()) {
 420              $this->execute('behat_general::i_click_on_in_the', [get_string('go'), 'button',
 421                  "#{$formid}", 'css_element']);
 422          }
 423      }
 424  
 425      /**
 426       * Confirm if a value is within the search widget within the gradebook.
 427       *
 428       * Examples:
 429       * - I confirm "User" in "user" search within the gradebook widget exists
 430       * - I confirm "Group" in "group" search within the gradebook widget exists
 431       * - I confirm "Grade item" in "grade" search within the gradebook widget exists
 432       *
 433       * @Given /^I confirm "(?P<needle>(?:[^"]|\\")*)" in "(?P<haystack>(?:[^"]|\\")*)" search within the gradebook widget exists$/
 434       * @param string $needle The value to search for.
 435       * @param string $haystack The type of the search widget.
 436       */
 437      public function i_confirm_in_search_within_the_gradebook_widget_exists($needle, $haystack) {
 438          $triggercssselector = ".search-widget[data-searchtype='{$haystack}']";
 439  
 440          // Make sure that the dropdown menu is visible.
 441          $node = $this->find("css_element", "{$triggercssselector} .dropdown-menu");
 442          if (!$node->isVisible()) {
 443              $this->execute("behat_general::i_click_on", [$triggercssselector, "css_element"]);
 444          }
 445  
 446          $this->execute("behat_general::wait_until_the_page_is_ready");
 447          $this->execute("behat_general::assert_element_contains_text",
 448              [$needle, "{$triggercssselector} .dropdown-menu", "css_element"]);
 449      }
 450  
 451      /**
 452       * Confirm if a value is not within the search widget within the gradebook.
 453       *
 454       * Examples:
 455       * - I confirm "User" in "user" search within the gradebook widget does not exist
 456       * - I confirm "Group" in "group" search within the gradebook widget does not exist
 457       * - I confirm "Grade item" in "grade" search within the gradebook widget does not exist
 458       *
 459       * @Given /^I confirm "(?P<needle>(?:[^"]|\\")*)" in "(?P<haystack>(?:[^"]|\\")*)" search within the gradebook widget does not exist$/
 460       * @param string $needle The value to search for.
 461       * @param string $haystack The type of the search widget.
 462       */
 463      public function i_confirm_in_search_within_the_gradebook_widget_does_not_exist($needle, $haystack) {
 464          $triggercssselector = ".search-widget[data-searchtype='{$haystack}']";
 465  
 466          // Make sure that the dropdown menu is visible.
 467          $node = $this->find("css_element", "{$triggercssselector} .dropdown-menu");
 468          if (!$node->isVisible()) {
 469              $this->execute("behat_general::i_click_on", [$triggercssselector, "css_element"]);
 470          }
 471  
 472          $this->execute("behat_general::wait_until_the_page_is_ready");
 473          $this->execute("behat_general::assert_element_not_contains_text",
 474              [$needle, "{$triggercssselector} .dropdown-menu", "css_element"]);
 475      }
 476  
 477      /**
 478       * Clicks on an option from the specified search widget in the current gradebook page.
 479       *
 480       * Examples:
 481       * - I click on "Student" in the "user" search widget
 482       * - I click on "Group" in the "group" search widget
 483       * - I click on "Grade item" in the "grade" search widget
 484       *
 485       * @Given /^I click on "(?P<needle>(?:[^"]|\\")*)" in the "(?P<haystack>(?:[^"]|\\")*)" search widget$/
 486       * @param string $needle The value to search for.
 487       * @param string $haystack The type of the search widget.
 488       */
 489      public function i_click_on_in_search_widget(string $needle, string $haystack) {
 490          $this->execute("behat_general::wait_until_the_page_is_ready");
 491  
 492          $triggercssselector = ".search-widget[data-searchtype='{$haystack}']";
 493  
 494          $this->execute("behat_general::i_click_on", [$triggercssselector, "css_element"]);
 495          $this->execute("behat_general::wait_until_the_page_is_ready");
 496          $this->execute('behat_general::i_click_on_in_the',
 497              [$needle, "link", "{$triggercssselector} .dropdown-menu", "css_element"]);
 498      }
 499  }