See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 get_string('addresourceoractivity', 'moodle'), 219 'button', 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('behat_general::i_click_on_in_the', 1042 array(get_string('yes'), "button", "Confirm", "dialogue") 1043 ); 1044 } else { 1045 $this->execute("behat_forms::press_button", get_string('yes')); 1046 } 1047 1048 return $steps; 1049 } 1050 1051 /** 1052 * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on. 1053 * 1054 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ 1055 * @param string $activityname 1056 */ 1057 public function i_duplicate_activity($activityname) { 1058 $steps = array(); 1059 $activity = $this->escape($activityname); 1060 if ($this->running_javascript()) { 1061 $this->i_open_actions_menu($activity); 1062 } 1063 $this->execute('behat_course::i_click_on_in_the_activity', 1064 array(get_string('duplicate'), "link", $activity) 1065 ); 1066 1067 } 1068 1069 /** 1070 * 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. 1071 * 1072 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/ 1073 * @param string $activityname 1074 * @param TableNode $data 1075 */ 1076 public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) { 1077 1078 $activity = $this->escape($activityname); 1079 $activityliteral = behat_context_helper::escape($activityname); 1080 1081 $this->execute("behat_course::i_duplicate_activity", $activity); 1082 1083 // Determine the future new activity xpath from the former one. 1084 $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" . 1085 "[contains(., $activityliteral)]/following-sibling::li"; 1086 $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']"; 1087 1088 if ($this->running_javascript()) { 1089 // We wait until the AJAX request finishes and the section is visible again. 1090 $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" . 1091 "[contains(., $activityliteral)]" . 1092 "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" . 1093 "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]"; 1094 1095 $this->execute("behat_general::wait_until_exists", 1096 array($this->escape($hiddenlightboxxpath), "xpath_element") 1097 ); 1098 1099 // Close the original activity actions menu. 1100 $this->i_close_actions_menu($activity); 1101 1102 // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at 1103 // this point, it don't even exists in the DOM (the steps are executed when we return them). 1104 $this->execute('behat_general::i_click_on', 1105 array($this->escape($duplicatedactionsmenuxpath), "xpath_element") 1106 ); 1107 } 1108 1109 // We force the xpath as otherwise mink tries to interact with the former one. 1110 $this->execute('behat_general::i_click_on_in_the', 1111 array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element") 1112 ); 1113 1114 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); 1115 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse')); 1116 1117 } 1118 1119 /** 1120 * 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. 1121 * 1122 * Using the protected method as this method will be usually 1123 * called by other methods which are not returning a set of 1124 * steps and performs the actions directly, so it would not 1125 * be executed if it returns another step. 1126 * 1127 * Hopefully we would not require test writers to use this step 1128 * and we will manage it from other step definitions. 1129 * 1130 * @Given /^I wait until section "(?P<section_number>\d+)" is available$/ 1131 * @param int $sectionnumber 1132 * @return void 1133 */ 1134 public function i_wait_until_section_is_available($sectionnumber) { 1135 1136 // Looks for a hidden lightbox or a non-existent lightbox in that section. 1137 $sectionxpath = $this->section_exists($sectionnumber); 1138 $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" . 1139 " | " . 1140 $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]"; 1141 1142 $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element'); 1143 } 1144 1145 /** 1146 * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. 1147 * 1148 * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ 1149 * @param string $element 1150 * @param string $selectortype 1151 * @param string $activityname 1152 */ 1153 public function i_click_on_in_the_activity($element, $selectortype, $activityname) { 1154 $element = $this->get_activity_element($element, $selectortype, $activityname); 1155 $element->click(); 1156 } 1157 1158 /** 1159 * Clicks on the specified element inside the activity container. 1160 * 1161 * @throws ElementNotFoundException 1162 * @param string $element 1163 * @param string $selectortype 1164 * @param string $activityname 1165 * @return NodeElement 1166 */ 1167 protected function get_activity_element($element, $selectortype, $activityname) { 1168 $activitynode = $this->get_activity_node($activityname); 1169 1170 $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '$activityname}'"); 1171 return $this->find($selectortype, $element, $exception, $activitynode); 1172 } 1173 1174 /** 1175 * Checks if the course section exists. 1176 * 1177 * @throws ElementNotFoundException Thrown by behat_base::find 1178 * @param int $sectionnumber 1179 * @return string The xpath of the section. 1180 */ 1181 protected function section_exists($sectionnumber) { 1182 1183 // Just to give more info in case it does not exist. 1184 $xpath = "//li[@id='section-" . $sectionnumber . "']"; 1185 $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber "); 1186 $this->find('xpath', $xpath, $exception); 1187 1188 return $xpath; 1189 } 1190 1191 /** 1192 * Returns the show section icon or throws an exception. 1193 * 1194 * @throws ElementNotFoundException Thrown by behat_base::find 1195 * @param int $sectionnumber 1196 * @return NodeElement 1197 */ 1198 protected function show_section_link_exists($sectionnumber) { 1199 1200 // Gets the section xpath and ensure it exists. 1201 $xpath = $this->section_exists($sectionnumber); 1202 1203 // We need to know the course format as the text strings depends on them. 1204 $courseformat = $this->get_course_format(); 1205 1206 // Checking the show button alt text and show icon. 1207 $showtext = get_string('showfromothers', $courseformat); 1208 $linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]"; 1209 1210 $exception = new ElementNotFoundException($this->getSession(), 'Show section link'); 1211 1212 // Returing the link so both Non-JS and JS browsers can interact with it. 1213 return $this->find('xpath', $linkxpath, $exception); 1214 } 1215 1216 /** 1217 * Returns the hide section icon link if it exists or throws exception. 1218 * 1219 * @throws ElementNotFoundException Thrown by behat_base::find 1220 * @param int $sectionnumber 1221 * @return NodeElement 1222 */ 1223 protected function hide_section_link_exists($sectionnumber) { 1224 1225 // Gets the section xpath and ensure it exists. 1226 $xpath = $this->section_exists($sectionnumber); 1227 1228 // We need to know the course format as the text strings depends on them. 1229 $courseformat = $this->get_course_format(); 1230 1231 // Checking the hide button alt text and hide icon. 1232 $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat)); 1233 $linkxpath = $xpath . "/descendant::a[@title=$hidetext]"; 1234 1235 $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon '); 1236 $this->find('icon', 'Hide', $exception); 1237 1238 // Returing the link so both Non-JS and JS browsers can interact with it. 1239 return $this->find('xpath', $linkxpath, $exception); 1240 } 1241 1242 /** 1243 * Gets the current course format. 1244 * 1245 * @throws ExpectationException If we are not in the course view page. 1246 * @return string The course format in a frankenstyled name. 1247 */ 1248 protected function get_course_format() { 1249 1250 $exception = new ExpectationException('You are not in a course page', $this->getSession()); 1251 1252 // The moodle body's id attribute contains the course format. 1253 $node = $this->getSession()->getPage()->find('css', 'body'); 1254 if (!$node) { 1255 throw $exception; 1256 } 1257 1258 if (!$bodyid = $node->getAttribute('id')) { 1259 throw $exception; 1260 } 1261 1262 if (strstr($bodyid, 'page-course-view-') === false) { 1263 throw $exception; 1264 } 1265 1266 return 'format_' . str_replace('page-course-view-', '', $bodyid); 1267 } 1268 1269 /** 1270 * Gets the section's activites DOM nodes. 1271 * 1272 * @param string $sectionxpath 1273 * @return array NodeElement instances 1274 */ 1275 protected function get_section_activities($sectionxpath) { 1276 1277 $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]"; 1278 1279 // We spin here, as activities usually require a lot of time to load. 1280 try { 1281 $activities = $this->find_all('xpath', $xpath); 1282 } catch (ElementNotFoundException $e) { 1283 return false; 1284 } 1285 1286 return $activities; 1287 } 1288 1289 /** 1290 * Returns the DOM node of the activity from <li>. 1291 * 1292 * @throws ElementNotFoundException Thrown by behat_base::find 1293 * @param string $activityname The activity name 1294 * @return NodeElement 1295 */ 1296 protected function get_activity_node($activityname) { 1297 1298 $activityname = behat_context_helper::escape($activityname); 1299 $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]"; 1300 1301 return $this->find('xpath', $xpath); 1302 } 1303 1304 /** 1305 * Gets the activity instance name from the activity node. 1306 * 1307 * @throws ElementNotFoundException 1308 * @param NodeElement $activitynode 1309 * @return string 1310 */ 1311 protected function get_activity_name($activitynode) { 1312 $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode); 1313 return $instancenamenode->getText(); 1314 } 1315 1316 /** 1317 * Returns whether the user can edit the course contents or not. 1318 * 1319 * @return bool 1320 */ 1321 protected function is_course_editor(): bool { 1322 try { 1323 $this->find('field', get_string('editmode'), false, false, 0); 1324 return true; 1325 } catch (ElementNotFoundException $e) { 1326 return false; 1327 } 1328 } 1329 1330 /** 1331 * Returns whether the user can edit the course contents and the editing mode is on. 1332 * 1333 * @return bool 1334 */ 1335 protected function is_editing_on() { 1336 $body = $this->find('xpath', "//body", false, false, 0); 1337 return $body->hasClass('editing'); 1338 } 1339 1340 /** 1341 * Returns the category node from within the listing on the management page. 1342 * 1343 * @param string $idnumber 1344 * @return \Behat\Mink\Element\NodeElement 1345 */ 1346 protected function get_management_category_listing_node_by_idnumber($idnumber) { 1347 $id = $this->get_category_id($idnumber); 1348 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id); 1349 return $this->find('css', $selector); 1350 } 1351 1352 /** 1353 * Returns a category node from within the management interface. 1354 * 1355 * @param string $name The name of the category. 1356 * @param bool $link If set to true we'll resolve to the link rather than just the node. 1357 * @return \Behat\Mink\Element\NodeElement 1358 */ 1359 protected function get_management_category_listing_node_by_name($name, $link = false) { 1360 $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']"; 1361 if ($link === false) { 1362 $selector .= "/ancestor::li[@data-id][1]"; 1363 } 1364 return $this->find('xpath', $selector); 1365 } 1366 1367 /** 1368 * Returns a course node from within the management interface. 1369 * 1370 * @param string $name The name of the course. 1371 * @param bool $link If set to true we'll resolve to the link rather than just the node. 1372 * @return \Behat\Mink\Element\NodeElement 1373 */ 1374 protected function get_management_course_listing_node_by_name($name, $link = false) { 1375 $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']"; 1376 if ($link === false) { 1377 $selector .= "/ancestor::li[@data-id]"; 1378 } 1379 return $this->find('xpath', $selector); 1380 } 1381 1382 /** 1383 * Returns the course node from within the listing on the management page. 1384 * 1385 * @param string $idnumber 1386 * @return \Behat\Mink\Element\NodeElement 1387 */ 1388 protected function get_management_course_listing_node_by_idnumber($idnumber) { 1389 $id = $this->get_course_id($idnumber); 1390 $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id); 1391 return $this->find('css', $selector); 1392 } 1393 1394 /** 1395 * Clicks on a category in the management interface. 1396 * 1397 * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1398 * @param string $name 1399 */ 1400 public function i_click_on_category_in_the_management_interface($name) { 1401 $node = $this->get_management_category_listing_node_by_name($name, true); 1402 $node->click(); 1403 } 1404 1405 /** 1406 * Clicks on a course in the management interface. 1407 * 1408 * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1409 * @param string $name 1410 */ 1411 public function i_click_on_course_in_the_management_interface($name) { 1412 $node = $this->get_management_course_listing_node_by_name($name, true); 1413 $node->click(); 1414 } 1415 1416 /** 1417 * Clicks on a category checkbox in the management interface, if not checked. 1418 * 1419 * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1420 * @param string $name 1421 */ 1422 public function i_select_category_in_the_management_interface($name) { 1423 $node = $this->get_management_category_listing_node_by_name($name); 1424 $node = $node->findField('bcat[]'); 1425 if (!$node->isChecked()) { 1426 $node->click(); 1427 } 1428 } 1429 1430 /** 1431 * Clicks on a category checkbox in the management interface, if checked. 1432 * 1433 * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1434 * @param string $name 1435 */ 1436 public function i_unselect_category_in_the_management_interface($name) { 1437 $node = $this->get_management_category_listing_node_by_name($name); 1438 $node = $node->findField('bcat[]'); 1439 if ($node->isChecked()) { 1440 $node->click(); 1441 } 1442 } 1443 1444 /** 1445 * Clicks course checkbox in the management interface, if not checked. 1446 * 1447 * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1448 * @param string $name 1449 */ 1450 public function i_select_course_in_the_management_interface($name) { 1451 $node = $this->get_management_course_listing_node_by_name($name); 1452 $node = $node->findField('bc[]'); 1453 if (!$node->isChecked()) { 1454 $node->click(); 1455 } 1456 } 1457 1458 /** 1459 * Clicks course checkbox in the management interface, if checked. 1460 * 1461 * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ 1462 * @param string $name 1463 */ 1464 public function i_unselect_course_in_the_management_interface($name) { 1465 $node = $this->get_management_course_listing_node_by_name($name); 1466 $node = $node->findField('bc[]'); 1467 if ($node->isChecked()) { 1468 $node->click(); 1469 } 1470 } 1471 1472 /** 1473 * Move selected categories to top level in the management interface. 1474 * 1475 * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/ 1476 * @param string $name 1477 */ 1478 public function i_move_category_to_top_level_in_the_management_interface($name) { 1479 $this->i_select_category_in_the_management_interface($name); 1480 1481 $this->execute('behat_forms::i_set_the_field_to', 1482 array('menumovecategoriesto', core_course_category::get(0)->get_formatted_name()) 1483 ); 1484 1485 // Save event. 1486 $this->execute("behat_forms::press_button", "bulkmovecategories"); 1487 } 1488 1489 /** 1490 * Checks that a category is a subcategory of specific category. 1491 * 1492 * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/ 1493 * @throws ExpectationException 1494 * @param string $subcatidnumber 1495 * @param string $catidnumber 1496 */ 1497 public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) { 1498 $categorynodeid = $this->get_category_id($catidnumber); 1499 $subcategoryid = $this->get_category_id($subcatidnumber); 1500 $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession()); 1501 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid); 1502 $this->find('css', $selector, $exception); 1503 } 1504 1505 /** 1506 * Checks that a category is not a subcategory of specific category. 1507 * 1508 * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/ 1509 * @throws ExpectationException 1510 * @param string $subcatidnumber 1511 * @param string $catidnumber 1512 */ 1513 public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) { 1514 try { 1515 $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber); 1516 } catch (ExpectationException $e) { 1517 // ExpectedException means that it is not highlighted. 1518 return; 1519 } 1520 throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession()); 1521 } 1522 1523 /** 1524 * Click to expand a category revealing its sub categories within the management UI. 1525 * 1526 * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/ 1527 * @param string $idnumber 1528 */ 1529 public function i_click_to_expand_category_in_the_management_interface($idnumber) { 1530 $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber); 1531 $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession()); 1532 $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode); 1533 $togglenode->click(); 1534 } 1535 1536 /** 1537 * Checks that a category within the management interface is visible. 1538 * 1539 * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/ 1540 * @param string $idnumber 1541 */ 1542 public function category_in_management_listing_should_be_visible($idnumber) { 1543 $id = $this->get_category_id($idnumber); 1544 $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession()); 1545 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id); 1546 $this->find('css', $selector, $exception); 1547 } 1548 1549 /** 1550 * Checks that a category within the management interface is dimmed. 1551 * 1552 * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/ 1553 * @param string $idnumber 1554 */ 1555 public function category_in_management_listing_should_be_dimmed($idnumber) { 1556 $id = $this->get_category_id($idnumber); 1557 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id); 1558 $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession()); 1559 $this->find('css', $selector, $exception); 1560 } 1561 1562 /** 1563 * Checks that a course within the management interface is visible. 1564 * 1565 * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/ 1566 * @param string $idnumber 1567 */ 1568 public function course_in_management_listing_should_be_visible($idnumber) { 1569 $id = $this->get_course_id($idnumber); 1570 $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession()); 1571 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id); 1572 $this->find('css', $selector, $exception); 1573 } 1574 1575 /** 1576 * Checks that a course within the management interface is dimmed. 1577 * 1578 * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/ 1579 * @param string $idnumber 1580 */ 1581 public function course_in_management_listing_should_be_dimmed($idnumber) { 1582 $id = $this->get_course_id($idnumber); 1583 $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession()); 1584 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id); 1585 $this->find('css', $selector, $exception); 1586 } 1587 1588 /** 1589 * Toggles the visibility of a course in the management UI. 1590 * 1591 * If it was visible it will be hidden. If it is hidden it will be made visible. 1592 * 1593 * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/ 1594 * @param string $idnumber 1595 */ 1596 public function i_toggle_visibility_of_course_in_management_listing($idnumber) { 1597 $id = $this->get_course_id($idnumber); 1598 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id); 1599 $node = $this->find('css', $selector); 1600 $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession()); 1601 if ($node->getAttribute('data-visible') === '1') { 1602 $toggle = $this->find('css', '.action-hide', $exception, $node); 1603 } else { 1604 $toggle = $this->find('css', '.action-show', $exception, $node); 1605 } 1606 $toggle->click(); 1607 } 1608 1609 /** 1610 * Toggles the visibility of a category in the management UI. 1611 * 1612 * If it was visible it will be hidden. If it is hidden it will be made visible. 1613 * 1614 * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/ 1615 */ 1616 public function i_toggle_visibility_of_category_in_management_listing($idnumber) { 1617 $id = $this->get_category_id($idnumber); 1618 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id); 1619 $node = $this->find('css', $selector); 1620 $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession()); 1621 if ($node->getAttribute('data-visible') === '1') { 1622 $toggle = $this->find('css', '.action-hide', $exception, $node); 1623 } else { 1624 $toggle = $this->find('css', '.action-show', $exception, $node); 1625 } 1626 $toggle->click(); 1627 } 1628 1629 /** 1630 * Moves a category displayed in the management interface up or down one place. 1631 * 1632 * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/ 1633 * 1634 * @param string $idnumber The category idnumber 1635 * @param string $direction The direction to move in, either up or down 1636 */ 1637 public function i_click_to_move_category_by_one($idnumber, $direction) { 1638 $node = $this->get_management_category_listing_node_by_idnumber($idnumber); 1639 $this->user_moves_listing_by_one('category', $node, $direction); 1640 } 1641 1642 /** 1643 * Moves a course displayed in the management interface up or down one place. 1644 * 1645 * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/ 1646 * 1647 * @param string $idnumber The course idnumber 1648 * @param string $direction The direction to move in, either up or down 1649 */ 1650 public function i_click_to_move_course_by_one($idnumber, $direction) { 1651 $node = $this->get_management_course_listing_node_by_idnumber($idnumber); 1652 $this->user_moves_listing_by_one('course', $node, $direction); 1653 } 1654 1655 /** 1656 * Moves a course or category listing within the management interface up or down by one. 1657 * 1658 * @param string $listingtype One of course or category 1659 * @param \Behat\Mink\Element\NodeElement $listingnode 1660 * @param string $direction One of up or down. 1661 * @param bool $highlight If set to false we don't check the node has been highlighted. 1662 */ 1663 protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) { 1664 $up = (strtolower($direction) === 'up'); 1665 if ($up) { 1666 $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession()); 1667 $button = $this->find('css', 'a.action-moveup', $exception, $listingnode); 1668 } else { 1669 $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession()); 1670 $button = $this->find('css', 'a.action-movedown', $exception, $listingnode); 1671 } 1672 $button->click(); 1673 if ($this->running_javascript() && $highlight) { 1674 $listitem = $listingnode->getParent(); 1675 $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession()); 1676 $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true); 1677 } 1678 } 1679 1680 /** 1681 * Used by spin to determine the callback has been highlighted. 1682 * 1683 * @param behat_course $self A self reference (default first arg from a spin callback) 1684 * @param \Behat\Mink\Element\NodeElement $selector 1685 * @return bool 1686 */ 1687 protected function listing_is_highlighted($self, $selector) { 1688 $listitem = $this->find('css', $selector); 1689 return $listitem->hasClass('highlight'); 1690 } 1691 1692 /** 1693 * Check that one course appears before another in the course category management listings. 1694 * 1695 * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/ 1696 * 1697 * @param string $preceedingcourse The first course to find 1698 * @param string $followingcourse The second course to find (should be AFTER the first course) 1699 * @throws ExpectationException 1700 */ 1701 public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) { 1702 $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']"; 1703 $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course"; 1704 if (!$this->getSession()->getDriver()->find($xpath)) { 1705 throw new ExpectationException($msg, $this->getSession()); 1706 } 1707 } 1708 1709 /** 1710 * Check that one category appears before another in the course category management listings. 1711 * 1712 * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/ 1713 * 1714 * @param string $preceedingcategory The first category to find 1715 * @param string $followingcategory The second category to find (should be after the first category) 1716 * @throws ExpectationException 1717 */ 1718 public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) { 1719 $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']"; 1720 $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category"; 1721 if (!$this->getSession()->getDriver()->find($xpath)) { 1722 throw new ExpectationException($msg, $this->getSession()); 1723 } 1724 } 1725 1726 /** 1727 * Checks that we are on the course management page that we expect to be on and that no course has been selected. 1728 * 1729 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/ 1730 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses' 1731 */ 1732 public function i_should_see_the_courses_management_page($mode) { 1733 switch ($mode) { 1734 case "Courses": 1735 $heading = "Manage courses"; 1736 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element")); 1737 $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); 1738 break; 1739 1740 case "Course categories": 1741 $heading = "Manage course categories"; 1742 $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); 1743 $this->execute("behat_general::should_not_exist", array("#course-listing", "css_element")); 1744 break; 1745 1746 case "Courses categories and courses": 1747 default: 1748 $heading = "Manage course categories and courses"; 1749 $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); 1750 $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); 1751 break; 1752 } 1753 1754 $this->execute("behat_general::assert_element_contains_text", 1755 array($heading, "h2", "css_element") 1756 ); 1757 1758 $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element")); 1759 } 1760 1761 /** 1762 * Checks that we are on the course management page that we expect to be on and that a course has been selected. 1763 * 1764 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/ 1765 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses' 1766 */ 1767 public function i_should_see_the_courses_management_page_with_a_course_selected($mode) { 1768 switch ($mode) { 1769 case "Courses": 1770 $heading = "Manage courses"; 1771 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element")); 1772 $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); 1773 break; 1774 1775 case "Course categories": 1776 $heading = "Manage course categories"; 1777 $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); 1778 $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); 1779 break; 1780 1781 case "Courses categories and courses": 1782 default: 1783 $heading = "Manage course categories and courses"; 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 1789 $this->execute("behat_general::assert_element_contains_text", 1790 array($heading, "h2", "css_element")); 1791 1792 $this->execute("behat_general::should_exist", array("#course-detail", "css_element")); 1793 } 1794 1795 /** 1796 * Locates a course in the course category management interface and then triggers an action for it. 1797 * 1798 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/ 1799 * 1800 * @param string $action The action to take. One of 1801 * @param string $name The name of the course as it is displayed in the management interface. 1802 */ 1803 public function i_click_on_action_for_item_in_management_course_listing($action, $name) { 1804 $node = $this->get_management_course_listing_node_by_name($name); 1805 $this->user_clicks_on_management_listing_action('course', $node, $action); 1806 } 1807 1808 /** 1809 * Locates a category in the course category management interface and then triggers an action for it. 1810 * 1811 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/ 1812 * 1813 * @param string $action The action to take. One of 1814 * @param string $name The name of the category as it is displayed in the management interface. 1815 */ 1816 public function i_click_on_action_for_item_in_management_category_listing($action, $name) { 1817 $node = $this->get_management_category_listing_node_by_name($name); 1818 $this->user_clicks_on_management_listing_action('category', $node, $action); 1819 } 1820 1821 /** 1822 * Clicks to expand or collapse a category displayed on the frontpage 1823 * 1824 * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/ 1825 * @throws ExpectationException 1826 * @param string $categoryname 1827 */ 1828 public function i_toggle_category_children_visibility_in_frontpage($categoryname) { 1829 1830 $headingtags = array(); 1831 for ($i = 1; $i <= 6; $i++) { 1832 $headingtags[] = 'self::h' . $i; 1833 } 1834 1835 $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession()); 1836 $categoryliteral = behat_context_helper::escape($categoryname); 1837 $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . 1838 "][contains(@class,'categoryname')][./descendant::a[.=$categoryliteral]]"; 1839 $node = $this->find('xpath', $xpath, $exception); 1840 $node->click(); 1841 1842 // Smooth expansion. 1843 $this->getSession()->wait(1000); 1844 } 1845 1846 /** 1847 * Finds the node to use for a management listitem action and clicks it. 1848 * 1849 * @param string $listingtype Either course or category. 1850 * @param \Behat\Mink\Element\NodeElement $listingnode 1851 * @param string $action The action being taken 1852 * @throws Behat\Mink\Exception\ExpectationException 1853 */ 1854 protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) { 1855 $actionsnode = $listingnode->find('xpath', "//*" . 1856 "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]"); 1857 if (!$actionsnode) { 1858 throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession()); 1859 } 1860 $actionnode = $actionsnode->find('css', '.action-'.$action); 1861 if (!$actionnode) { 1862 throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession()); 1863 } 1864 if ($this->running_javascript() && !$actionnode->isVisible()) { 1865 $actionsnode->find('css', 'a[data-toggle=dropdown]')->click(); 1866 $actionnode = $actionsnode->find('css', '.action-'.$action); 1867 } 1868 $actionnode->click(); 1869 } 1870 1871 /** 1872 * Clicks on a category in the management interface. 1873 * 1874 * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/ 1875 * @param string $name The name of the category to click. 1876 */ 1877 public function i_click_on_category_in_the_management_category_listing($name) { 1878 $node = $this->get_management_category_listing_node_by_name($name); 1879 $node->find('css', 'a.categoryname')->click(); 1880 } 1881 1882 /** 1883 * Locates a category in the course category management interface and then opens action menu for it. 1884 * 1885 * @Given /^I open the action menu for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/ 1886 * 1887 * @param string $name The name of the category as it is displayed in the management interface. 1888 */ 1889 public function i_open_the_action_menu_for_item_in_management_category_listing($name) { 1890 $node = $this->get_management_category_listing_node_by_name($name); 1891 $node->find('xpath', "//*[contains(@class, 'category-item-actions')]//a[@data-toggle='dropdown']")->click(); 1892 } 1893 1894 /** 1895 * Checks that the specified category actions menu contains an item. 1896 * 1897 * @Then /^"(?P<name_string>(?:[^"]|\\")*)" category actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ 1898 * 1899 * @param string $name 1900 * @param string $menuitem 1901 * @throws Behat\Mink\Exception\ExpectationException 1902 */ 1903 public function category_actions_menu_should_have_item($name, $menuitem) { 1904 $node = $this->get_management_category_listing_node_by_name($name); 1905 1906 $notfoundexception = new ExpectationException('"' . $name . '" doesn\'t have a "' . 1907 $menuitem . '" item', $this->getSession()); 1908 $this->find('named_partial', ['link', $menuitem], $notfoundexception, $node); 1909 } 1910 1911 /** 1912 * Checks that the specified category actions menu does not contain an item. 1913 * 1914 * @Then /^"(?P<name_string>(?:[^"]|\\")*)" category actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ 1915 * 1916 * @param string $name 1917 * @param string $menuitem 1918 * @throws Behat\Mink\Exception\ExpectationException 1919 */ 1920 public function category_actions_menu_should_not_have_item($name, $menuitem) { 1921 $node = $this->get_management_category_listing_node_by_name($name); 1922 1923 try { 1924 $this->find('named_partial', ['link', $menuitem], false, $node); 1925 throw new ExpectationException('"' . $name . '" has a "' . $menuitem . 1926 '" item when it should not', $this->getSession()); 1927 } catch (ElementNotFoundException $e) { 1928 // This is good, the menu item should not be there. 1929 } 1930 } 1931 1932 /** 1933 * Go to the course participants 1934 * 1935 * @Given /^I navigate to course participants$/ 1936 */ 1937 public function i_navigate_to_course_participants() { 1938 $this->execute('behat_navigation::i_select_from_secondary_navigation', get_string('participants')); 1939 } 1940 1941 /** 1942 * Check that one teacher appears before another in the course contacts. 1943 * 1944 * @Given /^I should see teacher "(?P<pteacher_string>(?:[^"]|\\")*)" before "(?P<fteacher_string>(?:[^"]|\\")*)" in the course contact listing$/ 1945 * 1946 * @param string $pteacher The first teacher to find 1947 * @param string $fteacher The second teacher to find (should be after the first teacher) 1948 * 1949 * @throws ExpectationException 1950 */ 1951 public function i_should_see_teacher_before($pteacher, $fteacher) { 1952 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']"; 1953 $msg = "Teacher {$pteacher} does not appear before Teacher {$fteacher}"; 1954 if (!$this->getSession()->getDriver()->find($xpath)) { 1955 throw new ExpectationException($msg, $this->getSession()); 1956 } 1957 } 1958 1959 /** 1960 * Check that one teacher oes not appears after another in the course contacts. 1961 * 1962 * @Given /^I should not see teacher "(?P<fteacher_string>(?:[^"]|\\")*)" after "(?P<pteacher_string>(?:[^"]|\\")*)" in the course contact listing$/ 1963 * 1964 * @param string $fteacher The teacher that should not be found (after the other teacher) 1965 * @param string $pteacher The teacher after who the other should not be found (this teacher must be found!) 1966 * 1967 * @throws ExpectationException 1968 */ 1969 public function i_should_not_see_teacher_after($fteacher, $pteacher) { 1970 $xpathliteral = behat_context_helper::escape($pteacher); 1971 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . 1972 "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; 1973 try { 1974 $nodes = $this->find_all('xpath', $xpath); 1975 } catch (ElementNotFoundException $e) { 1976 throw new ExpectationException('"' . $pteacher . '" text was not found in the page', $this->getSession()); 1977 } 1978 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']"; 1979 $msg = "Teacher {$fteacher} appears after Teacher {$pteacher}"; 1980 if ($this->getSession()->getDriver()->find($xpath)) { 1981 throw new ExpectationException($msg, $this->getSession()); 1982 } 1983 } 1984 1985 /** 1986 * Open the activity chooser in a course. 1987 * 1988 * @Given /^I open the activity chooser$/ 1989 */ 1990 public function i_open_the_activity_chooser() { 1991 $this->execute('behat_general::i_click_on', 1992 array('//button[@data-action="open-chooser"]', 'xpath_element')); 1993 1994 $node = $this->get_selected_node('xpath_element', '//div[@data-region="modules"]'); 1995 $this->ensure_node_is_visible($node); 1996 } 1997 1998 /** 1999 * Checks the presence of the given text in the activity's displayed dates. 2000 * 2001 * @Given /^the activity date in "(?P<activityname>(?:[^"]|\\")*)" should contain "(?P<text>(?:[^"]|\\")*)"$/ 2002 * @param string $activityname The activity name. 2003 * @param string $text The text to be searched in the activity date. 2004 */ 2005 public function activity_date_in_activity_should_contain_text(string $activityname, string $text): void { 2006 $containerselector = "//div[@data-activityname='$activityname']"; 2007 $containerselector .= "//div[@data-region='activity-dates']"; 2008 2009 $params = [$text, $containerselector, 'xpath_element']; 2010 $this->execute("behat_general::assert_element_contains_text", $params); 2011 } 2012 2013 /** 2014 * Checks the presence of activity dates information in the activity information output component. 2015 * 2016 * @Given /^the activity date information in "(?P<activityname>(?:[^"]|\\")*)" should exist$/ 2017 * @param string $activityname The activity name. 2018 */ 2019 public function activity_dates_information_in_activity_should_exist(string $activityname): void { 2020 $containerselector = "//div[@data-activityname='$activityname']"; 2021 $elementselector = "//div[@data-region='activity-dates']"; 2022 $params = [$elementselector, "xpath_element", $containerselector, "xpath_element"]; 2023 $this->execute("behat_general::should_exist_in_the", $params); 2024 } 2025 2026 /** 2027 * Checks the absence of activity dates information in the activity information output component. 2028 * 2029 * @Given /^the activity date information in "(?P<activityname>(?:[^"]|\\")*)" should not exist$/ 2030 * @param string $activityname The activity name. 2031 */ 2032 public function activity_dates_information_in_activity_should_not_exist(string $activityname): void { 2033 $containerselector = "//div[@data-region='activity-information'][@data-activityname='$activityname']"; 2034 try { 2035 $this->find('xpath_element', $containerselector); 2036 } catch (ElementNotFoundException $e) { 2037 // If activity information container does not exist (activity dates not shown, completion info not shown), all good. 2038 return; 2039 } 2040 2041 // Otherwise, ensure that the completion information does not exist. 2042 $elementselector = "//div[@data-region='activity-dates']"; 2043 $params = [$elementselector, "xpath_element", $containerselector, "xpath_element"]; 2044 $this->execute("behat_general::should_not_exist_in_the", $params); 2045 } 2046 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body