[ Index ]

PHP Cross Reference of moodle-2.8

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1