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