2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Behat course-related steps definitions.
20 * @package core_course
22 * @copyright 2012 David Monllaó
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
28 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
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;
36 * Course-related steps definitions.
38 * @package core_course
40 * @copyright 2012 David Monllaó
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 class behat_course extends behat_base {
46 * Return the list of partial named selectors.
50 public static function get_partial_named_selectors(): array {
52 new behat_component_named_selector(
53 'Activity chooser screen', [
54 "%core_course/activityChooser%//*[@data-region=%locator%][contains(concat(' ', @class, ' '), ' carousel-item ')]"
57 new behat_component_named_selector(
58 'Activity chooser tab', [
59 "%core_course/activityChooser%//*[@data-region=%locator%][contains(concat(' ', @class, ' '), ' tab-pane ')]"
66 * Return a list of the Mink named replacements for the component.
68 * Named replacements allow you to define parts of an xpath that can be reused multiple times, or in multiple
71 * This method should return a list of {@link behat_component_named_replacement} and the docs on that class explain
74 * @return behat_component_named_replacement[]
76 public static function get_named_replacements(): array {
78 new behat_component_named_replacement(
80 ".//*[contains(concat(' ', @class, ' '), ' modchooser ')][contains(concat(' ', @class, ' '), ' modal-dialog ')]"
86 * Turns editing mode on.
87 * @Given /^I turn editing mode on$/
89 public function i_turn_editing_mode_on() {
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')]);
99 * Turns editing mode off.
100 * @Given /^I turn editing mode off$/
102 public function i_turn_editing_mode_off() {
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')]);
112 * Creates a new course with the provided table data matching course settings names with the desired values.
114 * @Given /^I create a course with:$/
115 * @param TableNode $table The course data
117 public function i_create_a_course_with(TableNode $table) {
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'));
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'));
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")
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;
143 // Setting the format separately.
144 if (!empty($formatfield)) {
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) {
154 $table = new TableNode($rows);
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");
160 $this->execute("behat_forms::i_set_the_field_to", array($formatfield, $formatvalue));
164 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $table);
166 // Save course settings.
167 $this->execute("behat_forms::press_button", get_string('savechangesanddisplay'));
172 * Goes to the system courses/categories management page.
174 * @Given /^I go to the courses management page$/
176 public function i_go_to_the_courses_management_page() {
178 $parentnodes = get_string('courses', 'admin');
181 $this->execute("behat_general::i_am_on_homepage");
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'))
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.
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
198 public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) {
200 // Add activity to section.
201 $this->execute("behat_course::i_add_to_section",
202 array($this->escape($activity), $this->escape($section))
205 // Wait to be redirected.
206 $this->execute('behat_general::wait_until_the_page_is_ready');
209 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
211 // Save course settings.
212 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
216 * Opens the activity chooser and opens the activity/resource form page. Sections 0 and 1 are also allowed on frontpage.
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
223 public function i_add_to_section($activity, $section) {
225 if ($this->getSession()->getPage()->find('css', 'body#page-site-index') && (int)$section <= 1) {
226 // We are on the frontpage.
228 // Section 1 represents the contents on the frontpage.
229 $sectionxpath = "//body[@id='page-site-index']" .
230 "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
232 // Section 0 represents "Site main menu" block.
233 $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
236 // We are inside the course.
237 $sectionxpath = "//li[@id='section-" . $section . "']";
240 $activityliteral = behat_context_helper::escape(ucfirst($activity));
242 if ($this->running_javascript()) {
244 // Clicks add activity or resource section link.
245 $sectionxpath = $sectionxpath . "/descendant::div" .
246 "[contains(concat(' ', normalize-space(@class) , ' '), ' section-modchooser ')]/button";
248 $this->execute('behat_general::i_click_on', [$sectionxpath, 'xpath']);
250 // Clicks the selected activity if it exists.
251 $activityxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' modchooser ')]" .
252 "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" .
253 "/descendant::p[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" .
254 "[normalize-space(.)=$activityliteral]" .
257 $this->execute('behat_general::i_click_on', [$activityxpath, 'xpath']);
260 // Without Javascript.
262 // Selecting the option from the select box which contains the option.
263 $selectxpath = $sectionxpath . "/descendant::div" .
264 "[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
265 "/descendant::select[option[normalize-space(.)=$activityliteral]]";
266 $selectnode = $this->find('xpath', $selectxpath);
267 $selectnode->selectOption($activity);
270 $gobuttonxpath = $selectxpath . "/ancestor::form/descendant::input[@type='submit']";
271 $gobutton = $this->find('xpath', $gobuttonxpath);
278 * Opens a section edit menu if it is not already opened.
280 * @Given /^I open section "(?P<section_number>\d+)" edit menu$/
281 * @throws DriverException The step is not available when Javascript is disabled
282 * @param string $sectionnumber
284 public function i_open_section_edit_menu($sectionnumber) {
285 if (!$this->running_javascript()) {
286 throw new DriverException('Section edit menu not available when Javascript is disabled');
289 // Wait for section to be available, before clicking on the menu.
290 $this->i_wait_until_section_is_available($sectionnumber);
292 // If it is already opened we do nothing.
293 $xpath = $this->section_exists($sectionnumber);
294 $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]";
296 $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
297 $menu = $this->find('xpath', $xpath, $exception);
299 $this->i_wait_until_section_is_available($sectionnumber);
303 * Deletes course section.
305 * @Given /^I delete section "(?P<section_number>\d+)"$/
306 * @param int $sectionnumber The section number
308 public function i_delete_section($sectionnumber) {
309 // Ensures the section exists.
310 $xpath = $this->section_exists($sectionnumber);
312 // We need to know the course format as the text strings depends on them.
313 $courseformat = $this->get_course_format();
314 if (get_string_manager()->string_exists('deletesection', $courseformat)) {
315 $strdelete = get_string('deletesection', $courseformat);
317 $strdelete = get_string('deletesection');
320 // If javascript is on, link is inside a menu.
321 if ($this->running_javascript()) {
322 $this->i_open_section_edit_menu($sectionnumber);
325 // Click on delete link.
326 $this->execute('behat_general::i_click_on_in_the',
327 array($strdelete, "link", $this->escape($xpath), "xpath_element")
333 * Turns course section highlighting on.
335 * @Given /^I turn section "(?P<section_number>\d+)" highlighting on$/
336 * @param int $sectionnumber The section number
338 public function i_turn_section_highlighting_on($sectionnumber) {
340 // Ensures the section exists.
341 $xpath = $this->section_exists($sectionnumber);
343 // If javascript is on, link is inside a menu.
344 if ($this->running_javascript()) {
345 $this->i_open_section_edit_menu($sectionnumber);
348 // Click on highlight topic link.
349 $this->execute('behat_general::i_click_on_in_the',
350 array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element")
355 * Turns course section highlighting off.
357 * @Given /^I turn section "(?P<section_number>\d+)" highlighting off$/
358 * @param int $sectionnumber The section number
360 public function i_turn_section_highlighting_off($sectionnumber) {
362 // Ensures the section exists.
363 $xpath = $this->section_exists($sectionnumber);
365 // If javascript is on, link is inside a menu.
366 if ($this->running_javascript()) {
367 $this->i_open_section_edit_menu($sectionnumber);
370 // Click on un-highlight topic link.
371 $this->execute('behat_general::i_click_on_in_the',
372 array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element")
377 * Shows the specified hidden section. You need to be in the course page and on editing mode.
379 * @Given /^I show section "(?P<section_number>\d+)"$/
380 * @param int $sectionnumber
382 public function i_show_section($sectionnumber) {
383 $showlink = $this->show_section_link_exists($sectionnumber);
385 // Ensure section edit menu is open before interacting with it.
386 if ($this->running_javascript()) {
387 $this->i_open_section_edit_menu($sectionnumber);
391 if ($this->running_javascript()) {
392 $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
393 $this->i_wait_until_section_is_available($sectionnumber);
398 * Hides the specified visible section. You need to be in the course page and on editing mode.
400 * @Given /^I hide section "(?P<section_number>\d+)"$/
401 * @param int $sectionnumber
403 public function i_hide_section($sectionnumber) {
404 // Ensures the section exists.
405 $xpath = $this->section_exists($sectionnumber);
407 // We need to know the course format as the text strings depends on them.
408 $courseformat = $this->get_course_format();
409 if (get_string_manager()->string_exists('hidefromothers', $courseformat)) {
410 $strhide = get_string('hidefromothers', $courseformat);
412 $strhide = get_string('hidesection');
415 // If javascript is on, link is inside a menu.
416 if ($this->running_javascript()) {
417 $this->i_open_section_edit_menu($sectionnumber);
420 // Click on delete link.
421 $this->execute('behat_general::i_click_on_in_the',
422 array($strhide, "link", $this->escape($xpath), "xpath_element")
425 if ($this->running_javascript()) {
426 $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
427 $this->i_wait_until_section_is_available($sectionnumber);
432 * Go to editing section page for specified section number. You need to be in the course page and on editing mode.
434 * @Given /^I edit the section "(?P<section_number>\d+)"$/
435 * @param int $sectionnumber
437 public function i_edit_the_section($sectionnumber) {
438 // If javascript is on, link is inside a menu.
439 if ($this->running_javascript()) {
440 $this->i_open_section_edit_menu($sectionnumber);
443 // We need to know the course format as the text strings depends on them.
444 $courseformat = $this->get_course_format();
445 if ($sectionnumber > 0 && get_string_manager()->string_exists('editsection', $courseformat)) {
446 $stredit = get_string('editsection', $courseformat);
448 $stredit = get_string('editsection');
451 // Click on un-highlight topic link.
452 $this->execute('behat_general::i_click_on_in_the',
453 array($stredit, "link", "#section-" . $sectionnumber, "css_element")
459 * Edit specified section and fill the form data with the specified field/value pairs.
461 * @When /^I edit the section "(?P<section_number>\d+)" and I fill the form with:$/
462 * @param int $sectionnumber The section number
463 * @param TableNode $data The activity field/value data
465 public function i_edit_the_section_and_i_fill_the_form_with($sectionnumber, TableNode $data) {
467 // Edit given section.
468 $this->execute("behat_course::i_edit_the_section", $sectionnumber);
471 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
473 // Save section settings.
474 $this->execute("behat_forms::press_button", get_string('savechanges'));
478 * Checks if the specified course section hightlighting is turned on. You need to be in the course page on editing mode.
480 * @Then /^section "(?P<section_number>\d+)" should be highlighted$/
481 * @throws ExpectationException
482 * @param int $sectionnumber The section number
484 public function section_should_be_highlighted($sectionnumber) {
486 // Ensures the section exists.
487 $xpath = $this->section_exists($sectionnumber);
489 // The important checking, we can not check the img.
490 $this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']);
494 * Checks if the specified course section highlighting is turned off. You need to be in the course page on editing mode.
496 * @Then /^section "(?P<section_number>\d+)" should not be highlighted$/
497 * @throws ExpectationException
498 * @param int $sectionnumber The section number
500 public function section_should_not_be_highlighted($sectionnumber) {
502 // We only catch ExpectationException, ElementNotFoundException should be thrown if the specified section does not exist.
504 $this->section_should_be_highlighted($sectionnumber);
505 } catch (ExpectationException $e) {
506 // ExpectedException means that it is not highlighted.
510 throw new ExpectationException('The "' . $sectionnumber . '" section is highlighted', $this->getSession());
514 * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
516 * @Then /^section "(?P<section_number>\d+)" should be hidden$/
517 * @throws ExpectationException
518 * @throws ElementNotFoundException Thrown by behat_base::find
519 * @param int $sectionnumber
521 public function section_should_be_hidden($sectionnumber) {
523 $sectionxpath = $this->section_exists($sectionnumber);
525 // Preventive in case there is any action in progress.
526 // Adding it here because we are interacting (click) with
527 // the elements, not necessary when we just find().
528 $this->i_wait_until_section_is_available($sectionnumber);
530 // Section should be hidden.
531 $exception = new ExpectationException('The section is not hidden', $this->getSession());
532 $this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception);
536 * Checks that all actiities in the specified section are hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
538 * @Then /^all activities in section "(?P<section_number>\d+)" should be hidden$/
539 * @throws ExpectationException
540 * @throws ElementNotFoundException Thrown by behat_base::find
541 * @param int $sectionnumber
543 public function section_activities_should_be_hidden($sectionnumber) {
544 $sectionxpath = $this->section_exists($sectionnumber);
546 // Preventive in case there is any action in progress.
547 // Adding it here because we are interacting (click) with
548 // the elements, not necessary when we just find().
549 $this->i_wait_until_section_is_available($sectionnumber);
551 // The checking are different depending on user permissions.
552 if ($this->is_course_editor()) {
554 // The section must be hidden.
555 $this->show_section_link_exists($sectionnumber);
557 // If there are activities they should be hidden and the visibility icon should not be available.
558 if ($activities = $this->get_section_activities($sectionxpath)) {
560 $dimmedexception = new ExpectationException('There are activities that are not dimmed', $this->getSession());
561 foreach ($activities as $activity) {
563 $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
564 "//a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
568 // There shouldn't be activities.
569 if ($this->get_section_activities($sectionxpath)) {
570 throw new ExpectationException('There are activities in the section and they should be hidden', $this->getSession());
577 * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
579 * @Then /^section "(?P<section_number>\d+)" should be visible$/
580 * @throws ExpectationException
581 * @param int $sectionnumber
583 public function section_should_be_visible($sectionnumber) {
585 $sectionxpath = $this->section_exists($sectionnumber);
587 // Section should not be hidden.
588 $xpath = $sectionxpath . "[not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]";
589 if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
590 throw new ExpectationException('The section is hidden', $this->getSession());
593 // Edit menu should be visible.
594 if ($this->is_course_editor()) {
595 $xpath = $sectionxpath .
596 "/descendant::div[contains(@class, 'section-actions')]" .
597 "/descendant::a[contains(@data-toggle, 'dropdown')]";
598 if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
599 throw new ExpectationException('The section edit menu is not available', $this->getSession());
605 * Moves up the specified section, this step only works with Javascript disabled. Editing mode should be on.
607 * @Given /^I move up section "(?P<section_number>\d+)"$/
608 * @throws DriverException Step not available when Javascript is enabled
609 * @param int $sectionnumber
611 public function i_move_up_section($sectionnumber) {
613 if ($this->running_javascript()) {
614 throw new DriverException('Move a section up step is not available with Javascript enabled');
617 // Ensures the section exists.
618 $sectionxpath = $this->section_exists($sectionnumber);
620 // If javascript is on, link is inside a menu.
621 if ($this->running_javascript()) {
622 $this->i_open_section_edit_menu($sectionnumber);
626 $moveuplink = $this->get_node_in_container('link', get_string('moveup'), 'xpath_element', $sectionxpath);
627 $moveuplink->click();
631 * Moves down the specified section, this step only works with Javascript disabled. Editing mode should be on.
633 * @Given /^I move down section "(?P<section_number>\d+)"$/
634 * @throws DriverException Step not available when Javascript is enabled
635 * @param int $sectionnumber
637 public function i_move_down_section($sectionnumber) {
639 if ($this->running_javascript()) {
640 throw new DriverException('Move a section down step is not available with Javascript enabled');
643 // Ensures the section exists.
644 $sectionxpath = $this->section_exists($sectionnumber);
646 // If javascript is on, link is inside a menu.
647 if ($this->running_javascript()) {
648 $this->i_open_section_edit_menu($sectionnumber);
652 $movedownlink = $this->get_node_in_container('link', get_string('movedown'), 'xpath_element', $sectionxpath);
653 $movedownlink->click();
657 * Checks that the specified activity is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
659 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be visible$/
660 * @param string $activityname
661 * @throws ExpectationException
663 public function activity_should_be_visible($activityname) {
665 // The activity must exists and be visible.
666 $activitynode = $this->get_activity_node($activityname);
668 if ($this->is_course_editor()) {
670 // The activity should not be dimmed.
672 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
673 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
674 $this->find('xpath', $xpath, false, $activitynode);
675 throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
676 } catch (ElementNotFoundException $e) {
680 // Additional check if this is a teacher in editing mode.
681 if ($this->is_editing_on()) {
682 // The 'Hide' button should be available.
683 $nohideexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
684 get_string('hide') . '" icon', $this->getSession());
685 $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
691 * Checks that the specified activity is visible. You need to be in the course page.
692 * It can be used being logged as a student and as a teacher on editing mode.
694 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be available but hidden from course page$/
695 * @param string $activityname
696 * @throws ExpectationException
698 public function activity_should_be_available_but_hidden_from_course_page($activityname) {
700 if ($this->is_course_editor()) {
702 // The activity must exists and be visible.
703 $activitynode = $this->get_activity_node($activityname);
705 // The activity should not be dimmed.
707 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | " .
708 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
709 $this->find('xpath', $xpath, false, $activitynode);
710 throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
711 } catch (ElementNotFoundException $e) {
715 // Should has "stealth" class.
716 $exception = new ExpectationException('"' . $activityname . '" does not have CSS class "stealth"', $this->getSession());
717 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' stealth ')]";
718 $this->find('xpath', $xpath, $exception, $activitynode);
720 // Additional check if this is a teacher in editing mode.
721 if ($this->is_editing_on()) {
722 // Also has either 'Hide' or 'Make unavailable' edit control.
723 $nohideexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('hide') .
724 '" nor "' . get_string('makeunavailable') . '" icons', $this->getSession());
726 $this->find('named_partial', array('link', get_string('hide')), false, $activitynode);
727 } catch (ElementNotFoundException $e) {
728 $this->find('named_partial', array('link', get_string('makeunavailable')), $nohideexception, $activitynode);
734 // Student should not see the activity at all.
736 $this->get_activity_node($activityname);
737 throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
738 } catch (ElementNotFoundException $e) {
739 // This is good, the activity should not be there.
745 * Checks that the specified activity is hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
747 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be hidden$/
748 * @param string $activityname
749 * @throws ExpectationException
751 public function activity_should_be_hidden($activityname) {
753 if ($this->is_course_editor()) {
755 // The activity should exist.
756 $activitynode = $this->get_activity_node($activityname);
759 $exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
760 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
761 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
762 $this->find('xpath', $xpath, $exception, $activitynode);
764 // Additional check if this is a teacher in editing mode.
765 if ($this->is_editing_on()) {
766 // Also has either 'Show' or 'Make available' edit control.
767 $noshowexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('show') .
768 '" nor "' . get_string('makeavailable') . '" icons', $this->getSession());
770 $this->find('named_partial', array('link', get_string('show')), false, $activitynode);
771 } catch (ElementNotFoundException $e) {
772 $this->find('named_partial', array('link', get_string('makeavailable')), $noshowexception, $activitynode);
778 // It should not exist at all.
780 $this->get_activity_node($activityname);
781 throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
782 } catch (ElementNotFoundException $e) {
783 // This is good, the activity should not be there.
790 * Checks that the specified activity is dimmed. You need to be in the course page.
792 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be dimmed$/
793 * @param string $activityname
794 * @throws ExpectationException
796 public function activity_should_be_dimmed($activityname) {
798 // The activity should exist.
799 $activitynode = $this->get_activity_node($activityname);
802 $exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
803 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
804 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
805 $this->find('xpath', $xpath, $exception, $activitynode);
810 * Moves the specified activity to the first slot of a section. This step is experimental when using it in Javascript tests. Editing mode should be on.
812 * @Given /^I move "(?P<activity_name_string>(?:[^"]|\\")*)" activity to section "(?P<section_number>\d+)"$/
813 * @param string $activityname The activity name
814 * @param int $sectionnumber The number of section
816 public function i_move_activity_to_section($activityname, $sectionnumber) {
818 // Ensure the destination is valid.
819 $sectionxpath = $this->section_exists($sectionnumber);
822 if ($this->running_javascript()) {
824 $activitynode = $this->get_activity_element('Move', 'icon', $activityname);
825 $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
827 $this->execute("behat_general::i_drag_and_i_drop_it_in",
828 array($this->escape($activitynode->getXpath()), "xpath_element",
829 $this->escape($destinationxpath), "xpath_element")
833 // Following links with no-JS.
835 // Moving to the fist spot of the section (before all other section's activities).
836 $this->execute('behat_course::i_click_on_in_the_activity',
837 array("a.editing_move", "css_element", $this->escape($activityname))
840 $this->execute('behat_general::i_click_on_in_the',
841 array("li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element")
847 * Edits the activity name through the edit activity; this step only works with Javascript enabled. Editing mode should be on.
849 * @Given /^I change "(?P<activity_name_string>(?:[^"]|\\")*)" activity name to "(?P<new_name_string>(?:[^"]|\\")*)"$/
850 * @throws DriverException Step not available when Javascript is disabled
851 * @param string $activityname
852 * @param string $newactivityname
854 public function i_change_activity_name_to($activityname, $newactivityname) {
856 if (!$this->running_javascript()) {
857 throw new DriverException('Change activity name step is not available with Javascript disabled');
860 $activity = $this->escape($activityname);
862 $this->execute('behat_course::i_click_on_in_the_activity',
863 array(get_string('edittitle'), "link", $activity)
866 // Adding chr(10) to save changes.
867 $this->execute('behat_forms::i_set_the_field_to',
868 array('title', $this->escape($newactivityname) . chr(10))
874 * Opens an activity actions menu if it is not already opened.
876 * @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
877 * @throws DriverException The step is not available when Javascript is disabled
878 * @param string $activityname
880 public function i_open_actions_menu($activityname) {
882 if (!$this->running_javascript()) {
883 throw new DriverException('Activities actions menu not available when Javascript is disabled');
886 // If it is already opened we do nothing.
887 $activitynode = $this->get_activity_node($activityname);
890 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
892 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
893 $this->getSession());
895 $expanded = $menunode->getAttribute('aria-expanded');
896 if ($expanded == 'true') {
900 $this->execute('behat_course::i_click_on_in_the_activity',
901 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
904 $this->actions_menu_should_be_open($activityname);
908 * Closes an activity actions menu if it is not already closed.
910 * @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
911 * @throws DriverException The step is not available when Javascript is disabled
912 * @param string $activityname
914 public function i_close_actions_menu($activityname) {
916 if (!$this->running_javascript()) {
917 throw new DriverException('Activities actions menu not available when Javascript is disabled');
920 // If it is already closed we do nothing.
921 $activitynode = $this->get_activity_node($activityname);
923 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
925 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
926 $this->getSession());
928 $expanded = $menunode->getAttribute('aria-expanded');
929 if ($expanded != 'true') {
933 $this->execute('behat_course::i_click_on_in_the_activity',
934 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
939 * Checks that the specified activity's action menu is open.
941 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/
942 * @throws DriverException The step is not available when Javascript is disabled
943 * @param string $activityname
945 public function actions_menu_should_be_open($activityname) {
947 if (!$this->running_javascript()) {
948 throw new DriverException('Activities actions menu not available when Javascript is disabled');
951 $activitynode = $this->get_activity_node($activityname);
953 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
955 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
956 $this->getSession());
958 $expanded = $menunode->getAttribute('aria-expanded');
959 if ($expanded != 'true') {
960 throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
965 * Checks that the specified activity's action menu contains an item.
967 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
968 * @throws DriverException The step is not available when Javascript is disabled
969 * @param string $activityname
970 * @param string $menuitem
972 public function actions_menu_should_have_item($activityname, $menuitem) {
973 $activitynode = $this->get_activity_node($activityname);
975 $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
976 $menuitem . '" item', $this->getSession());
977 $this->find('named_partial', array('link', $menuitem), $notfoundexception, $activitynode);
981 * Checks that the specified activity's action menu does not contains an item.
983 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
984 * @throws DriverException The step is not available when Javascript is disabled
985 * @param string $activityname
986 * @param string $menuitem
988 public function actions_menu_should_not_have_item($activityname, $menuitem) {
989 $activitynode = $this->get_activity_node($activityname);
992 $this->find('named_partial', array('link', $menuitem), false, $activitynode);
993 throw new ExpectationException('"' . $activityname . '" has a "' . $menuitem .
994 '" item when it should not', $this->getSession());
995 } catch (ElementNotFoundException $e) {
996 // This is good, the menu item should not be there.
1001 * Indents to the right the activity or resource specified by it's name. Editing mode should be on.
1003 * @Given /^I indent right "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1004 * @param string $activityname
1006 public function i_indent_right_activity($activityname) {
1008 $activity = $this->escape($activityname);
1009 if ($this->running_javascript()) {
1010 $this->i_open_actions_menu($activity);
1013 $this->execute('behat_course::i_click_on_in_the_activity',
1014 array(get_string('moveright'), "link", $this->escape($activity))
1020 * Indents to the left the activity or resource specified by it's name. Editing mode should be on.
1022 * @Given /^I indent left "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1023 * @param string $activityname
1025 public function i_indent_left_activity($activityname) {
1027 $activity = $this->escape($activityname);
1028 if ($this->running_javascript()) {
1029 $this->i_open_actions_menu($activity);
1032 $this->execute('behat_course::i_click_on_in_the_activity',
1033 array(get_string('moveleft'), "link", $this->escape($activity))
1039 * 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.
1041 * @Given /^I delete "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1042 * @param string $activityname
1044 public function i_delete_activity($activityname) {
1046 $activity = $this->escape($activityname);
1047 if ($this->running_javascript()) {
1048 $this->i_open_actions_menu($activity);
1051 $this->execute('behat_course::i_click_on_in_the_activity',
1052 array(get_string('delete'), "link", $this->escape($activity))
1056 // Not using chain steps here because the exceptions catcher have problems detecting
1057 // JS modal windows and avoiding interacting them at the same time.
1058 if ($this->running_javascript()) {
1059 $this->execute('behat_general::i_click_on_in_the',
1060 array(get_string('yes'), "button", "Confirm", "dialogue")
1063 $this->execute("behat_forms::press_button", get_string('yes'));
1070 * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on.
1072 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1073 * @param string $activityname
1075 public function i_duplicate_activity($activityname) {
1077 $activity = $this->escape($activityname);
1078 if ($this->running_javascript()) {
1079 $this->i_open_actions_menu($activity);
1081 $this->execute('behat_course::i_click_on_in_the_activity',
1082 array(get_string('duplicate'), "link", $activity)
1088 * 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.
1090 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/
1091 * @param string $activityname
1092 * @param TableNode $data
1094 public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
1096 $activity = $this->escape($activityname);
1097 $activityliteral = behat_context_helper::escape($activityname);
1099 $this->execute("behat_course::i_duplicate_activity", $activity);
1101 // Determine the future new activity xpath from the former one.
1102 $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
1103 "[contains(., $activityliteral)]/following-sibling::li";
1104 $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']";
1106 if ($this->running_javascript()) {
1107 // We wait until the AJAX request finishes and the section is visible again.
1108 $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
1109 "[contains(., $activityliteral)]" .
1110 "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
1111 "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
1113 $this->execute("behat_general::wait_until_exists",
1114 array($this->escape($hiddenlightboxxpath), "xpath_element")
1117 // Close the original activity actions menu.
1118 $this->i_close_actions_menu($activity);
1120 // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
1121 // this point, it don't even exists in the DOM (the steps are executed when we return them).
1122 $this->execute('behat_general::i_click_on',
1123 array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
1127 // We force the xpath as otherwise mink tries to interact with the former one.
1128 $this->execute('behat_general::i_click_on_in_the',
1129 array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
1132 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
1133 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
1138 * 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.
1140 * Using the protected method as this method will be usually
1141 * called by other methods which are not returning a set of
1142 * steps and performs the actions directly, so it would not
1143 * be executed if it returns another step.
1145 * Hopefully we would not require test writers to use this step
1146 * and we will manage it from other step definitions.
1148 * @Given /^I wait until section "(?P<section_number>\d+)" is available$/
1149 * @param int $sectionnumber
1152 public function i_wait_until_section_is_available($sectionnumber) {
1154 // Looks for a hidden lightbox or a non-existent lightbox in that section.
1155 $sectionxpath = $this->section_exists($sectionnumber);
1156 $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
1158 $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
1160 $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element');
1164 * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
1166 * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1167 * @param string $element
1168 * @param string $selectortype
1169 * @param string $activityname
1171 public function i_click_on_in_the_activity($element, $selectortype, $activityname) {
1172 $element = $this->get_activity_element($element, $selectortype, $activityname);
1177 * Clicks on the specified element inside the activity container.
1179 * @throws ElementNotFoundException
1180 * @param string $element
1181 * @param string $selectortype
1182 * @param string $activityname
1183 * @return NodeElement
1185 protected function get_activity_element($element, $selectortype, $activityname) {
1186 $activitynode = $this->get_activity_node($activityname);
1188 $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
1189 return $this->find($selectortype, $element, $exception, $activitynode);
1193 * Checks if the course section exists.
1195 * @throws ElementNotFoundException Thrown by behat_base::find
1196 * @param int $sectionnumber
1197 * @return string The xpath of the section.
1199 protected function section_exists($sectionnumber) {
1201 // Just to give more info in case it does not exist.
1202 $xpath = "//li[@id='section-" . $sectionnumber . "']";
1203 $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber ");
1204 $this->find('xpath', $xpath, $exception);
1210 * Returns the show section icon or throws an exception.
1212 * @throws ElementNotFoundException Thrown by behat_base::find
1213 * @param int $sectionnumber
1214 * @return NodeElement
1216 protected function show_section_link_exists($sectionnumber) {
1218 // Gets the section xpath and ensure it exists.
1219 $xpath = $this->section_exists($sectionnumber);
1221 // We need to know the course format as the text strings depends on them.
1222 $courseformat = $this->get_course_format();
1224 // Checking the show button alt text and show icon.
1225 $showtext = get_string('showfromothers', $courseformat);
1226 $linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]";
1228 $exception = new ElementNotFoundException($this->getSession(), 'Show section link');
1230 // Returing the link so both Non-JS and JS browsers can interact with it.
1231 return $this->find('xpath', $linkxpath, $exception);
1235 * Returns the hide section icon link if it exists or throws exception.
1237 * @throws ElementNotFoundException Thrown by behat_base::find
1238 * @param int $sectionnumber
1239 * @return NodeElement
1241 protected function hide_section_link_exists($sectionnumber) {
1243 // Gets the section xpath and ensure it exists.
1244 $xpath = $this->section_exists($sectionnumber);
1246 // We need to know the course format as the text strings depends on them.
1247 $courseformat = $this->get_course_format();
1249 // Checking the hide button alt text and hide icon.
1250 $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat));
1251 $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
1253 $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon ');
1254 $this->find('icon', 'Hide', $exception);
1256 // Returing the link so both Non-JS and JS browsers can interact with it.
1257 return $this->find('xpath', $linkxpath, $exception);
1261 * Gets the current course format.
1263 * @throws ExpectationException If we are not in the course view page.
1264 * @return string The course format in a frankenstyled name.
1266 protected function get_course_format() {
1268 $exception = new ExpectationException('You are not in a course page', $this->getSession());
1270 // The moodle body's id attribute contains the course format.
1271 $node = $this->getSession()->getPage()->find('css', 'body');
1276 if (!$bodyid = $node->getAttribute('id')) {
1280 if (strstr($bodyid, 'page-course-view-') === false) {
1284 return 'format_' . str_replace('page-course-view-', '', $bodyid);
1288 * Gets the section's activites DOM nodes.
1290 * @param string $sectionxpath
1291 * @return array NodeElement instances
1293 protected function get_section_activities($sectionxpath) {
1295 $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]";
1297 // We spin here, as activities usually require a lot of time to load.
1299 $activities = $this->find_all('xpath', $xpath);
1300 } catch (ElementNotFoundException $e) {
1308 * Returns the DOM node of the activity from <li>.
1310 * @throws ElementNotFoundException Thrown by behat_base::find
1311 * @param string $activityname The activity name
1312 * @return NodeElement
1314 protected function get_activity_node($activityname) {
1316 $activityname = behat_context_helper::escape($activityname);
1317 $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]";
1319 return $this->find('xpath', $xpath);
1323 * Gets the activity instance name from the activity node.
1325 * @throws ElementNotFoundException
1326 * @param NodeElement $activitynode
1329 protected function get_activity_name($activitynode) {
1330 $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode);
1331 return $instancenamenode->getText();
1335 * Returns whether the user can edit the course contents or not.
1339 protected function is_course_editor() {
1341 // We don't need to behat_base::spin() here as all is already loaded.
1342 if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
1343 !$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
1351 * Returns whether the user can edit the course contents and the editing mode is on.
1355 protected function is_editing_on() {
1356 return $this->getSession()->getPage()->findButton(get_string('turneditingoff')) ? true : false;
1360 * Returns the id of the category with the given idnumber.
1362 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1364 * @param string $idnumber
1366 * @throws ExpectationException
1368 protected function get_category_id($idnumber) {
1371 return $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1372 } catch (dml_missing_record_exception $ex) {
1373 throw new ExpectationException(sprintf("There is no category in the database with the idnumber '%s'", $idnumber));
1378 * Returns the id of the course with the given idnumber.
1380 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1382 * @param string $idnumber
1384 * @throws ExpectationException
1386 protected function get_course_id($idnumber) {
1389 return $DB->get_field('course', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1390 } catch (dml_missing_record_exception $ex) {
1391 throw new ExpectationException(sprintf("There is no course in the database with the idnumber '%s'", $idnumber));
1396 * Returns the category node from within the listing on the management page.
1398 * @param string $idnumber
1399 * @return \Behat\Mink\Element\NodeElement
1401 protected function get_management_category_listing_node_by_idnumber($idnumber) {
1402 $id = $this->get_category_id($idnumber);
1403 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id);
1404 return $this->find('css', $selector);
1408 * Returns a category node from within the management interface.
1410 * @param string $name The name of the category.
1411 * @param bool $link If set to true we'll resolve to the link rather than just the node.
1412 * @return \Behat\Mink\Element\NodeElement
1414 protected function get_management_category_listing_node_by_name($name, $link = false) {
1415 $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']";
1416 if ($link === false) {
1417 $selector .= "/ancestor::li[@data-id][1]";
1419 return $this->find('xpath', $selector);
1423 * Returns a course node from within the management interface.
1425 * @param string $name The name of the course.
1426 * @param bool $link If set to true we'll resolve to the link rather than just the node.
1427 * @return \Behat\Mink\Element\NodeElement
1429 protected function get_management_course_listing_node_by_name($name, $link = false) {
1430 $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']";
1431 if ($link === false) {
1432 $selector .= "/ancestor::li[@data-id]";
1434 return $this->find('xpath', $selector);
1438 * Returns the course node from within the listing on the management page.
1440 * @param string $idnumber
1441 * @return \Behat\Mink\Element\NodeElement
1443 protected function get_management_course_listing_node_by_idnumber($idnumber) {
1444 $id = $this->get_course_id($idnumber);
1445 $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id);
1446 return $this->find('css', $selector);
1450 * Clicks on a category in the management interface.
1452 * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1453 * @param string $name
1455 public function i_click_on_category_in_the_management_interface($name) {
1456 $node = $this->get_management_category_listing_node_by_name($name, true);
1461 * Clicks on a course in the management interface.
1463 * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1464 * @param string $name
1466 public function i_click_on_course_in_the_management_interface($name) {
1467 $node = $this->get_management_course_listing_node_by_name($name, true);
1472 * Clicks on a category checkbox in the management interface, if not checked.
1474 * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1475 * @param string $name
1477 public function i_select_category_in_the_management_interface($name) {
1478 $node = $this->get_management_category_listing_node_by_name($name);
1479 $node = $node->findField('bcat[]');
1480 if (!$node->isChecked()) {
1486 * Clicks on a category checkbox in the management interface, if checked.
1488 * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1489 * @param string $name
1491 public function i_unselect_category_in_the_management_interface($name) {
1492 $node = $this->get_management_category_listing_node_by_name($name);
1493 $node = $node->findField('bcat[]');
1494 if ($node->isChecked()) {
1500 * Clicks course checkbox in the management interface, if not checked.
1502 * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1503 * @param string $name
1505 public function i_select_course_in_the_management_interface($name) {
1506 $node = $this->get_management_course_listing_node_by_name($name);
1507 $node = $node->findField('bc[]');
1508 if (!$node->isChecked()) {
1514 * Clicks course checkbox in the management interface, if checked.
1516 * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
1517 * @param string $name
1519 public function i_unselect_course_in_the_management_interface($name) {
1520 $node = $this->get_management_course_listing_node_by_name($name);
1521 $node = $node->findField('bc[]');
1522 if ($node->isChecked()) {
1528 * Move selected categories to top level in the management interface.
1530 * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/
1531 * @param string $name
1533 public function i_move_category_to_top_level_in_the_management_interface($name) {
1534 $this->i_select_category_in_the_management_interface($name);
1536 $this->execute('behat_forms::i_set_the_field_to',
1537 array('menumovecategoriesto', core_course_category::get(0)->get_formatted_name())
1541 $this->execute("behat_forms::press_button", "bulkmovecategories");
1545 * Checks that a category is a subcategory of specific category.
1547 * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
1548 * @throws ExpectationException
1549 * @param string $subcatidnumber
1550 * @param string $catidnumber
1552 public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1553 $categorynodeid = $this->get_category_id($catidnumber);
1554 $subcategoryid = $this->get_category_id($subcatidnumber);
1555 $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession());
1556 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid);
1557 $this->find('css', $selector, $exception);
1561 * Checks that a category is not a subcategory of specific category.
1563 * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
1564 * @throws ExpectationException
1565 * @param string $subcatidnumber
1566 * @param string $catidnumber
1568 public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1570 $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber);
1571 } catch (ExpectationException $e) {
1572 // ExpectedException means that it is not highlighted.
1575 throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession());
1579 * Click to expand a category revealing its sub categories within the management UI.
1581 * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/
1582 * @param string $idnumber
1584 public function i_click_to_expand_category_in_the_management_interface($idnumber) {
1585 $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber);
1586 $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession());
1587 $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode);
1588 $togglenode->click();
1592 * Checks that a category within the management interface is visible.
1594 * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1595 * @param string $idnumber
1597 public function category_in_management_listing_should_be_visible($idnumber) {
1598 $id = $this->get_category_id($idnumber);
1599 $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession());
1600 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id);
1601 $this->find('css', $selector, $exception);
1605 * Checks that a category within the management interface is dimmed.
1607 * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1608 * @param string $idnumber
1610 public function category_in_management_listing_should_be_dimmed($idnumber) {
1611 $id = $this->get_category_id($idnumber);
1612 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id);
1613 $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession());
1614 $this->find('css', $selector, $exception);
1618 * Checks that a course within the management interface is visible.
1620 * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1621 * @param string $idnumber
1623 public function course_in_management_listing_should_be_visible($idnumber) {
1624 $id = $this->get_course_id($idnumber);
1625 $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession());
1626 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id);
1627 $this->find('css', $selector, $exception);
1631 * Checks that a course within the management interface is dimmed.
1633 * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
1634 * @param string $idnumber
1636 public function course_in_management_listing_should_be_dimmed($idnumber) {
1637 $id = $this->get_course_id($idnumber);
1638 $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession());
1639 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id);
1640 $this->find('css', $selector, $exception);
1644 * Toggles the visibility of a course in the management UI.
1646 * If it was visible it will be hidden. If it is hidden it will be made visible.
1648 * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
1649 * @param string $idnumber
1651 public function i_toggle_visibility_of_course_in_management_listing($idnumber) {
1652 $id = $this->get_course_id($idnumber);
1653 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id);
1654 $node = $this->find('css', $selector);
1655 $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1656 if ($node->getAttribute('data-visible') === '1') {
1657 $toggle = $this->find('css', '.action-hide', $exception, $node);
1659 $toggle = $this->find('css', '.action-show', $exception, $node);
1665 * Toggles the visibility of a category in the management UI.
1667 * If it was visible it will be hidden. If it is hidden it will be made visible.
1669 * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
1671 public function i_toggle_visibility_of_category_in_management_listing($idnumber) {
1672 $id = $this->get_category_id($idnumber);
1673 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id);
1674 $node = $this->find('css', $selector);
1675 $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1676 if ($node->getAttribute('data-visible') === '1') {
1677 $toggle = $this->find('css', '.action-hide', $exception, $node);
1679 $toggle = $this->find('css', '.action-show', $exception, $node);
1685 * Moves a category displayed in the management interface up or down one place.
1687 * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
1689 * @param string $idnumber The category idnumber
1690 * @param string $direction The direction to move in, either up or down
1692 public function i_click_to_move_category_by_one($idnumber, $direction) {
1693 $node = $this->get_management_category_listing_node_by_idnumber($idnumber);
1694 $this->user_moves_listing_by_one('category', $node, $direction);
1698 * Moves a course displayed in the management interface up or down one place.
1700 * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
1702 * @param string $idnumber The course idnumber
1703 * @param string $direction The direction to move in, either up or down
1705 public function i_click_to_move_course_by_one($idnumber, $direction) {
1706 $node = $this->get_management_course_listing_node_by_idnumber($idnumber);
1707 $this->user_moves_listing_by_one('course', $node, $direction);
1711 * Moves a course or category listing within the management interface up or down by one.
1713 * @param string $listingtype One of course or category
1714 * @param \Behat\Mink\Element\NodeElement $listingnode
1715 * @param string $direction One of up or down.
1716 * @param bool $highlight If set to false we don't check the node has been highlighted.
1718 protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) {
1719 $up = (strtolower($direction) === 'up');
1721 $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession());
1722 $button = $this->find('css', 'a.action-moveup', $exception, $listingnode);
1724 $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession());
1725 $button = $this->find('css', 'a.action-movedown', $exception, $listingnode);
1728 if ($this->running_javascript() && $highlight) {
1729 $listitem = $listingnode->getParent();
1730 $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession());
1731 $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true);
1736 * Used by spin to determine the callback has been highlighted.
1738 * @param behat_course $self A self reference (default first arg from a spin callback)
1739 * @param \Behat\Mink\Element\NodeElement $selector
1742 protected function listing_is_highlighted($self, $selector) {
1743 $listitem = $this->find('css', $selector);
1744 return $listitem->hasClass('highlight');
1748 * Check that one course appears before another in the course category management listings.
1750 * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/
1752 * @param string $preceedingcourse The first course to find
1753 * @param string $followingcourse The second course to find (should be AFTER the first course)
1754 * @throws ExpectationException
1756 public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) {
1757 $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']";
1758 $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course";
1759 if (!$this->getSession()->getDriver()->find($xpath)) {
1760 throw new ExpectationException($msg, $this->getSession());
1765 * Check that one category appears before another in the course category management listings.
1767 * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/
1769 * @param string $preceedingcategory The first category to find
1770 * @param string $followingcategory The second category to find (should be after the first category)
1771 * @throws ExpectationException
1773 public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) {
1774 $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']";
1775 $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category";
1776 if (!$this->getSession()->getDriver()->find($xpath)) {
1777 throw new ExpectationException($msg, $this->getSession());
1782 * Checks that we are on the course management page that we expect to be on and that no course has been selected.
1784 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/
1785 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
1787 public function i_should_see_the_courses_management_page($mode) {
1788 $this->execute("behat_general::assert_element_contains_text",
1789 array("Course and category management", "h2", "css_element")
1794 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1795 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1798 case "Course categories":
1799 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1800 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1803 case "Courses categories and courses":
1805 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1806 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1810 $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element"));
1814 * Checks that we are on the course management page that we expect to be on and that a course has been selected.
1816 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/
1817 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
1819 public function i_should_see_the_courses_management_page_with_a_course_selected($mode) {
1820 $this->execute("behat_general::assert_element_contains_text",
1821 array("Course and category management", "h2", "css_element"));
1825 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1826 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1829 case "Course categories":
1830 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1831 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1834 case "Courses categories and courses":
1836 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1837 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1841 $this->execute("behat_general::should_exist", array("#course-detail", "css_element"));
1845 * Locates a course in the course category management interface and then triggers an action for it.
1847 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/
1849 * @param string $action The action to take. One of
1850 * @param string $name The name of the course as it is displayed in the management interface.
1852 public function i_click_on_action_for_item_in_management_course_listing($action, $name) {
1853 $node = $this->get_management_course_listing_node_by_name($name);
1854 $this->user_clicks_on_management_listing_action('course', $node, $action);
1858 * Locates a category in the course category management interface and then triggers an action for it.
1860 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/
1862 * @param string $action The action to take. One of
1863 * @param string $name The name of the category as it is displayed in the management interface.
1865 public function i_click_on_action_for_item_in_management_category_listing($action, $name) {
1866 $node = $this->get_management_category_listing_node_by_name($name);
1867 $this->user_clicks_on_management_listing_action('category', $node, $action);
1871 * Clicks to expand or collapse a category displayed on the frontpage
1873 * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/
1874 * @throws ExpectationException
1875 * @param string $categoryname
1877 public function i_toggle_category_children_visibility_in_frontpage($categoryname) {
1879 $headingtags = array();
1880 for ($i = 1; $i <= 6; $i++) {
1881 $headingtags[] = 'self::h' . $i;
1884 $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
1885 $categoryliteral = behat_context_helper::escape($categoryname);
1886 $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
1887 $node = $this->find('xpath', $xpath, $exception);
1890 // Smooth expansion.
1891 $this->getSession()->wait(1000);
1895 * Finds the node to use for a management listitem action and clicks it.
1897 * @param string $listingtype Either course or category.
1898 * @param \Behat\Mink\Element\NodeElement $listingnode
1899 * @param string $action The action being taken
1900 * @throws Behat\Mink\Exception\ExpectationException
1902 protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
1903 $actionsnode = $listingnode->find('xpath', "//*" .
1904 "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
1905 if (!$actionsnode) {
1906 throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
1908 $actionnode = $actionsnode->find('css', '.action-'.$action);
1910 throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
1912 if ($this->running_javascript() && !$actionnode->isVisible()) {
1913 $actionsnode->find('css', 'a[data-toggle=dropdown]')->click();
1914 $actionnode = $actionsnode->find('css', '.action-'.$action);
1916 $actionnode->click();
1920 * Clicks on a category in the management interface.
1922 * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/
1923 * @param string $name The name of the category to click.
1925 public function i_click_on_category_in_the_management_category_listing($name) {
1926 $node = $this->get_management_category_listing_node_by_name($name);
1927 $node->find('css', 'a.categoryname')->click();
1931 * Go to the course participants
1933 * @Given /^I navigate to course participants$/
1935 public function i_navigate_to_course_participants() {
1936 $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('participants'));
1940 * Check that one teacher appears before another in the course contacts.
1942 * @Given /^I should see teacher "(?P<pteacher_string>(?:[^"]|\\")*)" before "(?P<fteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1944 * @param string $pteacher The first teacher to find
1945 * @param string $fteacher The second teacher to find (should be after the first teacher)
1947 * @throws ExpectationException
1949 public function i_should_see_teacher_before($pteacher, $fteacher) {
1950 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1951 $msg = "Teacher {$pteacher} does not appear before Teacher {$fteacher}";
1952 if (!$this->getSession()->getDriver()->find($xpath)) {
1953 throw new ExpectationException($msg, $this->getSession());
1958 * Check that one teacher oes not appears after another in the course contacts.
1960 * @Given /^I should not see teacher "(?P<fteacher_string>(?:[^"]|\\")*)" after "(?P<pteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1962 * @param string $fteacher The teacher that should not be found (after the other teacher)
1963 * @param string $pteacher The teacher after who the other should not be found (this teacher must be found!)
1965 * @throws ExpectationException
1967 public function i_should_not_see_teacher_after($fteacher, $pteacher) {
1968 $xpathliteral = behat_context_helper::escape($pteacher);
1969 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
1970 "[count(descendant::*[contains(., $xpathliteral)]) = 0]";
1972 $nodes = $this->find_all('xpath', $xpath);
1973 } catch (ElementNotFoundException $e) {
1974 throw new ExpectationException('"' . $pteacher . '" text was not found in the page', $this->getSession());
1976 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1977 $msg = "Teacher {$fteacher} appears after Teacher {$pteacher}";
1978 if ($this->getSession()->getDriver()->find($xpath)) {
1979 throw new ExpectationException($msg, $this->getSession());
1984 * Open the activity chooser in a course.
1986 * @Given /^I open the activity chooser$/
1988 public function i_open_the_activity_chooser() {
1989 $this->execute('behat_general::i_click_on',
1990 array('//button[@data-action="open-chooser"]', 'xpath_element'));
1992 $node = $this->get_selected_node('xpath_element', '//div[@data-region="modules"]');
1993 $this->ensure_node_is_visible($node);