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