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