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