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