[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/course/tests/behat/ -> behat_course.php (source)

   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 course-related steps definitions.
  19   *
  20   * @package    core_course
  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  
  30  use Behat\Gherkin\Node\TableNode as TableNode,
  31      Behat\Mink\Exception\ExpectationException as ExpectationException,
  32      Behat\Mink\Exception\DriverException as DriverException,
  33      Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
  34  
  35  /**
  36   * Course-related steps definitions.
  37   *
  38   * @package    core_course
  39   * @category   test
  40   * @copyright  2012 David Monllaó
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class behat_course extends behat_base {
  44  
  45      /**
  46       * Turns editing mode on.
  47       * @Given /^I turn editing mode on$/
  48       */
  49      public function i_turn_editing_mode_on() {
  50  
  51          $this->execute("behat_forms::press_button", get_string('turneditingon'));
  52      }
  53  
  54      /**
  55       * Turns editing mode off.
  56       * @Given /^I turn editing mode off$/
  57       */
  58      public function i_turn_editing_mode_off() {
  59  
  60          $this->execute("behat_forms::press_button", get_string('turneditingoff'));
  61      }
  62  
  63      /**
  64       * Creates a new course with the provided table data matching course settings names with the desired values.
  65       *
  66       * @Given /^I create a course with:$/
  67       * @param TableNode $table The course data
  68       */
  69      public function i_create_a_course_with(TableNode $table) {
  70  
  71          // Go to course management page.
  72          $this->i_go_to_the_courses_management_page();
  73          // Ensure you are on course management page.
  74          $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categories'));
  75  
  76          // Select Miscellaneous category.
  77          $this->i_click_on_category_in_the_management_interface(get_string('miscellaneous'));
  78          $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcoures'));
  79  
  80          // Click create new course.
  81          $this->execute('behat_general::i_click_on_in_the',
  82              array(get_string('createnewcourse'), "link", "#course-listing", "css_element")
  83          );
  84  
  85          // If the course format is one of the fields we change how we
  86          // fill the form as we need to wait for the form to be set.
  87          $rowshash = $table->getRowsHash();
  88          $formatfieldrefs = array(get_string('format'), 'format', 'id_format');
  89          foreach ($formatfieldrefs as $fieldref) {
  90              if (!empty($rowshash[$fieldref])) {
  91                  $formatfield = $fieldref;
  92              }
  93          }
  94  
  95          // Setting the format separately.
  96          if (!empty($formatfield)) {
  97  
  98              // Removing the format field from the TableNode.
  99              $rows = $table->getRows();
 100              $formatvalue = $rowshash[$formatfield];
 101              foreach ($rows as $key => $row) {
 102                  if ($row[0] == $formatfield) {
 103                      unset($rows[$key]);
 104                  }
 105              }
 106              $table = new TableNode($rows);
 107  
 108              // Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
 109              // format field when the editor is being rendered and the click misses the field coordinates.
 110              $this->execute("behat_forms::i_expand_all_fieldsets");
 111  
 112              $this->execute("behat_forms::i_set_the_field_to", array($formatfield, $formatvalue));
 113          }
 114  
 115          // Set form fields.
 116          $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $table);
 117  
 118          // Save course settings.
 119          $this->execute("behat_forms::press_button", get_string('savechangesanddisplay'));
 120  
 121      }
 122  
 123      /**
 124       * Goes to the system courses/categories management page.
 125       *
 126       * @Given /^I go to the courses management page$/
 127       */
 128      public function i_go_to_the_courses_management_page() {
 129  
 130          $parentnodes = get_string('administrationsite') . ' > ' . get_string('courses', 'admin');
 131  
 132          // Go to home page.
 133          $this->execute("behat_general::i_am_on_homepage");
 134  
 135          // Navigate to course management page via navigation block.
 136          $this->execute("behat_navigation::i_navigate_to_node_in",
 137              array(get_string('coursemgmt', 'admin'), $parentnodes)
 138          );
 139  
 140      }
 141  
 142      /**
 143       * Adds the selected activity/resource filling the form data with the specified field/value pairs. Sections 0 and 1 are also allowed on frontpage.
 144       *
 145       * @When /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)" and I fill the form with:$/
 146       * @param string $activity The activity name
 147       * @param int $section The section number
 148       * @param TableNode $data The activity field/value data
 149       */
 150      public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) {
 151  
 152          // Add activity to section.
 153          $this->execute("behat_course::i_add_to_section",
 154              array($this->escape($activity), $this->escape($section))
 155          );
 156  
 157          // Wait to be redirected.
 158          $this->execute('behat_general::wait_until_the_page_is_ready');
 159  
 160          // Set form fields.
 161          $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
 162  
 163          // Save course settings.
 164          $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
 165      }
 166  
 167      /**
 168       * Opens the activity chooser and opens the activity/resource form page. Sections 0 and 1 are also allowed on frontpage.
 169       *
 170       * @Given /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)"$/
 171       * @throws ElementNotFoundException Thrown by behat_base::find
 172       * @param string $activity
 173       * @param int $section
 174       */
 175      public function i_add_to_section($activity, $section) {
 176  
 177          if ($this->getSession()->getPage()->find('css', 'body#page-site-index') && (int)$section <= 1) {
 178              // We are on the frontpage.
 179              if ($section) {
 180                  // Section 1 represents the contents on the frontpage.
 181                  $sectionxpath = "//body[@id='page-site-index']/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
 182              } else {
 183                  // Section 0 represents "Site main menu" block.
 184                  $sectionxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
 185              }
 186          } else {
 187              // We are inside the course.
 188              $sectionxpath = "//li[@id='section-" . $section . "']";
 189          }
 190  
 191          $activityliteral = behat_context_helper::escape(ucfirst($activity));
 192  
 193          if ($this->running_javascript()) {
 194  
 195              // Clicks add activity or resource section link.
 196              $sectionxpath = $sectionxpath . "/descendant::div[@class='section-modchooser']/span/a";
 197              $sectionnode = $this->find('xpath', $sectionxpath);
 198              $sectionnode->click();
 199  
 200              // Clicks the selected activity if it exists.
 201              $activityxpath = "//div[@id='chooseform']/descendant::label" .
 202                  "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
 203                  "[normalize-space(.)=$activityliteral]" .
 204                  "/parent::label/child::input";
 205              $activitynode = $this->find('xpath', $activityxpath);
 206              $activitynode->doubleClick();
 207  
 208          } else {
 209              // Without Javascript.
 210  
 211              // Selecting the option from the select box which contains the option.
 212              $selectxpath = $sectionxpath . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
 213                  "/descendant::select[option[normalize-space(.)=$activityliteral]]";
 214              $selectnode = $this->find('xpath', $selectxpath);
 215              $selectnode->selectOption($activity);
 216  
 217              // Go button.
 218              $gobuttonxpath = $selectxpath . "/ancestor::form/descendant::input[@type='submit']";
 219              $gobutton = $this->find('xpath', $gobuttonxpath);
 220              $gobutton->click();
 221          }
 222  
 223      }
 224  
 225  
 226      /**
 227       * Opens a section edit menu if it is not already opened.
 228       *
 229       * @Given /^I open section "(?P<section_number>\d+)" edit menu$/
 230       * @throws DriverException The step is not available when Javascript is disabled
 231       * @param string $sectionnumber
 232       */
 233      public function i_open_section_edit_menu($sectionnumber) {
 234          if (!$this->running_javascript()) {
 235              throw new DriverException('Section edit menu not available when Javascript is disabled');
 236          }
 237  
 238          // Wait for section to be available, before clicking on the menu.
 239          $this->i_wait_until_section_is_available($sectionnumber);
 240  
 241          // If it is already opened we do nothing.
 242          $xpath = $this->section_exists($sectionnumber);
 243          $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@class, 'textmenu')]";
 244  
 245          $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
 246          $menu = $this->find('xpath', $xpath, $exception);
 247          $menu->click();
 248          $this->i_wait_until_section_is_available($sectionnumber);
 249      }
 250  
 251      /**
 252       * Deletes course section.
 253       *
 254       * @Given /^I delete section "(?P<section_number>\d+)"$/
 255       * @param int $sectionnumber The section number
 256       */
 257      public function i_delete_section($sectionnumber) {
 258          // Ensures the section exists.
 259          $xpath = $this->section_exists($sectionnumber);
 260  
 261          // We need to know the course format as the text strings depends on them.
 262          $courseformat = $this->get_course_format();
 263          if (get_string_manager()->string_exists('deletesection', $courseformat)) {
 264              $strdelete = get_string('deletesection', $courseformat);
 265          } else {
 266              $strdelete = get_string('deletesection');
 267          }
 268  
 269          // If javascript is on, link is inside a menu.
 270          if ($this->running_javascript()) {
 271              $this->i_open_section_edit_menu($sectionnumber);
 272          }
 273  
 274          // Click on delete link.
 275          $this->execute('behat_general::i_click_on_in_the',
 276              array($strdelete, "link", $this->escape($xpath), "xpath_element")
 277          );
 278  
 279      }
 280  
 281      /**
 282       * Turns course section highlighting on.
 283       *
 284       * @Given /^I turn section "(?P<section_number>\d+)" highlighting on$/
 285       * @param int $sectionnumber The section number
 286       */
 287      public function i_turn_section_highlighting_on($sectionnumber) {
 288  
 289          // Ensures the section exists.
 290          $xpath = $this->section_exists($sectionnumber);
 291  
 292          // If javascript is on, link is inside a menu.
 293          if ($this->running_javascript()) {
 294              $this->i_open_section_edit_menu($sectionnumber);
 295          }
 296  
 297          // Click on highlight topic link.
 298          $this->execute('behat_general::i_click_on_in_the',
 299              array(get_string('markthistopic'), "link", $this->escape($xpath), "xpath_element")
 300          );
 301      }
 302  
 303      /**
 304       * Turns course section highlighting off.
 305       *
 306       * @Given /^I turn section "(?P<section_number>\d+)" highlighting off$/
 307       * @param int $sectionnumber The section number
 308       */
 309      public function i_turn_section_highlighting_off($sectionnumber) {
 310  
 311          // Ensures the section exists.
 312          $xpath = $this->section_exists($sectionnumber);
 313  
 314          // If javascript is on, link is inside a menu.
 315          if ($this->running_javascript()) {
 316              $this->i_open_section_edit_menu($sectionnumber);
 317          }
 318  
 319          // Click on un-highlight topic link.
 320          $this->execute('behat_general::i_click_on_in_the',
 321              array(get_string('markedthistopic'), "link", $this->escape($xpath), "xpath_element")
 322          );
 323      }
 324  
 325      /**
 326       * Shows the specified hidden section. You need to be in the course page and on editing mode.
 327       *
 328       * @Given /^I show section "(?P<section_number>\d+)"$/
 329       * @param int $sectionnumber
 330       */
 331      public function i_show_section($sectionnumber) {
 332          $showlink = $this->show_section_icon_exists($sectionnumber);
 333  
 334          // Ensure section edit menu is open before interacting with it.
 335          if ($this->running_javascript()) {
 336              $this->i_open_section_edit_menu($sectionnumber);
 337          }
 338          $showlink->click();
 339  
 340          if ($this->running_javascript()) {
 341              $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
 342              $this->i_wait_until_section_is_available($sectionnumber);
 343          }
 344      }
 345  
 346      /**
 347       * Hides the specified visible section. You need to be in the course page and on editing mode.
 348       *
 349       * @Given /^I hide section "(?P<section_number>\d+)"$/
 350       * @param int $sectionnumber
 351       */
 352      public function i_hide_section($sectionnumber) {
 353          $hidelink = $this->hide_section_icon_exists($sectionnumber);
 354  
 355          // Ensure section edit menu is open before interacting with it.
 356          if ($this->running_javascript()) {
 357              $this->i_open_section_edit_menu($sectionnumber);
 358          }
 359          $hidelink->click();
 360  
 361          if ($this->running_javascript()) {
 362              $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
 363              $this->i_wait_until_section_is_available($sectionnumber);
 364          }
 365      }
 366  
 367      /**
 368       * Go to editing section page for specified section number. You need to be in the course page and on editing mode.
 369       *
 370       * @Given /^I edit the section "(?P<section_number>\d+)"$/
 371       * @param int $sectionnumber
 372       */
 373      public function i_edit_the_section($sectionnumber) {
 374          // If javascript is on, link is inside a menu.
 375          if ($this->running_javascript()) {
 376              $this->i_open_section_edit_menu($sectionnumber);
 377          }
 378  
 379          // We need to know the course format as the text strings depends on them.
 380          $courseformat = $this->get_course_format();
 381          if (get_string_manager()->string_exists('editsection', $courseformat)) {
 382              $stredit = get_string('editsection', $courseformat);
 383          } else {
 384              $stredit = get_string('editsection');
 385          }
 386  
 387          // Click on un-highlight topic link.
 388          $this->execute('behat_general::i_click_on_in_the',
 389              array($stredit, "link", "#section-" . $sectionnumber, "css_element")
 390          );
 391  
 392      }
 393  
 394      /**
 395       * Edit specified section and fill the form data with the specified field/value pairs.
 396       *
 397       * @When /^I edit the section "(?P<section_number>\d+)" and I fill the form with:$/
 398       * @param int $sectionnumber The section number
 399       * @param TableNode $data The activity field/value data
 400       */
 401      public function i_edit_the_section_and_i_fill_the_form_with($sectionnumber, TableNode $data) {
 402  
 403          // Edit given section.
 404          $this->execute("behat_course::i_edit_the_section", $sectionnumber);
 405  
 406          // Set form fields.
 407          $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
 408  
 409          // Save section settings.
 410          $this->execute("behat_forms::press_button", get_string('savechanges'));
 411      }
 412  
 413      /**
 414       * Checks if the specified course section hightlighting is turned on. You need to be in the course page on editing mode.
 415       *
 416       * @Then /^section "(?P<section_number>\d+)" should be highlighted$/
 417       * @throws ExpectationException
 418       * @param int $sectionnumber The section number
 419       */
 420      public function section_should_be_highlighted($sectionnumber) {
 421  
 422          // Ensures the section exists.
 423          $xpath = $this->section_exists($sectionnumber);
 424  
 425          // The important checking, we can not check the img.
 426          $xpath = $xpath . "/descendant::img[contains(@src, 'marked')]";
 427          $exception = new ExpectationException('The "' . $sectionnumber . '" section is not highlighted', $this->getSession());
 428          $this->find('xpath', $xpath, $exception);
 429      }
 430  
 431      /**
 432       * Checks if the specified course section highlighting is turned off. You need to be in the course page on editing mode.
 433       *
 434       * @Then /^section "(?P<section_number>\d+)" should not be highlighted$/
 435       * @throws ExpectationException
 436       * @param int $sectionnumber The section number
 437       */
 438      public function section_should_not_be_highlighted($sectionnumber) {
 439  
 440          // We only catch ExpectationException, ElementNotFoundException should be thrown if the specified section does not exist.
 441          try {
 442              $this->section_should_be_highlighted($sectionnumber);
 443          } catch (ExpectationException $e) {
 444              // ExpectedException means that it is not highlighted.
 445              return;
 446          }
 447  
 448          throw new ExpectationException('The "' . $sectionnumber . '" section is highlighted', $this->getSession());
 449      }
 450  
 451      /**
 452       * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
 453       *
 454       * @Then /^section "(?P<section_number>\d+)" should be hidden$/
 455       * @throws ExpectationException
 456       * @throws ElementNotFoundException Thrown by behat_base::find
 457       * @param int $sectionnumber
 458       */
 459      public function section_should_be_hidden($sectionnumber) {
 460  
 461          $sectionxpath = $this->section_exists($sectionnumber);
 462  
 463          // Preventive in case there is any action in progress.
 464          // Adding it here because we are interacting (click) with
 465          // the elements, not necessary when we just find().
 466          $this->i_wait_until_section_is_available($sectionnumber);
 467  
 468          // Section should be hidden.
 469          $exception = new ExpectationException('The section is not hidden', $this->getSession());
 470          $this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception);
 471      }
 472  
 473      /**
 474       * Checks that all actiities in the specified section are hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
 475       *
 476       * @Then /^all activities in section "(?P<section_number>\d+)" should be hidden$/
 477       * @throws ExpectationException
 478       * @throws ElementNotFoundException Thrown by behat_base::find
 479       * @param int $sectionnumber
 480       */
 481      public function section_activities_should_be_hidden($sectionnumber) {
 482          $sectionxpath = $this->section_exists($sectionnumber);
 483  
 484          // Preventive in case there is any action in progress.
 485          // Adding it here because we are interacting (click) with
 486          // the elements, not necessary when we just find().
 487          $this->i_wait_until_section_is_available($sectionnumber);
 488  
 489          // The checking are different depending on user permissions.
 490          if ($this->is_course_editor()) {
 491  
 492              // The section must be hidden.
 493              $this->show_section_icon_exists($sectionnumber);
 494  
 495              // If there are activities they should be hidden and the visibility icon should not be available.
 496              if ($activities = $this->get_section_activities($sectionxpath)) {
 497  
 498                  $dimmedexception = new ExpectationException('There are activities that are not dimmed', $this->getSession());
 499                  foreach ($activities as $activity) {
 500                      // Dimmed.
 501                      $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
 502                          "//a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
 503                  }
 504              }
 505          } else {
 506              // There shouldn't be activities.
 507              if ($this->get_section_activities($sectionxpath)) {
 508                  throw new ExpectationException('There are activities in the section and they should be hidden', $this->getSession());
 509              }
 510          }
 511  
 512      }
 513  
 514      /**
 515       * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
 516       *
 517       * @Then /^section "(?P<section_number>\d+)" should be visible$/
 518       * @throws ExpectationException
 519       * @param int $sectionnumber
 520       */
 521      public function section_should_be_visible($sectionnumber) {
 522  
 523          $sectionxpath = $this->section_exists($sectionnumber);
 524  
 525          // Section should not be hidden.
 526          $xpath = $sectionxpath . "[not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]";
 527          if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
 528              throw new ExpectationException('The section is hidden', $this->getSession());
 529          }
 530  
 531          // Edit menu should be visible.
 532          if ($this->is_course_editor()) {
 533              $xpath = $sectionxpath .
 534                       "/descendant::div[contains(@class, 'section-actions')]" .
 535                       "/descendant::a[contains(@class, 'textmenu')]";
 536              if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
 537                  throw new ExpectationException('The section edit menu is not available', $this->getSession());
 538              }
 539          }
 540      }
 541  
 542      /**
 543       * Moves up the specified section, this step only works with Javascript disabled. Editing mode should be on.
 544       *
 545       * @Given /^I move up section "(?P<section_number>\d+)"$/
 546       * @throws DriverException Step not available when Javascript is enabled
 547       * @param int $sectionnumber
 548       */
 549      public function i_move_up_section($sectionnumber) {
 550  
 551          if ($this->running_javascript()) {
 552              throw new DriverException('Move a section up step is not available with Javascript enabled');
 553          }
 554  
 555          // Ensures the section exists.
 556          $sectionxpath = $this->section_exists($sectionnumber);
 557  
 558          // If javascript is on, link is inside a menu.
 559          if ($this->running_javascript()) {
 560              $this->i_open_section_edit_menu($sectionnumber);
 561          }
 562  
 563          // Follows the link
 564          $moveuplink = $this->get_node_in_container('link', get_string('moveup'), 'xpath_element', $sectionxpath);
 565          $moveuplink->click();
 566      }
 567  
 568      /**
 569       * Moves down the specified section, this step only works with Javascript disabled. Editing mode should be on.
 570       *
 571       * @Given /^I move down section "(?P<section_number>\d+)"$/
 572       * @throws DriverException Step not available when Javascript is enabled
 573       * @param int $sectionnumber
 574       */
 575      public function i_move_down_section($sectionnumber) {
 576  
 577          if ($this->running_javascript()) {
 578              throw new DriverException('Move a section down step is not available with Javascript enabled');
 579          }
 580  
 581          // Ensures the section exists.
 582          $sectionxpath = $this->section_exists($sectionnumber);
 583  
 584          // If javascript is on, link is inside a menu.
 585          if ($this->running_javascript()) {
 586              $this->i_open_section_edit_menu($sectionnumber);
 587          }
 588  
 589          // Follows the link
 590          $movedownlink = $this->get_node_in_container('link', get_string('movedown'), 'xpath_element', $sectionxpath);
 591          $movedownlink->click();
 592      }
 593  
 594      /**
 595       * Checks that the specified activity is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
 596       *
 597       * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be visible$/
 598       * @param string $activityname
 599       * @throws ExpectationException
 600       */
 601      public function activity_should_be_visible($activityname) {
 602  
 603          // The activity must exists and be visible.
 604          $activitynode = $this->get_activity_node($activityname);
 605  
 606          if ($this->is_course_editor()) {
 607  
 608              // The activity should not be dimmed.
 609              try {
 610                  $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
 611                           "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
 612                  $this->find('xpath', $xpath, false, $activitynode);
 613                  throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
 614              } catch (ElementNotFoundException $e) {
 615                  // All ok.
 616              }
 617  
 618              // The 'Hide' button should be available.
 619              $nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
 620              $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
 621          }
 622      }
 623  
 624      /**
 625       * Checks that the specified activity is hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
 626       *
 627       * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be hidden$/
 628       * @param string $activityname
 629       * @throws ExpectationException
 630       */
 631      public function activity_should_be_hidden($activityname) {
 632  
 633          if ($this->is_course_editor()) {
 634  
 635              // The activity should exist.
 636              $activitynode = $this->get_activity_node($activityname);
 637  
 638              // Should be hidden.
 639              $exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
 640              $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
 641                       "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
 642              $this->find('xpath', $xpath, $exception, $activitynode);
 643  
 644              // Also 'Show' icon.
 645              $noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
 646              $this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
 647  
 648          } else {
 649  
 650              // It should not exist at all.
 651              try {
 652                  $this->find_link($activityname);
 653                  throw new ExpectationException('The "' . $activityname . '" should not appear');
 654              } catch (ElementNotFoundException $e) {
 655                  // This is good, the activity should not be there.
 656              }
 657          }
 658  
 659      }
 660  
 661      /**
 662       * Moves the specified activity to the first slot of a section. This step is experimental when using it in Javascript tests. Editing mode should be on.
 663       *
 664       * @Given /^I move "(?P<activity_name_string>(?:[^"]|\\")*)" activity to section "(?P<section_number>\d+)"$/
 665       * @param string $activityname The activity name
 666       * @param int $sectionnumber The number of section
 667       */
 668      public function i_move_activity_to_section($activityname, $sectionnumber) {
 669  
 670          // Ensure the destination is valid.
 671          $sectionxpath = $this->section_exists($sectionnumber);
 672  
 673          $activitynode = $this->get_activity_element('.editing_move img', 'css_element', $activityname);
 674  
 675          // JS enabled.
 676          if ($this->running_javascript()) {
 677  
 678              $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
 679  
 680              $this->execute("behat_general::i_drag_and_i_drop_it_in",
 681                  array($this->escape($activitynode->getXpath()), "xpath_element",
 682                      $this->escape($destinationxpath), "xpath_element")
 683              );
 684  
 685          } else {
 686              // Following links with no-JS.
 687  
 688              // Moving to the fist spot of the section (before all other section's activities).
 689              $this->execute('behat_course::i_click_on_in_the_activity',
 690                  array("a.editing_move", "css_element", $this->escape($activityname))
 691              );
 692  
 693              $this->execute('behat_general::i_click_on_in_the',
 694                  array("li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element")
 695              );
 696          }
 697      }
 698  
 699      /**
 700       * Edits the activity name through the edit activity; this step only works with Javascript enabled. Editing mode should be on.
 701       *
 702       * @Given /^I change "(?P<activity_name_string>(?:[^"]|\\")*)" activity name to "(?P<new_name_string>(?:[^"]|\\")*)"$/
 703       * @throws DriverException Step not available when Javascript is disabled
 704       * @param string $activityname
 705       * @param string $newactivityname
 706       */
 707      public function i_change_activity_name_to($activityname, $newactivityname) {
 708  
 709          if (!$this->running_javascript()) {
 710              throw new DriverException('Change activity name step is not available with Javascript disabled');
 711          }
 712  
 713          $activity = $this->escape($activityname);
 714  
 715          $this->execute('behat_course::i_click_on_in_the_activity',
 716              array(get_string('edittitle'), "link", $activity)
 717          );
 718  
 719          // Adding chr(10) to save changes.
 720          $this->execute('behat_forms::i_set_the_field_to',
 721              array('title', $this->escape($newactivityname) . chr(10))
 722          );
 723  
 724      }
 725  
 726      /**
 727       * Opens an activity actions menu if it is not already opened.
 728       *
 729       * @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
 730       * @throws DriverException The step is not available when Javascript is disabled
 731       * @param string $activityname
 732       */
 733      public function i_open_actions_menu($activityname) {
 734  
 735          if (!$this->running_javascript()) {
 736              throw new DriverException('Activities actions menu not available when Javascript is disabled');
 737          }
 738  
 739          // If it is already opened we do nothing.
 740          $activitynode = $this->get_activity_node($activityname);
 741          $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
 742          if (!empty($classes['action-menu-shown'])) {
 743              return;
 744          }
 745  
 746          $this->execute('behat_course::i_click_on_in_the_activity',
 747              array("a[role='menuitem']", "css_element", $this->escape($activityname))
 748          );
 749  
 750      }
 751  
 752      /**
 753       * Closes an activity actions menu if it is not already closed.
 754       *
 755       * @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
 756       * @throws DriverException The step is not available when Javascript is disabled
 757       * @param string $activityname
 758       */
 759      public function i_close_actions_menu($activityname) {
 760  
 761          if (!$this->running_javascript()) {
 762              throw new DriverException('Activities actions menu not available when Javascript is disabled');
 763          }
 764  
 765          // If it is already closed we do nothing.
 766          $activitynode = $this->get_activity_node($activityname);
 767          $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
 768          if (empty($classes['action-menu-shown'])) {
 769              return;
 770          }
 771  
 772          $this->execute('behat_course::i_click_on_in_the_activity',
 773              array("a[role='menuitem']", "css_element", $this->escape($activityname))
 774          );
 775      }
 776  
 777      /**
 778       * Checks that the specified activity's action menu is open.
 779       *
 780       * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/
 781       * @throws DriverException The step is not available when Javascript is disabled
 782       * @param string $activityname
 783       */
 784      public function actions_menu_should_be_open($activityname) {
 785  
 786          if (!$this->running_javascript()) {
 787              throw new DriverException('Activities actions menu not available when Javascript is disabled');
 788          }
 789  
 790          // If it is already closed we do nothing.
 791          $activitynode = $this->get_activity_node($activityname);
 792          $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
 793          if (empty($classes['action-menu-shown'])) {
 794              throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
 795          }
 796      }
 797  
 798      /**
 799       * Indents to the right the activity or resource specified by it's name. Editing mode should be on.
 800       *
 801       * @Given /^I indent right "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
 802       * @param string $activityname
 803       */
 804      public function i_indent_right_activity($activityname) {
 805  
 806          $activity = $this->escape($activityname);
 807          if ($this->running_javascript()) {
 808              $this->i_open_actions_menu($activity);
 809          }
 810  
 811          $this->execute('behat_course::i_click_on_in_the_activity',
 812              array(get_string('moveright'), "link", $this->escape($activity))
 813          );
 814  
 815      }
 816  
 817      /**
 818       * Indents to the left the activity or resource specified by it's name. Editing mode should be on.
 819       *
 820       * @Given /^I indent left "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
 821       * @param string $activityname
 822       */
 823      public function i_indent_left_activity($activityname) {
 824  
 825          $activity = $this->escape($activityname);
 826          if ($this->running_javascript()) {
 827              $this->i_open_actions_menu($activity);
 828          }
 829  
 830          $this->execute('behat_course::i_click_on_in_the_activity',
 831              array(get_string('moveleft'), "link", $this->escape($activity))
 832          );
 833  
 834      }
 835  
 836      /**
 837       * Deletes the activity or resource specified by it's name. This step is experimental when using it in Javascript tests. You should be in the course page with editing mode on.
 838       *
 839       * @Given /^I delete "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
 840       * @param string $activityname
 841       */
 842      public function i_delete_activity($activityname) {
 843          $steps = array();
 844          $activity = $this->escape($activityname);
 845          if ($this->running_javascript()) {
 846              $this->i_open_actions_menu($activity);
 847          }
 848  
 849          $this->execute('behat_course::i_click_on_in_the_activity',
 850              array(get_string('delete'), "link", $this->escape($activity))
 851          );
 852  
 853          // JS enabled.
 854          // Not using chain steps here because the exceptions catcher have problems detecting
 855          // JS modal windows and avoiding interacting them at the same time.
 856          if ($this->running_javascript()) {
 857              $this->execute('behat_general::i_click_on_in_the',
 858                  array(get_string('yes'), "button", "Confirm", "dialogue")
 859              );
 860          } else {
 861              $this->execute("behat_forms::press_button", get_string('yes'));
 862          }
 863  
 864          return $steps;
 865      }
 866  
 867      /**
 868       * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on.
 869       *
 870       * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
 871       * @param string $activityname
 872       */
 873      public function i_duplicate_activity($activityname) {
 874          $steps = array();
 875          $activity = $this->escape($activityname);
 876          if ($this->running_javascript()) {
 877              $this->i_open_actions_menu($activity);
 878          }
 879          $this->execute('behat_course::i_click_on_in_the_activity',
 880              array(get_string('duplicate'), "link", $activity)
 881          );
 882  
 883      }
 884  
 885      /**
 886       * Duplicates the activity or resource and modifies the new activity with the provided data. You should be in the course page with editing mode on.
 887       *
 888       * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/
 889       * @param string $activityname
 890       * @param TableNode $data
 891       */
 892      public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
 893  
 894          $activity = $this->escape($activityname);
 895          $activityliteral = behat_context_helper::escape($activityname);
 896  
 897          $this->execute("behat_course::i_duplicate_activity", $activity);
 898  
 899          // Determine the future new activity xpath from the former one.
 900          $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
 901              "[contains(., $activityliteral)]/following-sibling::li";
 902          $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@role='menuitem']";
 903  
 904          if ($this->running_javascript()) {
 905              // We wait until the AJAX request finishes and the section is visible again.
 906              $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
 907                  "[contains(., $activityliteral)]" .
 908                  "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
 909                  "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
 910  
 911              $this->execute("behat_general::wait_until_exists",
 912                  array($this->escape($hiddenlightboxxpath), "xpath_element")
 913              );
 914  
 915              // Close the original activity actions menu.
 916              $this->i_close_actions_menu($activity);
 917  
 918              // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
 919              // this point, it don't even exists in the DOM (the steps are executed when we return them).
 920              $this->execute('behat_general::i_click_on',
 921                  array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
 922              );
 923          }
 924  
 925          // We force the xpath as otherwise mink tries to interact with the former one.
 926          $this->execute('behat_general::i_click_on_in_the',
 927              array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
 928          );
 929  
 930          $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
 931          $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
 932  
 933      }
 934  
 935      /**
 936       * Waits until the section is available to interact with it. Useful when the section is performing an action and the section is overlayed with a loading layout.
 937       *
 938       * Using the protected method as this method will be usually
 939       * called by other methods which are not returning a set of
 940       * steps and performs the actions directly, so it would not
 941       * be executed if it returns another step.
 942       *
 943       * Hopefully we would not require test writers to use this step
 944       * and we will manage it from other step definitions.
 945       *
 946       * @Given /^I wait until section "(?P<section_number>\d+)" is available$/
 947       * @param int $sectionnumber
 948       * @return void
 949       */
 950      public function i_wait_until_section_is_available($sectionnumber) {
 951  
 952          // Looks for a hidden lightbox or a non-existent lightbox in that section.
 953          $sectionxpath = $this->section_exists($sectionnumber);
 954          $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
 955              " | " .
 956              $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
 957  
 958          $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element');
 959      }
 960  
 961      /**
 962       * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
 963       *
 964       * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
 965       * @param string $element
 966       * @param string $selectortype
 967       * @param string $activityname
 968       */
 969      public function i_click_on_in_the_activity($element, $selectortype, $activityname) {
 970          $element = $this->get_activity_element($element, $selectortype, $activityname);
 971          $element->click();
 972      }
 973  
 974      /**
 975       * Clicks on the specified element inside the activity container.
 976       *
 977       * @throws ElementNotFoundException
 978       * @param string $element
 979       * @param string $selectortype
 980       * @param string $activityname
 981       * @return NodeElement
 982       */
 983      protected function get_activity_element($element, $selectortype, $activityname) {
 984          $activitynode = $this->get_activity_node($activityname);
 985  
 986          // Transforming to Behat selector/locator.
 987          list($selector, $locator) = $this->transform_selector($selectortype, $element);
 988          $exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
 989  
 990          return $this->find($selector, $locator, $exception, $activitynode);
 991      }
 992  
 993      /**
 994       * Checks if the course section exists.
 995       *
 996       * @throws ElementNotFoundException Thrown by behat_base::find
 997       * @param int $sectionnumber
 998       * @return string The xpath of the section.
 999       */
1000      protected function section_exists($sectionnumber) {
1001  
1002          // Just to give more info in case it does not exist.
1003          $xpath = "//li[@id='section-" . $sectionnumber . "']";
1004          $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber ");
1005          $this->find('xpath', $xpath, $exception);
1006  
1007          return $xpath;
1008      }
1009  
1010      /**
1011       * Returns the show section icon or throws an exception.
1012       *
1013       * @throws ElementNotFoundException Thrown by behat_base::find
1014       * @param int $sectionnumber
1015       * @return NodeElement
1016       */
1017      protected function show_section_icon_exists($sectionnumber) {
1018  
1019          // Gets the section xpath and ensure it exists.
1020          $xpath = $this->section_exists($sectionnumber);
1021  
1022          // We need to know the course format as the text strings depends on them.
1023          $courseformat = $this->get_course_format();
1024  
1025          // Checking the show button alt text and show icon.
1026          $showtext = behat_context_helper::escape(get_string('showfromothers', $courseformat));
1027          $linkxpath = $xpath . "/descendant::a[@title=$showtext]";
1028          $imgxpath = $linkxpath . "/descendant::img[contains(@src, 'show')]";
1029  
1030          $exception = new ElementNotFoundException($this->getSession(), 'Show section icon ');
1031          $this->find('xpath', $imgxpath, $exception);
1032  
1033          // Returing the link so both Non-JS and JS browsers can interact with it.
1034          return $this->find('xpath', $linkxpath, $exception);
1035      }
1036  
1037      /**
1038       * Returns the hide section icon link if it exists or throws exception.
1039       *
1040       * @throws ElementNotFoundException Thrown by behat_base::find
1041       * @param int $sectionnumber
1042       * @return NodeElement
1043       */
1044      protected function hide_section_icon_exists($sectionnumber) {
1045  
1046          // Gets the section xpath and ensure it exists.
1047          $xpath = $this->section_exists($sectionnumber);
1048  
1049          // We need to know the course format as the text strings depends on them.
1050          $courseformat = $this->get_course_format();
1051  
1052          // Checking the hide button alt text and hide icon.
1053          $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat));
1054          $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
1055          $imgxpath = $linkxpath . "/descendant::img[contains(@src, 'hide')]";
1056  
1057          $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon ');
1058          $this->find('xpath', $imgxpath, $exception);
1059  
1060          // Returing the link so both Non-JS and JS browsers can interact with it.
1061          return $this->find('xpath', $linkxpath, $exception);
1062      }
1063  
1064      /**
1065       * Gets the current course format.
1066       *
1067       * @throws ExpectationException If we are not in the course view page.
1068       * @return string The course format in a frankenstyled name.
1069       */
1070      protected function get_course_format() {
1071  
1072          $exception = new ExpectationException('You are not in a course page', $this->getSession());
1073  
1074          // The moodle body's id attribute contains the course format.
1075          $node = $this->getSession()->getPage()->find('css', 'body');
1076          if (!$node) {
1077              throw $exception;
1078          }
1079  
1080          if (!$bodyid = $node->getAttribute('id')) {
1081              throw $exception;
1082          }
1083  
1084          if (strstr($bodyid, 'page-course-view-') === false) {
1085              throw $exception;
1086          }
1087  
1088          return 'format_' . str_replace('page-course-view-', '', $bodyid);
1089      }
1090  
1091      /**
1092       * Gets the section's activites DOM nodes.
1093       *
1094       * @param string $sectionxpath
1095       * @return array NodeElement instances
1096       */
1097      protected function get_section_activities($sectionxpath) {
1098  
1099          $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]";
1100  
1101          // We spin here, as activities usually require a lot of time to load.
1102          try {
1103              $activities = $this->find_all('xpath', $xpath);
1104          } catch (ElementNotFoundException $e) {
1105              return false;
1106          }
1107  
1108          return $activities;
1109      }
1110  
1111      /**
1112       * Returns the DOM node of the activity from <li>.
1113       *
1114       * @throws ElementNotFoundException Thrown by behat_base::find
1115       * @param string $activityname The activity name
1116       * @return NodeElement
1117       */
1118      protected function get_activity_node($activityname) {
1119  
1120          $activityname = behat_context_helper::escape($activityname);
1121          $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]";
1122  
1123          return $this->find('xpath', $xpath);
1124      }
1125  
1126      /**
1127       * Gets the activity instance name from the activity node.
1128       *
1129       * @throws ElementNotFoundException
1130       * @param NodeElement $activitynode
1131       * @return string
1132       */
1133      protected function get_activity_name($activitynode) {
1134          $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode);
1135          return $instancenamenode->getText();
1136      }
1137  
1138      /**
1139       * Returns whether the user can edit the course contents or not.
1140       *
1141       * @return bool
1142       */
1143      protected function is_course_editor() {
1144  
1145          // We don't need to behat_base::spin() here as all is already loaded.
1146          if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
1147                  !$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
1148              return false;
1149          }
1150  
1151          return true;
1152      }
1153  
1154      /**
1155       * Returns the id of the category with the given idnumber.
1156       *
1157       * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1158       *
1159       * @param string $idnumber
1160       * @return string
1161       * @throws ExpectationException
1162       */
1163      protected function get_category_id($idnumber) {
1164          global $DB;
1165          try {
1166              return $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1167          } catch (dml_missing_record_exception $ex) {
1168              throw new ExpectationException(sprintf("There is no category in the database with the idnumber '%s'", $idnumber));
1169          }
1170      }
1171  
1172      /**
1173       * Returns the id of the course with the given idnumber.
1174       *
1175       * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1176       *
1177       * @param string $idnumber
1178       * @return string
1179       * @throws ExpectationException
1180       */
1181      protected function get_course_id($idnumber) {
1182          global $DB;
1183          try {
1184              return $DB->get_field('course', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1185          } catch (dml_missing_record_exception $ex) {
1186              throw new ExpectationException(sprintf("There is no course in the database with the idnumber '%s'", $idnumber));
1187          }
1188      }
1189  
1190      /**
1191       * Returns the category node from within the listing on the management page.
1192       *
1193       * @param string $idnumber
1194       * @return \Behat\Mink\Element\NodeElement
1195       */
1196      protected function get_management_category_listing_node_by_idnumber($idnumber) {
1197          $id = $this->get_category_id($idnumber);
1198          $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id);
1199          return $this->find('css', $selector);
1200      }
1201  
1202      /**
1203       * Returns a category node from within the management interface.
1204       *
1205       * @param string $name The name of the category.
1206       * @param bool $link If set to true we'll resolve to the link rather than just the node.
1207       * @return \Behat\Mink\Element\NodeElement
1208       */
1209      protected function get_management_category_listing_node_by_name($name, $link = false) {
1210          $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']";
1211          if ($link === false) {
1212              $selector .= "/ancestor::li[@data-id][1]";
1213          }
1214          return $this->find('xpath', $selector);
1215      }
1216  
1217      /**
1218       * Returns a course node from within the management interface.
1219       *
1220       * @param string $name The name of the course.
1221       * @param bool $link If set to true we'll resolve to the link rather than just the node.
1222       * @return \Behat\Mink\Element\NodeElement
1223       */
1224      protected function get_management_course_listing_node_by_name($name, $link = false) {
1225          $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']";
1226          if ($link === false) {
1227              $selector .= "/ancestor::li[@data-id]";
1228          }
1229          return $this->find('xpath', $selector);
1230      }
1231  
1232      /**
1233       * Returns the course node from within the listing on the management page.
1234       *
1235       * @param string $idnumber
1236       * @return \Behat\Mink\Element\NodeElement
1237       */
1238      protected function get_management_course_listing_node_by_idnumber($idnumber) {
1239          $id = $this->get_course_id($idnumber);
1240          $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id);
1241          return $this->find('css', $selector);
1242      }
1243  
1244      /**
1245       * Clicks on a category in the management interface.
1246       *
1247       * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1248       * @param string $name
1249       */
1250      public function i_click_on_category_in_the_management_interface($name) {
1251          $node = $this->get_management_category_listing_node_by_name($name, true);
1252          $node->click();
1253      }
1254  
1255      /**
1256       * Clicks on a course in the management interface.
1257       *
1258       * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1259       * @param string $name
1260       */
1261      public function i_click_on_course_in_the_management_interface($name) {
1262          $node = $this->get_management_course_listing_node_by_name($name, true);
1263          $node->click();
1264      }
1265  
1266      /**
1267       * Clicks on a category checkbox in the management interface, if not checked.
1268       *
1269       * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1270       * @param string $name
1271       */
1272      public function i_select_category_in_the_management_interface($name) {
1273          $node = $this->get_management_category_listing_node_by_name($name);
1274          $node = $node->findField('bcat[]');
1275          if (!$node->isChecked()) {
1276              $node->click();
1277          }
1278      }
1279  
1280      /**
1281       * Clicks on a category checkbox in the management interface, if checked.
1282       *
1283       * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1284       * @param string $name
1285       */
1286      public function i_unselect_category_in_the_management_interface($name) {
1287          $node = $this->get_management_category_listing_node_by_name($name);
1288          $node = $node->findField('bcat[]');
1289          if ($node->isChecked()) {
1290              $node->click();
1291          }
1292      }
1293  
1294      /**
1295       * Clicks course checkbox in the management interface, if not checked.
1296       *
1297       * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1298       * @param string $name
1299       */
1300      public function i_select_course_in_the_management_interface($name) {
1301          $node = $this->get_management_course_listing_node_by_name($name);
1302          $node = $node->findField('bc[]');
1303          if (!$node->isChecked()) {
1304              $node->click();
1305          }
1306      }
1307  
1308      /**
1309       * Clicks course checkbox in the management interface, if checked.
1310       *
1311       * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1312       * @param string $name
1313       */
1314      public function i_unselect_course_in_the_management_interface($name) {
1315          $node = $this->get_management_course_listing_node_by_name($name);
1316          $node = $node->findField('bc[]');
1317          if ($node->isChecked()) {
1318              $node->click();
1319          }
1320      }
1321  
1322      /**
1323       * Move selected categories to top level in the management interface.
1324       *
1325       * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/
1326       * @param string $name
1327       */
1328      public function i_move_category_to_top_level_in_the_management_interface($name) {
1329          $this->i_select_category_in_the_management_interface($name);
1330  
1331          $this->execute('behat_forms::i_set_the_field_to',
1332              array('menumovecategoriesto', coursecat::get(0)->get_formatted_name())
1333          );
1334  
1335          // Save event.
1336          $this->execute("behat_forms::press_button", "bulkmovecategories");
1337      }
1338  
1339      /**
1340       * Checks that a category is a subcategory of specific category.
1341       *
1342       * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
1343       * @throws ExpectationException
1344       * @param string $subcatidnumber
1345       * @param string $catidnumber
1346       */
1347      public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1348          $categorynodeid = $this->get_category_id($catidnumber);
1349          $subcategoryid = $this->get_category_id($subcatidnumber);
1350          $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession());
1351          $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid);
1352          $this->find('css', $selector, $exception);
1353      }
1354  
1355      /**
1356       * Checks that a category is not a subcategory of specific category.
1357       *
1358       * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
1359       * @throws ExpectationException
1360       * @param string $subcatidnumber
1361       * @param string $catidnumber
1362       */
1363      public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1364          try {
1365              $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber);
1366          } catch (ExpectationException $e) {
1367              // ExpectedException means that it is not highlighted.
1368              return;
1369          }
1370          throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession());
1371      }
1372  
1373      /**
1374       * Click to expand a category revealing its sub categories within the management UI.
1375       *
1376       * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/
1377       * @param string $idnumber
1378       */
1379      public function i_click_to_expand_category_in_the_management_interface($idnumber) {
1380          $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber);
1381          $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession());
1382          $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode);
1383          $togglenode->click();
1384      }
1385  
1386      /**
1387       * Checks that a category within the management interface is visible.
1388       *
1389       * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1390       * @param string $idnumber
1391       */
1392      public function category_in_management_listing_should_be_visible($idnumber) {
1393          $id = $this->get_category_id($idnumber);
1394          $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession());
1395          $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id);
1396          $this->find('css', $selector, $exception);
1397      }
1398  
1399      /**
1400       * Checks that a category within the management interface is dimmed.
1401       *
1402       * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1403       * @param string $idnumber
1404       */
1405      public function category_in_management_listing_should_be_dimmed($idnumber) {
1406          $id = $this->get_category_id($idnumber);
1407          $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id);
1408          $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession());
1409          $this->find('css', $selector, $exception);
1410      }
1411  
1412      /**
1413       * Checks that a course within the management interface is visible.
1414       *
1415       * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1416       * @param string $idnumber
1417       */
1418      public function course_in_management_listing_should_be_visible($idnumber) {
1419          $id = $this->get_course_id($idnumber);
1420          $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession());
1421          $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id);
1422          $this->find('css', $selector, $exception);
1423      }
1424  
1425      /**
1426       * Checks that a course within the management interface is dimmed.
1427       *
1428       * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1429       * @param string $idnumber
1430       */
1431      public function course_in_management_listing_should_be_dimmed($idnumber) {
1432          $id = $this->get_course_id($idnumber);
1433          $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession());
1434          $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id);
1435          $this->find('css', $selector, $exception);
1436      }
1437  
1438      /**
1439       * Toggles the visibility of a course in the management UI.
1440       *
1441       * If it was visible it will be hidden. If it is hidden it will be made visible.
1442       *
1443       * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
1444       * @param string $idnumber
1445       */
1446      public function i_toggle_visibility_of_course_in_management_listing($idnumber) {
1447          $id = $this->get_course_id($idnumber);
1448          $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id);
1449          $node = $this->find('css', $selector);
1450          $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1451          if ($node->getAttribute('data-visible') === '1') {
1452              $toggle = $this->find('css', '.action-hide', $exception, $node);
1453          } else {
1454              $toggle = $this->find('css', '.action-show', $exception, $node);
1455          }
1456          $toggle->click();
1457      }
1458  
1459      /**
1460       * Toggles the visibility of a category in the management UI.
1461       *
1462       * If it was visible it will be hidden. If it is hidden it will be made visible.
1463       *
1464       * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
1465       */
1466      public function i_toggle_visibility_of_category_in_management_listing($idnumber) {
1467          $id = $this->get_category_id($idnumber);
1468          $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id);
1469          $node = $this->find('css', $selector);
1470          $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1471          if ($node->getAttribute('data-visible') === '1') {
1472              $toggle = $this->find('css', '.action-hide', $exception, $node);
1473          } else {
1474              $toggle = $this->find('css', '.action-show', $exception, $node);
1475          }
1476          $toggle->click();
1477      }
1478  
1479      /**
1480       * Moves a category displayed in the management interface up or down one place.
1481       *
1482       * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
1483       *
1484       * @param string $idnumber The category idnumber
1485       * @param string $direction The direction to move in, either up or down
1486       */
1487      public function i_click_to_move_category_by_one($idnumber, $direction) {
1488          $node = $this->get_management_category_listing_node_by_idnumber($idnumber);
1489          $this->user_moves_listing_by_one('category', $node, $direction);
1490      }
1491  
1492      /**
1493       * Moves a course displayed in the management interface up or down one place.
1494       *
1495       * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
1496       *
1497       * @param string $idnumber The course idnumber
1498       * @param string $direction The direction to move in, either up or down
1499       */
1500      public function i_click_to_move_course_by_one($idnumber, $direction) {
1501          $node = $this->get_management_course_listing_node_by_idnumber($idnumber);
1502          $this->user_moves_listing_by_one('course', $node, $direction);
1503      }
1504  
1505      /**
1506       * Moves a course or category listing within the management interface up or down by one.
1507       *
1508       * @param string $listingtype One of course or category
1509       * @param \Behat\Mink\Element\NodeElement $listingnode
1510       * @param string $direction One of up or down.
1511       * @param bool $highlight If set to false we don't check the node has been highlighted.
1512       */
1513      protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) {
1514          $up = (strtolower($direction) === 'up');
1515          if ($up) {
1516              $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession());
1517              $button = $this->find('css', 'a.action-moveup', $exception, $listingnode);
1518          } else {
1519              $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession());
1520              $button = $this->find('css', 'a.action-movedown', $exception, $listingnode);
1521          }
1522          $button->click();
1523          if ($this->running_javascript() && $highlight) {
1524              $listitem = $listingnode->getParent();
1525              $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession());
1526              $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true);
1527          }
1528      }
1529  
1530      /**
1531       * Used by spin to determine the callback has been highlighted.
1532       *
1533       * @param behat_course $self A self reference (default first arg from a spin callback)
1534       * @param \Behat\Mink\Element\NodeElement $selector
1535       * @return bool
1536       */
1537      protected function listing_is_highlighted($self, $selector) {
1538          $listitem = $this->find('css', $selector);
1539          return $listitem->hasClass('highlight');
1540      }
1541  
1542      /**
1543       * Check that one course appears before another in the course category management listings.
1544       *
1545       * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/
1546       *
1547       * @param string $preceedingcourse The first course to find
1548       * @param string $followingcourse The second course to find (should be AFTER the first course)
1549       * @throws ExpectationException
1550       */
1551      public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) {
1552          $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']";
1553          $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course";
1554          if (!$this->getSession()->getDriver()->find($xpath)) {
1555              throw new ExpectationException($msg, $this->getSession());
1556          }
1557      }
1558  
1559      /**
1560       * Check that one category appears before another in the course category management listings.
1561       *
1562       * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/
1563       *
1564       * @param string $preceedingcategory The first category to find
1565       * @param string $followingcategory The second category to find (should be after the first category)
1566       * @throws ExpectationException
1567       */
1568      public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) {
1569          $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']";
1570          $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category";
1571          if (!$this->getSession()->getDriver()->find($xpath)) {
1572              throw new ExpectationException($msg, $this->getSession());
1573          }
1574      }
1575  
1576      /**
1577       * Checks that we are on the course management page that we expect to be on and that no course has been selected.
1578       *
1579       * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/
1580       * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
1581       */
1582      public function i_should_see_the_courses_management_page($mode) {
1583          $this->execute("behat_general::assert_element_contains_text",
1584              array("Course and category management", "h2", "css_element")
1585          );
1586  
1587          switch ($mode) {
1588              case "Courses":
1589                  $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1590                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1591                  break;
1592  
1593              case "Course categories":
1594                  $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1595                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1596                  break;
1597  
1598              case "Courses categories and courses":
1599              default:
1600                  $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1601                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1602                  break;
1603          }
1604  
1605          $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element"));
1606      }
1607  
1608      /**
1609       * Checks that we are on the course management page that we expect to be on and that a course has been selected.
1610       *
1611       * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/
1612       * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
1613       */
1614      public function i_should_see_the_courses_management_page_with_a_course_selected($mode) {
1615          $this->execute("behat_general::assert_element_contains_text",
1616              array("Course and category management", "h2", "css_element"));
1617  
1618          switch ($mode) {
1619              case "Courses":
1620                  $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1621                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1622                  break;
1623  
1624              case "Course categories":
1625                  $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1626                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1627                  break;
1628  
1629              case "Courses categories and courses":
1630              default:
1631                  $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1632                  $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1633                  break;
1634          }
1635  
1636          $this->execute("behat_general::should_exist", array("#course-detail", "css_element"));
1637      }
1638  
1639      /**
1640       * Locates a course in the course category management interface and then triggers an action for it.
1641       *
1642       * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/
1643       *
1644       * @param string $action The action to take. One of
1645       * @param string $name The name of the course as it is displayed in the management interface.
1646       */
1647      public function i_click_on_action_for_item_in_management_course_listing($action, $name) {
1648          $node = $this->get_management_course_listing_node_by_name($name);
1649          $this->user_clicks_on_management_listing_action('course', $node, $action);
1650      }
1651  
1652      /**
1653       * Locates a category in the course category management interface and then triggers an action for it.
1654       *
1655       * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/
1656       *
1657       * @param string $action The action to take. One of
1658       * @param string $name The name of the category as it is displayed in the management interface.
1659       */
1660      public function i_click_on_action_for_item_in_management_category_listing($action, $name) {
1661          $node = $this->get_management_category_listing_node_by_name($name);
1662          $this->user_clicks_on_management_listing_action('category', $node, $action);
1663      }
1664  
1665      /**
1666       * Clicks to expand or collapse a category displayed on the frontpage
1667       *
1668       * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/
1669       * @throws ExpectationException
1670       * @param string $categoryname
1671       */
1672      public function i_toggle_category_children_visibility_in_frontpage($categoryname) {
1673  
1674          $headingtags = array();
1675          for ($i = 1; $i <= 6; $i++) {
1676              $headingtags[] = 'self::h' . $i;
1677          }
1678  
1679          $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
1680          $categoryliteral = behat_context_helper::escape($categoryname);
1681          $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
1682          $node = $this->find('xpath', $xpath, $exception);
1683          $node->click();
1684  
1685          // Smooth expansion.
1686          $this->getSession()->wait(1000, false);
1687      }
1688  
1689      /**
1690       * Finds the node to use for a management listitem action and clicks it.
1691       *
1692       * @param string $listingtype Either course or category.
1693       * @param \Behat\Mink\Element\NodeElement $listingnode
1694       * @param string $action The action being taken
1695       * @throws Behat\Mink\Exception\ExpectationException
1696       */
1697      protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
1698          $actionsnode = $listingnode->find('xpath', "//*[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
1699          if (!$actionsnode) {
1700              throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
1701          }
1702          $actionnode = $actionsnode->find('css', '.action-'.$action);
1703          if (!$actionnode) {
1704              throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
1705          }
1706          if ($this->running_javascript() && !$actionnode->isVisible()) {
1707              $actionsnode->find('css', 'a.toggle-display')->click();
1708              $actionnode = $actionsnode->find('css', '.action-'.$action);
1709          }
1710          $actionnode->click();
1711      }
1712  
1713      /**
1714       * Clicks on a category in the management interface.
1715       *
1716       * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/
1717       * @param string $name The name of the category to click.
1718       */
1719      public function i_click_on_category_in_the_management_category_listing($name) {
1720          $node = $this->get_management_category_listing_node_by_name($name);
1721          $node->find('css', 'a.categoryname')->click();
1722      }
1723  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1